diff options
| -rw-r--r-- | debian/copyright | 378 | ||||
| -rw-r--r-- | lib/Makefile | 2 | ||||
| -rw-r--r-- | lib/http_client.c | 174 | ||||
| -rw-r--r-- | lib/http_client.h | 38 | ||||
| -rw-r--r-- | lib/json.c | 742 | ||||
| -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 | ||||
| -rw-r--r-- | protocols/msn/msn.c | 1 | ||||
| -rw-r--r-- | protocols/msn/ns.c | 4 | ||||
| -rw-r--r-- | protocols/nogaim.c | 23 | ||||
| -rw-r--r-- | protocols/nogaim.h | 2 | ||||
| -rw-r--r-- | protocols/twitter/twitter.c | 23 | ||||
| -rw-r--r-- | protocols/twitter/twitter.h | 1 | ||||
| -rw-r--r-- | protocols/twitter/twitter_http.c | 31 | ||||
| -rw-r--r-- | protocols/twitter/twitter_http.h | 4 | ||||
| -rw-r--r-- | protocols/twitter/twitter_lib.c | 456 | ||||
| -rw-r--r-- | protocols/twitter/twitter_lib.h | 45 | 
20 files changed, 1623 insertions, 689 deletions
| diff --git a/debian/copyright b/debian/copyright index 42f70d03..9002c219 100644 --- a/debian/copyright +++ b/debian/copyright @@ -7,8 +7,17 @@ Authors: Wilmer van der Gaast, Sjoerd Hemminga, Jelmer Vernooij,           Maurits Dijkstra, Geert Mulders, Miklos Vajna and others.  Mainly Copyright 2002-2012 Wilmer van der Gaast. -Some parts are borrowed from Gaim (version 0.58) <http://gaim.sf.net/>. -For the copyrights on those parts, please read the Gaim source code. + + +Bits of third party code, also GPLed: +* Some parts (mostly protocols/oscar/) are borrowed from Gaim (version +  0.58), now known as Pidgin <http://www.pidgin.im/>. +* protocols/yahoo/ is libyahoo2 <http://libyahoo2.sf.net/>. + +Other license (but GPL-compatible): +* lib/json.[ch] is from <https://github.com/udp/json-parser> and licensed +  under the modified BSD license. +  BitlBee License: @@ -32,362 +41,15 @@ BitlBee License:  The SGML-formatted documentation is written by Jelmer Vernooij -<jelmer@nl.linux.org> under the GNU Free Documentation License: +<jelmer@samba.org> under the GNU Free Documentation License:  ============================================================================ -		GNU Free Documentation License -		   Version 1.1, March 2000 - - Copyright (C) 2000  Free Software Foundation, Inc. - 	51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - -0. PREAMBLE - -The purpose of this License is to make a manual, textbook, or other -written document "free" in the sense of freedom: to assure everyone -the effective freedom to copy and redistribute it, with or without -modifying it, either commercially or noncommercially.  Secondarily, -this License preserves for the author and publisher a way to get -credit for their work, while not being considered responsible for -modifications made by others. - -This License is a kind of "copyleft", which means that derivative -works of the document must themselves be free in the same sense.  It -complements the GNU General Public License, which is a copyleft -license designed for free software. - -We have designed this License in order to use it for manuals for free -software, because free software needs free documentation: a free -program should come with manuals providing the same freedoms that the -software does.  But this License is not limited to software manuals; -it can be used for any textual work, regardless of subject matter or -whether it is published as a printed book.  We recommend this License -principally for works whose purpose is instruction or reference. - - -1. APPLICABILITY AND DEFINITIONS - -This License applies to any manual or other work that contains a -notice placed by the copyright holder saying it can be distributed -under the terms of this License.  The "Document", below, refers to any -such manual or work.  Any member of the public is a licensee, and is -addressed as "you". - -A "Modified Version" of the Document means any work containing the -Document or a portion of it, either copied verbatim, or with -modifications and/or translated into another language. - -A "Secondary Section" is a named appendix or a front-matter section of -the Document that deals exclusively with the relationship of the -publishers or authors of the Document to the Document's overall subject -(or to related matters) and contains nothing that could fall directly -within that overall subject.  (For example, if the Document is in part a -textbook of mathematics, a Secondary Section may not explain any -mathematics.)  The relationship could be a matter of historical -connection with the subject or with related matters, or of legal, -commercial, philosophical, ethical or political position regarding -them. - -The "Invariant Sections" are certain Secondary Sections whose titles -are designated, as being those of Invariant Sections, in the notice -that says that the Document is released under this License. - -The "Cover Texts" are certain short passages of text that are listed, -as Front-Cover Texts or Back-Cover Texts, in the notice that says that -the Document is released under this License. - -A "Transparent" copy of the Document means a machine-readable copy, -represented in a format whose specification is available to the -general public, whose contents can be viewed and edited directly and -straightforwardly with generic text editors or (for images composed of -pixels) generic paint programs or (for drawings) some widely available -drawing editor, and that is suitable for input to text formatters or -for automatic translation to a variety of formats suitable for input -to text formatters.  A copy made in an otherwise Transparent file -format whose markup has been designed to thwart or discourage -subsequent modification by readers is not Transparent.  A copy that is -not "Transparent" is called "Opaque". - -Examples of suitable formats for Transparent copies include plain -ASCII without markup, Texinfo input format, LaTeX input format, SGML -or XML using a publicly available DTD, and standard-conforming simple -HTML designed for human modification.  Opaque formats include -PostScript, PDF, proprietary formats that can be read and edited only -by proprietary word processors, SGML or XML for which the DTD and/or -processing tools are not generally available, and the -machine-generated HTML produced by some word processors for output -purposes only. - -The "Title Page" means, for a printed book, the title page itself, -plus such following pages as are needed to hold, legibly, the material -this License requires to appear in the title page.  For works in -formats which do not have any title page as such, "Title Page" means -the text near the most prominent appearance of the work's title, -preceding the beginning of the body of the text. - - -2. VERBATIM COPYING - -You may copy and distribute the Document in any medium, either -commercially or noncommercially, provided that this License, the -copyright notices, and the license notice saying this License applies -to the Document are reproduced in all copies, and that you add no other -conditions whatsoever to those of this License.  You may not use -technical measures to obstruct or control the reading or further -copying of the copies you make or distribute.  However, you may accept -compensation in exchange for copies.  If you distribute a large enough -number of copies you must also follow the conditions in section 3. - -You may also lend copies, under the same conditions stated above, and -you may publicly display copies. - - -3. COPYING IN QUANTITY - -If you publish printed copies of the Document numbering more than 100, -and the Document's license notice requires Cover Texts, you must enclose -the copies in covers that carry, clearly and legibly, all these Cover -Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on -the back cover.  Both covers must also clearly and legibly identify -you as the publisher of these copies.  The front cover must present -the full title with all words of the title equally prominent and -visible.  You may add other material on the covers in addition. -Copying with changes limited to the covers, as long as they preserve -the title of the Document and satisfy these conditions, can be treated -as verbatim copying in other respects. - -If the required texts for either cover are too voluminous to fit -legibly, you should put the first ones listed (as many as fit -reasonably) on the actual cover, and continue the rest onto adjacent -pages. - -If you publish or distribute Opaque copies of the Document numbering -more than 100, you must either include a machine-readable Transparent -copy along with each Opaque copy, or state in or with each Opaque copy -a publicly-accessible computer-network location containing a complete -Transparent copy of the Document, free of added material, which the -general network-using public has access to download anonymously at no -charge using public-standard network protocols.  If you use the latter -option, you must take reasonably prudent steps, when you begin -distribution of Opaque copies in quantity, to ensure that this -Transparent copy will remain thus accessible at the stated location -until at least one year after the last time you distribute an Opaque -copy (directly or through your agents or retailers) of that edition to -the public. - -It is requested, but not required, that you contact the authors of the -Document well before redistributing any large number of copies, to give -them a chance to provide you with an updated version of the Document. - - -4. MODIFICATIONS - -You may copy and distribute a Modified Version of the Document under -the conditions of sections 2 and 3 above, provided that you release -the Modified Version under precisely this License, with the Modified -Version filling the role of the Document, thus licensing distribution -and modification of the Modified Version to whoever possesses a copy -of it.  In addition, you must do these things in the Modified Version: - -A. Use in the Title Page (and on the covers, if any) a title distinct -   from that of the Document, and from those of previous versions -   (which should, if there were any, be listed in the History section -   of the Document).  You may use the same title as a previous version -   if the original publisher of that version gives permission. -B. List on the Title Page, as authors, one or more persons or entities -   responsible for authorship of the modifications in the Modified -   Version, together with at least five of the principal authors of the -   Document (all of its principal authors, if it has less than five). -C. State on the Title page the name of the publisher of the -   Modified Version, as the publisher. -D. Preserve all the copyright notices of the Document. -E. Add an appropriate copyright notice for your modifications -   adjacent to the other copyright notices. -F. Include, immediately after the copyright notices, a license notice -   giving the public permission to use the Modified Version under the -   terms of this License, in the form shown in the Addendum below. -G. Preserve in that license notice the full lists of Invariant Sections -   and required Cover Texts given in the Document's license notice. -H. Include an unaltered copy of this License. -I. Preserve the section entitled "History", and its title, and add to -   it an item stating at least the title, year, new authors, and -   publisher of the Modified Version as given on the Title Page.  If -   there is no section entitled "History" in the Document, create one -   stating the title, year, authors, and publisher of the Document as -   given on its Title Page, then add an item describing the Modified -   Version as stated in the previous sentence. -J. Preserve the network location, if any, given in the Document for -   public access to a Transparent copy of the Document, and likewise -   the network locations given in the Document for previous versions -   it was based on.  These may be placed in the "History" section. -   You may omit a network location for a work that was published at -   least four years before the Document itself, or if the original -   publisher of the version it refers to gives permission. -K. In any section entitled "Acknowledgements" or "Dedications", -   preserve the section's title, and preserve in the section all the -   substance and tone of each of the contributor acknowledgements -   and/or dedications given therein. -L. Preserve all the Invariant Sections of the Document, -   unaltered in their text and in their titles.  Section numbers -   or the equivalent are not considered part of the section titles. -M. Delete any section entitled "Endorsements".  Such a section -   may not be included in the Modified Version. -N. Do not retitle any existing section as "Endorsements" -   or to conflict in title with any Invariant Section. - -If the Modified Version includes new front-matter sections or -appendices that qualify as Secondary Sections and contain no material -copied from the Document, you may at your option designate some or all -of these sections as invariant.  To do this, add their titles to the -list of Invariant Sections in the Modified Version's license notice. -These titles must be distinct from any other section titles. - -You may add a section entitled "Endorsements", provided it contains -nothing but endorsements of your Modified Version by various -parties--for example, statements of peer review or that the text has -been approved by an organization as the authoritative definition of a -standard. - -You may add a passage of up to five words as a Front-Cover Text, and a -passage of up to 25 words as a Back-Cover Text, to the end of the list -of Cover Texts in the Modified Version.  Only one passage of -Front-Cover Text and one of Back-Cover Text may be added by (or -through arrangements made by) any one entity.  If the Document already -includes a cover text for the same cover, previously added by you or -by arrangement made by the same entity you are acting on behalf of, -you may not add another; but you may replace the old one, on explicit -permission from the previous publisher that added the old one. - -The author(s) and publisher(s) of the Document do not by this License -give permission to use their names for publicity for or to assert or -imply endorsement of any Modified Version. - - -5. COMBINING DOCUMENTS - -You may combine the Document with other documents released under this -License, under the terms defined in section 4 above for modified -versions, provided that you include in the combination all of the -Invariant Sections of all of the original documents, unmodified, and -list them all as Invariant Sections of your combined work in its -license notice. - -The combined work need only contain one copy of this License, and -multiple identical Invariant Sections may be replaced with a single -copy.  If there are multiple Invariant Sections with the same name but -different contents, make the title of each such section unique by -adding at the end of it, in parentheses, the name of the original -author or publisher of that section if known, or else a unique number. -Make the same adjustment to the section titles in the list of -Invariant Sections in the license notice of the combined work. - -In the combination, you must combine any sections entitled "History" -in the various original documents, forming one section entitled -"History"; likewise combine any sections entitled "Acknowledgements", -and any sections entitled "Dedications".  You must delete all sections -entitled "Endorsements." - - -6. COLLECTIONS OF DOCUMENTS - -You may make a collection consisting of the Document and other documents -released under this License, and replace the individual copies of this -License in the various documents with a single copy that is included in -the collection, provided that you follow the rules of this License for -verbatim copying of each of the documents in all other respects. - -You may extract a single document from such a collection, and distribute -it individually under this License, provided you insert a copy of this -License into the extracted document, and follow this License in all -other respects regarding verbatim copying of that document. - - -7. AGGREGATION WITH INDEPENDENT WORKS - -A compilation of the Document or its derivatives with other separate -and independent documents or works, in or on a volume of a storage or -distribution medium, does not as a whole count as a Modified Version -of the Document, provided no compilation copyright is claimed for the -compilation.  Such a compilation is called an "aggregate", and this -License does not apply to the other self-contained works thus compiled -with the Document, on account of their being thus compiled, if they -are not themselves derivative works of the Document. - -If the Cover Text requirement of section 3 is applicable to these -copies of the Document, then if the Document is less than one quarter -of the entire aggregate, the Document's Cover Texts may be placed on -covers that surround only the Document within the aggregate. -Otherwise they must appear on covers around the whole aggregate. - - -8. TRANSLATION - -Translation is considered a kind of modification, so you may -distribute translations of the Document under the terms of section 4. -Replacing Invariant Sections with translations requires special -permission from their copyright holders, but you may include -translations of some or all Invariant Sections in addition to the -original versions of these Invariant Sections.  You may include a -translation of this License provided that you also include the -original English version of this License.  In case of a disagreement -between the translation and the original English version of this -License, the original English version will prevail. - - -9. TERMINATION - -You may not copy, modify, sublicense, or distribute the Document except -as expressly provided for under this License.  Any other attempt to -copy, modify, sublicense or distribute the Document is void, and will -automatically terminate your rights under this License.  However, -parties who have received copies, or rights, from you under this -License will not have their licenses terminated so long as such -parties remain in full compliance. - - -10. FUTURE REVISIONS OF THIS LICENSE - -The Free Software Foundation may publish new, revised versions -of the GNU Free Documentation License from time to time.  Such new -versions will be similar in spirit to the present version, but may -differ in detail to address new problems or concerns.  See -http://www.gnu.org/copyleft/. - -Each version of the License is given a distinguishing version number. -If the Document specifies that a particular numbered version of this -License "or any later version" applies to it, you have the option of -following the terms and conditions either of that specified version or -of any later version that has been published (not as a draft) by the -Free Software Foundation.  If the Document does not specify a version -number of this License, you may choose any version ever published (not -as a draft) by the Free Software Foundation. - - -ADDENDUM: How to use this License for your documents - -To use this License in a document you have written, include a copy of -the License in the document and put the following copyright and -license notices just after the title page: - -      Copyright (c)  YEAR  YOUR NAME. -      Permission is granted to copy, distribute and/or modify this document -      under the terms of the GNU Free Documentation License, Version 1.1 -      or any later version published by the Free Software Foundation; -      with the Invariant Sections being LIST THEIR TITLES, with the -      Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. -      A copy of the license is included in the section entitled "GNU -      Free Documentation License". - -If you have no Invariant Sections, write "with no Invariant Sections" -instead of saying which ones are invariant.  If you have no -Front-Cover Texts, write "no Front-Cover Texts" instead of -"Front-Cover Texts being LIST"; likewise for Back-Cover Texts. - -If your document contains nontrivial examples of program code, we -recommend releasing these examples in parallel under your choice of -free software license, such as the GNU General Public License, -to permit their use in free software. +Copyright (c)  2002-2012 Jelmer Vernooij, Wilmer van der Gaast. + +Permission is granted to copy, distribute and/or modify this document +under the terms of the GNU Free Documentation License, Version 1.1 or +any later version published by the Free Software Foundation; with no +Invariant Sections, with no Front-Cover Texts, and with no Back-Cover +Texts.  A copy of the license is included in the section entitled `GNU +Free Documentation License''.  ============================================================================ 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..793c5cc1 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 *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,38 @@ 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->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 +603,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..48b711a4 100644 --- a/lib/http_client.h +++ b/lib/http_client.h @@ -25,23 +25,24 @@  /* 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, +} http_client_flags_t; +  /* Your callback function should look like this: */  typedef void (*http_input_function)( struct http_request * ); @@ -52,28 +53,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 +86,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..e37c83fa --- /dev/null +++ b/lib/json.c @@ -0,0 +1,742 @@ + +/* 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 <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 & 0x3) << 3); +                           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 = strtod (i, (json_char **) &i); +                           else +                              top->u.integer = strtol (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 87965d04..62db3500 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  	{ @@ -135,62 +149,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 );  } diff --git a/protocols/msn/msn.c b/protocols/msn/msn.c index 71570ce0..845f9cf8 100644 --- a/protocols/msn/msn.c +++ b/protocols/msn/msn.c @@ -52,6 +52,7 @@ static void msn_login( account_t *acc )  	struct msn_data *md = g_new0( struct msn_data, 1 );  	ic->proto_data = md; +	ic->flags |= OPT_PONGS | OPT_PONGED;  	if( strchr( acc->user, '@' ) == NULL )  	{ diff --git a/protocols/msn/ns.c b/protocols/msn/ns.c index d9a558f9..7acf4654 100644 --- a/protocols/msn/ns.c +++ b/protocols/msn/ns.c @@ -576,6 +576,10 @@ static int msn_ns_command( struct msn_handler_data *handler, char **cmd, int num  		if( num_parts >= 7 )  			handler->msglen = atoi( cmd[6] );  	} +	else if( strcmp( cmd[0], "QNG" ) == 0 ) +	{ +		ic->flags |= OPT_PONGED; +	}  	else if( isdigit( cmd[0][0] ) )  	{  		int num = atoi( cmd[0] ); diff --git a/protocols/nogaim.c b/protocols/nogaim.c index 773e3877..2f85a3eb 100644 --- a/protocols/nogaim.c +++ b/protocols/nogaim.c @@ -260,12 +260,32 @@ static gboolean send_keepalive( gpointer d, gint fd, b_input_condition cond )  {  	struct im_connection *ic = d; +	if( ( ic->flags & OPT_PONGS ) && !( ic->flags & OPT_PONGED ) ) +	{ +		/* This protocol is expected to ack keepalives and hasn't +		   since the last time we were here. */ +		imcb_error( ic, "Connection timeout" ); +		imc_logout( ic, TRUE ); +		return FALSE; +	} +	ic->flags &= ~OPT_PONGED; +	  	if( ic->acc->prpl->keepalive )  		ic->acc->prpl->keepalive( ic );  	return TRUE;  } +void start_keepalives( struct im_connection *ic, int interval ) +{ +	b_event_remove( ic->keepalive ); +	ic->keepalive = b_timeout_add( interval, send_keepalive, ic ); +	 +	/* Connecting successfully counts as a first successful pong. */ +	if( ic->flags & OPT_PONGS ) +		ic->flags |= OPT_PONGED; +} +  void imcb_connected( struct im_connection *ic )  {  	/* MSN servers sometimes redirect you to a different server and do @@ -276,9 +296,8 @@ void imcb_connected( struct im_connection *ic )  	imcb_log( ic, "Logged in" ); -	b_event_remove( ic->keepalive ); -	ic->keepalive = b_timeout_add( 60000, send_keepalive, ic );  	ic->flags |= OPT_LOGGED_IN; +	start_keepalives( ic, 60000 );  	/* Necessary to send initial presence status, even if we're not away. */  	imc_away_send_update( ic ); diff --git a/protocols/nogaim.h b/protocols/nogaim.h index 0536ce69..aa6ba7c0 100644 --- a/protocols/nogaim.h +++ b/protocols/nogaim.h @@ -67,6 +67,8 @@  #define OPT_TYPING      0x00000100 /* Some pieces of code make assumptions */  #define OPT_THINKING    0x00000200 /* about these values... Stupid me! */  #define OPT_NOOTR       0x00001000 /* protocol not suitable for OTR */ +#define OPT_PONGS       0x00010000 /* Service sends us keep-alives */ +#define OPT_PONGED      0x00020000 /* Received a keep-alive during last interval */  /* ok. now the fun begins. first we create a connection structure */  struct im_connection diff --git a/protocols/twitter/twitter.c b/protocols/twitter/twitter.c index 93ef4ae2..6bde497a 100644 --- a/protocols/twitter/twitter.c +++ b/protocols/twitter/twitter.c @@ -4,6 +4,7 @@  *  Simple module to facilitate twitter functionality.                       *  *                                                                           *  *  Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com>                 * +*  Copyright 2010-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               * @@ -65,11 +66,19 @@ static void twitter_main_loop_start(struct im_connection *ic)  	// Run this once. After this queue the main loop function.  	twitter_main_loop(ic, -1, 0); - -	// Queue the main_loop -	// Save the return value, so we can remove the timeout on logout. -	td->main_loop_id = -	    b_timeout_add(set_getint(&ic->acc->set, "fetch_interval") * 1000, twitter_main_loop, ic); +	 +	if (set_getbool(&ic->acc->set, "stream")) { +		/* That fetch was just to get backlog, the stream will give +		   us the rest. \o/ */ +		twitter_open_stream(ic); +		ic->flags |= OPT_PONGS; +	} else { +		/* Not using the streaming API, so keep polling the old- +		   fashioned way. :-( */ +		td->main_loop_id = +		    b_timeout_add(set_getint(&ic->acc->set, "fetch_interval") * 1000, +		                  twitter_main_loop, ic); +	}  }  static void twitter_oauth_start(struct im_connection *ic); @@ -278,6 +287,9 @@ static void twitter_init(account_t * acc)  	s = set_add(&acc->set, "show_old_mentions", "20", set_eval_int, acc);  	s = set_add(&acc->set, "strip_newlines", "false", set_eval_bool, acc); +	 +	s = set_add(&acc->set, "stream", "true", set_eval_bool, acc); +	s->flags |= ACC_SET_OFFLINE_ONLY;  }  /** @@ -362,6 +374,7 @@ static void twitter_logout(struct im_connection *ic)  		imcb_chat_free(td->timeline_gc);  	if (td) { +		http_close(td->stream);  		oauth_info_free(td->oauth_info);  		g_free(td->user);  		g_free(td->prefix); diff --git a/protocols/twitter/twitter.h b/protocols/twitter/twitter.h index 14e43824..f2afb754 100644 --- a/protocols/twitter/twitter.h +++ b/protocols/twitter/twitter.h @@ -56,6 +56,7 @@ struct twitter_data  	guint64 last_status_id; /* For undo */  	gint main_loop_id; +	struct http_request *stream;  	struct groupchat *timeline_gc;  	gint http_fails;  	twitter_flags_t flags; diff --git a/protocols/twitter/twitter_http.c b/protocols/twitter/twitter_http.c index dbac5461..216dad9e 100644 --- a/protocols/twitter/twitter_http.c +++ b/protocols/twitter/twitter_http.c @@ -46,14 +46,15 @@ static char *twitter_url_append(char *url, char *key, char *value);   * Do a request.   * This is actually pretty generic function... Perhaps it should move to the lib/http_client.c   */ -void *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, -		   gpointer data, int is_post, char **arguments, int arguments_len) +struct http_request *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, +		                  gpointer data, int is_post, char **arguments, int arguments_len)  {  	struct twitter_data *td = ic->proto_data;  	char *tmp;  	GString *request = g_string_new("");  	void *ret;  	char *url_arguments; +	url_t *base_url = NULL;  	url_arguments = g_strdup(""); @@ -66,20 +67,34 @@ void *twitter_http(struct im_connection *ic, char *url_string, http_input_functi  			url_arguments = tmp;  		}  	} +	 +	if (strstr(url_string, "://")) { +		base_url = g_new0(url_t, 1); +		if (!url_set(base_url, url_string)) { +			g_free(base_url); +			return NULL; +		} +	} +	  	// Make the request.  	g_string_printf(request, "%s %s%s%s%s HTTP/1.0\r\n"  			"Host: %s\r\n"  			"User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n",  			is_post ? "POST" : "GET", -			td->url_path, url_string, -			is_post ? "" : "?", is_post ? "" : url_arguments, td->url_host); +			base_url ? base_url->file : td->url_path, +			base_url ? "" : url_string, +			is_post ? "" : "?", is_post ? "" : url_arguments, +			base_url ? base_url->host : td->url_host);  	// If a pass and user are given we append them to the request.  	if (td->oauth_info) {  		char *full_header;  		char *full_url; -		full_url = g_strconcat(set_getstr(&ic->acc->set, "base_url"), url_string, NULL); +		if (base_url) +			full_url = g_strdup(url_string); +		else +			full_url = g_strconcat(set_getstr(&ic->acc->set, "base_url"), url_string, NULL);  		full_header = oauth_http_header(td->oauth_info, is_post ? "POST" : "GET",  						full_url, url_arguments); @@ -108,10 +123,14 @@ void *twitter_http(struct im_connection *ic, char *url_string, http_input_functi  		g_string_append(request, "\r\n");  	} -	ret = http_dorequest(td->url_host, td->url_port, td->url_ssl, request->str, func, data); +	if (base_url) +		ret = http_dorequest(base_url->host, base_url->port, base_url->proto == PROTO_HTTPS, request->str, func, data); +	else +		ret = http_dorequest(td->url_host, td->url_port, td->url_ssl, request->str, func, data);  	g_free(url_arguments);  	g_string_free(request, TRUE); +	g_free(base_url);  	return ret;  } diff --git a/protocols/twitter/twitter_http.h b/protocols/twitter/twitter_http.h index 393a1c26..02a7ea43 100644 --- a/protocols/twitter/twitter_http.h +++ b/protocols/twitter/twitter_http.h @@ -29,8 +29,8 @@  struct oauth_info; -void *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, -                   gpointer data, int is_post, char** arguments, int arguments_len); +struct http_request *twitter_http(struct im_connection *ic, char *url_string, http_input_function func, +                                  gpointer data, int is_post, char** arguments, int arguments_len);  #endif //_TWITTER_HTTP_H diff --git a/protocols/twitter/twitter_lib.c b/protocols/twitter/twitter_lib.c index 4a09cbb1..57c84f24 100644 --- a/protocols/twitter/twitter_lib.c +++ b/protocols/twitter/twitter_lib.c @@ -34,8 +34,8 @@  #include "url.h"  #include "misc.h"  #include "base64.h" -#include "xmltree.h"  #include "twitter_lib.h" +#include "json_util.h"  #include <ctype.h>  #include <errno.h> @@ -167,32 +167,34 @@ static void twitter_add_buddy(struct im_connection *ic, char *name, const char *  char *twitter_parse_error(struct http_request *req)  {  	static char *ret = NULL; -	struct xt_node *root, *node, *err; +	json_value *root, *err;  	g_free(ret);  	ret = NULL;  	if (req->body_size > 0) { -		root = xt_from_string(req->reply_body, req->body_size); -		 -		for (node = root; node; node = node->next) -			if ((err = xt_find_node(node->children, "error")) && err->text_len > 0) { -				ret = g_strdup_printf("%s (%s)", req->status_string, err->text); -				break; -			} - -		xt_free_node(root); +		root = json_parse(req->reply_body); +		err = json_o_get(root, "errors"); +		if (err && err->type == json_array && (err = err->u.array.values[0]) && +		    err->type == json_object) { +			const char *msg = json_o_str(err, "message"); +			if (msg) +				ret = g_strdup_printf("%s (%s)", req->status_string, msg); +		} +		json_value_free(root);  	}  	return ret ? ret : req->status_string;  } -static struct xt_node *twitter_parse_response(struct im_connection *ic, struct http_request *req) +/* WATCH OUT: This function might or might not destroy your connection. +   Sub-optimal indeed, but just be careful when this returns NULL! */ +static json_value *twitter_parse_response(struct im_connection *ic, struct http_request *req)  {  	gboolean logging_in = !(ic->flags & OPT_LOGGED_IN);  	gboolean periodic;  	struct twitter_data *td = ic->proto_data; -	struct xt_node *ret; +	json_value *ret;  	char path[64] = "", *s;  	if ((s = strchr(req->request, ' '))) { @@ -210,7 +212,8 @@ static struct xt_node *twitter_parse_response(struct im_connection *ic, struct h  		/* IIRC Twitter once had an outage where they were randomly  		   throwing 401s so I'll keep treating this one as fatal  		   only during login. */ -		imcb_error(ic, "Authentication failure"); +		imcb_error(ic, "Authentication failure (%s)", +		               twitter_parse_error(req));  		imc_logout(ic, FALSE);  		return NULL;  	} else if (req->status_code != 200) { @@ -226,7 +229,7 @@ static struct xt_node *twitter_parse_response(struct im_connection *ic, struct h  		td->http_fails = 0;  	} -	if ((ret = xt_from_string(req->reply_body, req->body_size)) == NULL) { +	if ((ret = json_parse(req->reply_body)) == NULL) {  		imcb_error(ic, "Could not retrieve %s: %s",  			   path, "XML parse error");  	} @@ -250,45 +253,36 @@ void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor)  }  /** - * Function to help fill a list. - */ -static xt_status twitter_xt_next_cursor(struct xt_node *node, struct twitter_xml_list *txl) -{ -	char *end = NULL; - -	if (node->text) -		txl->next_cursor = g_ascii_strtoll(node->text, &end, 10); -	if (end == NULL) -		txl->next_cursor = -1; - -	return XT_HANDLED; -} - -/**   * Fill a list of ids.   */ -static xt_status twitter_xt_get_friends_id_list(struct xt_node *node, struct twitter_xml_list *txl) +static gboolean twitter_xt_get_friends_id_list(json_value *node, struct twitter_xml_list *txl)  { -	struct xt_node *child; +	json_value *c; +	int i;  	// Set the list type.  	txl->type = TXL_ID; -	// The root <statuses> node should hold the list of statuses <status> -	// Walk over the nodes children. -	for (child = node->children; child; child = child->next) { -		if (g_strcasecmp("ids", child->name) == 0) { -			struct xt_node *idc; -			for (idc = child->children; idc; idc = idc->next) -				if (g_strcasecmp(idc->name, "id") == 0) -					txl->list = g_slist_prepend(txl->list, -						g_memdup(idc->text, idc->text_len + 1)); -		} else if (g_strcasecmp("next_cursor", child->name) == 0) { -			twitter_xt_next_cursor(child, txl); -		} -	} +	c = json_o_get(node, "ids"); +	if (!c || c->type != json_array) +		return FALSE; -	return XT_HANDLED; +	for (i = 0; i < c->u.array.length; i ++) { +		if (c->u.array.values[i]->type != json_integer) +			continue; +		 +		txl->list = g_slist_prepend(txl->list, +			g_strdup_printf("%lld", c->u.array.values[i]->u.integer)); +		 +	} +	 +	c = json_o_get(node, "next_cursor"); +	if (c && c->type == json_integer) +		txl->next_cursor = c->u.integer; +	else +		txl->next_cursor = -1; +	 +	return TRUE;  }  static void twitter_get_users_lookup(struct im_connection *ic); @@ -299,7 +293,7 @@ static void twitter_get_users_lookup(struct im_connection *ic);  static void twitter_http_get_friends_ids(struct http_request *req)  {  	struct im_connection *ic; -	struct xt_node *parsed; +	json_value *parsed;  	struct twitter_xml_list *txl;  	struct twitter_data *td; @@ -321,8 +315,9 @@ static void twitter_http_get_friends_ids(struct http_request *req)  	// Parse the data.  	if (!(parsed = twitter_parse_response(ic, req)))  		return; +	  	twitter_xt_get_friends_id_list(parsed, txl); -	xt_free_node(parsed); +	json_value_free(parsed);  	td->follow_ids = txl->list;  	if (txl->next_cursor) @@ -337,7 +332,7 @@ static void twitter_http_get_friends_ids(struct http_request *req)  	txl_free(txl);  } -static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl); +static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl);  static void twitter_http_get_users_lookup(struct http_request *req);  static void twitter_get_users_lookup(struct im_connection *ic) @@ -378,7 +373,7 @@ static void twitter_get_users_lookup(struct im_connection *ic)  static void twitter_http_get_users_lookup(struct http_request *req)  {  	struct im_connection *ic = req->data; -	struct xt_node *parsed; +	json_value *parsed;  	struct twitter_xml_list *txl;  	GSList *l = NULL;  	struct twitter_xml_user *user; @@ -394,7 +389,7 @@ static void twitter_http_get_users_lookup(struct http_request *req)  	if (!(parsed = twitter_parse_response(ic, req)))  		return;  	twitter_xt_get_users(parsed, txl); -	xt_free_node(parsed); +	json_value_free(parsed);  	// Add the users as buddies.  	for (l = txl->list; l; l = g_slist_next(l)) { @@ -408,25 +403,15 @@ static void twitter_http_get_users_lookup(struct http_request *req)  	twitter_get_users_lookup(ic);  } -/** - * Function to fill a twitter_xml_user struct. - * It sets: - *  - the name and - *  - the screen_name. - */ -static xt_status twitter_xt_get_user(struct xt_node *node, struct twitter_xml_user *txu) +struct twitter_xml_user *twitter_xt_get_user(const json_value *node)  { -	struct xt_node *child; - -	// Walk over the nodes children. -	for (child = node->children; child; child = child->next) { -		if (g_strcasecmp("name", child->name) == 0) { -			txu->name = g_memdup(child->text, child->text_len + 1); -		} else if (g_strcasecmp("screen_name", child->name) == 0) { -			txu->screen_name = g_memdup(child->text, child->text_len + 1); -		} -	} -	return XT_HANDLED; +	struct twitter_xml_user *txu; +	 +	txu = g_new0(struct twitter_xml_user, 1); +	txu->name = g_strdup(json_o_str(node, "name")); +	txu->screen_name = g_strdup(json_o_str(node, "screen_name")); +	 +	return txu;  }  /** @@ -434,26 +419,26 @@ static xt_status twitter_xt_get_user(struct xt_node *node, struct twitter_xml_us   * It sets:   *  - all <user>s from the <users> element.   */ -static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl) +static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl)  {  	struct twitter_xml_user *txu; -	struct xt_node *child; +	int i;  	// Set the type of the list.  	txl->type = TXL_USER; +	if (!node || node->type != json_array) +		return FALSE; +  	// The root <users> node should hold the list of users <user>  	// Walk over the nodes children. -	for (child = node->children; child; child = child->next) { -		if (g_strcasecmp("user", child->name) == 0) { -			txu = g_new0(struct twitter_xml_user, 1); -			twitter_xt_get_user(child, txu); -			// Put the item in the front of the list. +	for (i = 0; i < node->u.array.length; i ++) { +		txu = twitter_xt_get_user(node->u.array.values[i]); +		if (txu)  			txl->list = g_slist_prepend(txl->list, txu); -		}  	} -	return XT_HANDLED; +	return TRUE;  }  #ifdef __GLIBC__ @@ -462,6 +447,8 @@ static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_l  #define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S +0000 %Y"  #endif +static char* expand_entities(char* text, const json_value *entities); +  /**   * Function to fill a twitter_xml_status struct.   * It sets: @@ -470,31 +457,34 @@ static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_l   *  - the status id and   *  - the user in a twitter_xml_user struct.   */ -static xt_status twitter_xt_get_status(struct xt_node *node, struct twitter_xml_status *txs) +static gboolean twitter_xt_get_status(const json_value *node, struct twitter_xml_status *txs)  { -	struct xt_node *child, *rt = NULL; - -	// Walk over the nodes children. -	for (child = node->children; child; child = child->next) { -		if (g_strcasecmp("text", child->name) == 0) { -			txs->text = g_memdup(child->text, child->text_len + 1); -		} else if (g_strcasecmp("retweeted_status", child->name) == 0) { -			rt = child; -		} else if (g_strcasecmp("created_at", child->name) == 0) { +	const json_value *rt = NULL, *entities = NULL; +	 +	if (node->type != json_object) +		return FALSE; + +	JSON_O_FOREACH (node, k, v) { +		if (strcmp("text", k) == 0 && v->type == json_string) { +			txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1); +		} else if (strcmp("retweeted_status", k) == 0 && v->type == json_object) { +			rt = v; +		} else if (strcmp("created_at", k) == 0 && v->type == json_string) {  			struct tm parsed;  			/* Very sensitive to changes to the formatting of  			   this field. :-( Also assumes the timezone used  			   is UTC since C time handling functions suck. */ -			if (strptime(child->text, TWITTER_TIME_FORMAT, &parsed) != NULL) +			if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL)  				txs->created_at = mktime_utc(&parsed); -		} else if (g_strcasecmp("user", child->name) == 0) { -			txs->user = g_new0(struct twitter_xml_user, 1); -			twitter_xt_get_user(child, txs->user); -		} else if (g_strcasecmp("id", child->name) == 0) { -			txs->id = g_ascii_strtoull(child->text, NULL, 10); -		} else if (g_strcasecmp("in_reply_to_status_id", child->name) == 0) { -			txs->reply_to = g_ascii_strtoull(child->text, NULL, 10); +		} else if (strcmp("user", k) == 0 && v->type == json_object) { +			txs->user = twitter_xt_get_user(v); +		} else if (strcmp("id", k) == 0 && v->type == json_integer) { +			txs->id = v->u.integer; +		} else if (strcmp("in_reply_to_status_id", k) == 0 && v->type == json_integer) { +			txs->reply_to = v->u.integer; +		} else if (strcmp("entities", k) == 0 && v->type == json_object) { +			entities = v;  		}  	} @@ -502,45 +492,86 @@ static xt_status twitter_xt_get_status(struct xt_node *node, struct twitter_xml_  	   wasn't truncated because it may be lying. */  	if (rt) {  		struct twitter_xml_status *rtxs = g_new0(struct twitter_xml_status, 1); -		if (twitter_xt_get_status(rt, rtxs) != XT_HANDLED) { +		if (!twitter_xt_get_status(rt, rtxs)) {  			txs_free(rtxs); -			return XT_HANDLED; +			return TRUE;  		}  		g_free(txs->text);  		txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text);  		txs_free(rtxs); -	} else { -		struct xt_node *urls, *url; +	} else if (entities) { +		txs->text = expand_entities(txs->text, entities); +	} + +	return txs->text && txs->user && txs->id; +} + +/** + * Function to fill a twitter_xml_status struct (DM variant). + */ +static gboolean twitter_xt_get_dm(const json_value *node, struct twitter_xml_status *txs) +{ +	const json_value *entities = NULL; +	 +	if (node->type != json_object) +		return FALSE; + +	JSON_O_FOREACH (node, k, v) { +		if (strcmp("text", k) == 0 && v->type == json_string) { +			txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1); +		} else if (strcmp("created_at", k) == 0 && v->type == json_string) { +			struct tm parsed; + +			/* Very sensitive to changes to the formatting of +			   this field. :-( Also assumes the timezone used +			   is UTC since C time handling functions suck. */ +			if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL) +				txs->created_at = mktime_utc(&parsed); +		} else if (strcmp("sender", k) == 0 && v->type == json_object) { +			txs->user = twitter_xt_get_user(v); +		} else if (strcmp("id", k) == 0 && v->type == json_integer) { +			txs->id = v->u.integer; +		} +	} + +	if (entities) { +		txs->text = expand_entities(txs->text, entities); +	} + +	return txs->text && txs->user && txs->id; +} + +static char* expand_entities(char* text, const json_value *entities) { +	JSON_O_FOREACH (entities, k, v) { +		int i; -		urls = xt_find_path(node, "entities"); -		if (urls != NULL) -			urls = urls->children; -		for (; urls; urls = urls->next) { -			if (strcmp(urls->name, "urls") != 0 && strcmp(urls->name, "media") != 0) +		if (v->type != json_array) +			continue; +		if (strcmp(k, "urls") != 0 && strcmp(k, "media") != 0) +			continue; +		 +		for (i = 0; i < v->u.array.length; i ++) { +			if (v->u.array.values[i]->type != json_object)  				continue; -			for (url = urls ? urls->children : NULL; url; url = url->next) { -				/* "short" is a reserved word. :-P */ -				struct xt_node *kort = xt_find_node(url->children, "url"); -				struct xt_node *disp = xt_find_node(url->children, "display_url"); -				char *pos, *new; -				 -				if (!kort || !kort->text || !disp || !disp->text || -				    !(pos = strstr(txs->text, kort->text))) -					continue; -				 -				*pos = '\0'; -				new = g_strdup_printf("%s%s <%s>%s", txs->text, kort->text, -				                      disp->text, pos + strlen(kort->text)); -				 -				g_free(txs->text); -				txs->text = new; -			} +			const char *kort = json_o_str(v->u.array.values[i], "url"); +			const char *disp = json_o_str(v->u.array.values[i], "display_url"); +			char *pos, *new; +			 +			if (!kort || !disp || !(pos = strstr(text, kort))) +				continue; +			 +			*pos = '\0'; +			new = g_strdup_printf("%s%s <%s>%s", text, kort, +			                      disp, pos + strlen(kort)); +			 +			g_free(text); +			text = new;  		}  	} - -	return XT_HANDLED; +	 +	return text;  }  /** @@ -549,40 +580,39 @@ static xt_status twitter_xt_get_status(struct xt_node *node, struct twitter_xml_   *  - all <status>es within the <status> element and   *  - the next_cursor.   */ -static xt_status twitter_xt_get_status_list(struct im_connection *ic, struct xt_node *node, -					    struct twitter_xml_list *txl) +static gboolean twitter_xt_get_status_list(struct im_connection *ic, const json_value *node, +                                           struct twitter_xml_list *txl)  {  	struct twitter_xml_status *txs; -	struct xt_node *child;  	bee_user_t *bu; +	int i;  	// Set the type of the list.  	txl->type = TXL_STATUS; +	 +	if (node->type != json_array) +		return FALSE;  	// The root <statuses> node should hold the list of statuses <status>  	// Walk over the nodes children. -	for (child = node->children; child; child = child->next) { -		if (g_strcasecmp("status", child->name) == 0) { -			txs = g_new0(struct twitter_xml_status, 1); -			twitter_xt_get_status(child, txs); -			// Put the item in the front of the list. -			txl->list = g_slist_prepend(txl->list, txs); - -			if (txs->user && txs->user->screen_name && -			    (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) { -				struct twitter_user_data *tud = bu->data; - -				if (txs->id > tud->last_id) { -					tud->last_id = txs->id; -					tud->last_time = txs->created_at; -				} +	for (i = 0; i < node->u.array.length; i ++) { +		txs = g_new0(struct twitter_xml_status, 1); +		twitter_xt_get_status(node->u.array.values[i], txs); +		// Put the item in the front of the list. +		txl->list = g_slist_prepend(txl->list, txs); + +		if (txs->user && txs->user->screen_name && +		    (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) { +			struct twitter_user_data *tud = bu->data; + +			if (txs->id > tud->last_id) { +				tud->last_id = txs->id; +				tud->last_time = txs->created_at;  			} -		} else if (g_strcasecmp("next_cursor", child->name) == 0) { -			twitter_xt_next_cursor(child, txl);  		}  	} -	return XT_HANDLED; +	return TRUE;  }  static char *twitter_msg_add_id(struct im_connection *ic, @@ -739,8 +769,102 @@ static void twitter_private_message_chat(struct im_connection *ic, GSList * list  	}  } -static void twitter_http_get_home_timeline(struct http_request *req); -static void twitter_http_get_mentions(struct http_request *req); +static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o); + +static void twitter_http_stream(struct http_request *req) +{ +	struct im_connection *ic = req->data; +	struct twitter_data *td; +	json_value *parsed; +	int len = 0; +	char c, *nl; +	 +	if (!g_slist_find(twitter_connections, ic)) +		return; +	 +	ic->flags |= OPT_PONGED; +	td = ic->proto_data; +	 +	if ((req->flags & HTTPC_EOF) || !req->reply_body) { +		td->stream = NULL; +		imcb_error(ic, "Stream closed (%s)", req->status_string); +		imc_logout(ic, TRUE); +		return; +	} +	 +	printf( "%d bytes in stream\n", req->body_size ); +	 +	/* MUST search for CRLF, not just LF: +	   https://dev.twitter.com/docs/streaming-apis/processing#Parsing_responses */ +	nl = strstr(req->reply_body, "\r\n"); +	 +	if (!nl) { +		printf("Incomplete data\n"); +		return; +	} +	 +	len = nl - req->reply_body; +	if (len > 0) { +		c = req->reply_body[len]; +		req->reply_body[len] = '\0'; +		 +		printf("JSON: %s\n", req->reply_body); +		printf("parsed: %p\n", (parsed = json_parse(req->reply_body))); +		if (parsed) { +			twitter_stream_handle_object(ic, parsed); +		} +		json_value_free(parsed); +		req->reply_body[len] = c; +	} +	 +	http_flush_bytes(req, len + 2); +	 +	/* One notification might bring multiple events! */ +	if (req->body_size > 0) +		twitter_http_stream(req); +} + +static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o) +{ +	struct twitter_xml_status *txs = g_new0(struct twitter_xml_status, 1); +	json_value *c; +	 +	if (twitter_xt_get_status(o, txs)) { +		GSList *output = g_slist_append(NULL, txs); +		twitter_groupchat(ic, output); +		txs_free(txs); +		g_slist_free(output); +		return TRUE; +	} else if ((c = json_o_get(o, "direct_message")) && +	           twitter_xt_get_dm(c, txs)) { +		GSList *output = g_slist_append(NULL, txs); +		twitter_private_message_chat(ic, output); +		txs_free(txs); +		g_slist_free(output); +		return TRUE; +	} +	txs_free(txs); +	return FALSE; +} + +gboolean twitter_open_stream(struct im_connection *ic) +{ +	struct twitter_data *td = ic->proto_data; +	char *args[2] = {"with", "followings"}; +	 +	if ((td->stream = twitter_http(ic, TWITTER_USER_STREAM_URL, +	                               twitter_http_stream, ic, 0, args, 2))) { +		/* This flag must be enabled or we'll get no data until EOF +		   (which err, kind of, defeats the purpose of a streaming API). */ +		td->stream->flags |= HTTPC_STREAMING; +		return TRUE; +	} +	 +	return FALSE; +} + +static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor); +static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor);  /**   * Get the timeline with optionally mentions @@ -822,10 +946,13 @@ void twitter_flush_timeline(struct im_connection *ic)  	td->home_timeline_obj = td->mentions_obj = NULL;  } +static void twitter_http_get_home_timeline(struct http_request *req); +static void twitter_http_get_mentions(struct http_request *req); +  /**   * Get the timeline.   */ -void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor) +static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)  {  	struct twitter_data *td = ic->proto_data; @@ -861,7 +988,7 @@ void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)  /**   * Get mentions.   */ -void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor) +static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)  {  	struct twitter_data *td = ic->proto_data; @@ -892,9 +1019,7 @@ void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)  	}  	g_free(args[1]); -	if (td->timeline_id) { -		g_free(args[5]); -	} +	g_free(args[5]);  }  /** @@ -904,7 +1029,7 @@ static void twitter_http_get_home_timeline(struct http_request *req)  {  	struct im_connection *ic = req->data;  	struct twitter_data *td; -	struct xt_node *parsed; +	json_value *parsed;  	struct twitter_xml_list *txl;  	// Check if the connection is still active. @@ -920,11 +1045,14 @@ static void twitter_http_get_home_timeline(struct http_request *req)  	if (!(parsed = twitter_parse_response(ic, req)))  		goto end;  	twitter_xt_get_status_list(ic, parsed, txl); -	xt_free_node(parsed); +	json_value_free(parsed);  	td->home_timeline_obj = txl;        end: +	if (!g_slist_find(twitter_connections, ic)) +		return; +  	td->flags |= TWITTER_GOT_TIMELINE;  	twitter_flush_timeline(ic); @@ -937,7 +1065,7 @@ static void twitter_http_get_mentions(struct http_request *req)  {  	struct im_connection *ic = req->data;  	struct twitter_data *td; -	struct xt_node *parsed; +	json_value *parsed;  	struct twitter_xml_list *txl;  	// Check if the connection is still active. @@ -953,11 +1081,14 @@ static void twitter_http_get_mentions(struct http_request *req)  	if (!(parsed = twitter_parse_response(ic, req)))  		goto end;  	twitter_xt_get_status_list(ic, parsed, txl); -	xt_free_node(parsed); +	json_value_free(parsed);  	td->mentions_obj = txl;        end: +	if (!g_slist_find(twitter_connections, ic)) +		return; +  	td->flags |= TWITTER_GOT_MENTIONS;  	twitter_flush_timeline(ic); @@ -971,7 +1102,7 @@ static void twitter_http_post(struct http_request *req)  {  	struct im_connection *ic = req->data;  	struct twitter_data *td; -	struct xt_node *parsed, *node; +	json_value *parsed, *id;  	// Check if the connection is still active.  	if (!g_slist_find(twitter_connections, ic)) @@ -983,9 +1114,8 @@ static void twitter_http_post(struct http_request *req)  	if (!(parsed = twitter_parse_response(ic, req)))  		return; -	if ((node = xt_find_node(parsed, "status")) && -	    (node = xt_find_node(node->children, "id")) && node->text) -		td->last_status_id = g_ascii_strtoull(node->text, NULL, 10); +	if ((id = json_o_get(parsed, "id")) && id->type == json_integer) +		td->last_status_id = id->u.integer;  }  /** @@ -1031,7 +1161,7 @@ void twitter_status_destroy(struct im_connection *ic, guint64 id)  {  	char *url;  	url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_DESTROY_URL, -	                      (unsigned long long) id, ".xml"); +	                      (unsigned long long) id, ".json");  	twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);  	g_free(url);  } @@ -1040,7 +1170,7 @@ void twitter_status_retweet(struct im_connection *ic, guint64 id)  {  	char *url;  	url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_RETWEET_URL, -	                      (unsigned long long) id, ".xml"); +	                      (unsigned long long) id, ".json");  	twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);  	g_free(url);  } @@ -1066,7 +1196,7 @@ void twitter_favourite_tweet(struct im_connection *ic, guint64 id)  {  	char *url;  	url = g_strdup_printf("%s%llu%s", TWITTER_FAVORITE_CREATE_URL, -	                      (unsigned long long) id, ".xml"); +	                      (unsigned long long) id, ".json");  	twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);  	g_free(url);  } diff --git a/protocols/twitter/twitter_lib.h b/protocols/twitter/twitter_lib.h index 2404e4eb..d5f5b16a 100644 --- a/protocols/twitter/twitter_lib.h +++ b/protocols/twitter/twitter_lib.h @@ -28,46 +28,46 @@  #include "nogaim.h"  #include "twitter_http.h" -#define TWITTER_API_URL "http://api.twitter.com/1" +#define TWITTER_API_URL "http://api.twitter.com/1.1"  #define IDENTICA_API_URL "https://identi.ca/api"  /* Status URLs */ -#define TWITTER_STATUS_UPDATE_URL "/statuses/update.xml" +#define TWITTER_STATUS_UPDATE_URL "/statuses/update.json"  #define TWITTER_STATUS_SHOW_URL "/statuses/show/"  #define TWITTER_STATUS_DESTROY_URL "/statuses/destroy/"  #define TWITTER_STATUS_RETWEET_URL "/statuses/retweet/"  /* Timeline URLs */ -#define TWITTER_PUBLIC_TIMELINE_URL "/statuses/public_timeline.xml" -#define TWITTER_FEATURED_USERS_URL "/statuses/featured.xml" -#define TWITTER_FRIENDS_TIMELINE_URL "/statuses/friends_timeline.xml" -#define TWITTER_HOME_TIMELINE_URL "/statuses/home_timeline.xml" -#define TWITTER_MENTIONS_URL "/statuses/mentions.xml" -#define TWITTER_USER_TIMELINE_URL "/statuses/user_timeline.xml" +#define TWITTER_PUBLIC_TIMELINE_URL "/statuses/public_timeline.json" +#define TWITTER_FEATURED_USERS_URL "/statuses/featured.json" +#define TWITTER_FRIENDS_TIMELINE_URL "/statuses/friends_timeline.json" +#define TWITTER_HOME_TIMELINE_URL "/statuses/home_timeline.json" +#define TWITTER_MENTIONS_URL "/statuses/mentions_timeline.json" +#define TWITTER_USER_TIMELINE_URL "/statuses/user_timeline.json"  /* Users URLs */ -#define TWITTER_USERS_LOOKUP_URL "/users/lookup.xml" +#define TWITTER_USERS_LOOKUP_URL "/users/lookup.json"  /* Direct messages URLs */ -#define TWITTER_DIRECT_MESSAGES_URL "/direct_messages.xml" -#define TWITTER_DIRECT_MESSAGES_NEW_URL "/direct_messages/new.xml" -#define TWITTER_DIRECT_MESSAGES_SENT_URL "/direct_messages/sent.xml" +#define TWITTER_DIRECT_MESSAGES_URL "/direct_messages.json" +#define TWITTER_DIRECT_MESSAGES_NEW_URL "/direct_messages/new.json" +#define TWITTER_DIRECT_MESSAGES_SENT_URL "/direct_messages/sent.json"  #define TWITTER_DIRECT_MESSAGES_DESTROY_URL "/direct_messages/destroy/"  /* Friendships URLs */ -#define TWITTER_FRIENDSHIPS_CREATE_URL "/friendships/create.xml" -#define TWITTER_FRIENDSHIPS_DESTROY_URL "/friendships/destroy.xml" -#define TWITTER_FRIENDSHIPS_SHOW_URL "/friendships/show.xml" +#define TWITTER_FRIENDSHIPS_CREATE_URL "/friendships/create.json" +#define TWITTER_FRIENDSHIPS_DESTROY_URL "/friendships/destroy.json" +#define TWITTER_FRIENDSHIPS_SHOW_URL "/friendships/show.json"  /* Social graphs URLs */ -#define TWITTER_FRIENDS_IDS_URL "/friends/ids.xml" -#define TWITTER_FOLLOWERS_IDS_URL "/followers/ids.xml" +#define TWITTER_FRIENDS_IDS_URL "/friends/ids.json" +#define TWITTER_FOLLOWERS_IDS_URL "/followers/ids.json"  /* Account URLs */ -#define TWITTER_ACCOUNT_RATE_LIMIT_URL "/account/rate_limit_status.xml" +#define TWITTER_ACCOUNT_RATE_LIMIT_URL "/account/rate_limit_status.json"  /* Favorites URLs */ -#define TWITTER_FAVORITES_GET_URL "/favorites.xml" +#define TWITTER_FAVORITES_GET_URL "/favorites.json"  #define TWITTER_FAVORITE_CREATE_URL "/favorites/create/"  #define TWITTER_FAVORITE_DESTROY_URL "/favorites/destroy/" @@ -76,12 +76,13 @@  #define TWITTER_BLOCKS_DESTROY_URL "/blocks/destroy/"  /* Report spam */ -#define TWITTER_REPORT_SPAM_URL "/report_spam.xml" +#define TWITTER_REPORT_SPAM_URL "/report_spam.json" +#define TWITTER_USER_STREAM_URL "https://userstream.twitter.com/1.1/user.json" + +gboolean twitter_open_stream(struct im_connection *ic);  void twitter_get_timeline(struct im_connection *ic, gint64 next_cursor);  void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor); -void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor); -void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor);  void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor);  void twitter_post_status(struct im_connection *ic, char *msg, guint64 in_reply_to); | 
