diff --git a/i3bar/include/common.h b/i3bar/include/common.h index 212b9dd1d..59fcc44a6 100644 --- a/i3bar/include/common.h +++ b/i3bar/include/common.h @@ -56,5 +56,6 @@ TAILQ_HEAD(statusline_head, status_block) statusline_head; #include "xcb.h" #include "config.h" #include "libi3.h" +#include "determine_json_version.h" #endif diff --git a/i3bar/include/determine_json_version.h b/i3bar/include/determine_json_version.h new file mode 100644 index 000000000..52c6f5d25 --- /dev/null +++ b/i3bar/include/determine_json_version.h @@ -0,0 +1,29 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3bar - an xcb-based status- and ws-bar for i3 + * © 2010-2012 Axel Wagner and contributors (see also: LICENSE) + * + * determine_json_version.c: Determines the JSON protocol version based on the + * first line of input from a child program. + * + */ +#ifndef DETERMINE_JSON_VERSION_H_ +#define DETERMINE_JSON_VERSION_H_ + +#include + +/* + * Determines the JSON i3bar protocol version from the given buffer. In case + * the buffer does not contain valid JSON, or no version field is found, this + * function returns -1. The amount of bytes consumed by parsing the header is + * returned in *consumed (if non-NULL). + * + * The return type is an int32_t to avoid machines with different sizes of + * 'int' to allow different values here. It’s highly unlikely we ever exceed + * even an int8_t, but still… + * + */ +int32_t determine_json_version(const unsigned char *buffer, int length, unsigned int *consumed); + +#endif diff --git a/i3bar/src/child.c b/i3bar/src/child.c index 12782caf1..51f5fe825 100644 --- a/i3bar/src/child.c +++ b/i3bar/src/child.c @@ -192,19 +192,18 @@ void stdin_io_cb(struct ev_loop *loop, ev_io *watcher, int revents) { if (first_line) { DLOG("Detecting input type based on buffer *%.*s*\n", rec, buffer); /* Detect whether this is JSON or plain text. */ - plaintext = (strncasecmp((char*)buffer, "{\"version\":", strlen("{\"version\":")) != 0); + unsigned int consumed = 0; + /* At the moment, we don’t care for the version. This might change + * in the future, but for now, we just discard it. */ + plaintext = (determine_json_version(buffer, buffer_len, &consumed) == -1); if (plaintext) { /* In case of plaintext, we just add a single block and change its * full_text pointer later. */ struct status_block *new_block = scalloc(sizeof(struct status_block)); TAILQ_INSERT_TAIL(&statusline_head, new_block, blocks); } else { - /* At the moment, we don’t care for the version. This might change - * in the future, but for now, we just discard it. */ - while (*json_input != '\n' && *json_input != '\0') { - json_input++; - rec--; - } + json_input += consumed; + rec -= consumed; } first_line = false; } diff --git a/i3bar/src/determine_json_version.c b/i3bar/src/determine_json_version.c new file mode 100644 index 000000000..abd430383 --- /dev/null +++ b/i3bar/src/determine_json_version.c @@ -0,0 +1,104 @@ +/* + * vim:ts=4:sw=4:expandtab + * + * i3bar - an xcb-based status- and ws-bar for i3 + * © 2010-2012 Axel Wagner and contributors (see also: LICENSE) + * + * determine_json_version.c: Determines the JSON protocol version based on the + * first line of input from a child program. + * + */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static bool version_key; +static int32_t version_number; + +#if YAJL_MAJOR >= 2 +static int version_integer(void *ctx, long long val) { +#else +static int version_integer(void *ctx, long val) { +#endif + if (version_key) + version_number = (uint32_t)val; + return 1; +} + +#if YAJL_MAJOR >= 2 +static int version_map_key(void *ctx, const unsigned char *stringval, size_t stringlen) { +#else +static int version_map_key(void *ctx, const unsigned char *stringval, unsigned int stringlen) { +#endif + version_key = (stringlen == strlen("version") && + strncmp((const char*)stringval, "version", strlen("version")) == 0); + return 1; +} + +static yajl_callbacks version_callbacks = { + NULL, /* null */ + NULL, /* boolean */ + &version_integer, + NULL, /* double */ + NULL, /* number */ + NULL, /* string */ + NULL, /* start_map */ + &version_map_key, + NULL, /* end_map */ + NULL, /* start_array */ + NULL /* end_array */ +}; + +/* + * Determines the JSON i3bar protocol version from the given buffer. In case + * the buffer does not contain valid JSON, or no version field is found, this + * function returns -1. The amount of bytes consumed by parsing the header is + * returned in *consumed (if non-NULL). + * + * The return type is an int32_t to avoid machines with different sizes of + * 'int' to allow different values here. It’s highly unlikely we ever exceed + * even an int8_t, but still… + * + */ +int32_t determine_json_version(const unsigned char *buffer, int length, unsigned int *consumed) { +#if YAJL_MAJOR >= 2 + yajl_handle handle = yajl_alloc(&version_callbacks, NULL, NULL); + /* Allow trailing garbage. yajl 1 always behaves that way anyways, but for + * yajl 2, we need to be explicit. */ + yajl_config(handle, yajl_allow_trailing_garbage, 1); +#else + yajl_parser_config parse_conf = { 0, 0 }; + + yajl_handle handle = yajl_alloc(&version_callbacks, &parse_conf, NULL, NULL); +#endif + + version_key = false; + version_number = -1; + + yajl_status state = yajl_parse(handle, buffer, length); + if (state != yajl_status_ok) { + version_number = -1; + if (consumed != NULL) + *consumed = 0; + } else { + if (consumed != NULL) + *consumed = yajl_get_bytes_consumed(handle); + } + + yajl_free(handle); + + return version_number; +}