Index: lib/Makefile
===================================================================
--- lib/Makefile	(revision 3bda2c2bc34fa21ef143f068a4f4f3feb7eb48d5)
+++ lib/Makefile	(revision c1bc24a856826973153c386e6ad52dcabb80a21b)
@@ -13,5 +13,5 @@
 
 # [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
Index: lib/http_client.c
===================================================================
--- lib/http_client.c	(revision 3bda2c2bc34fa21ef143f068a4f4f3feb7eb48d5)
+++ lib/http_client.c	(revision c1bc24a856826973153c386e6ad52dcabb80a21b)
@@ -193,10 +193,11 @@
 }
 
+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;
@@ -218,10 +219,10 @@
 				   packets that abort connections! \o/ */
 				
-				goto got_reply;
+				goto eof;
 			}
 		}
 		else if( st == 0 )
 		{
-			goto got_reply;
+			goto eof;
 		}
 	}
@@ -239,14 +240,57 @@
 		else if( st == 0 )
 		{
-			goto got_reply;
-		}
-	}
-	
-	if( st > 0 )
+			goto eof;
+		}
+	}
+	
+	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! */
@@ -255,10 +299,9 @@
 	                         http_incoming_data, req );
 	
-	if( ssl_pending( req->ssl ) )
-		return http_incoming_data( data, source, cond );
-	else
-		return FALSE;
-
-got_reply:
+	return FALSE;
+
+eof:
+	req->flags |= HTTPC_EOF;
+	
 	/* Maybe if the webserver is overloaded, or when there's bad SSL
 	   support... */
@@ -269,6 +312,48 @@
 	}
 	
+	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
@@ -289,5 +374,5 @@
 	{
 		req->status_string = g_strdup( "Malformed HTTP reply" );
-		goto cleanup;
+		return TRUE;
 	}
 	
@@ -306,5 +391,5 @@
 	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" );
@@ -349,5 +434,5 @@
 		{
 			req->status_string = g_strdup( "Can't locate Location: header" );
-			goto cleanup;
+			return TRUE;
 		}
 		
@@ -369,5 +454,5 @@
 			req->status_string = g_strdup( "Can't handle recursive redirects" );
 			
-			goto cleanup;
+			return TRUE;
 		}
 		else
@@ -380,5 +465,5 @@
 			s = strstr( loc, "\r\n" );
 			if( s == NULL )
-				goto cleanup;
+				return TRUE;
 			
 			url = g_new0( url_t, 1 );
@@ -389,5 +474,5 @@
 				req->status_string = g_strdup( "Malformed redirect URL" );
 				g_free( url );
-				goto cleanup;
+				return TRUE;
 			}
 			
@@ -401,5 +486,5 @@
 				req->status_string = g_strdup( "Error while rebuilding request string" );
 				g_free( url );
-				goto cleanup;
+				return TRUE;
 			}
 			
@@ -467,5 +552,5 @@
 			req->status_string = g_strdup( "Connection problem during redirect" );
 			g_free( new_request );
-			goto cleanup;
+			return TRUE;
 		}
 		
@@ -480,9 +565,33 @@
 	}
 	
-	/* Assume that a closed connection means we're finished, this indeed
-	   breaks with keep-alive connections and faulty connections. */
-	/* req->finished = 1; */
-
-cleanup:
+	return TRUE;
+}
+
+void http_flush_bytes( struct http_request *req, size_t len )
+{
+	if( len <= 0 || len > req->body_size || !( req->flags & HTTPC_STREAMING ) )
+		return;
+	
+	req->reply_body += len;
+	req->body_size -= len;
+	
+	if( req->reply_body - req->sbuf >= 512 )
+	{
+		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;
+	}
+}
+
+void http_close( struct http_request *req )
+{
+	if( !req )
+		return;
+	
+	if( req->inpa > 0 )
+		b_event_remove( req->inpa );
+	
 	if( req->ssl )
 		ssl_disconnect( req->ssl );
@@ -490,23 +599,5 @@
 		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;
 }
 
@@ -516,5 +607,5 @@
 	g_free( req->reply_headers );
 	g_free( req->status_string );
+	g_free( req->sbuf );
 	g_free( req );
 }
-
Index: lib/http_client.h
===================================================================
--- lib/http_client.h	(revision 3bda2c2bc34fa21ef143f068a4f4f3feb7eb48d5)
+++ lib/http_client.h	(revision c1bc24a856826973153c386e6ad52dcabb80a21b)
@@ -26,15 +26,10 @@
 /* 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>
@@ -42,4 +37,12 @@
 
 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: */
@@ -53,5 +56,5 @@
 	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. */
@@ -59,8 +62,8 @@
 	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;
@@ -68,5 +71,4 @@
 	
 	/* Please don't touch the things down here, you shouldn't need them. */
-	
 	void *ssl;
 	int fd;
@@ -75,4 +77,8 @@
 	int bytes_written;
 	int bytes_read;
+	
+	/* Used in streaming mode. Caller should read from reply_body. */
+	char *sbuf;
+	size_t sblen;
 };
 
@@ -83,2 +89,6 @@
 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 );
Index: lib/json.c
===================================================================
--- lib/json.c	(revision c1bc24a856826973153c386e6ad52dcabb80a21b)
+++ lib/json.c	(revision c1bc24a856826973153c386e6ad52dcabb80a21b)
@@ -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);
+   }
+}
+
+
Index: lib/json.h
===================================================================
--- lib/json.h	(revision c1bc24a856826973153c386e6ad52dcabb80a21b)
+++ lib/json.h	(revision c1bc24a856826973153c386e6ad52dcabb80a21b)
@@ -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
+
+
Index: lib/json_util.c
===================================================================
--- lib/json_util.c	(revision c1bc24a856826973153c386e6ad52dcabb80a21b)
+++ lib/json_util.c	(revision c1bc24a856826973153c386e6ad52dcabb80a21b)
@@ -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;
+}
Index: lib/json_util.h
===================================================================
--- lib/json_util.h	(revision c1bc24a856826973153c386e6ad52dcabb80a21b)
+++ lib/json_util.h	(revision c1bc24a856826973153c386e6ad52dcabb80a21b)
@@ -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 );
Index: lib/oauth2.c
===================================================================
--- lib/oauth2.c	(revision 3bda2c2bc34fa21ef143f068a4f4f3feb7eb48d5)
+++ lib/oauth2.c	(revision c1bc24a856826973153c386e6ad52dcabb80a21b)
@@ -4,5 +4,5 @@
 *  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     *
@@ -26,4 +26,5 @@
 #include "oauth2.h"
 #include "oauth.h"
+#include "json.h"
 #include "url.h"
 
@@ -44,5 +45,4 @@
 };
 
-static char *oauth2_json_dumb_get( const char *json, const char *key );
 static void oauth2_access_token_done( struct http_request *req );
 
@@ -115,6 +115,20 @@
 	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
@@ -137,61 +151,2 @@
 	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;
-}
Index: lib/ssl_gnutls.c
===================================================================
--- lib/ssl_gnutls.c	(revision 3bda2c2bc34fa21ef143f068a4f4f3feb7eb48d5)
+++ lib/ssl_gnutls.c	(revision c1bc24a856826973153c386e6ad52dcabb80a21b)
@@ -38,5 +38,5 @@
 
 static gboolean initialized = FALSE;
-gnutls_certificate_credentials xcred;
+gnutls_certificate_credentials_t xcred;
 
 #include <limits.h>
@@ -60,5 +60,5 @@
 	gboolean verify;
 	
-	gnutls_session session;
+	gnutls_session_t session;
 };
 
@@ -134,5 +134,5 @@
 	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
@@ -171,7 +171,7 @@
 	int verifyret = 0;
 	gnutls_x509_crt_t cert;
-	const char *hostname;
-	
-	hostname = gnutls_session_get_ptr( session );
+	struct scd *conn;
+	
+	conn = gnutls_session_get_ptr( session );
 
 	gnutlsret = gnutls_certificate_verify_peers2( session, &status );
@@ -211,5 +211,5 @@
 		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;
@@ -267,6 +267,5 @@
 	
 	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 );
@@ -276,5 +275,5 @@
 	
 	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 );
@@ -402,4 +401,5 @@
 	if( conn->session )
 		gnutls_deinit( conn->session );
+	g_free( conn->hostname );
 	g_free( conn );
 }
