i3bar: Fully parse the JSON header

This commit is contained in:
Quentin Glidic 2012-09-03 10:43:29 +02:00 committed by Michael Stapelberg
parent 34dc6d4d64
commit 1e114d7ab5
4 changed files with 54 additions and 37 deletions

View File

@ -4,12 +4,12 @@
* i3bar - an xcb-based status- and ws-bar for i3 * i3bar - an xcb-based status- and ws-bar for i3
* © 2010-2012 Axel Wagner and contributors (see also: LICENSE) * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
* *
* determine_json_version.c: Determines the JSON protocol version based on the * parse_json_header.c: Parse the JSON protocol header to determine
* first line of input from a child program. * protocol version and features.
* *
*/ */
#ifndef DETERMINE_JSON_VERSION_H_ #ifndef PARSE_JSON_HEADER_H_
#define DETERMINE_JSON_VERSION_H_ #define PARSE_JSON_HEADER_H_
#include <stdint.h> #include <stdint.h>
@ -24,6 +24,6 @@
* even an int8_t, but still * even an int8_t, but still
* *
*/ */
int32_t determine_json_version(const unsigned char *buffer, int length, unsigned int *consumed); void parse_json_header(i3bar_child *child, const unsigned char *buffer, int length, unsigned int *consumed);
#endif #endif

View File

@ -16,6 +16,8 @@
#undef MIN #undef MIN
#define MIN(x,y) ((x) < (y) ? (x) : (y)) #define MIN(x,y) ((x) < (y) ? (x) : (y))
#define STARTS_WITH(string, len, needle) ((len >= strlen(needle)) && strncasecmp(string, needle, strlen(needle)) == 0)
/* Securely free p */ /* Securely free p */
#define FREE(p) do { \ #define FREE(p) do { \
if (p != NULL) { \ if (p != NULL) { \

View File

@ -249,7 +249,7 @@ void stdin_io_first_line_cb(struct ev_loop *loop, ev_io *watcher, int revents) {
unsigned int consumed = 0; unsigned int consumed = 0;
/* At the moment, we dont care for the version. This might change /* At the moment, we dont care for the version. This might change
* in the future, but for now, we just discard it. */ * in the future, but for now, we just discard it. */
child.version = determine_json_version(buffer, rec, &consumed); parse_json_header(&child, buffer, rec, &consumed);
if (child.version > 0) { if (child.version > 0) {
read_json_input(buffer + consumed, rec - consumed); read_json_input(buffer + consumed, rec - consumed);
} else { } else {

View File

@ -4,8 +4,8 @@
* i3bar - an xcb-based status- and ws-bar for i3 * i3bar - an xcb-based status- and ws-bar for i3
* © 2010-2012 Axel Wagner and contributors (see also: LICENSE) * © 2010-2012 Axel Wagner and contributors (see also: LICENSE)
* *
* determine_json_version.c: Determines the JSON protocol version based on the * parse_json_header.c: Parse the JSON protocol header to determine
* first line of input from a child program. * protocol version and features.
* *
*/ */
#include <stdlib.h> #include <stdlib.h>
@ -25,72 +25,89 @@
#include <yajl/yajl_parse.h> #include <yajl/yajl_parse.h>
#include <yajl/yajl_version.h> #include <yajl/yajl_version.h>
static bool version_key; #include "common.h"
static int32_t version_number;
static enum {
KEY_VERSION,
NO_KEY
} current_key;
#if YAJL_MAJOR >= 2 #if YAJL_MAJOR >= 2
static int version_integer(void *ctx, long long val) { static int header_integer(void *ctx, long long val) {
#else #else
static int version_integer(void *ctx, long val) { static int header_integer(void *ctx, long val) {
#endif #endif
if (version_key) i3bar_child *child = ctx;
version_number = (uint32_t)val;
switch (current_key) {
case KEY_VERSION:
child->version = val;
break;
default:
break;
}
return 1; return 1;
} }
#define CHECK_KEY(name) (stringlen == strlen(name) && \
STARTS_WITH((const char*)stringval, stringlen, name))
#if YAJL_MAJOR >= 2 #if YAJL_MAJOR >= 2
static int version_map_key(void *ctx, const unsigned char *stringval, size_t stringlen) { static int header_map_key(void *ctx, const unsigned char *stringval, size_t stringlen) {
#else #else
static int version_map_key(void *ctx, const unsigned char *stringval, unsigned int stringlen) { static int header_map_key(void *ctx, const unsigned char *stringval, unsigned int stringlen) {
#endif #endif
version_key = (stringlen == strlen("version") && if (CHECK_KEY("version")) {
strncmp((const char*)stringval, "version", strlen("version")) == 0); current_key = KEY_VERSION;
}
return 1; return 1;
} }
static yajl_callbacks version_callbacks = { static yajl_callbacks version_callbacks = {
NULL, /* null */ NULL, /* null */
NULL, /* boolean */ NULL, /* boolean */
&version_integer, &header_integer,
NULL, /* double */ NULL, /* double */
NULL, /* number */ NULL, /* number */
NULL, /* string */ NULL, /* string */
NULL, /* start_map */ NULL, /* start_map */
&version_map_key, &header_map_key,
NULL, /* end_map */ NULL, /* end_map */
NULL, /* start_array */ NULL, /* start_array */
NULL /* end_array */ NULL /* end_array */
}; };
static void child_init(i3bar_child *child) {
child->version = 0;
}
/* /*
* Determines the JSON i3bar protocol version from the given buffer. In case * Parse the JSON protocol header to determine protocol version and features.
* the buffer does not contain valid JSON, or no version field is found, this * In case the buffer does not contain a valid header (invalid JSON, or no
* function returns -1. The amount of bytes consumed by parsing the header is * version field found), the 'correct' field of the returned header is set to
* returned in *consumed (if non-NULL). * false. 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. Its 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) { void parse_json_header(i3bar_child *child, const unsigned char *buffer, int length, unsigned int *consumed) {
child_init(child);
current_key = NO_KEY;
#if YAJL_MAJOR >= 2 #if YAJL_MAJOR >= 2
yajl_handle handle = yajl_alloc(&version_callbacks, NULL, NULL); yajl_handle handle = yajl_alloc(&version_callbacks, NULL, child);
/* Allow trailing garbage. yajl 1 always behaves that way anyways, but for /* Allow trailing garbage. yajl 1 always behaves that way anyways, but for
* yajl 2, we need to be explicit. */ * yajl 2, we need to be explicit. */
yajl_config(handle, yajl_allow_trailing_garbage, 1); yajl_config(handle, yajl_allow_trailing_garbage, 1);
#else #else
yajl_parser_config parse_conf = { 0, 0 }; yajl_parser_config parse_conf = { 0, 0 };
yajl_handle handle = yajl_alloc(&version_callbacks, &parse_conf, NULL, NULL); yajl_handle handle = yajl_alloc(&version_callbacks, &parse_conf, NULL, child);
#endif #endif
version_key = false;
version_number = -1;
yajl_status state = yajl_parse(handle, buffer, length); yajl_status state = yajl_parse(handle, buffer, length);
if (state != yajl_status_ok) { if (state != yajl_status_ok) {
version_number = -1; child_init(child);
if (consumed != NULL) if (consumed != NULL)
*consumed = 0; *consumed = 0;
} else { } else {
@ -99,6 +116,4 @@ int32_t determine_json_version(const unsigned char *buffer, int length, unsigned
} }
yajl_free(handle); yajl_free(handle);
return version_number;
} }