diff options
author | Wilmer van der Gaast <wilmer@gaast.net> | 2012-12-22 01:14:26 +0100 |
---|---|---|
committer | Wilmer van der Gaast <wilmer@gaast.net> | 2012-12-22 01:14:26 +0100 |
commit | cc6fdf8fe5a044db58ed74e69673cf4270080d45 (patch) | |
tree | d58edb70d76e84bf24016fccf9cd27764ea7ae4c /lib | |
parent | 92d30446251591a6805168f51a4b07ff65b3cc24 (diff) | |
parent | 573e274c58bf7d154b35ab5cd9d0b711f7ede715 (diff) |
Merging JSON branch. It's very stable by now, and I want more testers.
Diffstat (limited to 'lib')
-rw-r--r-- | lib/Makefile | 2 | ||||
-rw-r--r-- | lib/http_client.c | 179 | ||||
-rw-r--r-- | lib/http_client.h | 40 | ||||
-rw-r--r-- | lib/json.c | 743 | ||||
-rw-r--r-- | lib/json.h | 192 | ||||
-rw-r--r-- | lib/json_util.c | 62 | ||||
-rw-r--r-- | lib/json_util.h | 35 | ||||
-rw-r--r-- | lib/oauth2.c | 81 | ||||
-rw-r--r-- | lib/ssl_gnutls.c | 18 |
9 files changed, 1220 insertions, 132 deletions
diff --git a/lib/Makefile b/lib/Makefile index 324ab646..f20b3797 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -12,7 +12,7 @@ _SRCDIR_ := $(_SRCDIR_)lib/ endif # [SH] Program variables -objects = arc.o base64.o $(EVENT_HANDLER) ftutil.o http_client.o ini.o md5.o misc.o oauth.o oauth2.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o +objects = arc.o base64.o $(EVENT_HANDLER) ftutil.o http_client.o ini.o json.o json_util.o md5.o misc.o oauth.o oauth2.o proxy.o sha1.o $(SSL_CLIENT) url.o xmltree.o LFLAGS += -r diff --git a/lib/http_client.c b/lib/http_client.c index 7ed539d0..e368c0dc 100644 --- a/lib/http_client.c +++ b/lib/http_client.c @@ -192,12 +192,13 @@ static gboolean http_ssl_connected( gpointer data, int returncode, void *source, return http_connected( data, req->fd, cond ); } +static gboolean http_handle_headers( struct http_request *req ); + static gboolean http_incoming_data( gpointer data, int source, b_input_condition cond ) { struct http_request *req = data; - int evil_server = 0; - char buffer[2048]; - char *end1, *end2, *s; + char buffer[4096]; + char *s; size_t content_length; int st; @@ -217,12 +218,12 @@ static gboolean http_incoming_data( gpointer data, int source, b_input_condition servers that LOVE to send invalid TLS packets that abort connections! \o/ */ - goto got_reply; + goto eof; } } else if( st == 0 ) { - goto got_reply; + goto eof; } } else @@ -238,28 +239,70 @@ static gboolean http_incoming_data( gpointer data, int source, b_input_condition } else if( st == 0 ) { - goto got_reply; + goto eof; } } - if( st > 0 ) + if( st > 0 && !req->sbuf ) { req->reply_headers = g_realloc( req->reply_headers, req->bytes_read + st + 1 ); memcpy( req->reply_headers + req->bytes_read, buffer, st ); req->bytes_read += st; + + st = 0; + } + + if( st >= 0 && ( req->flags & HTTPC_STREAMING ) ) + { + if( !req->reply_body && + ( strstr( req->reply_headers, "\r\n\r\n" ) || + strstr( req->reply_headers, "\n\n" ) ) ) + { + size_t hlen; + + /* We've now received all headers, so process them once + before we start feeding back data. */ + if( !http_handle_headers( req ) ) + return FALSE; + + hlen = req->reply_body - req->reply_headers; + + req->sblen = req->bytes_read - hlen; + req->sbuf = g_memdup( req->reply_body, req->sblen + 1 ); + req->reply_headers = g_realloc( req->reply_headers, hlen + 1 ); + + req->reply_body = req->sbuf; + } + + if( st > 0 ) + { + int pos = req->reply_body - req->sbuf; + req->sbuf = g_realloc( req->sbuf, req->sblen + st + 1 ); + memcpy( req->sbuf + req->sblen, buffer, st ); + req->bytes_read += st; + req->sblen += st; + req->sbuf[req->sblen] = '\0'; + req->reply_body = req->sbuf + pos; + req->body_size = req->sblen - pos; + } + + if( req->reply_body ) + req->func( req ); } + if( ssl_pending( req->ssl ) ) + return http_incoming_data( data, source, cond ); + /* There will be more! */ req->inpa = b_input_add( req->fd, req->ssl ? ssl_getdirection( req->ssl ) : B_EV_IO_READ, http_incoming_data, req ); - if( ssl_pending( req->ssl ) ) - return http_incoming_data( data, source, cond ); - else - return FALSE; + return FALSE; -got_reply: +eof: + req->flags |= HTTPC_EOF; + /* Maybe if the webserver is overloaded, or when there's bad SSL support... */ if( req->bytes_read == 0 ) @@ -268,8 +311,50 @@ got_reply: goto cleanup; } + if( !( req->flags & HTTPC_STREAMING ) ) + { + /* Returns FALSE if we were redirected, in which case we should abort + and not run any callback yet. */ + if( !http_handle_headers( req ) ) + return FALSE; + } + +cleanup: + if( req->ssl ) + ssl_disconnect( req->ssl ); + else + closesocket( req->fd ); + + if( ( s = get_rfc822_header( req->reply_headers, "Content-Length", 0 ) ) && + sscanf( s, "%zd", &content_length ) == 1 ) + { + if( content_length < req->body_size ) + { + req->status_code = -1; + g_free( req->status_string ); + req->status_string = g_strdup( "Response truncated" ); + } + } + g_free( s ); + + if( getenv( "BITLBEE_DEBUG" ) && req ) + printf( "Finishing HTTP request with status: %s\n", + req->status_string ? req->status_string : "NULL" ); + + req->func( req ); + http_free( req ); + return FALSE; +} + +/* Splits headers and body. Checks result code, in case of 300s it'll handle + redirects. If this returns FALSE, don't call any callbacks! */ +static gboolean http_handle_headers( struct http_request *req ) +{ + char *end1, *end2; + int evil_server = 0; + /* Zero termination is very convenient. */ - req->reply_headers[req->bytes_read] = 0; + req->reply_headers[req->bytes_read] = '\0'; /* Find the separation between headers and body, and keep stupid webservers in mind. */ @@ -288,7 +373,7 @@ got_reply: else { req->status_string = g_strdup( "Malformed HTTP reply" ); - goto cleanup; + return TRUE; } *end1 = 0; @@ -305,7 +390,7 @@ got_reply: if( ( end1 = strchr( req->reply_headers, ' ' ) ) != NULL ) { - if( sscanf( end1 + 1, "%d", &req->status_code ) != 1 ) + if( sscanf( end1 + 1, "%hd", &req->status_code ) != 1 ) { req->status_string = g_strdup( "Can't parse status code" ); req->status_code = -1; @@ -348,7 +433,7 @@ got_reply: if( loc == NULL ) /* We can't handle this redirect... */ { req->status_string = g_strdup( "Can't locate Location: header" ); - goto cleanup; + return TRUE; } loc += 11; @@ -368,7 +453,7 @@ got_reply: req->status_string = g_strdup( "Can't handle recursive redirects" ); - goto cleanup; + return TRUE; } else { @@ -379,7 +464,7 @@ got_reply: s = strstr( loc, "\r\n" ); if( s == NULL ) - goto cleanup; + return TRUE; url = g_new0( url_t, 1 ); *s = 0; @@ -388,7 +473,7 @@ got_reply: { req->status_string = g_strdup( "Malformed redirect URL" ); g_free( url ); - goto cleanup; + return TRUE; } /* Find all headers and, if necessary, the POST request contents. @@ -400,7 +485,7 @@ got_reply: { req->status_string = g_strdup( "Error while rebuilding request string" ); g_free( url ); - goto cleanup; + return TRUE; } /* More or less HTTP/1.0 compliant, from my reading of RFC 2616. @@ -466,7 +551,7 @@ got_reply: { req->status_string = g_strdup( "Connection problem during redirect" ); g_free( new_request ); - goto cleanup; + return TRUE; } g_free( req->request ); @@ -479,35 +564,41 @@ got_reply: return FALSE; } - /* Assume that a closed connection means we're finished, this indeed - breaks with keep-alive connections and faulty connections. */ - /* req->finished = 1; */ + return TRUE; +} -cleanup: - if( req->ssl ) - ssl_disconnect( req->ssl ); - else - closesocket( req->fd ); +void http_flush_bytes( struct http_request *req, size_t len ) +{ + if( len <= 0 || len > req->body_size || !( req->flags & HTTPC_STREAMING ) ) + return; - if( ( s = get_rfc822_header( req->reply_headers, "Content-Length", 0 ) ) && - sscanf( s, "%zd", &content_length ) == 1 ) + req->reply_body += len; + req->body_size -= len; + + if( req->reply_body - req->sbuf >= 512 ) { - if( content_length < req->body_size ) - { - req->status_code = -1; - g_free( req->status_string ); - req->status_string = g_strdup( "Response truncated" ); - } + printf( "Wasting %ld bytes, cleaning up stream buffer\n", req->reply_body - req->sbuf ); + char *new = g_memdup( req->reply_body, req->body_size + 1 ); + g_free( req->sbuf ); + req->reply_body = req->sbuf = new; + req->sblen = req->body_size; } - g_free( s ); +} + +void http_close( struct http_request *req ) +{ + if( !req ) + return; - if( getenv( "BITLBEE_DEBUG" ) && req ) - printf( "Finishing HTTP request with status: %s\n", - req->status_string ? req->status_string : "NULL" ); + if( req->inpa > 0 ) + b_event_remove( req->inpa ); + + if( req->ssl ) + ssl_disconnect( req->ssl ); + else + closesocket( req->fd ); - req->func( req ); http_free( req ); - return FALSE; } static void http_free( struct http_request *req ) @@ -515,6 +606,6 @@ static void http_free( struct http_request *req ) g_free( req->request ); g_free( req->reply_headers ); g_free( req->status_string ); + g_free( req->sbuf ); g_free( req ); } - diff --git a/lib/http_client.h b/lib/http_client.h index 623f17a0..e2c0319a 100644 --- a/lib/http_client.h +++ b/lib/http_client.h @@ -25,23 +25,26 @@ /* http_client allows you to talk (asynchronously, again) to HTTP servers. In the "background" it will send the whole query and wait for a complete - response to come back. Right now it's only used by the MSN Passport - authentication code, but it might be useful for other things too (for - example the AIM usericon patch uses this so icons can be stored on - webservers instead of the local filesystem). + response to come back. Initially written for MS Passport authentication, + but used for many other things now like OAuth and Twitter. - Didn't test this too much, but it seems to work well. Just don't look - at the code that handles HTTP 30x redirects. ;-) The function is - probably not very useful for downloading lots of data since it keeps - everything in a memory buffer until the download is completed (and - can't pass any data or whatever before then). It's very useful for - doing quick requests without blocking the whole program, though. */ + It's very useful for doing quick requests without blocking the whole + program. Unfortunately it doesn't support fancy stuff like HTTP keep- + alives. */ #include <glib.h> #include "ssl_client.h" struct http_request; +typedef enum http_client_flags +{ + HTTPC_STREAMING = 1, + HTTPC_EOF = 2, + + /* Let's reserve 0x1000000+ for lib users. */ +} http_client_flags_t; + /* Your callback function should look like this: */ typedef void (*http_input_function)( struct http_request * ); @@ -52,28 +55,31 @@ struct http_request { char *request; /* The request to send to the server. */ int request_length; /* Its size. */ - int status_code; /* The numeric HTTP status code. (Or -1 + short status_code; /* The numeric HTTP status code. (Or -1 if something really went wrong) */ char *status_string; /* The error text. */ char *reply_headers; char *reply_body; int body_size; /* The number of bytes in reply_body. */ - /* int finished; Set to non-0 if the request was completed - successfully. */ - int redir_ttl; /* You can set it to 0 if you don't want + short redir_ttl; /* You can set it to 0 if you don't want http_client to follow them. */ + http_client_flags_t flags; + http_input_function func; gpointer data; /* Please don't touch the things down here, you shouldn't need them. */ - void *ssl; int fd; int inpa; int bytes_written; int bytes_read; + + /* Used in streaming mode. Caller should read from reply_body. */ + char *sbuf; + size_t sblen; }; /* The _url variant is probably more useful than the raw version. The raw @@ -82,3 +88,7 @@ struct http_request are also supported (using ssl_client). */ struct http_request *http_dorequest( char *host, int port, int ssl, char *request, http_input_function func, gpointer data ); struct http_request *http_dorequest_url( char *url_string, http_input_function func, gpointer data ); + +/* For streaming connections only; flushes len bytes at the start of the buffer. */ +void http_flush_bytes( struct http_request *req, size_t len ); +void http_close( struct http_request *req ); diff --git a/lib/json.c b/lib/json.c new file mode 100644 index 00000000..52e22d0e --- /dev/null +++ b/lib/json.c @@ -0,0 +1,743 @@ + +/* vim: set et ts=3 sw=3 ft=c: + * + * Copyright (C) 2012 James McLaughlin et al. All rights reserved. + * https://github.com/udp/json-parser + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "json.h" + +#ifdef _MSC_VER + #ifndef _CRT_SECURE_NO_WARNINGS + #define _CRT_SECURE_NO_WARNINGS + #endif +#endif + +#ifdef __cplusplus + const struct _json_value json_value_none; /* zero-d by ctor */ +#else + const struct _json_value json_value_none = { 0 }; +#endif + +#include <glib.h> +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> + +typedef unsigned short json_uchar; + +static unsigned char hex_value (json_char c) +{ + if (c >= 'A' && c <= 'F') + return (c - 'A') + 10; + + if (c >= 'a' && c <= 'f') + return (c - 'a') + 10; + + if (c >= '0' && c <= '9') + return c - '0'; + + return 0xFF; +} + +typedef struct +{ + json_settings settings; + int first_pass; + + unsigned long used_memory; + + unsigned int uint_max; + unsigned long ulong_max; + +} json_state; + +static void * json_alloc (json_state * state, unsigned long size, int zero) +{ + void * mem; + + if ((state->ulong_max - state->used_memory) < size) + return 0; + + if (state->settings.max_memory + && (state->used_memory += size) > state->settings.max_memory) + { + return 0; + } + + if (! (mem = zero ? calloc (size, 1) : malloc (size))) + return 0; + + return mem; +} + +static int new_value + (json_state * state, json_value ** top, json_value ** root, json_value ** alloc, json_type type) +{ + json_value * value; + int values_size; + + if (!state->first_pass) + { + value = *top = *alloc; + *alloc = (*alloc)->_reserved.next_alloc; + + if (!*root) + *root = value; + + switch (value->type) + { + case json_array: + + if (! (value->u.array.values = (json_value **) json_alloc + (state, value->u.array.length * sizeof (json_value *), 0)) ) + { + return 0; + } + + break; + + case json_object: + + values_size = sizeof (*value->u.object.values) * value->u.object.length; + + if (! ((*(void **) &value->u.object.values) = json_alloc + (state, values_size + ((unsigned long) value->u.object.values), 0)) ) + { + return 0; + } + + value->_reserved.object_mem = (*(char **) &value->u.object.values) + values_size; + + break; + + case json_string: + + if (! (value->u.string.ptr = (json_char *) json_alloc + (state, (value->u.string.length + 1) * sizeof (json_char), 0)) ) + { + return 0; + } + + break; + + default: + break; + }; + + value->u.array.length = 0; + + return 1; + } + + value = (json_value *) json_alloc (state, sizeof (json_value), 1); + + if (!value) + return 0; + + if (!*root) + *root = value; + + value->type = type; + value->parent = *top; + + if (*alloc) + (*alloc)->_reserved.next_alloc = value; + + *alloc = *top = value; + + return 1; +} + +#define e_off \ + ((int) (i - cur_line_begin)) + +#define whitespace \ + case '\n': ++ cur_line; cur_line_begin = i; \ + case ' ': case '\t': case '\r' + +#define string_add(b) \ + do { if (!state.first_pass) string [string_length] = b; ++ string_length; } while (0); + +const static int + flag_next = 1, flag_reproc = 2, flag_need_comma = 4, flag_seek_value = 8, flag_exponent = 16, + flag_got_exponent_sign = 32, flag_escaped = 64, flag_string = 128, flag_need_colon = 256, + flag_done = 512; + +json_value * json_parse_ex (json_settings * settings, const json_char * json, char * error_buf) +{ + json_char error [128]; + unsigned int cur_line; + const json_char * cur_line_begin, * i; + json_value * top, * root, * alloc = 0; + json_state state; + int flags; + + error[0] = '\0'; + + memset (&state, 0, sizeof (json_state)); + memcpy (&state.settings, settings, sizeof (json_settings)); + + memset (&state.uint_max, 0xFF, sizeof (state.uint_max)); + memset (&state.ulong_max, 0xFF, sizeof (state.ulong_max)); + + state.uint_max -= 8; /* limit of how much can be added before next check */ + state.ulong_max -= 8; + + for (state.first_pass = 1; state.first_pass >= 0; -- state.first_pass) + { + json_uchar uchar; + unsigned char uc_b1, uc_b2, uc_b3, uc_b4; + json_char * string; + unsigned int string_length; + + top = root = 0; + flags = flag_seek_value; + + cur_line = 1; + cur_line_begin = json; + + for (i = json ;; ++ i) + { + json_char b = *i; + + if (flags & flag_done) + { + if (!b) + break; + + switch (b) + { + whitespace: + continue; + + default: + sprintf (error, "%d:%d: Trailing garbage: `%c`", cur_line, e_off, b); + goto e_failed; + }; + } + + if (flags & flag_string) + { + if (!b) + { sprintf (error, "Unexpected EOF in string (at %d:%d)", cur_line, e_off); + goto e_failed; + } + + if (string_length > state.uint_max) + goto e_overflow; + + if (flags & flag_escaped) + { + flags &= ~ flag_escaped; + + switch (b) + { + case 'b': string_add ('\b'); break; + case 'f': string_add ('\f'); break; + case 'n': string_add ('\n'); break; + case 'r': string_add ('\r'); break; + case 't': string_add ('\t'); break; + case 'u': + + if ((uc_b1 = hex_value (*++ i)) == 0xFF || (uc_b2 = hex_value (*++ i)) == 0xFF + || (uc_b3 = hex_value (*++ i)) == 0xFF || (uc_b4 = hex_value (*++ i)) == 0xFF) + { + sprintf (error, "Invalid character value `%c` (at %d:%d)", b, cur_line, e_off); + goto e_failed; + } + + uc_b1 = uc_b1 * 16 + uc_b2; + uc_b2 = uc_b3 * 16 + uc_b4; + + uchar = ((json_char) uc_b1) * 256 + uc_b2; + + if (sizeof (json_char) >= sizeof (json_uchar) || (uc_b1 == 0 && uc_b2 <= 0x7F)) + { + string_add ((json_char) uchar); + break; + } + + if (uchar <= 0x7FF) + { + if (state.first_pass) + string_length += 2; + else + { string [string_length ++] = 0xC0 | ((uc_b2 & 0xC0) >> 6) | ((uc_b1 & 0x7) << 2); + string [string_length ++] = 0x80 | (uc_b2 & 0x3F); + } + + break; + } + + if (state.first_pass) + string_length += 3; + else + { string [string_length ++] = 0xE0 | ((uc_b1 & 0xF0) >> 4); + string [string_length ++] = 0x80 | ((uc_b1 & 0xF) << 2) | ((uc_b2 & 0xC0) >> 6); + string [string_length ++] = 0x80 | (uc_b2 & 0x3F); + } + + break; + + default: + string_add (b); + }; + + continue; + } + + if (b == '\\') + { + flags |= flag_escaped; + continue; + } + + if (b == '"') + { + if (!state.first_pass) + string [string_length] = 0; + + flags &= ~ flag_string; + string = 0; + + switch (top->type) + { + case json_string: + + top->u.string.length = string_length; + flags |= flag_next; + + break; + + case json_object: + + if (state.first_pass) + (*(json_char **) &top->u.object.values) += string_length + 1; + else + { + top->u.object.values [top->u.object.length].name + = (json_char *) top->_reserved.object_mem; + + (*(json_char **) &top->_reserved.object_mem) += string_length + 1; + } + + flags |= flag_seek_value | flag_need_colon; + continue; + + default: + break; + }; + } + else + { + string_add (b); + continue; + } + } + + if (flags & flag_seek_value) + { + switch (b) + { + whitespace: + continue; + + case ']': + + if (top->type == json_array) + flags = (flags & ~ (flag_need_comma | flag_seek_value)) | flag_next; + else if (!state.settings.settings & json_relaxed_commas) + { sprintf (error, "%d:%d: Unexpected ]", cur_line, e_off); + goto e_failed; + } + + break; + + default: + + if (flags & flag_need_comma) + { + if (b == ',') + { flags &= ~ flag_need_comma; + continue; + } + else + { sprintf (error, "%d:%d: Expected , before %c", cur_line, e_off, b); + goto e_failed; + } + } + + if (flags & flag_need_colon) + { + if (b == ':') + { flags &= ~ flag_need_colon; + continue; + } + else + { sprintf (error, "%d:%d: Expected : before %c", cur_line, e_off, b); + goto e_failed; + } + } + + flags &= ~ flag_seek_value; + + switch (b) + { + case '{': + + if (!new_value (&state, &top, &root, &alloc, json_object)) + goto e_alloc_failure; + + continue; + + case '[': + + if (!new_value (&state, &top, &root, &alloc, json_array)) + goto e_alloc_failure; + + flags |= flag_seek_value; + continue; + + case '"': + + if (!new_value (&state, &top, &root, &alloc, json_string)) + goto e_alloc_failure; + + flags |= flag_string; + + string = top->u.string.ptr; + string_length = 0; + + continue; + + case 't': + + if (*(++ i) != 'r' || *(++ i) != 'u' || *(++ i) != 'e') + goto e_unknown_value; + + if (!new_value (&state, &top, &root, &alloc, json_boolean)) + goto e_alloc_failure; + + top->u.boolean = 1; + + flags |= flag_next; + break; + + case 'f': + + if (*(++ i) != 'a' || *(++ i) != 'l' || *(++ i) != 's' || *(++ i) != 'e') + goto e_unknown_value; + + if (!new_value (&state, &top, &root, &alloc, json_boolean)) + goto e_alloc_failure; + + flags |= flag_next; + break; + + case 'n': + + if (*(++ i) != 'u' || *(++ i) != 'l' || *(++ i) != 'l') + goto e_unknown_value; + + if (!new_value (&state, &top, &root, &alloc, json_null)) + goto e_alloc_failure; + + flags |= flag_next; + break; + + default: + + if (isdigit (b) || b == '-') + { + if (!new_value (&state, &top, &root, &alloc, json_integer)) + goto e_alloc_failure; + + flags &= ~ (flag_exponent | flag_got_exponent_sign); + + if (state.first_pass) + continue; + + if (top->type == json_double) + top->u.dbl = g_ascii_strtod (i, (json_char **) &i); + else + top->u.integer = g_ascii_strtoll (i, (json_char **) &i, 10); + + flags |= flag_next | flag_reproc; + } + else + { sprintf (error, "%d:%d: Unexpected %c when seeking value", cur_line, e_off, b); + goto e_failed; + } + }; + }; + } + else + { + switch (top->type) + { + case json_object: + + switch (b) + { + whitespace: + continue; + + case '"': + + if (flags & flag_need_comma && (!state.settings.settings & json_relaxed_commas)) + { + sprintf (error, "%d:%d: Expected , before \"", cur_line, e_off); + goto e_failed; + } + + flags |= flag_string; + + string = (json_char *) top->_reserved.object_mem; + string_length = 0; + + break; + + case '}': + + flags = (flags & ~ flag_need_comma) | flag_next; + break; + + case ',': + + if (flags & flag_need_comma) + { + flags &= ~ flag_need_comma; + break; + } + + default: + + sprintf (error, "%d:%d: Unexpected `%c` in object", cur_line, e_off, b); + goto e_failed; + }; + + break; + + case json_integer: + case json_double: + + if (isdigit (b)) + continue; + + if (b == 'e' || b == 'E') + { + if (!(flags & flag_exponent)) + { + flags |= flag_exponent; + top->type = json_double; + + continue; + } + } + else if (b == '+' || b == '-') + { + if (flags & flag_exponent && !(flags & flag_got_exponent_sign)) + { + flags |= flag_got_exponent_sign; + continue; + } + } + else if (b == '.' && top->type == json_integer) + { + top->type = json_double; + continue; + } + + flags |= flag_next | flag_reproc; + break; + + default: + break; + }; + } + + if (flags & flag_reproc) + { + flags &= ~ flag_reproc; + -- i; + } + + if (flags & flag_next) + { + flags = (flags & ~ flag_next) | flag_need_comma; + + if (!top->parent) + { + /* root value done */ + + flags |= flag_done; + continue; + } + + if (top->parent->type == json_array) + flags |= flag_seek_value; + + if (!state.first_pass) + { + json_value * parent = top->parent; + + switch (parent->type) + { + case json_object: + + parent->u.object.values + [parent->u.object.length].value = top; + + break; + + case json_array: + + parent->u.array.values + [parent->u.array.length] = top; + + break; + + default: + break; + }; + } + + if ( (++ top->parent->u.array.length) > state.uint_max) + goto e_overflow; + + top = top->parent; + + continue; + } + } + + alloc = root; + } + + return root; + +e_unknown_value: + + sprintf (error, "%d:%d: Unknown value", cur_line, e_off); + goto e_failed; + +e_alloc_failure: + + strcpy (error, "Memory allocation failure"); + goto e_failed; + +e_overflow: + + sprintf (error, "%d:%d: Too long (caught overflow)", cur_line, e_off); + goto e_failed; + +e_failed: + + if (error_buf) + { + if (*error) + strcpy (error_buf, error); + else + strcpy (error_buf, "Unknown error"); + } + + if (state.first_pass) + alloc = root; + + while (alloc) + { + top = alloc->_reserved.next_alloc; + free (alloc); + alloc = top; + } + + if (!state.first_pass) + json_value_free (root); + + return 0; +} + +json_value * json_parse (const json_char * json) +{ + json_settings settings; + memset (&settings, 0, sizeof (json_settings)); + + return json_parse_ex (&settings, json, 0); +} + +void json_value_free (json_value * value) +{ + json_value * cur_value; + + if (!value) + return; + + value->parent = 0; + + while (value) + { + switch (value->type) + { + case json_array: + + if (!value->u.array.length) + { + free (value->u.array.values); + break; + } + + value = value->u.array.values [-- value->u.array.length]; + continue; + + case json_object: + + if (!value->u.object.length) + { + free (value->u.object.values); + break; + } + + value = value->u.object.values [-- value->u.object.length].value; + continue; + + case json_string: + + free (value->u.string.ptr); + break; + + default: + break; + }; + + cur_value = value; + value = value->parent; + free (cur_value); + } +} + + diff --git a/lib/json.h b/lib/json.h new file mode 100644 index 00000000..3800565a --- /dev/null +++ b/lib/json.h @@ -0,0 +1,192 @@ + +/* vim: set et ts=3 sw=3 ft=c: + * + * Copyright (C) 2012 James McLaughlin et al. All rights reserved. + * https://github.com/udp/json-parser + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _JSON_H +#define _JSON_H + +#ifndef json_char + #define json_char char +#endif + +#ifdef __cplusplus + + #include <string.h> + + extern "C" + { + +#endif + +typedef struct +{ + unsigned long max_memory; + int settings; + +} json_settings; + +#define json_relaxed_commas 1 + +typedef enum +{ + json_none, + json_object, + json_array, + json_integer, + json_double, + json_string, + json_boolean, + json_null + +} json_type; + +extern const struct _json_value json_value_none; + +typedef struct _json_value +{ + struct _json_value * parent; + + json_type type; + + union + { + int boolean; + long long integer; + double dbl; + + struct + { + unsigned int length; + json_char * ptr; /* null terminated */ + + } string; + + struct + { + unsigned int length; + + struct + { + json_char * name; + struct _json_value * value; + + } * values; + + } object; + + struct + { + unsigned int length; + struct _json_value ** values; + + } array; + + } u; + + union + { + struct _json_value * next_alloc; + void * object_mem; + + } _reserved; + + + /* Some C++ operator sugar */ + + #ifdef __cplusplus + + public: + + inline _json_value () + { memset (this, 0, sizeof (_json_value)); + } + + inline const struct _json_value &operator [] (int index) const + { + if (type != json_array || index < 0 + || ((unsigned int) index) >= u.array.length) + { + return json_value_none; + } + + return *u.array.values [index]; + } + + inline const struct _json_value &operator [] (const char * index) const + { + if (type != json_object) + return json_value_none; + + for (unsigned int i = 0; i < u.object.length; ++ i) + if (!strcmp (u.object.values [i].name, index)) + return *u.object.values [i].value; + + return json_value_none; + } + + inline operator const char * () const + { + switch (type) + { + case json_string: + return u.string.ptr; + + default: + return ""; + }; + } + + inline operator long () const + { return u.integer; + } + + inline operator bool () const + { return u.boolean != 0; + } + + #endif + +} json_value; + +json_value * json_parse + (const json_char * json); + +json_value * json_parse_ex + (json_settings * settings, const json_char * json, char * error); + +void json_value_free (json_value *); + + +#ifdef __cplusplus + } /* extern "C" */ +#endif + +#endif + + diff --git a/lib/json_util.c b/lib/json_util.c new file mode 100644 index 00000000..941589ba --- /dev/null +++ b/lib/json_util.c @@ -0,0 +1,62 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Helper functions for json.c * +* * +* Copyright 2012 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Lesser General Public * +* License as published by the Free Software Foundation, version * +* 2.1. * +* * +* This library is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * +* Lesser General Public License for more details. * +* * +* You should have received a copy of the GNU Lesser General Public License * +* along with this library; if not, write to the Free Software Foundation, * +* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * +* * +****************************************************************************/ + +#include <stdlib.h> +#include <string.h> +#include <glib.h> + +#include "json_util.h" + +json_value *json_o_get( const json_value *obj, const json_char *name ) +{ + int i; + + if( !obj || obj->type != json_object ) + return NULL; + + for( i = 0; i < obj->u.object.length; ++ i) + if( strcmp( obj->u.object.values[i].name, name ) == 0 ) + return obj->u.object.values[i].value; + + return NULL; +} + +const char *json_o_str( const json_value *obj, const json_char *name ) +{ + json_value *ret = json_o_get( obj, name ); + + if( ret && ret->type == json_string ) + return ret->u.string.ptr; + else + return NULL; +} + +char *json_o_strdup( const json_value *obj, const json_char *name ) +{ + json_value *ret = json_o_get( obj, name ); + + if( ret && ret->type == json_string && ret->u.string.ptr ) + return g_memdup( ret->u.string.ptr, ret->u.string.length + 1 ); + else + return NULL; +} diff --git a/lib/json_util.h b/lib/json_util.h new file mode 100644 index 00000000..e0f1f7b8 --- /dev/null +++ b/lib/json_util.h @@ -0,0 +1,35 @@ +/***************************************************************************\ +* * +* BitlBee - An IRC to IM gateway * +* Helper functions for json.c * +* * +* Copyright 2012 Wilmer van der Gaast <wilmer@gaast.net> * +* * +* This library is free software; you can redistribute it and/or * +* modify it under the terms of the GNU Lesser General Public * +* License as published by the Free Software Foundation, version * +* 2.1. * +* * +* This library is distributed in the hope that it will be useful, * +* but WITHOUT ANY WARRANTY; without even the implied warranty of * +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * +* Lesser General Public License for more details. * +* * +* You should have received a copy of the GNU Lesser General Public License * +* along with this library; if not, write to the Free Software Foundation, * +* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * +* * +****************************************************************************/ + +#include "json.h" + +#define JSON_O_FOREACH(o, k, v) \ + char *k; json_value *v; int __i; \ + for( __i = 0; ( __i < (o)->u.object.length ) && \ + ( k = (o)->u.object.values[__i].name ) && \ + ( v = (o)->u.object.values[__i].value ); \ + __i ++ ) + +json_value *json_o_get( const json_value *obj, const json_char *name ); +const char *json_o_str( const json_value *obj, const json_char *name ); +char *json_o_strdup( const json_value *obj, const json_char *name ); diff --git a/lib/oauth2.c b/lib/oauth2.c index d3f6bc09..6921a6d5 100644 --- a/lib/oauth2.c +++ b/lib/oauth2.c @@ -3,7 +3,7 @@ * BitlBee - An IRC to IM gateway * * Simple OAuth client (consumer) implementation. * * * -* Copyright 2010-2011 Wilmer van der Gaast <wilmer@gaast.net> * +* Copyright 2010-2012 Wilmer van der Gaast <wilmer@gaast.net> * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -25,6 +25,7 @@ #include "http_client.h" #include "oauth2.h" #include "oauth.h" +#include "json.h" #include "url.h" char *oauth2_url( const struct oauth2_service *sp ) @@ -43,7 +44,6 @@ struct oauth2_access_token_data gpointer data; }; -static char *oauth2_json_dumb_get( const char *json, const char *key ); static void oauth2_access_token_done( struct http_request *req ); int oauth2_access_token( const struct oauth2_service *sp, @@ -114,8 +114,22 @@ static void oauth2_access_token_done( struct http_request *req ) } else if( content_type && strstr( content_type, "application/json" ) ) { - atoken = oauth2_json_dumb_get( req->reply_body, "access_token" ); - rtoken = oauth2_json_dumb_get( req->reply_body, "refresh_token" ); + json_value *js = json_parse( req->reply_body ); + if( js && js->type == json_object ) + { + int i; + + for( i = 0; i < js->u.object.length; i ++ ) + { + if( js->u.object.values[i].value->type != json_string ) + continue; + if( strcmp( js->u.object.values[i].name, "access_token" ) == 0 ) + atoken = g_strdup( js->u.object.values[i].value->u.string.ptr ); + if( strcmp( js->u.object.values[i].name, "refresh_token" ) == 0 ) + rtoken = g_strdup( js->u.object.values[i].value->u.string.ptr ); + } + } + json_value_free( js ); } else { @@ -136,62 +150,3 @@ static void oauth2_access_token_done( struct http_request *req ) g_free( rtoken ); g_free( cb_data ); } - -/* Super dumb. I absolutely refuse to use/add a complete json parser library - (adding a new dependency to BitlBee for the first time in.. 6 years?) just - to parse 100 bytes of data. So I have to do my own parsing because OAuth2 - dropped support for XML. (GRRR!) This is very dumb and for example won't - work for integer values, nor will it strip/handle backslashes. */ -static char *oauth2_json_dumb_get( const char *json, const char *key ) -{ - int is_key = 0; /* 1 == reading key, 0 == reading value */ - int found_key = 0; - - while( json && *json ) - { - /* Grab strings and see if they're what we're looking for. */ - if( *json == '"' || *json == '\'' ) - { - char q = *json; - const char *str_start; - json ++; - str_start = json; - - while( *json ) - { - /* \' and \" are not string terminators. */ - if( *json == '\\' && json[1] == q ) - json ++; - /* But without a \ it is. */ - else if( *json == q ) - break; - json ++; - } - if( *json == '\0' ) - return NULL; - - if( is_key && strncmp( str_start, key, strlen( key ) ) == 0 ) - { - found_key = 1; - } - else if( !is_key && found_key ) - { - char *ret = g_memdup( str_start, json - str_start + 1 ); - ret[json-str_start] = '\0'; - return ret; - } - - } - else if( *json == '{' || *json == ',' ) - { - found_key = 0; - is_key = 1; - } - else if( *json == ':' ) - is_key = 0; - - json ++; - } - - return NULL; -} diff --git a/lib/ssl_gnutls.c b/lib/ssl_gnutls.c index 41a76f09..45d24e6e 100644 --- a/lib/ssl_gnutls.c +++ b/lib/ssl_gnutls.c @@ -37,7 +37,7 @@ int ssl_errno = 0; static gboolean initialized = FALSE; -gnutls_certificate_credentials xcred; +gnutls_certificate_credentials_t xcred; #include <limits.h> @@ -59,7 +59,7 @@ struct scd char *hostname; gboolean verify; - gnutls_session session; + gnutls_session_t session; }; static gboolean ssl_connected( gpointer data, gint source, b_input_condition cond ); @@ -133,7 +133,7 @@ void *ssl_starttls( int fd, char *hostname, gboolean verify, ssl_input_function conn->func = func; conn->data = data; conn->inpa = -1; - conn->hostname = hostname; + conn->hostname = g_strdup( hostname ); /* For now, SSL verification is globally enabled by setting the cafile setting in bitlbee.conf. Commented out by default because probably @@ -170,9 +170,9 @@ static int verify_certificate_callback( gnutls_session_t session ) int gnutlsret; int verifyret = 0; gnutls_x509_crt_t cert; - const char *hostname; + struct scd *conn; - hostname = gnutls_session_get_ptr( session ); + conn = gnutls_session_get_ptr( session ); gnutlsret = gnutls_certificate_verify_peers2( session, &status ); if( gnutlsret < 0 ) @@ -210,7 +210,7 @@ static int verify_certificate_callback( gnutls_session_t session ) if( cert_list == NULL || gnutls_x509_crt_import( cert, &cert_list[0], GNUTLS_X509_FMT_DER ) < 0 ) return VERIFY_CERT_ERROR; - if( !gnutls_x509_crt_check_hostname( cert, hostname ) ) + if( !gnutls_x509_crt_check_hostname( cert, conn->hostname ) ) { verifyret |= VERIFY_CERT_INVALID; verifyret |= VERIFY_CERT_WRONG_HOSTNAME; @@ -266,8 +266,7 @@ static gboolean ssl_connected( gpointer data, gint source, b_input_condition con ssl_init(); gnutls_init( &conn->session, GNUTLS_CLIENT ); - if( conn->verify ) - gnutls_session_set_ptr( conn->session, (void *) conn->hostname ); + gnutls_session_set_ptr( conn->session, (void *) conn ); #if GNUTLS_VERSION_NUMBER < 0x020c00 gnutls_transport_set_lowat( conn->session, 0 ); #endif @@ -275,7 +274,7 @@ static gboolean ssl_connected( gpointer data, gint source, b_input_condition con gnutls_credentials_set( conn->session, GNUTLS_CRD_CERTIFICATE, xcred ); sock_make_nonblocking( conn->fd ); - gnutls_transport_set_ptr( conn->session, (gnutls_transport_ptr) GNUTLS_STUPID_CAST conn->fd ); + gnutls_transport_set_ptr( conn->session, (gnutls_transport_ptr_t) GNUTLS_STUPID_CAST conn->fd ); return ssl_handshake( data, source, cond ); } @@ -401,6 +400,7 @@ void ssl_disconnect( void *conn_ ) if( conn->session ) gnutls_deinit( conn->session ); + g_free( conn->hostname ); g_free( conn ); } |