Changes in / [b006464:e1d3f98]


Ignore:
Files:
4 deleted
16 edited

Legend:

Unmodified
Added
Removed
  • debian/copyright

    rb006464 re1d3f98  
    88
    99Mainly Copyright 2002-2012 Wilmer van der Gaast.
    10 
    11 
    12 Bits of third party code, also GPLed:
    13 * Some parts (mostly protocols/oscar/) are borrowed from Gaim (version
    14   0.58), now known as Pidgin <http://www.pidgin.im/>.
    15 * protocols/yahoo/ is libyahoo2 <http://libyahoo2.sf.net/>.
    16 
    17 Other license (but GPL-compatible):
    18 * lib/json.[ch] is from <https://github.com/udp/json-parser> and licensed
    19   under the modified BSD license.
    20 
     10Some parts are borrowed from Gaim (version 0.58) <http://gaim.sf.net/>.
     11For the copyrights on those parts, please read the Gaim source code.
    2112
    2213BitlBee License:
     
    4233
    4334The SGML-formatted documentation is written by Jelmer Vernooij
    44 <jelmer@samba.org> under the GNU Free Documentation License:
     35<jelmer@nl.linux.org> under the GNU Free Documentation License:
    4536
    4637============================================================================
    47 Copyright (c)  2002-2012 Jelmer Vernooij, Wilmer van der Gaast.
    48 
    49 Permission is granted to copy, distribute and/or modify this document
    50 under the terms of the GNU Free Documentation License, Version 1.1 or
    51 any later version published by the Free Software Foundation; with no
    52 Invariant Sections, with no Front-Cover Texts, and with no Back-Cover
    53 Texts.  A copy of the license is included in the section entitled `GNU
    54 Free Documentation License''.
     38                GNU Free Documentation License
     39                   Version 1.1, March 2000
     40
     41 Copyright (C) 2000  Free Software Foundation, Inc.
     42        51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.
     43 Everyone is permitted to copy and distribute verbatim copies
     44 of this license document, but changing it is not allowed.
     45
     46
     470. PREAMBLE
     48
     49The purpose of this License is to make a manual, textbook, or other
     50written document "free" in the sense of freedom: to assure everyone
     51the effective freedom to copy and redistribute it, with or without
     52modifying it, either commercially or noncommercially.  Secondarily,
     53this License preserves for the author and publisher a way to get
     54credit for their work, while not being considered responsible for
     55modifications made by others.
     56
     57This License is a kind of "copyleft", which means that derivative
     58works of the document must themselves be free in the same sense.  It
     59complements the GNU General Public License, which is a copyleft
     60license designed for free software.
     61
     62We have designed this License in order to use it for manuals for free
     63software, because free software needs free documentation: a free
     64program should come with manuals providing the same freedoms that the
     65software does.  But this License is not limited to software manuals;
     66it can be used for any textual work, regardless of subject matter or
     67whether it is published as a printed book.  We recommend this License
     68principally for works whose purpose is instruction or reference.
     69
     70
     711. APPLICABILITY AND DEFINITIONS
     72
     73This License applies to any manual or other work that contains a
     74notice placed by the copyright holder saying it can be distributed
     75under the terms of this License.  The "Document", below, refers to any
     76such manual or work.  Any member of the public is a licensee, and is
     77addressed as "you".
     78
     79A "Modified Version" of the Document means any work containing the
     80Document or a portion of it, either copied verbatim, or with
     81modifications and/or translated into another language.
     82
     83A "Secondary Section" is a named appendix or a front-matter section of
     84the Document that deals exclusively with the relationship of the
     85publishers or authors of the Document to the Document's overall subject
     86(or to related matters) and contains nothing that could fall directly
     87within that overall subject.  (For example, if the Document is in part a
     88textbook of mathematics, a Secondary Section may not explain any
     89mathematics.)  The relationship could be a matter of historical
     90connection with the subject or with related matters, or of legal,
     91commercial, philosophical, ethical or political position regarding
     92them.
     93
     94The "Invariant Sections" are certain Secondary Sections whose titles
     95are designated, as being those of Invariant Sections, in the notice
     96that says that the Document is released under this License.
     97
     98The "Cover Texts" are certain short passages of text that are listed,
     99as Front-Cover Texts or Back-Cover Texts, in the notice that says that
     100the Document is released under this License.
     101
     102A "Transparent" copy of the Document means a machine-readable copy,
     103represented in a format whose specification is available to the
     104general public, whose contents can be viewed and edited directly and
     105straightforwardly with generic text editors or (for images composed of
     106pixels) generic paint programs or (for drawings) some widely available
     107drawing editor, and that is suitable for input to text formatters or
     108for automatic translation to a variety of formats suitable for input
     109to text formatters.  A copy made in an otherwise Transparent file
     110format whose markup has been designed to thwart or discourage
     111subsequent modification by readers is not Transparent.  A copy that is
     112not "Transparent" is called "Opaque".
     113
     114Examples of suitable formats for Transparent copies include plain
     115ASCII without markup, Texinfo input format, LaTeX input format, SGML
     116or XML using a publicly available DTD, and standard-conforming simple
     117HTML designed for human modification.  Opaque formats include
     118PostScript, PDF, proprietary formats that can be read and edited only
     119by proprietary word processors, SGML or XML for which the DTD and/or
     120processing tools are not generally available, and the
     121machine-generated HTML produced by some word processors for output
     122purposes only.
     123
     124The "Title Page" means, for a printed book, the title page itself,
     125plus such following pages as are needed to hold, legibly, the material
     126this License requires to appear in the title page.  For works in
     127formats which do not have any title page as such, "Title Page" means
     128the text near the most prominent appearance of the work's title,
     129preceding the beginning of the body of the text.
     130
     131
     1322. VERBATIM COPYING
     133
     134You may copy and distribute the Document in any medium, either
     135commercially or noncommercially, provided that this License, the
     136copyright notices, and the license notice saying this License applies
     137to the Document are reproduced in all copies, and that you add no other
     138conditions whatsoever to those of this License.  You may not use
     139technical measures to obstruct or control the reading or further
     140copying of the copies you make or distribute.  However, you may accept
     141compensation in exchange for copies.  If you distribute a large enough
     142number of copies you must also follow the conditions in section 3.
     143
     144You may also lend copies, under the same conditions stated above, and
     145you may publicly display copies.
     146
     147
     1483. COPYING IN QUANTITY
     149
     150If you publish printed copies of the Document numbering more than 100,
     151and the Document's license notice requires Cover Texts, you must enclose
     152the copies in covers that carry, clearly and legibly, all these Cover
     153Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on
     154the back cover.  Both covers must also clearly and legibly identify
     155you as the publisher of these copies.  The front cover must present
     156the full title with all words of the title equally prominent and
     157visible.  You may add other material on the covers in addition.
     158Copying with changes limited to the covers, as long as they preserve
     159the title of the Document and satisfy these conditions, can be treated
     160as verbatim copying in other respects.
     161
     162If the required texts for either cover are too voluminous to fit
     163legibly, you should put the first ones listed (as many as fit
     164reasonably) on the actual cover, and continue the rest onto adjacent
     165pages.
     166
     167If you publish or distribute Opaque copies of the Document numbering
     168more than 100, you must either include a machine-readable Transparent
     169copy along with each Opaque copy, or state in or with each Opaque copy
     170a publicly-accessible computer-network location containing a complete
     171Transparent copy of the Document, free of added material, which the
     172general network-using public has access to download anonymously at no
     173charge using public-standard network protocols.  If you use the latter
     174option, you must take reasonably prudent steps, when you begin
     175distribution of Opaque copies in quantity, to ensure that this
     176Transparent copy will remain thus accessible at the stated location
     177until at least one year after the last time you distribute an Opaque
     178copy (directly or through your agents or retailers) of that edition to
     179the public.
     180
     181It is requested, but not required, that you contact the authors of the
     182Document well before redistributing any large number of copies, to give
     183them a chance to provide you with an updated version of the Document.
     184
     185
     1864. MODIFICATIONS
     187
     188You may copy and distribute a Modified Version of the Document under
     189the conditions of sections 2 and 3 above, provided that you release
     190the Modified Version under precisely this License, with the Modified
     191Version filling the role of the Document, thus licensing distribution
     192and modification of the Modified Version to whoever possesses a copy
     193of it.  In addition, you must do these things in the Modified Version:
     194
     195A. Use in the Title Page (and on the covers, if any) a title distinct
     196   from that of the Document, and from those of previous versions
     197   (which should, if there were any, be listed in the History section
     198   of the Document).  You may use the same title as a previous version
     199   if the original publisher of that version gives permission.
     200B. List on the Title Page, as authors, one or more persons or entities
     201   responsible for authorship of the modifications in the Modified
     202   Version, together with at least five of the principal authors of the
     203   Document (all of its principal authors, if it has less than five).
     204C. State on the Title page the name of the publisher of the
     205   Modified Version, as the publisher.
     206D. Preserve all the copyright notices of the Document.
     207E. Add an appropriate copyright notice for your modifications
     208   adjacent to the other copyright notices.
     209F. Include, immediately after the copyright notices, a license notice
     210   giving the public permission to use the Modified Version under the
     211   terms of this License, in the form shown in the Addendum below.
     212G. Preserve in that license notice the full lists of Invariant Sections
     213   and required Cover Texts given in the Document's license notice.
     214H. Include an unaltered copy of this License.
     215I. Preserve the section entitled "History", and its title, and add to
     216   it an item stating at least the title, year, new authors, and
     217   publisher of the Modified Version as given on the Title Page.  If
     218   there is no section entitled "History" in the Document, create one
     219   stating the title, year, authors, and publisher of the Document as
     220   given on its Title Page, then add an item describing the Modified
     221   Version as stated in the previous sentence.
     222J. Preserve the network location, if any, given in the Document for
     223   public access to a Transparent copy of the Document, and likewise
     224   the network locations given in the Document for previous versions
     225   it was based on.  These may be placed in the "History" section.
     226   You may omit a network location for a work that was published at
     227   least four years before the Document itself, or if the original
     228   publisher of the version it refers to gives permission.
     229K. In any section entitled "Acknowledgements" or "Dedications",
     230   preserve the section's title, and preserve in the section all the
     231   substance and tone of each of the contributor acknowledgements
     232   and/or dedications given therein.
     233L. Preserve all the Invariant Sections of the Document,
     234   unaltered in their text and in their titles.  Section numbers
     235   or the equivalent are not considered part of the section titles.
     236M. Delete any section entitled "Endorsements".  Such a section
     237   may not be included in the Modified Version.
     238N. Do not retitle any existing section as "Endorsements"
     239   or to conflict in title with any Invariant Section.
     240
     241If the Modified Version includes new front-matter sections or
     242appendices that qualify as Secondary Sections and contain no material
     243copied from the Document, you may at your option designate some or all
     244of these sections as invariant.  To do this, add their titles to the
     245list of Invariant Sections in the Modified Version's license notice.
     246These titles must be distinct from any other section titles.
     247
     248You may add a section entitled "Endorsements", provided it contains
     249nothing but endorsements of your Modified Version by various
     250parties--for example, statements of peer review or that the text has
     251been approved by an organization as the authoritative definition of a
     252standard.
     253
     254You may add a passage of up to five words as a Front-Cover Text, and a
     255passage of up to 25 words as a Back-Cover Text, to the end of the list
     256of Cover Texts in the Modified Version.  Only one passage of
     257Front-Cover Text and one of Back-Cover Text may be added by (or
     258through arrangements made by) any one entity.  If the Document already
     259includes a cover text for the same cover, previously added by you or
     260by arrangement made by the same entity you are acting on behalf of,
     261you may not add another; but you may replace the old one, on explicit
     262permission from the previous publisher that added the old one.
     263
     264The author(s) and publisher(s) of the Document do not by this License
     265give permission to use their names for publicity for or to assert or
     266imply endorsement of any Modified Version.
     267
     268
     2695. COMBINING DOCUMENTS
     270
     271You may combine the Document with other documents released under this
     272License, under the terms defined in section 4 above for modified
     273versions, provided that you include in the combination all of the
     274Invariant Sections of all of the original documents, unmodified, and
     275list them all as Invariant Sections of your combined work in its
     276license notice.
     277
     278The combined work need only contain one copy of this License, and
     279multiple identical Invariant Sections may be replaced with a single
     280copy.  If there are multiple Invariant Sections with the same name but
     281different contents, make the title of each such section unique by
     282adding at the end of it, in parentheses, the name of the original
     283author or publisher of that section if known, or else a unique number.
     284Make the same adjustment to the section titles in the list of
     285Invariant Sections in the license notice of the combined work.
     286
     287In the combination, you must combine any sections entitled "History"
     288in the various original documents, forming one section entitled
     289"History"; likewise combine any sections entitled "Acknowledgements",
     290and any sections entitled "Dedications".  You must delete all sections
     291entitled "Endorsements."
     292
     293
     2946. COLLECTIONS OF DOCUMENTS
     295
     296You may make a collection consisting of the Document and other documents
     297released under this License, and replace the individual copies of this
     298License in the various documents with a single copy that is included in
     299the collection, provided that you follow the rules of this License for
     300verbatim copying of each of the documents in all other respects.
     301
     302You may extract a single document from such a collection, and distribute
     303it individually under this License, provided you insert a copy of this
     304License into the extracted document, and follow this License in all
     305other respects regarding verbatim copying of that document.
     306
     307
     3087. AGGREGATION WITH INDEPENDENT WORKS
     309
     310A compilation of the Document or its derivatives with other separate
     311and independent documents or works, in or on a volume of a storage or
     312distribution medium, does not as a whole count as a Modified Version
     313of the Document, provided no compilation copyright is claimed for the
     314compilation.  Such a compilation is called an "aggregate", and this
     315License does not apply to the other self-contained works thus compiled
     316with the Document, on account of their being thus compiled, if they
     317are not themselves derivative works of the Document.
     318
     319If the Cover Text requirement of section 3 is applicable to these
     320copies of the Document, then if the Document is less than one quarter
     321of the entire aggregate, the Document's Cover Texts may be placed on
     322covers that surround only the Document within the aggregate.
     323Otherwise they must appear on covers around the whole aggregate.
     324
     325
     3268. TRANSLATION
     327
     328Translation is considered a kind of modification, so you may
     329distribute translations of the Document under the terms of section 4.
     330Replacing Invariant Sections with translations requires special
     331permission from their copyright holders, but you may include
     332translations of some or all Invariant Sections in addition to the
     333original versions of these Invariant Sections.  You may include a
     334translation of this License provided that you also include the
     335original English version of this License.  In case of a disagreement
     336between the translation and the original English version of this
     337License, the original English version will prevail.
     338
     339
     3409. TERMINATION
     341
     342You may not copy, modify, sublicense, or distribute the Document except
     343as expressly provided for under this License.  Any other attempt to
     344copy, modify, sublicense or distribute the Document is void, and will
     345automatically terminate your rights under this License.  However,
     346parties who have received copies, or rights, from you under this
     347License will not have their licenses terminated so long as such
     348parties remain in full compliance.
     349
     350
     35110. FUTURE REVISIONS OF THIS LICENSE
     352
     353The Free Software Foundation may publish new, revised versions
     354of the GNU Free Documentation License from time to time.  Such new
     355versions will be similar in spirit to the present version, but may
     356differ in detail to address new problems or concerns.  See
     357http://www.gnu.org/copyleft/.
     358
     359Each version of the License is given a distinguishing version number.
     360If the Document specifies that a particular numbered version of this
     361License "or any later version" applies to it, you have the option of
     362following the terms and conditions either of that specified version or
     363of any later version that has been published (not as a draft) by the
     364Free Software Foundation.  If the Document does not specify a version
     365number of this License, you may choose any version ever published (not
     366as a draft) by the Free Software Foundation.
     367
     368
     369ADDENDUM: How to use this License for your documents
     370
     371To use this License in a document you have written, include a copy of
     372the License in the document and put the following copyright and
     373license notices just after the title page:
     374
     375      Copyright (c)  YEAR  YOUR NAME.
     376      Permission is granted to copy, distribute and/or modify this document
     377      under the terms of the GNU Free Documentation License, Version 1.1
     378      or any later version published by the Free Software Foundation;
     379      with the Invariant Sections being LIST THEIR TITLES, with the
     380      Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST.
     381      A copy of the license is included in the section entitled "GNU
     382      Free Documentation License".
     383
     384If you have no Invariant Sections, write "with no Invariant Sections"
     385instead of saying which ones are invariant.  If you have no
     386Front-Cover Texts, write "no Front-Cover Texts" instead of
     387"Front-Cover Texts being LIST"; likewise for Back-Cover Texts.
     388
     389If your document contains nontrivial examples of program code, we
     390recommend releasing these examples in parallel under your choice of
     391free software license, such as the GNU General Public License,
     392to permit their use in free software.
    55393============================================================================
  • lib/Makefile

    rb006464 re1d3f98  
    1313
    1414# [SH] Program variables
    15 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
     15objects = 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
    1616
    1717LFLAGS += -r
  • lib/http_client.c

    rb006464 re1d3f98  
    193193}
    194194
    195 static gboolean http_handle_headers( struct http_request *req );
    196 
    197195static gboolean http_incoming_data( gpointer data, int source, b_input_condition cond )
    198196{
    199197        struct http_request *req = data;
     198        int evil_server = 0;
    200199        char buffer[2048];
    201         char *s;
     200        char *end1, *end2, *s;
    202201        size_t content_length;
    203202        int st;
     
    219218                                   packets that abort connections! \o/ */
    220219                               
    221                                 goto eof;
     220                                goto got_reply;
    222221                        }
    223222                }
    224223                else if( st == 0 )
    225224                {
    226                         goto eof;
     225                        goto got_reply;
    227226                }
    228227        }
     
    240239                else if( st == 0 )
    241240                {
    242                         goto eof;
    243                 }
    244         }
    245        
    246         if( st > 0 && !req->sbuf )
     241                        goto got_reply;
     242                }
     243        }
     244       
     245        if( st > 0 )
    247246        {
    248247                req->reply_headers = g_realloc( req->reply_headers, req->bytes_read + st + 1 );
    249248                memcpy( req->reply_headers + req->bytes_read, buffer, st );
    250249                req->bytes_read += st;
    251                
    252                 st = 0;
    253         }
    254        
    255         if( st >= 0 && ( req->flags & HTTPC_STREAMING ) )
    256         {
    257                 if( !req->reply_body &&
    258                     ( strstr( req->reply_headers, "\r\n\r\n" ) ||
    259                       strstr( req->reply_headers, "\n\n" ) ) )
    260                 {
    261                         size_t hlen;
    262                        
    263                         /* We've now received all headers, so process them once
    264                            before we start feeding back data. */
    265                         if( !http_handle_headers( req ) )
    266                                 return FALSE;
    267                        
    268                         hlen = req->reply_body - req->reply_headers;
    269                        
    270                         req->sblen = req->bytes_read - hlen;
    271                         req->sbuf = g_memdup( req->reply_body, req->sblen + 1 );
    272                         req->reply_headers = g_realloc( req->reply_headers, hlen + 1 );
    273                        
    274                         req->reply_body = req->sbuf;
    275                 }
    276                
    277                 if( st > 0 )
    278                 {
    279                         int pos = req->reply_body - req->sbuf;
    280                         req->sbuf = g_realloc( req->sbuf, req->sblen + st + 1 );
    281                         memcpy( req->sbuf + req->sblen, buffer, st );
    282                         req->bytes_read += st;
    283                         req->sblen += st;
    284                         req->sbuf[req->sblen] = '\0';
    285                         req->reply_body = req->sbuf + pos;
    286                         req->body_size = req->sblen - pos;
    287                 }
    288                
    289                 if( req->reply_body )
    290                         req->func( req );
    291         }
    292        
    293         if( ssl_pending( req->ssl ) )
    294                 return http_incoming_data( data, source, cond );
     250        }
    295251       
    296252        /* There will be more! */
     
    299255                                 http_incoming_data, req );
    300256       
    301         return FALSE;
    302 
    303 eof:
    304         req->flags |= HTTPC_EOF;
    305        
     257        if( ssl_pending( req->ssl ) )
     258                return http_incoming_data( data, source, cond );
     259        else
     260                return FALSE;
     261
     262got_reply:
    306263        /* Maybe if the webserver is overloaded, or when there's bad SSL
    307264           support... */
     
    312269        }
    313270       
    314         if( !( req->flags & HTTPC_STREAMING ) )
    315         {
    316                 /* Returns FALSE if we were redirected, in which case we should abort
    317                    and not run any callback yet. */
    318                 if( !http_handle_headers( req ) )
    319                         return FALSE;
    320         }
    321 
    322 cleanup:
    323         if( req->ssl )
    324                 ssl_disconnect( req->ssl );
    325         else
    326                 closesocket( req->fd );
    327        
    328         if( ( s = get_rfc822_header( req->reply_headers, "Content-Length", 0 ) ) &&
    329             sscanf( s, "%zd", &content_length ) == 1 )
    330         {
    331                 if( content_length < req->body_size )
    332                 {
    333                         req->status_code = -1;
    334                         g_free( req->status_string );
    335                         req->status_string = g_strdup( "Response truncated" );
    336                 }
    337         }
    338         g_free( s );
    339        
    340         if( getenv( "BITLBEE_DEBUG" ) && req )
    341                 printf( "Finishing HTTP request with status: %s\n",
    342                         req->status_string ? req->status_string : "NULL" );
    343        
    344         req->func( req );
    345         http_free( req );
    346         return FALSE;
    347 }
    348 
    349 /* Splits headers and body. Checks result code, in case of 300s it'll handle
    350    redirects. If this returns FALSE, don't call any callbacks! */
    351 static gboolean http_handle_headers( struct http_request *req )
    352 {
    353         char *end1, *end2;
    354         int evil_server = 0;
    355        
    356271        /* Zero termination is very convenient. */
    357         req->reply_headers[req->bytes_read] = '\0';
     272        req->reply_headers[req->bytes_read] = 0;
    358273       
    359274        /* Find the separation between headers and body, and keep stupid
     
    374289        {
    375290                req->status_string = g_strdup( "Malformed HTTP reply" );
    376                 return TRUE;
     291                goto cleanup;
    377292        }
    378293       
     
    391306        if( ( end1 = strchr( req->reply_headers, ' ' ) ) != NULL )
    392307        {
    393                 if( sscanf( end1 + 1, "%hd", &req->status_code ) != 1 )
     308                if( sscanf( end1 + 1, "%d", &req->status_code ) != 1 )
    394309                {
    395310                        req->status_string = g_strdup( "Can't parse status code" );
     
    434349                {
    435350                        req->status_string = g_strdup( "Can't locate Location: header" );
    436                         return TRUE;
     351                        goto cleanup;
    437352                }
    438353               
     
    454369                        req->status_string = g_strdup( "Can't handle recursive redirects" );
    455370                       
    456                         return TRUE;
     371                        goto cleanup;
    457372                }
    458373                else
     
    465380                        s = strstr( loc, "\r\n" );
    466381                        if( s == NULL )
    467                                 return TRUE;
     382                                goto cleanup;
    468383                       
    469384                        url = g_new0( url_t, 1 );
     
    474389                                req->status_string = g_strdup( "Malformed redirect URL" );
    475390                                g_free( url );
    476                                 return TRUE;
     391                                goto cleanup;
    477392                        }
    478393                       
     
    486401                                req->status_string = g_strdup( "Error while rebuilding request string" );
    487402                                g_free( url );
    488                                 return TRUE;
     403                                goto cleanup;
    489404                        }
    490405                       
     
    552467                        req->status_string = g_strdup( "Connection problem during redirect" );
    553468                        g_free( new_request );
    554                         return TRUE;
     469                        goto cleanup;
    555470                }
    556471               
     
    565480        }
    566481       
    567         return TRUE;
    568 }
    569 
    570 void http_flush_bytes( struct http_request *req, size_t len )
    571 {
    572         if( len <= 0 || len > req->body_size || !( req->flags & HTTPC_STREAMING ) )
    573                 return;
    574        
    575         req->reply_body += len;
    576         req->body_size -= len;
    577        
    578         if( req->reply_body - req->sbuf >= 512 )
    579         {
    580                 printf( "Wasting %ld bytes, cleaning up stream buffer\n", req->reply_body - req->sbuf );
    581                 char *new = g_memdup( req->reply_body, req->body_size + 1 );
    582                 g_free( req->sbuf );
    583                 req->reply_body = req->sbuf = new;
    584                 req->sblen = req->body_size;
    585         }
    586 }
    587 
    588 void http_close( struct http_request *req )
    589 {
    590         if( !req )
    591                 return;
    592        
     482        /* Assume that a closed connection means we're finished, this indeed
     483           breaks with keep-alive connections and faulty connections. */
     484        /* req->finished = 1; */
     485
     486cleanup:
    593487        if( req->ssl )
    594488                ssl_disconnect( req->ssl );
     
    596490                closesocket( req->fd );
    597491       
     492        if( ( s = get_rfc822_header( req->reply_headers, "Content-Length", 0 ) ) &&
     493            sscanf( s, "%zd", &content_length ) == 1 )
     494        {
     495                if( content_length < req->body_size )
     496                {
     497                        req->status_code = -1;
     498                        g_free( req->status_string );
     499                        req->status_string = g_strdup( "Response truncated" );
     500                }
     501        }
     502        g_free( s );
     503       
     504        if( getenv( "BITLBEE_DEBUG" ) && req )
     505                printf( "Finishing HTTP request with status: %s\n",
     506                        req->status_string ? req->status_string : "NULL" );
     507       
     508        req->func( req );
    598509        http_free( req );
     510        return FALSE;
    599511}
    600512
     
    604516        g_free( req->reply_headers );
    605517        g_free( req->status_string );
    606         g_free( req->sbuf );
    607518        g_free( req );
    608519}
     520
  • lib/http_client.h

    rb006464 re1d3f98  
    2626/* http_client allows you to talk (asynchronously, again) to HTTP servers.
    2727   In the "background" it will send the whole query and wait for a complete
    28    response to come back. Initially written for MS Passport authentication,
    29    but used for many other things now like OAuth and Twitter.
     28   response to come back. Right now it's only used by the MSN Passport
     29   authentication code, but it might be useful for other things too (for
     30   example the AIM usericon patch uses this so icons can be stored on
     31   webservers instead of the local filesystem).
    3032   
    31    It's very useful for doing quick requests without blocking the whole
    32    program. Unfortunately it doesn't support fancy stuff like HTTP keep-
    33    alives. */
     33   Didn't test this too much, but it seems to work well. Just don't look
     34   at the code that handles HTTP 30x redirects. ;-) The function is
     35   probably not very useful for downloading lots of data since it keeps
     36   everything in a memory buffer until the download is completed (and
     37   can't pass any data or whatever before then). It's very useful for
     38   doing quick requests without blocking the whole program, though. */
    3439
    3540#include <glib.h>
     
    3742
    3843struct http_request;
    39 
    40 typedef enum http_client_flags
    41 {
    42         HTTPC_STREAMING = 1,
    43         HTTPC_EOF = 2,
    44 } http_client_flags_t;
    4544
    4645/* Your callback function should look like this: */
     
    5453        char *request;          /* The request to send to the server. */
    5554        int request_length;     /* Its size. */
    56         short status_code;      /* The numeric HTTP status code. (Or -1
     55        int status_code;        /* The numeric HTTP status code. (Or -1
    5756                                   if something really went wrong) */
    5857        char *status_string;    /* The error text. */
     
    6059        char *reply_body;
    6160        int body_size;          /* The number of bytes in reply_body. */
    62         short redir_ttl;        /* You can set it to 0 if you don't want
     61        /* int finished;           Set to non-0 if the request was completed
     62                                   successfully. */
     63        int redir_ttl;          /* You can set it to 0 if you don't want
    6364                                   http_client to follow them. */
    64        
    65         http_client_flags_t flags;
    6665       
    6766        http_input_function func;
     
    6968       
    7069        /* Please don't touch the things down here, you shouldn't need them. */
     70       
    7171        void *ssl;
    7272        int fd;
     
    7575        int bytes_written;
    7676        int bytes_read;
    77        
    78         /* Used in streaming mode. Caller should read from reply_body. */
    79         char *sbuf;
    80         size_t sblen;
    8177};
    8278
     
    8783struct http_request *http_dorequest( char *host, int port, int ssl, char *request, http_input_function func, gpointer data );
    8884struct http_request *http_dorequest_url( char *url_string, http_input_function func, gpointer data );
    89 
    90 /* For streaming connections only; flushes len bytes at the start of the buffer. */
    91 void http_flush_bytes( struct http_request *req, size_t len );
    92 void http_close( struct http_request *req );
  • lib/oauth2.c

    rb006464 re1d3f98  
    44*  Simple OAuth client (consumer) implementation.                           *
    55*                                                                           *
    6 *  Copyright 2010-2012 Wilmer van der Gaast <wilmer@gaast.net>              *
     6*  Copyright 2010-2011 Wilmer van der Gaast <wilmer@gaast.net>              *
    77*                                                                           *
    88*  This program is free software; you can redistribute it and/or modify     *
     
    2626#include "oauth2.h"
    2727#include "oauth.h"
    28 #include "json.h"
    2928#include "url.h"
    3029
     
    4544};
    4645
     46static char *oauth2_json_dumb_get( const char *json, const char *key );
    4747static void oauth2_access_token_done( struct http_request *req );
    4848
     
    115115        else if( content_type && strstr( content_type, "application/json" ) )
    116116        {
    117                 json_value *js = json_parse( req->reply_body );
    118                 if( js && js->type == json_object )
    119                 {
    120                         int i;
    121                        
    122                         for( i = 0; i < js->u.object.length; i ++ )
    123                         {
    124                                 if( js->u.object.values[i].value->type != json_string )
    125                                         continue;
    126                                 if( strcmp( js->u.object.values[i].name, "access_token" ) == 0 )
    127                                         atoken = g_strdup( js->u.object.values[i].value->u.string.ptr );
    128                                 if( strcmp( js->u.object.values[i].name, "refresh_token" ) == 0 )
    129                                         rtoken = g_strdup( js->u.object.values[i].value->u.string.ptr );
    130                         }
    131                 }
    132                 json_value_free( js );
     117                atoken = oauth2_json_dumb_get( req->reply_body, "access_token" );
     118                rtoken = oauth2_json_dumb_get( req->reply_body, "refresh_token" );
    133119        }
    134120        else
     
    150136        g_free( cb_data );
    151137}
     138
     139/* Super dumb. I absolutely refuse to use/add a complete json parser library
     140   (adding a new dependency to BitlBee for the first time in.. 6 years?) just
     141   to parse 100 bytes of data. So I have to do my own parsing because OAuth2
     142   dropped support for XML. (GRRR!) This is very dumb and for example won't
     143   work for integer values, nor will it strip/handle backslashes. */
     144static char *oauth2_json_dumb_get( const char *json, const char *key )
     145{
     146        int is_key = 0; /* 1 == reading key, 0 == reading value */
     147        int found_key = 0;
     148               
     149        while( json && *json )
     150        {
     151                /* Grab strings and see if they're what we're looking for. */
     152                if( *json == '"' || *json == '\'' )
     153                {
     154                        char q = *json;
     155                        const char *str_start;
     156                        json ++;
     157                        str_start = json;
     158                       
     159                        while( *json )
     160                        {
     161                                /* \' and \" are not string terminators. */
     162                                if( *json == '\\' && json[1] == q )
     163                                        json ++;
     164                                /* But without a \ it is. */
     165                                else if( *json == q )
     166                                        break;
     167                                json ++;
     168                        }
     169                        if( *json == '\0' )
     170                                return NULL;
     171                       
     172                        if( is_key && strncmp( str_start, key, strlen( key ) ) == 0 )
     173                        {
     174                                found_key = 1;
     175                        }
     176                        else if( !is_key && found_key )
     177                        {
     178                                char *ret = g_memdup( str_start, json - str_start + 1 );
     179                                ret[json-str_start] = '\0';
     180                                return ret;
     181                        }
     182                       
     183                }
     184                else if( *json == '{' || *json == ',' )
     185                {
     186                        found_key = 0;
     187                        is_key = 1;
     188                }
     189                else if( *json == ':' )
     190                        is_key = 0;
     191               
     192                json ++;
     193        }
     194       
     195        return NULL;
     196}
  • lib/ssl_gnutls.c

    rb006464 re1d3f98  
    3838
    3939static gboolean initialized = FALSE;
    40 gnutls_certificate_credentials_t xcred;
     40gnutls_certificate_credentials xcred;
    4141
    4242#include <limits.h>
     
    6060        gboolean verify;
    6161       
    62         gnutls_session_t session;
     62        gnutls_session session;
    6363};
    6464
     
    134134        conn->data = data;
    135135        conn->inpa = -1;
    136         conn->hostname = g_strdup( hostname );
     136        conn->hostname = hostname;
    137137       
    138138        /* For now, SSL verification is globally enabled by setting the cafile
     
    171171        int verifyret = 0;
    172172        gnutls_x509_crt_t cert;
    173         struct scd *conn;
    174        
    175         conn = gnutls_session_get_ptr( session );
     173        const char *hostname;
     174       
     175        hostname = gnutls_session_get_ptr( session );
    176176
    177177        gnutlsret = gnutls_certificate_verify_peers2( session, &status );
     
    211211                return VERIFY_CERT_ERROR;
    212212
    213         if( !gnutls_x509_crt_check_hostname( cert, conn->hostname ) )
     213        if( !gnutls_x509_crt_check_hostname( cert, hostname ) )
    214214        {
    215215                verifyret |= VERIFY_CERT_INVALID;
     
    267267       
    268268        gnutls_init( &conn->session, GNUTLS_CLIENT );
    269         gnutls_session_set_ptr( conn->session, (void *) conn );
     269        if( conn->verify )
     270                gnutls_session_set_ptr( conn->session, (void *) conn->hostname );
    270271#if GNUTLS_VERSION_NUMBER < 0x020c00
    271272        gnutls_transport_set_lowat( conn->session, 0 );
     
    275276       
    276277        sock_make_nonblocking( conn->fd );
    277         gnutls_transport_set_ptr( conn->session, (gnutls_transport_ptr_t) GNUTLS_STUPID_CAST conn->fd );
     278        gnutls_transport_set_ptr( conn->session, (gnutls_transport_ptr) GNUTLS_STUPID_CAST conn->fd );
    278279       
    279280        return ssl_handshake( data, source, cond );
     
    401402        if( conn->session )
    402403                gnutls_deinit( conn->session );
    403         g_free( conn->hostname );
    404404        g_free( conn );
    405405}
  • protocols/msn/msn.c

    rb006464 re1d3f98  
    5353       
    5454        ic->proto_data = md;
    55         ic->flags |= OPT_PONGS | OPT_PONGED;
    5655       
    5756        if( strchr( acc->user, '@' ) == NULL )
  • protocols/msn/ns.c

    rb006464 re1d3f98  
    576576                if( num_parts >= 7 )
    577577                        handler->msglen = atoi( cmd[6] );
    578         }
    579         else if( strcmp( cmd[0], "QNG" ) == 0 )
    580         {
    581                 ic->flags |= OPT_PONGED;
    582578        }
    583579        else if( isdigit( cmd[0][0] ) )
  • protocols/nogaim.c

    rb006464 re1d3f98  
    261261        struct im_connection *ic = d;
    262262       
    263         if( ( ic->flags & OPT_PONGS ) && !( ic->flags & OPT_PONGED ) )
    264         {
    265                 /* This protocol is expected to ack keepalives and hasn't
    266                    since the last time we were here. */
    267                 imcb_error( ic, "Connection timeout" );
    268                 imc_logout( ic, TRUE );
    269                 return FALSE;
    270         }
    271         ic->flags &= ~OPT_PONGED;
    272        
    273263        if( ic->acc->prpl->keepalive )
    274264                ic->acc->prpl->keepalive( ic );
    275265       
    276266        return TRUE;
    277 }
    278 
    279 void start_keepalives( struct im_connection *ic, int interval )
    280 {
    281         b_event_remove( ic->keepalive );
    282         ic->keepalive = b_timeout_add( interval, send_keepalive, ic );
    283        
    284         /* Connecting successfully counts as a first successful pong. */
    285         if( ic->flags & OPT_PONGS )
    286                 ic->flags |= OPT_PONGED;
    287267}
    288268
     
    297277        imcb_log( ic, "Logged in" );
    298278       
     279        b_event_remove( ic->keepalive );
     280        ic->keepalive = b_timeout_add( 60000, send_keepalive, ic );
    299281        ic->flags |= OPT_LOGGED_IN;
    300         start_keepalives( ic, 60000 );
    301282       
    302283        /* Necessary to send initial presence status, even if we're not away. */
  • protocols/nogaim.h

    rb006464 re1d3f98  
    6868#define OPT_THINKING    0x00000200 /* about these values... Stupid me! */
    6969#define OPT_NOOTR       0x00001000 /* protocol not suitable for OTR */
    70 #define OPT_PONGS       0x00010000 /* Service sends us keep-alives */
    71 #define OPT_PONGED      0x00020000 /* Received a keep-alive during last interval */
    7270
    7371/* ok. now the fun begins. first we create a connection structure */
  • protocols/twitter/twitter.c

    rb006464 re1d3f98  
    55*                                                                           *
    66*  Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com>                 *
    7 *  Copyright 2010-2012 Wilmer van der Gaast <wilmer@gaast.net>              *
    87*                                                                           *
    98*  This library is free software; you can redistribute it and/or            *
     
    6766        // Run this once. After this queue the main loop function.
    6867        twitter_main_loop(ic, -1, 0);
    69        
    70         if (set_getbool(&ic->acc->set, "stream")) {
    71                 /* That fetch was just to get backlog, the stream will give
    72                    us the rest. \o/ */
    73                 twitter_open_stream(ic);
    74                 ic->flags |= OPT_PONGS;
    75         } else {
    76                 /* Not using the streaming API, so keep polling the old-
    77                    fashioned way. :-( */
    78                 td->main_loop_id =
    79                     b_timeout_add(set_getint(&ic->acc->set, "fetch_interval") * 1000,
    80                                   twitter_main_loop, ic);
    81         }
     68
     69        // Queue the main_loop
     70        // Save the return value, so we can remove the timeout on logout.
     71        td->main_loop_id =
     72            b_timeout_add(set_getint(&ic->acc->set, "fetch_interval") * 1000, twitter_main_loop, ic);
    8273}
    8374
     
    288279
    289280        s = set_add(&acc->set, "strip_newlines", "false", set_eval_bool, acc);
    290        
    291         s = set_add(&acc->set, "stream", "true", set_eval_bool, acc);
    292         s->flags |= ACC_SET_OFFLINE_ONLY;
    293281}
    294282
     
    375363
    376364        if (td) {
    377                 http_close(td->stream);
    378365                oauth_info_free(td->oauth_info);
    379366                g_free(td->user);
  • protocols/twitter/twitter.h

    rb006464 re1d3f98  
    5757        guint64 last_status_id; /* For undo */
    5858        gint main_loop_id;
    59         struct http_request *stream;
    6059        struct groupchat *timeline_gc;
    6160        gint http_fails;
  • protocols/twitter/twitter_http.c

    rb006464 re1d3f98  
    4747 * This is actually pretty generic function... Perhaps it should move to the lib/http_client.c
    4848 */
    49 struct http_request *twitter_http(struct im_connection *ic, char *url_string, http_input_function func,
    50                                   gpointer data, int is_post, char **arguments, int arguments_len)
     49void *twitter_http(struct im_connection *ic, char *url_string, http_input_function func,
     50                   gpointer data, int is_post, char **arguments, int arguments_len)
    5151{
    5252        struct twitter_data *td = ic->proto_data;
     
    5555        void *ret;
    5656        char *url_arguments;
    57         url_t *base_url = NULL;
    5857
    5958        url_arguments = g_strdup("");
     
    6867                }
    6968        }
    70        
    71         if (strstr(url_string, "://")) {
    72                 base_url = g_new0(url_t, 1);
    73                 if (!url_set(base_url, url_string)) {
    74                         g_free(base_url);
    75                         return NULL;
    76                 }
    77         }
    78        
    7969        // Make the request.
    8070        g_string_printf(request, "%s %s%s%s%s HTTP/1.0\r\n"
     
    8272                        "User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n",
    8373                        is_post ? "POST" : "GET",
    84                         base_url ? base_url->file : td->url_path,
    85                         base_url ? "" : url_string,
    86                         is_post ? "" : "?", is_post ? "" : url_arguments,
    87                         base_url ? base_url->host : td->url_host);
     74                        td->url_path, url_string,
     75                        is_post ? "" : "?", is_post ? "" : url_arguments, td->url_host);
    8876
    8977        // If a pass and user are given we append them to the request.
     
    9280                char *full_url;
    9381
    94                 if (base_url)
    95                         full_url = g_strdup(url_string);
    96                 else
    97                         full_url = g_strconcat(set_getstr(&ic->acc->set, "base_url"), url_string, NULL);
     82                full_url = g_strconcat(set_getstr(&ic->acc->set, "base_url"), url_string, NULL);
    9883                full_header = oauth_http_header(td->oauth_info, is_post ? "POST" : "GET",
    9984                                                full_url, url_arguments);
     
    124109        }
    125110
    126         if (base_url)
    127                 ret = http_dorequest(base_url->host, base_url->port, base_url->proto == PROTO_HTTPS, request->str, func, data);
    128         else
    129                 ret = http_dorequest(td->url_host, td->url_port, td->url_ssl, request->str, func, data);
     111        ret = http_dorequest(td->url_host, td->url_port, td->url_ssl, request->str, func, data);
    130112
    131113        g_free(url_arguments);
    132114        g_string_free(request, TRUE);
    133         g_free(base_url);
    134115        return ret;
    135116}
  • protocols/twitter/twitter_http.h

    rb006464 re1d3f98  
    3030struct oauth_info;
    3131
    32 struct http_request *twitter_http(struct im_connection *ic, char *url_string, http_input_function func,
    33                                   gpointer data, int is_post, char** arguments, int arguments_len);
     32void *twitter_http(struct im_connection *ic, char *url_string, http_input_function func,
     33                   gpointer data, int is_post, char** arguments, int arguments_len);
    3434
    3535#endif //_TWITTER_HTTP_H
  • protocols/twitter/twitter_lib.c

    rb006464 re1d3f98  
    3535#include "misc.h"
    3636#include "base64.h"
     37#include "xmltree.h"
    3738#include "twitter_lib.h"
    38 #include "json_util.h"
    3939#include <ctype.h>
    4040#include <errno.h>
     
    168168{
    169169        static char *ret = NULL;
    170         json_value *root, *err;
     170        struct xt_node *root, *node, *err;
    171171
    172172        g_free(ret);
     
    174174
    175175        if (req->body_size > 0) {
    176                 root = json_parse(req->reply_body);
    177                 err = json_o_get(root, "errors");
    178                 if (err && err->type == json_array && (err = err->u.array.values[0]) &&
    179                     err->type == json_object) {
    180                         const char *msg = json_o_str(err, "message");
    181                         if (msg)
    182                                 ret = g_strdup_printf("%s (%s)", req->status_string, msg);
    183                 }
    184                 json_value_free(root);
     176                root = xt_from_string(req->reply_body, req->body_size);
     177               
     178                for (node = root; node; node = node->next)
     179                        if ((err = xt_find_node(node->children, "error")) && err->text_len > 0) {
     180                                ret = g_strdup_printf("%s (%s)", req->status_string, err->text);
     181                                break;
     182                        }
     183
     184                xt_free_node(root);
    185185        }
    186186
     
    188188}
    189189
    190 /* WATCH OUT: This function might or might not destroy your connection.
    191    Sub-optimal indeed, but just be careful when this returns NULL! */
    192 static json_value *twitter_parse_response(struct im_connection *ic, struct http_request *req)
     190static struct xt_node *twitter_parse_response(struct im_connection *ic, struct http_request *req)
    193191{
    194192        gboolean logging_in = !(ic->flags & OPT_LOGGED_IN);
    195193        gboolean periodic;
    196194        struct twitter_data *td = ic->proto_data;
    197         json_value *ret;
     195        struct xt_node *ret;
    198196        char path[64] = "", *s;
    199197       
     
    213211                   throwing 401s so I'll keep treating this one as fatal
    214212                   only during login. */
    215                 imcb_error(ic, "Authentication failure (%s)",
    216                                twitter_parse_error(req));
     213                imcb_error(ic, "Authentication failure");
    217214                imc_logout(ic, FALSE);
    218215                return NULL;
     
    230227        }
    231228
    232         if ((ret = json_parse(req->reply_body)) == NULL) {
     229        if ((ret = xt_from_string(req->reply_body, req->body_size)) == NULL) {
    233230                imcb_error(ic, "Could not retrieve %s: %s",
    234231                           path, "XML parse error");
     
    254251
    255252/**
     253 * Function to help fill a list.
     254 */
     255static xt_status twitter_xt_next_cursor(struct xt_node *node, struct twitter_xml_list *txl)
     256{
     257        char *end = NULL;
     258
     259        if (node->text)
     260                txl->next_cursor = g_ascii_strtoll(node->text, &end, 10);
     261        if (end == NULL)
     262                txl->next_cursor = -1;
     263
     264        return XT_HANDLED;
     265}
     266
     267/**
    256268 * Fill a list of ids.
    257269 */
    258 static gboolean twitter_xt_get_friends_id_list(json_value *node, struct twitter_xml_list *txl)
    259 {
    260         json_value *c;
    261         int i;
     270static xt_status twitter_xt_get_friends_id_list(struct xt_node *node, struct twitter_xml_list *txl)
     271{
     272        struct xt_node *child;
    262273
    263274        // Set the list type.
    264275        txl->type = TXL_ID;
    265276
    266         c = json_o_get(node, "ids");
    267         if (!c || c->type != json_array)
    268                 return FALSE;
    269 
    270         for (i = 0; i < c->u.array.length; i ++) {
    271                 if (c->u.array.values[i]->type != json_integer)
    272                         continue;
    273                
    274                 txl->list = g_slist_prepend(txl->list,
    275                         g_strdup_printf("%lld", c->u.array.values[i]->u.integer));
    276                
    277         }
    278        
    279         c = json_o_get(node, "next_cursor");
    280         if (c && c->type == json_integer)
    281                 txl->next_cursor = c->u.integer;
    282         else
    283                 txl->next_cursor = -1;
    284        
    285         return TRUE;
     277        // The root <statuses> node should hold the list of statuses <status>
     278        // Walk over the nodes children.
     279        for (child = node->children; child; child = child->next) {
     280                if (g_strcasecmp("ids", child->name) == 0) {
     281                        struct xt_node *idc;
     282                        for (idc = child->children; idc; idc = idc->next)
     283                                if (g_strcasecmp(idc->name, "id") == 0)
     284                                        txl->list = g_slist_prepend(txl->list,
     285                                                g_memdup(idc->text, idc->text_len + 1));
     286                } else if (g_strcasecmp("next_cursor", child->name) == 0) {
     287                        twitter_xt_next_cursor(child, txl);
     288                }
     289        }
     290
     291        return XT_HANDLED;
    286292}
    287293
     
    294300{
    295301        struct im_connection *ic;
    296         json_value *parsed;
     302        struct xt_node *parsed;
    297303        struct twitter_xml_list *txl;
    298304        struct twitter_data *td;
     
    316322        if (!(parsed = twitter_parse_response(ic, req)))
    317323                return;
    318        
    319324        twitter_xt_get_friends_id_list(parsed, txl);
    320         json_value_free(parsed);
     325        xt_free_node(parsed);
    321326
    322327        td->follow_ids = txl->list;
     
    333338}
    334339
    335 static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl);
     340static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl);
    336341static void twitter_http_get_users_lookup(struct http_request *req);
    337342
     
    374379{
    375380        struct im_connection *ic = req->data;
    376         json_value *parsed;
     381        struct xt_node *parsed;
    377382        struct twitter_xml_list *txl;
    378383        GSList *l = NULL;
     
    390395                return;
    391396        twitter_xt_get_users(parsed, txl);
    392         json_value_free(parsed);
     397        xt_free_node(parsed);
    393398
    394399        // Add the users as buddies.
     
    404409}
    405410
    406 struct twitter_xml_user *twitter_xt_get_user(const json_value *node)
    407 {
    408         struct twitter_xml_user *txu;
    409        
    410         txu = g_new0(struct twitter_xml_user, 1);
    411         txu->name = g_strdup(json_o_str(node, "name"));
    412         txu->screen_name = g_strdup(json_o_str(node, "screen_name"));
    413        
    414         return txu;
     411/**
     412 * Function to fill a twitter_xml_user struct.
     413 * It sets:
     414 *  - the name and
     415 *  - the screen_name.
     416 */
     417static xt_status twitter_xt_get_user(struct xt_node *node, struct twitter_xml_user *txu)
     418{
     419        struct xt_node *child;
     420
     421        // Walk over the nodes children.
     422        for (child = node->children; child; child = child->next) {
     423                if (g_strcasecmp("name", child->name) == 0) {
     424                        txu->name = g_memdup(child->text, child->text_len + 1);
     425                } else if (g_strcasecmp("screen_name", child->name) == 0) {
     426                        txu->screen_name = g_memdup(child->text, child->text_len + 1);
     427                }
     428        }
     429        return XT_HANDLED;
    415430}
    416431
     
    420435 *  - all <user>s from the <users> element.
    421436 */
    422 static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl)
     437static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl)
    423438{
    424439        struct twitter_xml_user *txu;
    425         int i;
     440        struct xt_node *child;
    426441
    427442        // Set the type of the list.
    428443        txl->type = TXL_USER;
    429444
    430         if (!node || node->type != json_array)
    431                 return FALSE;
    432 
    433445        // The root <users> node should hold the list of users <user>
    434446        // Walk over the nodes children.
    435         for (i = 0; i < node->u.array.length; i ++) {
    436                 txu = twitter_xt_get_user(node->u.array.values[i]);
    437                 if (txu)
     447        for (child = node->children; child; child = child->next) {
     448                if (g_strcasecmp("user", child->name) == 0) {
     449                        txu = g_new0(struct twitter_xml_user, 1);
     450                        twitter_xt_get_user(child, txu);
     451                        // Put the item in the front of the list.
    438452                        txl->list = g_slist_prepend(txl->list, txu);
    439         }
    440 
    441         return TRUE;
     453                }
     454        }
     455
     456        return XT_HANDLED;
    442457}
    443458
     
    447462#define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S +0000 %Y"
    448463#endif
    449 
    450 static char* expand_entities(char* text, const json_value *entities);
    451464
    452465/**
     
    458471 *  - the user in a twitter_xml_user struct.
    459472 */
    460 static gboolean twitter_xt_get_status(const json_value *node, struct twitter_xml_status *txs)
    461 {
    462         const json_value *rt = NULL, *entities = NULL;
    463        
    464         if (node->type != json_object)
    465                 return FALSE;
    466 
    467         JSON_O_FOREACH (node, k, v) {
    468                 if (strcmp("text", k) == 0 && v->type == json_string) {
    469                         txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1);
    470                 } else if (strcmp("retweeted_status", k) == 0 && v->type == json_object) {
    471                         rt = v;
    472                 } else if (strcmp("created_at", k) == 0 && v->type == json_string) {
     473static xt_status twitter_xt_get_status(struct xt_node *node, struct twitter_xml_status *txs)
     474{
     475        struct xt_node *child, *rt = NULL;
     476
     477        // Walk over the nodes children.
     478        for (child = node->children; child; child = child->next) {
     479                if (g_strcasecmp("text", child->name) == 0) {
     480                        txs->text = g_memdup(child->text, child->text_len + 1);
     481                } else if (g_strcasecmp("retweeted_status", child->name) == 0) {
     482                        rt = child;
     483                } else if (g_strcasecmp("created_at", child->name) == 0) {
    473484                        struct tm parsed;
    474485
     
    476487                           this field. :-( Also assumes the timezone used
    477488                           is UTC since C time handling functions suck. */
    478                         if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL)
     489                        if (strptime(child->text, TWITTER_TIME_FORMAT, &parsed) != NULL)
    479490                                txs->created_at = mktime_utc(&parsed);
    480                 } else if (strcmp("user", k) == 0 && v->type == json_object) {
    481                         txs->user = twitter_xt_get_user(v);
    482                 } else if (strcmp("id", k) == 0 && v->type == json_integer) {
    483                         txs->id = v->u.integer;
    484                 } else if (strcmp("in_reply_to_status_id", k) == 0 && v->type == json_integer) {
    485                         txs->reply_to = v->u.integer;
    486                 } else if (strcmp("entities", k) == 0 && v->type == json_object) {
    487                         entities = v;
     491                } else if (g_strcasecmp("user", child->name) == 0) {
     492                        txs->user = g_new0(struct twitter_xml_user, 1);
     493                        twitter_xt_get_user(child, txs->user);
     494                } else if (g_strcasecmp("id", child->name) == 0) {
     495                        txs->id = g_ascii_strtoull(child->text, NULL, 10);
     496                } else if (g_strcasecmp("in_reply_to_status_id", child->name) == 0) {
     497                        txs->reply_to = g_ascii_strtoull(child->text, NULL, 10);
    488498                }
    489499        }
     
    493503        if (rt) {
    494504                struct twitter_xml_status *rtxs = g_new0(struct twitter_xml_status, 1);
    495                 if (!twitter_xt_get_status(rt, rtxs)) {
     505                if (twitter_xt_get_status(rt, rtxs) != XT_HANDLED) {
    496506                        txs_free(rtxs);
    497                         return TRUE;
     507                        return XT_HANDLED;
    498508                }
    499509
     
    501511                txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text);
    502512                txs_free(rtxs);
    503         } else if (entities) {
    504                 txs->text = expand_entities(txs->text, entities);
    505         }
    506 
    507         return txs->text && txs->user && txs->id;
    508 }
    509 
    510 /**
    511  * Function to fill a twitter_xml_status struct (DM variant).
    512  */
    513 static gboolean twitter_xt_get_dm(const json_value *node, struct twitter_xml_status *txs)
    514 {
    515         const json_value *entities = NULL;
    516        
    517         if (node->type != json_object)
    518                 return FALSE;
    519 
    520         JSON_O_FOREACH (node, k, v) {
    521                 if (strcmp("text", k) == 0 && v->type == json_string) {
    522                         txs->text = g_memdup(v->u.string.ptr, v->u.string.length + 1);
    523                 } else if (strcmp("created_at", k) == 0 && v->type == json_string) {
    524                         struct tm parsed;
    525 
    526                         /* Very sensitive to changes to the formatting of
    527                            this field. :-( Also assumes the timezone used
    528                            is UTC since C time handling functions suck. */
    529                         if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL)
    530                                 txs->created_at = mktime_utc(&parsed);
    531                 } else if (strcmp("sender", k) == 0 && v->type == json_object) {
    532                         txs->user = twitter_xt_get_user(v);
    533                 } else if (strcmp("id", k) == 0 && v->type == json_integer) {
    534                         txs->id = v->u.integer;
    535                 }
    536         }
    537 
    538         if (entities) {
    539                 txs->text = expand_entities(txs->text, entities);
    540         }
    541 
    542         return txs->text && txs->user && txs->id;
    543 }
    544 
    545 static char* expand_entities(char* text, const json_value *entities) {
    546         JSON_O_FOREACH (entities, k, v) {
    547                 int i;
     513        } else {
     514                struct xt_node *urls, *url;
    548515               
    549                 if (v->type != json_array)
    550                         continue;
    551                 if (strcmp(k, "urls") != 0 && strcmp(k, "media") != 0)
    552                         continue;
    553                
    554                 for (i = 0; i < v->u.array.length; i ++) {
    555                         if (v->u.array.values[i]->type != json_object)
     516                urls = xt_find_path(node, "entities");
     517                if (urls != NULL)
     518                        urls = urls->children;
     519                for (; urls; urls = urls->next) {
     520                        if (strcmp(urls->name, "urls") != 0 && strcmp(urls->name, "media") != 0)
    556521                                continue;
    557522                       
    558                         const char *kort = json_o_str(v->u.array.values[i], "url");
    559                         const char *disp = json_o_str(v->u.array.values[i], "display_url");
    560                         char *pos, *new;
    561                        
    562                         if (!kort || !disp || !(pos = strstr(text, kort)))
    563                                 continue;
    564                        
    565                         *pos = '\0';
    566                         new = g_strdup_printf("%s%s &lt;%s&gt;%s", text, kort,
    567                                               disp, pos + strlen(kort));
    568                        
    569                         g_free(text);
    570                         text = new;
    571                 }
    572         }
    573        
    574         return text;
     523                        for (url = urls ? urls->children : NULL; url; url = url->next) {
     524                                /* "short" is a reserved word. :-P */
     525                                struct xt_node *kort = xt_find_node(url->children, "url");
     526                                struct xt_node *disp = xt_find_node(url->children, "display_url");
     527                                char *pos, *new;
     528                               
     529                                if (!kort || !kort->text || !disp || !disp->text ||
     530                                    !(pos = strstr(txs->text, kort->text)))
     531                                        continue;
     532                               
     533                                *pos = '\0';
     534                                new = g_strdup_printf("%s%s &lt;%s&gt;%s", txs->text, kort->text,
     535                                                      disp->text, pos + strlen(kort->text));
     536                               
     537                                g_free(txs->text);
     538                                txs->text = new;
     539                        }
     540                }
     541        }
     542
     543        return XT_HANDLED;
    575544}
    576545
     
    581550 *  - the next_cursor.
    582551 */
    583 static gboolean twitter_xt_get_status_list(struct im_connection *ic, const json_value *node,
    584                                            struct twitter_xml_list *txl)
     552static xt_status twitter_xt_get_status_list(struct im_connection *ic, struct xt_node *node,
     553                                            struct twitter_xml_list *txl)
    585554{
    586555        struct twitter_xml_status *txs;
     556        struct xt_node *child;
    587557        bee_user_t *bu;
    588         int i;
    589558
    590559        // Set the type of the list.
    591560        txl->type = TXL_STATUS;
    592        
    593         if (node->type != json_array)
    594                 return FALSE;
    595561
    596562        // The root <statuses> node should hold the list of statuses <status>
    597563        // Walk over the nodes children.
    598         for (i = 0; i < node->u.array.length; i ++) {
    599                 txs = g_new0(struct twitter_xml_status, 1);
    600                 twitter_xt_get_status(node->u.array.values[i], txs);
    601                 // Put the item in the front of the list.
    602                 txl->list = g_slist_prepend(txl->list, txs);
    603 
    604                 if (txs->user && txs->user->screen_name &&
    605                     (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) {
    606                         struct twitter_user_data *tud = bu->data;
    607 
    608                         if (txs->id > tud->last_id) {
    609                                 tud->last_id = txs->id;
    610                                 tud->last_time = txs->created_at;
     564        for (child = node->children; child; child = child->next) {
     565                if (g_strcasecmp("status", child->name) == 0) {
     566                        txs = g_new0(struct twitter_xml_status, 1);
     567                        twitter_xt_get_status(child, txs);
     568                        // Put the item in the front of the list.
     569                        txl->list = g_slist_prepend(txl->list, txs);
     570
     571                        if (txs->user && txs->user->screen_name &&
     572                            (bu = bee_user_by_handle(ic->bee, ic, txs->user->screen_name))) {
     573                                struct twitter_user_data *tud = bu->data;
     574
     575                                if (txs->id > tud->last_id) {
     576                                        tud->last_id = txs->id;
     577                                        tud->last_time = txs->created_at;
     578                                }
    611579                        }
    612                 }
    613         }
    614 
    615         return TRUE;
     580                } else if (g_strcasecmp("next_cursor", child->name) == 0) {
     581                        twitter_xt_next_cursor(child, txl);
     582                }
     583        }
     584
     585        return XT_HANDLED;
    616586}
    617587
     
    770740}
    771741
    772 static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o);
    773 
    774 static void twitter_http_stream(struct http_request *req)
    775 {
    776         struct im_connection *ic = req->data;
    777         struct twitter_data *td;
    778         json_value *parsed;
    779         int len = 0;
    780         char c, *nl;
    781        
    782         if (!g_slist_find(twitter_connections, ic))
    783                 return;
    784        
    785         ic->flags |= OPT_PONGED;
    786         td = ic->proto_data;
    787        
    788         if ((req->flags & HTTPC_EOF) || !req->reply_body) {
    789                 td->stream = NULL;
    790                 imcb_error(ic, "Stream closed (%s)", req->status_string);
    791                 imc_logout(ic, TRUE);
    792                 return;
    793         }
    794        
    795         printf( "%d bytes in stream\n", req->body_size );
    796        
    797         /* MUST search for CRLF, not just LF:
    798            https://dev.twitter.com/docs/streaming-apis/processing#Parsing_responses */
    799         nl = strstr(req->reply_body, "\r\n");
    800        
    801         if (!nl) {
    802                 printf("Incomplete data\n");
    803                 return;
    804         }
    805        
    806         len = nl - req->reply_body;
    807         if (len > 0) {
    808                 c = req->reply_body[len];
    809                 req->reply_body[len] = '\0';
    810                
    811                 printf("JSON: %s\n", req->reply_body);
    812                 printf("parsed: %p\n", (parsed = json_parse(req->reply_body)));
    813                 if (parsed) {
    814                         twitter_stream_handle_object(ic, parsed);
    815                 }
    816                 json_value_free(parsed);
    817                 req->reply_body[len] = c;
    818         }
    819        
    820         http_flush_bytes(req, len + 2);
    821        
    822         /* One notification might bring multiple events! */
    823         if (req->body_size > 0)
    824                 twitter_http_stream(req);
    825 }
    826 
    827 static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o)
    828 {
    829         struct twitter_xml_status *txs = g_new0(struct twitter_xml_status, 1);
    830         json_value *c;
    831        
    832         if (twitter_xt_get_status(o, txs)) {
    833                 GSList *output = g_slist_append(NULL, txs);
    834                 twitter_groupchat(ic, output);
    835                 txs_free(txs);
    836                 g_slist_free(output);
    837                 return TRUE;
    838         } else if ((c = json_o_get(o, "direct_message")) &&
    839                    twitter_xt_get_dm(c, txs)) {
    840                 GSList *output = g_slist_append(NULL, txs);
    841                 twitter_private_message_chat(ic, output);
    842                 txs_free(txs);
    843                 g_slist_free(output);
    844                 return TRUE;
    845         }
    846         txs_free(txs);
    847         return FALSE;
    848 }
    849 
    850 gboolean twitter_open_stream(struct im_connection *ic)
    851 {
    852         struct twitter_data *td = ic->proto_data;
    853         char *args[2] = {"with", "followings"};
    854        
    855         if ((td->stream = twitter_http(ic, TWITTER_USER_STREAM_URL,
    856                                        twitter_http_stream, ic, 0, args, 2))) {
    857                 /* This flag must be enabled or we'll get no data until EOF
    858                    (which err, kind of, defeats the purpose of a streaming API). */
    859                 td->stream->flags |= HTTPC_STREAMING;
    860                 return TRUE;
    861         }
    862        
    863         return FALSE;
    864 }
    865 
    866 static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor);
    867 static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor);
     742static void twitter_http_get_home_timeline(struct http_request *req);
     743static void twitter_http_get_mentions(struct http_request *req);
    868744
    869745/**
     
    947823}
    948824
    949 static void twitter_http_get_home_timeline(struct http_request *req);
    950 static void twitter_http_get_mentions(struct http_request *req);
    951 
    952825/**
    953826 * Get the timeline.
    954827 */
    955 static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
     828void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
    956829{
    957830        struct twitter_data *td = ic->proto_data;
     
    989862 * Get mentions.
    990863 */
    991 static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)
     864void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)
    992865{
    993866        struct twitter_data *td = ic->proto_data;
     
    1020893
    1021894        g_free(args[1]);
    1022         g_free(args[5]);
     895        if (td->timeline_id) {
     896                g_free(args[5]);
     897        }
    1023898}
    1024899
     
    1030905        struct im_connection *ic = req->data;
    1031906        struct twitter_data *td;
    1032         json_value *parsed;
     907        struct xt_node *parsed;
    1033908        struct twitter_xml_list *txl;
    1034909
     
    1046921                goto end;
    1047922        twitter_xt_get_status_list(ic, parsed, txl);
    1048         json_value_free(parsed);
     923        xt_free_node(parsed);
    1049924
    1050925        td->home_timeline_obj = txl;
    1051926
    1052927      end:
    1053         if (!g_slist_find(twitter_connections, ic))
    1054                 return;
    1055 
    1056928        td->flags |= TWITTER_GOT_TIMELINE;
    1057929
     
    1066938        struct im_connection *ic = req->data;
    1067939        struct twitter_data *td;
    1068         json_value *parsed;
     940        struct xt_node *parsed;
    1069941        struct twitter_xml_list *txl;
    1070942
     
    1082954                goto end;
    1083955        twitter_xt_get_status_list(ic, parsed, txl);
    1084         json_value_free(parsed);
     956        xt_free_node(parsed);
    1085957
    1086958        td->mentions_obj = txl;
    1087959
    1088960      end:
    1089         if (!g_slist_find(twitter_connections, ic))
    1090                 return;
    1091 
    1092961        td->flags |= TWITTER_GOT_MENTIONS;
    1093962
     
    1103972        struct im_connection *ic = req->data;
    1104973        struct twitter_data *td;
    1105         json_value *parsed, *id;
     974        struct xt_node *parsed, *node;
    1106975
    1107976        // Check if the connection is still active.
     
    1115984                return;
    1116985       
    1117         if ((id = json_o_get(parsed, "id")) && id->type == json_integer)
    1118                 td->last_status_id = id->u.integer;
     986        if ((node = xt_find_node(parsed, "status")) &&
     987            (node = xt_find_node(node->children, "id")) && node->text)
     988                td->last_status_id = g_ascii_strtoull(node->text, NULL, 10);
    1119989}
    1120990
     
    11621032        char *url;
    11631033        url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_DESTROY_URL,
    1164                               (unsigned long long) id, ".json");
     1034                              (unsigned long long) id, ".xml");
    11651035        twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);
    11661036        g_free(url);
     
    11711041        char *url;
    11721042        url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_RETWEET_URL,
    1173                               (unsigned long long) id, ".json");
     1043                              (unsigned long long) id, ".xml");
    11741044        twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);
    11751045        g_free(url);
     
    11971067        char *url;
    11981068        url = g_strdup_printf("%s%llu%s", TWITTER_FAVORITE_CREATE_URL,
    1199                               (unsigned long long) id, ".json");
     1069                              (unsigned long long) id, ".xml");
    12001070        twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);
    12011071        g_free(url);
  • protocols/twitter/twitter_lib.h

    rb006464 re1d3f98  
    2929#include "twitter_http.h"
    3030
    31 #define TWITTER_API_URL "http://api.twitter.com/1.1"
     31#define TWITTER_API_URL "http://api.twitter.com/1"
    3232#define IDENTICA_API_URL "https://identi.ca/api"
    3333
    3434/* Status URLs */
    35 #define TWITTER_STATUS_UPDATE_URL "/statuses/update.json"
     35#define TWITTER_STATUS_UPDATE_URL "/statuses/update.xml"
    3636#define TWITTER_STATUS_SHOW_URL "/statuses/show/"
    3737#define TWITTER_STATUS_DESTROY_URL "/statuses/destroy/"
     
    3939
    4040/* Timeline URLs */
    41 #define TWITTER_PUBLIC_TIMELINE_URL "/statuses/public_timeline.json"
    42 #define TWITTER_FEATURED_USERS_URL "/statuses/featured.json"
    43 #define TWITTER_FRIENDS_TIMELINE_URL "/statuses/friends_timeline.json"
    44 #define TWITTER_HOME_TIMELINE_URL "/statuses/home_timeline.json"
    45 #define TWITTER_MENTIONS_URL "/statuses/mentions_timeline.json"
    46 #define TWITTER_USER_TIMELINE_URL "/statuses/user_timeline.json"
     41#define TWITTER_PUBLIC_TIMELINE_URL "/statuses/public_timeline.xml"
     42#define TWITTER_FEATURED_USERS_URL "/statuses/featured.xml"
     43#define TWITTER_FRIENDS_TIMELINE_URL "/statuses/friends_timeline.xml"
     44#define TWITTER_HOME_TIMELINE_URL "/statuses/home_timeline.xml"
     45#define TWITTER_MENTIONS_URL "/statuses/mentions.xml"
     46#define TWITTER_USER_TIMELINE_URL "/statuses/user_timeline.xml"
    4747
    4848/* Users URLs */
    49 #define TWITTER_USERS_LOOKUP_URL "/users/lookup.json"
     49#define TWITTER_USERS_LOOKUP_URL "/users/lookup.xml"
    5050
    5151/* Direct messages URLs */
    52 #define TWITTER_DIRECT_MESSAGES_URL "/direct_messages.json"
    53 #define TWITTER_DIRECT_MESSAGES_NEW_URL "/direct_messages/new.json"
    54 #define TWITTER_DIRECT_MESSAGES_SENT_URL "/direct_messages/sent.json"
     52#define TWITTER_DIRECT_MESSAGES_URL "/direct_messages.xml"
     53#define TWITTER_DIRECT_MESSAGES_NEW_URL "/direct_messages/new.xml"
     54#define TWITTER_DIRECT_MESSAGES_SENT_URL "/direct_messages/sent.xml"
    5555#define TWITTER_DIRECT_MESSAGES_DESTROY_URL "/direct_messages/destroy/"
    5656
    5757/* Friendships URLs */
    58 #define TWITTER_FRIENDSHIPS_CREATE_URL "/friendships/create.json"
    59 #define TWITTER_FRIENDSHIPS_DESTROY_URL "/friendships/destroy.json"
    60 #define TWITTER_FRIENDSHIPS_SHOW_URL "/friendships/show.json"
     58#define TWITTER_FRIENDSHIPS_CREATE_URL "/friendships/create.xml"
     59#define TWITTER_FRIENDSHIPS_DESTROY_URL "/friendships/destroy.xml"
     60#define TWITTER_FRIENDSHIPS_SHOW_URL "/friendships/show.xml"
    6161
    6262/* Social graphs URLs */
    63 #define TWITTER_FRIENDS_IDS_URL "/friends/ids.json"
    64 #define TWITTER_FOLLOWERS_IDS_URL "/followers/ids.json"
     63#define TWITTER_FRIENDS_IDS_URL "/friends/ids.xml"
     64#define TWITTER_FOLLOWERS_IDS_URL "/followers/ids.xml"
    6565
    6666/* Account URLs */
    67 #define TWITTER_ACCOUNT_RATE_LIMIT_URL "/account/rate_limit_status.json"
     67#define TWITTER_ACCOUNT_RATE_LIMIT_URL "/account/rate_limit_status.xml"
    6868
    6969/* Favorites URLs */
    70 #define TWITTER_FAVORITES_GET_URL "/favorites.json"
     70#define TWITTER_FAVORITES_GET_URL "/favorites.xml"
    7171#define TWITTER_FAVORITE_CREATE_URL "/favorites/create/"
    7272#define TWITTER_FAVORITE_DESTROY_URL "/favorites/destroy/"
     
    7777
    7878/* Report spam */
    79 #define TWITTER_REPORT_SPAM_URL "/report_spam.json"
     79#define TWITTER_REPORT_SPAM_URL "/report_spam.xml"
    8080
    81 #define TWITTER_USER_STREAM_URL "https://userstream.twitter.com/1.1/user.json"
    82 
    83 gboolean twitter_open_stream(struct im_connection *ic);
    8481void twitter_get_timeline(struct im_connection *ic, gint64 next_cursor);
    8582void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor);
     83void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor);
     84void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor);
    8685void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor);
    8786
Note: See TracChangeset for help on using the changeset viewer.