Changes in / [e1d3f98:b006464]


Ignore:
Files:
4 added
16 edited

Legend:

Unmodified
Added
Removed
  • debian/copyright

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

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

    re1d3f98 rb006464  
    193193}
    194194
     195static gboolean http_handle_headers( struct http_request *req );
     196
    195197static gboolean http_incoming_data( gpointer data, int source, b_input_condition cond )
    196198{
    197199        struct http_request *req = data;
    198         int evil_server = 0;
    199200        char buffer[2048];
    200         char *end1, *end2, *s;
     201        char *s;
    201202        size_t content_length;
    202203        int st;
     
    218219                                   packets that abort connections! \o/ */
    219220                               
    220                                 goto got_reply;
     221                                goto eof;
    221222                        }
    222223                }
    223224                else if( st == 0 )
    224225                {
    225                         goto got_reply;
     226                        goto eof;
    226227                }
    227228        }
     
    239240                else if( st == 0 )
    240241                {
    241                         goto got_reply;
    242                 }
    243         }
    244        
    245         if( st > 0 )
     242                        goto eof;
     243                }
     244        }
     245       
     246        if( st > 0 && !req->sbuf )
    246247        {
    247248                req->reply_headers = g_realloc( req->reply_headers, req->bytes_read + st + 1 );
    248249                memcpy( req->reply_headers + req->bytes_read, buffer, st );
    249250                req->bytes_read += st;
    250         }
     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 );
    251295       
    252296        /* There will be more! */
     
    255299                                 http_incoming_data, req );
    256300       
    257         if( ssl_pending( req->ssl ) )
    258                 return http_incoming_data( data, source, cond );
    259         else
    260                 return FALSE;
    261 
    262 got_reply:
     301        return FALSE;
     302
     303eof:
     304        req->flags |= HTTPC_EOF;
     305       
    263306        /* Maybe if the webserver is overloaded, or when there's bad SSL
    264307           support... */
     
    269312        }
    270313       
     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
     322cleanup:
     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! */
     351static gboolean http_handle_headers( struct http_request *req )
     352{
     353        char *end1, *end2;
     354        int evil_server = 0;
     355       
    271356        /* Zero termination is very convenient. */
    272         req->reply_headers[req->bytes_read] = 0;
     357        req->reply_headers[req->bytes_read] = '\0';
    273358       
    274359        /* Find the separation between headers and body, and keep stupid
     
    289374        {
    290375                req->status_string = g_strdup( "Malformed HTTP reply" );
    291                 goto cleanup;
     376                return TRUE;
    292377        }
    293378       
     
    306391        if( ( end1 = strchr( req->reply_headers, ' ' ) ) != NULL )
    307392        {
    308                 if( sscanf( end1 + 1, "%d", &req->status_code ) != 1 )
     393                if( sscanf( end1 + 1, "%hd", &req->status_code ) != 1 )
    309394                {
    310395                        req->status_string = g_strdup( "Can't parse status code" );
     
    349434                {
    350435                        req->status_string = g_strdup( "Can't locate Location: header" );
    351                         goto cleanup;
     436                        return TRUE;
    352437                }
    353438               
     
    369454                        req->status_string = g_strdup( "Can't handle recursive redirects" );
    370455                       
    371                         goto cleanup;
     456                        return TRUE;
    372457                }
    373458                else
     
    380465                        s = strstr( loc, "\r\n" );
    381466                        if( s == NULL )
    382                                 goto cleanup;
     467                                return TRUE;
    383468                       
    384469                        url = g_new0( url_t, 1 );
     
    389474                                req->status_string = g_strdup( "Malformed redirect URL" );
    390475                                g_free( url );
    391                                 goto cleanup;
     476                                return TRUE;
    392477                        }
    393478                       
     
    401486                                req->status_string = g_strdup( "Error while rebuilding request string" );
    402487                                g_free( url );
    403                                 goto cleanup;
     488                                return TRUE;
    404489                        }
    405490                       
     
    467552                        req->status_string = g_strdup( "Connection problem during redirect" );
    468553                        g_free( new_request );
    469                         goto cleanup;
     554                        return TRUE;
    470555                }
    471556               
     
    480565        }
    481566       
    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 
    486 cleanup:
     567        return TRUE;
     568}
     569
     570void 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
     588void http_close( struct http_request *req )
     589{
     590        if( !req )
     591                return;
     592       
    487593        if( req->ssl )
    488594                ssl_disconnect( req->ssl );
     
    490596                closesocket( req->fd );
    491597       
    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 );
    509598        http_free( req );
    510         return FALSE;
    511599}
    512600
     
    516604        g_free( req->reply_headers );
    517605        g_free( req->status_string );
     606        g_free( req->sbuf );
    518607        g_free( req );
    519608}
    520 
  • lib/http_client.h

    re1d3f98 rb006464  
    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. 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).
     28   response to come back. Initially written for MS Passport authentication,
     29   but used for many other things now like OAuth and Twitter.
    3230   
    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. */
     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. */
    3934
    4035#include <glib.h>
     
    4237
    4338struct http_request;
     39
     40typedef enum http_client_flags
     41{
     42        HTTPC_STREAMING = 1,
     43        HTTPC_EOF = 2,
     44} http_client_flags_t;
    4445
    4546/* Your callback function should look like this: */
     
    5354        char *request;          /* The request to send to the server. */
    5455        int request_length;     /* Its size. */
    55         int status_code;        /* The numeric HTTP status code. (Or -1
     56        short status_code;      /* The numeric HTTP status code. (Or -1
    5657                                   if something really went wrong) */
    5758        char *status_string;    /* The error text. */
     
    5960        char *reply_body;
    6061        int body_size;          /* The number of bytes in reply_body. */
    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
     62        short redir_ttl;        /* You can set it to 0 if you don't want
    6463                                   http_client to follow them. */
     64       
     65        http_client_flags_t flags;
    6566       
    6667        http_input_function func;
     
    6869       
    6970        /* 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;
    7781};
    7882
     
    8387struct http_request *http_dorequest( char *host, int port, int ssl, char *request, http_input_function func, gpointer data );
    8488struct 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. */
     91void http_flush_bytes( struct http_request *req, size_t len );
     92void http_close( struct http_request *req );
  • lib/oauth2.c

    re1d3f98 rb006464  
    44*  Simple OAuth client (consumer) implementation.                           *
    55*                                                                           *
    6 *  Copyright 2010-2011 Wilmer van der Gaast <wilmer@gaast.net>              *
     6*  Copyright 2010-2012 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"
    2829#include "url.h"
    2930
     
    4445};
    4546
    46 static 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                 atoken = oauth2_json_dumb_get( req->reply_body, "access_token" );
    118                 rtoken = oauth2_json_dumb_get( req->reply_body, "refresh_token" );
     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 );
    119133        }
    120134        else
     
    136150        g_free( cb_data );
    137151}
    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. */
    144 static 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

    re1d3f98 rb006464  
    3838
    3939static gboolean initialized = FALSE;
    40 gnutls_certificate_credentials xcred;
     40gnutls_certificate_credentials_t xcred;
    4141
    4242#include <limits.h>
     
    6060        gboolean verify;
    6161       
    62         gnutls_session session;
     62        gnutls_session_t session;
    6363};
    6464
     
    134134        conn->data = data;
    135135        conn->inpa = -1;
    136         conn->hostname = hostname;
     136        conn->hostname = g_strdup( 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         const char *hostname;
    174        
    175         hostname = gnutls_session_get_ptr( session );
     173        struct scd *conn;
     174       
     175        conn = 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, hostname ) )
     213        if( !gnutls_x509_crt_check_hostname( cert, conn->hostname ) )
    214214        {
    215215                verifyret |= VERIFY_CERT_INVALID;
     
    267267       
    268268        gnutls_init( &conn->session, GNUTLS_CLIENT );
    269         if( conn->verify )
    270                 gnutls_session_set_ptr( conn->session, (void *) conn->hostname );
     269        gnutls_session_set_ptr( conn->session, (void *) conn );
    271270#if GNUTLS_VERSION_NUMBER < 0x020c00
    272271        gnutls_transport_set_lowat( conn->session, 0 );
     
    276275       
    277276        sock_make_nonblocking( conn->fd );
    278         gnutls_transport_set_ptr( conn->session, (gnutls_transport_ptr) GNUTLS_STUPID_CAST conn->fd );
     277        gnutls_transport_set_ptr( conn->session, (gnutls_transport_ptr_t) GNUTLS_STUPID_CAST conn->fd );
    279278       
    280279        return ssl_handshake( data, source, cond );
     
    402401        if( conn->session )
    403402                gnutls_deinit( conn->session );
     403        g_free( conn->hostname );
    404404        g_free( conn );
    405405}
  • protocols/msn/msn.c

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

    re1d3f98 rb006464  
    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;
    578582        }
    579583        else if( isdigit( cmd[0][0] ) )
  • protocols/nogaim.c

    re1d3f98 rb006464  
    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       
    263273        if( ic->acc->prpl->keepalive )
    264274                ic->acc->prpl->keepalive( ic );
    265275       
    266276        return TRUE;
     277}
     278
     279void 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;
    267287}
    268288
     
    277297        imcb_log( ic, "Logged in" );
    278298       
    279         b_event_remove( ic->keepalive );
    280         ic->keepalive = b_timeout_add( 60000, send_keepalive, ic );
    281299        ic->flags |= OPT_LOGGED_IN;
     300        start_keepalives( ic, 60000 );
    282301       
    283302        /* Necessary to send initial presence status, even if we're not away. */
  • protocols/nogaim.h

    re1d3f98 rb006464  
    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 */
    7072
    7173/* ok. now the fun begins. first we create a connection structure */
  • protocols/twitter/twitter.c

    re1d3f98 rb006464  
    55*                                                                           *
    66*  Copyright 2009 Geert Mulders <g.c.w.m.mulders@gmail.com>                 *
     7*  Copyright 2010-2012 Wilmer van der Gaast <wilmer@gaast.net>              *
    78*                                                                           *
    89*  This library is free software; you can redistribute it and/or            *
     
    6667        // Run this once. After this queue the main loop function.
    6768        twitter_main_loop(ic, -1, 0);
    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);
     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        }
    7382}
    7483
     
    279288
    280289        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;
    281293}
    282294
     
    363375
    364376        if (td) {
     377                http_close(td->stream);
    365378                oauth_info_free(td->oauth_info);
    366379                g_free(td->user);
  • protocols/twitter/twitter.h

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

    re1d3f98 rb006464  
    4747 * This is actually pretty generic function... Perhaps it should move to the lib/http_client.c
    4848 */
    49 void *twitter_http(struct im_connection *ic, char *url_string, http_input_function func,
    50                    gpointer data, int is_post, char **arguments, int arguments_len)
     49struct 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)
    5151{
    5252        struct twitter_data *td = ic->proto_data;
     
    5555        void *ret;
    5656        char *url_arguments;
     57        url_t *base_url = NULL;
    5758
    5859        url_arguments = g_strdup("");
     
    6768                }
    6869        }
     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       
    6979        // Make the request.
    7080        g_string_printf(request, "%s %s%s%s%s HTTP/1.0\r\n"
     
    7282                        "User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n",
    7383                        is_post ? "POST" : "GET",
    74                         td->url_path, url_string,
    75                         is_post ? "" : "?", is_post ? "" : url_arguments, td->url_host);
     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);
    7688
    7789        // If a pass and user are given we append them to the request.
     
    8092                char *full_url;
    8193
    82                 full_url = g_strconcat(set_getstr(&ic->acc->set, "base_url"), url_string, NULL);
     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);
    8398                full_header = oauth_http_header(td->oauth_info, is_post ? "POST" : "GET",
    8499                                                full_url, url_arguments);
     
    109124        }
    110125
    111         ret = http_dorequest(td->url_host, td->url_port, td->url_ssl, request->str, func, data);
     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);
    112130
    113131        g_free(url_arguments);
    114132        g_string_free(request, TRUE);
     133        g_free(base_url);
    115134        return ret;
    116135}
  • protocols/twitter/twitter_http.h

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

    re1d3f98 rb006464  
    3535#include "misc.h"
    3636#include "base64.h"
    37 #include "xmltree.h"
    3837#include "twitter_lib.h"
     38#include "json_util.h"
    3939#include <ctype.h>
    4040#include <errno.h>
     
    168168{
    169169        static char *ret = NULL;
    170         struct xt_node *root, *node, *err;
     170        json_value *root, *err;
    171171
    172172        g_free(ret);
     
    174174
    175175        if (req->body_size > 0) {
    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);
     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);
    185185        }
    186186
     
    188188}
    189189
    190 static struct xt_node *twitter_parse_response(struct im_connection *ic, struct http_request *req)
     190/* WATCH OUT: This function might or might not destroy your connection.
     191   Sub-optimal indeed, but just be careful when this returns NULL! */
     192static json_value *twitter_parse_response(struct im_connection *ic, struct http_request *req)
    191193{
    192194        gboolean logging_in = !(ic->flags & OPT_LOGGED_IN);
    193195        gboolean periodic;
    194196        struct twitter_data *td = ic->proto_data;
    195         struct xt_node *ret;
     197        json_value *ret;
    196198        char path[64] = "", *s;
    197199       
     
    211213                   throwing 401s so I'll keep treating this one as fatal
    212214                   only during login. */
    213                 imcb_error(ic, "Authentication failure");
     215                imcb_error(ic, "Authentication failure (%s)",
     216                               twitter_parse_error(req));
    214217                imc_logout(ic, FALSE);
    215218                return NULL;
     
    227230        }
    228231
    229         if ((ret = xt_from_string(req->reply_body, req->body_size)) == NULL) {
     232        if ((ret = json_parse(req->reply_body)) == NULL) {
    230233                imcb_error(ic, "Could not retrieve %s: %s",
    231234                           path, "XML parse error");
     
    251254
    252255/**
    253  * Function to help fill a list.
    254  */
    255 static 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 /**
    268256 * Fill a list of ids.
    269257 */
    270 static xt_status twitter_xt_get_friends_id_list(struct xt_node *node, struct twitter_xml_list *txl)
    271 {
    272         struct xt_node *child;
     258static gboolean twitter_xt_get_friends_id_list(json_value *node, struct twitter_xml_list *txl)
     259{
     260        json_value *c;
     261        int i;
    273262
    274263        // Set the list type.
    275264        txl->type = TXL_ID;
    276265
    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;
     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;
    292286}
    293287
     
    300294{
    301295        struct im_connection *ic;
    302         struct xt_node *parsed;
     296        json_value *parsed;
    303297        struct twitter_xml_list *txl;
    304298        struct twitter_data *td;
     
    322316        if (!(parsed = twitter_parse_response(ic, req)))
    323317                return;
     318       
    324319        twitter_xt_get_friends_id_list(parsed, txl);
    325         xt_free_node(parsed);
     320        json_value_free(parsed);
    326321
    327322        td->follow_ids = txl->list;
     
    338333}
    339334
    340 static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl);
     335static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl);
    341336static void twitter_http_get_users_lookup(struct http_request *req);
    342337
     
    379374{
    380375        struct im_connection *ic = req->data;
    381         struct xt_node *parsed;
     376        json_value *parsed;
    382377        struct twitter_xml_list *txl;
    383378        GSList *l = NULL;
     
    395390                return;
    396391        twitter_xt_get_users(parsed, txl);
    397         xt_free_node(parsed);
     392        json_value_free(parsed);
    398393
    399394        // Add the users as buddies.
     
    409404}
    410405
    411 /**
    412  * Function to fill a twitter_xml_user struct.
    413  * It sets:
    414  *  - the name and
    415  *  - the screen_name.
    416  */
    417 static 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;
     406struct 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;
    430415}
    431416
     
    435420 *  - all <user>s from the <users> element.
    436421 */
    437 static xt_status twitter_xt_get_users(struct xt_node *node, struct twitter_xml_list *txl)
     422static gboolean twitter_xt_get_users(json_value *node, struct twitter_xml_list *txl)
    438423{
    439424        struct twitter_xml_user *txu;
    440         struct xt_node *child;
     425        int i;
    441426
    442427        // Set the type of the list.
    443428        txl->type = TXL_USER;
    444429
     430        if (!node || node->type != json_array)
     431                return FALSE;
     432
    445433        // The root <users> node should hold the list of users <user>
    446434        // Walk over the nodes children.
    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.
     435        for (i = 0; i < node->u.array.length; i ++) {
     436                txu = twitter_xt_get_user(node->u.array.values[i]);
     437                if (txu)
    452438                        txl->list = g_slist_prepend(txl->list, txu);
    453                 }
    454         }
    455 
    456         return XT_HANDLED;
     439        }
     440
     441        return TRUE;
    457442}
    458443
     
    462447#define TWITTER_TIME_FORMAT "%a %b %d %H:%M:%S +0000 %Y"
    463448#endif
     449
     450static char* expand_entities(char* text, const json_value *entities);
    464451
    465452/**
     
    471458 *  - the user in a twitter_xml_user struct.
    472459 */
    473 static 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) {
     460static 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) {
    484473                        struct tm parsed;
    485474
     
    487476                           this field. :-( Also assumes the timezone used
    488477                           is UTC since C time handling functions suck. */
    489                         if (strptime(child->text, TWITTER_TIME_FORMAT, &parsed) != NULL)
     478                        if (strptime(v->u.string.ptr, TWITTER_TIME_FORMAT, &parsed) != NULL)
    490479                                txs->created_at = mktime_utc(&parsed);
    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);
     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;
    498488                }
    499489        }
     
    503493        if (rt) {
    504494                struct twitter_xml_status *rtxs = g_new0(struct twitter_xml_status, 1);
    505                 if (twitter_xt_get_status(rt, rtxs) != XT_HANDLED) {
     495                if (!twitter_xt_get_status(rt, rtxs)) {
    506496                        txs_free(rtxs);
    507                         return XT_HANDLED;
     497                        return TRUE;
    508498                }
    509499
     
    511501                txs->text = g_strdup_printf("RT @%s: %s", rtxs->user->screen_name, rtxs->text);
    512502                txs_free(rtxs);
    513         } else {
    514                 struct xt_node *urls, *url;
     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 */
     513static 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
     545static char* expand_entities(char* text, const json_value *entities) {
     546        JSON_O_FOREACH (entities, k, v) {
     547                int i;
    515548               
    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)
     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)
    521556                                continue;
    522557                       
    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                         }
     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;
    540571                }
    541572        }
    542 
    543         return XT_HANDLED;
     573       
     574        return text;
    544575}
    545576
     
    550581 *  - the next_cursor.
    551582 */
    552 static xt_status twitter_xt_get_status_list(struct im_connection *ic, struct xt_node *node,
    553                                             struct twitter_xml_list *txl)
     583static gboolean twitter_xt_get_status_list(struct im_connection *ic, const json_value *node,
     584                                           struct twitter_xml_list *txl)
    554585{
    555586        struct twitter_xml_status *txs;
    556         struct xt_node *child;
    557587        bee_user_t *bu;
     588        int i;
    558589
    559590        // Set the type of the list.
    560591        txl->type = TXL_STATUS;
     592       
     593        if (node->type != json_array)
     594                return FALSE;
    561595
    562596        // The root <statuses> node should hold the list of statuses <status>
    563597        // Walk over the nodes children.
    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                                 }
     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;
    579611                        }
    580                 } else if (g_strcasecmp("next_cursor", child->name) == 0) {
    581                         twitter_xt_next_cursor(child, txl);
    582612                }
    583613        }
    584614
    585         return XT_HANDLED;
     615        return TRUE;
    586616}
    587617
     
    740770}
    741771
    742 static void twitter_http_get_home_timeline(struct http_request *req);
    743 static void twitter_http_get_mentions(struct http_request *req);
     772static gboolean twitter_stream_handle_object(struct im_connection *ic, json_value *o);
     773
     774static 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
     827static 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
     850gboolean 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
     866static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor);
     867static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor);
    744868
    745869/**
     
    823947}
    824948
     949static void twitter_http_get_home_timeline(struct http_request *req);
     950static void twitter_http_get_mentions(struct http_request *req);
     951
    825952/**
    826953 * Get the timeline.
    827954 */
    828 void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
     955static void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor)
    829956{
    830957        struct twitter_data *td = ic->proto_data;
     
    862989 * Get mentions.
    863990 */
    864 void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)
     991static void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor)
    865992{
    866993        struct twitter_data *td = ic->proto_data;
     
    8931020
    8941021        g_free(args[1]);
    895         if (td->timeline_id) {
    896                 g_free(args[5]);
    897         }
     1022        g_free(args[5]);
    8981023}
    8991024
     
    9051030        struct im_connection *ic = req->data;
    9061031        struct twitter_data *td;
    907         struct xt_node *parsed;
     1032        json_value *parsed;
    9081033        struct twitter_xml_list *txl;
    9091034
     
    9211046                goto end;
    9221047        twitter_xt_get_status_list(ic, parsed, txl);
    923         xt_free_node(parsed);
     1048        json_value_free(parsed);
    9241049
    9251050        td->home_timeline_obj = txl;
    9261051
    9271052      end:
     1053        if (!g_slist_find(twitter_connections, ic))
     1054                return;
     1055
    9281056        td->flags |= TWITTER_GOT_TIMELINE;
    9291057
     
    9381066        struct im_connection *ic = req->data;
    9391067        struct twitter_data *td;
    940         struct xt_node *parsed;
     1068        json_value *parsed;
    9411069        struct twitter_xml_list *txl;
    9421070
     
    9541082                goto end;
    9551083        twitter_xt_get_status_list(ic, parsed, txl);
    956         xt_free_node(parsed);
     1084        json_value_free(parsed);
    9571085
    9581086        td->mentions_obj = txl;
    9591087
    9601088      end:
     1089        if (!g_slist_find(twitter_connections, ic))
     1090                return;
     1091
    9611092        td->flags |= TWITTER_GOT_MENTIONS;
    9621093
     
    9721103        struct im_connection *ic = req->data;
    9731104        struct twitter_data *td;
    974         struct xt_node *parsed, *node;
     1105        json_value *parsed, *id;
    9751106
    9761107        // Check if the connection is still active.
     
    9841115                return;
    9851116       
    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);
     1117        if ((id = json_o_get(parsed, "id")) && id->type == json_integer)
     1118                td->last_status_id = id->u.integer;
    9891119}
    9901120
     
    10321162        char *url;
    10331163        url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_DESTROY_URL,
    1034                               (unsigned long long) id, ".xml");
     1164                              (unsigned long long) id, ".json");
    10351165        twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);
    10361166        g_free(url);
     
    10411171        char *url;
    10421172        url = g_strdup_printf("%s%llu%s", TWITTER_STATUS_RETWEET_URL,
    1043                               (unsigned long long) id, ".xml");
     1173                              (unsigned long long) id, ".json");
    10441174        twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);
    10451175        g_free(url);
     
    10671197        char *url;
    10681198        url = g_strdup_printf("%s%llu%s", TWITTER_FAVORITE_CREATE_URL,
    1069                               (unsigned long long) id, ".xml");
     1199                              (unsigned long long) id, ".json");
    10701200        twitter_http(ic, url, twitter_http_post, ic, 1, NULL, 0);
    10711201        g_free(url);
  • protocols/twitter/twitter_lib.h

    re1d3f98 rb006464  
    2929#include "twitter_http.h"
    3030
    31 #define TWITTER_API_URL "http://api.twitter.com/1"
     31#define TWITTER_API_URL "http://api.twitter.com/1.1"
    3232#define IDENTICA_API_URL "https://identi.ca/api"
    3333
    3434/* Status URLs */
    35 #define TWITTER_STATUS_UPDATE_URL "/statuses/update.xml"
     35#define TWITTER_STATUS_UPDATE_URL "/statuses/update.json"
    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.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"
     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"
    4747
    4848/* Users URLs */
    49 #define TWITTER_USERS_LOOKUP_URL "/users/lookup.xml"
     49#define TWITTER_USERS_LOOKUP_URL "/users/lookup.json"
    5050
    5151/* Direct messages URLs */
    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"
     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"
    5555#define TWITTER_DIRECT_MESSAGES_DESTROY_URL "/direct_messages/destroy/"
    5656
    5757/* Friendships URLs */
    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"
     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"
    6161
    6262/* Social graphs URLs */
    63 #define TWITTER_FRIENDS_IDS_URL "/friends/ids.xml"
    64 #define TWITTER_FOLLOWERS_IDS_URL "/followers/ids.xml"
     63#define TWITTER_FRIENDS_IDS_URL "/friends/ids.json"
     64#define TWITTER_FOLLOWERS_IDS_URL "/followers/ids.json"
    6565
    6666/* Account URLs */
    67 #define TWITTER_ACCOUNT_RATE_LIMIT_URL "/account/rate_limit_status.xml"
     67#define TWITTER_ACCOUNT_RATE_LIMIT_URL "/account/rate_limit_status.json"
    6868
    6969/* Favorites URLs */
    70 #define TWITTER_FAVORITES_GET_URL "/favorites.xml"
     70#define TWITTER_FAVORITES_GET_URL "/favorites.json"
    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.xml"
     79#define TWITTER_REPORT_SPAM_URL "/report_spam.json"
    8080
     81#define TWITTER_USER_STREAM_URL "https://userstream.twitter.com/1.1/user.json"
     82
     83gboolean twitter_open_stream(struct im_connection *ic);
    8184void twitter_get_timeline(struct im_connection *ic, gint64 next_cursor);
    8285void twitter_get_friends_ids(struct im_connection *ic, gint64 next_cursor);
    83 void twitter_get_home_timeline(struct im_connection *ic, gint64 next_cursor);
    84 void twitter_get_mentions(struct im_connection *ic, gint64 next_cursor);
    8586void twitter_get_statuses_friends(struct im_connection *ic, gint64 next_cursor);
    8687
Note: See TracChangeset for help on using the changeset viewer.