source: lib/http_client.c @ ae3dc99

Last change on this file since ae3dc99 was e046390, checked in by Wilmer van der Gaast <wilmer@…>, at 2009-10-10T23:25:54Z

Make purple use BitlBee's event handling API. Since the APIs never really
diverged too much this is fairly transparent. I did rename and redefine
GAIM_INPUT_* variables to really make it work without adding another stupid
layer in between.

One problem left, the new libpurple input API doesn't care about return
values. Fixing that in the next CL.

  • Property mode set to 100644
File size: 10.4 KB
Line 
1  /********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2005 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
7/* HTTP(S) module                                                       */
8
9/*
10  This program is free software; you can redistribute it and/or modify
11  it under the terms of the GNU General Public License as published by
12  the Free Software Foundation; either version 2 of the License, or
13  (at your option) any later version.
14
15  This program is distributed in the hope that it will be useful,
16  but WITHOUT ANY WARRANTY; without even the implied warranty of
17  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  GNU General Public License for more details.
19
20  You should have received a copy of the GNU General Public License with
21  the Debian GNU/Linux distribution in /usr/share/common-licenses/GPL;
22  if not, write to the Free Software Foundation, Inc., 59 Temple Place,
23  Suite 330, Boston, MA  02111-1307  USA
24*/
25
26#include <string.h>
27#include <stdio.h>
28
29#include "http_client.h"
30#include "url.h"
31#include "sock.h"
32
33
34static gboolean http_connected( gpointer data, int source, b_input_condition cond );
35static gboolean http_ssl_connected( gpointer data, void *source, b_input_condition cond );
36static gboolean http_incoming_data( gpointer data, int source, b_input_condition cond );
37
38
39void *http_dorequest( char *host, int port, int ssl, char *request, http_input_function func, gpointer data )
40{
41        struct http_request *req;
42        int error = 0;
43       
44        req = g_new0( struct http_request, 1 );
45       
46        if( ssl )
47        {
48                req->ssl = ssl_connect( host, port, http_ssl_connected, req );
49                if( req->ssl == NULL )
50                        error = 1;
51        }
52        else
53        {
54                req->fd = proxy_connect( host, port, http_connected, req );
55                if( req->fd < 0 )
56                        error = 1;
57        }
58       
59        if( error )
60        {
61                http_free( req );
62                return NULL;
63        }
64       
65        req->func = func;
66        req->data = data;
67        req->request = g_strdup( request );
68        req->request_length = strlen( request );
69       
70        return( req );
71}
72
73void *http_dorequest_url( char *url_string, http_input_function func, gpointer data )
74{
75        url_t *url = g_new0( url_t, 1 );
76        char *request;
77        void *ret;
78       
79        if( !url_set( url, url_string ) )
80        {
81                g_free( url );
82                return NULL;
83        }
84       
85        if( url->proto != PROTO_HTTP && url->proto != PROTO_HTTPS )
86        {
87                g_free( url );
88                return NULL;
89        }
90       
91        request = g_strdup_printf( "GET %s HTTP/1.0\r\n"
92                                   "Host: %s\r\n"
93                                   "User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n"
94                                   "\r\n", url->file, url->host );
95       
96        ret = http_dorequest( url->host, url->port,
97                              url->proto == PROTO_HTTPS, request, func, data );
98       
99        g_free( url );
100        g_free( request );
101        return ret;
102}
103
104/* This one is actually pretty simple... Might get more calls if we can't write
105   the whole request at once. */
106static gboolean http_connected( gpointer data, int source, b_input_condition cond )
107{
108        struct http_request *req = data;
109        int st;
110       
111        if( source < 0 )
112                goto error;
113       
114        if( req->inpa > 0 )
115                b_event_remove( req->inpa );
116       
117        sock_make_nonblocking( req->fd );
118       
119        if( req->ssl )
120        {
121                st = ssl_write( req->ssl, req->request + req->bytes_written,
122                                req->request_length - req->bytes_written );
123                if( st < 0 )
124                {
125                        if( ssl_errno != SSL_AGAIN )
126                        {
127                                ssl_disconnect( req->ssl );
128                                goto error;
129                        }
130                }
131        }
132        else
133        {
134                st = write( source, req->request + req->bytes_written,
135                                    req->request_length - req->bytes_written );
136                if( st < 0 )
137                {
138                        if( !sockerr_again() )
139                        {
140                                closesocket( req->fd );
141                                goto error;
142                        }
143                }
144        }
145       
146        if( st > 0 )
147                req->bytes_written += st;
148       
149        if( req->bytes_written < req->request_length )
150                req->inpa = b_input_add( source,
151                                         req->ssl ? ssl_getdirection( req->ssl ) : B_EV_IO_WRITE,
152                                         http_connected, req );
153        else
154                req->inpa = b_input_add( source, B_EV_IO_READ, http_incoming_data, req );
155       
156        return FALSE;
157       
158error:
159        req->status_string = g_strdup( "Error while writing HTTP request" );
160       
161        req->func( req );
162        http_free( req );
163        return FALSE;
164}
165
166static gboolean http_ssl_connected( gpointer data, void *source, b_input_condition cond )
167{
168        struct http_request *req = data;
169       
170        if( source == NULL )
171                return http_connected( data, -1, cond );
172       
173        req->fd = ssl_getfd( source );
174       
175        return http_connected( data, req->fd, cond );
176}
177
178static gboolean http_incoming_data( gpointer data, int source, b_input_condition cond )
179{
180        struct http_request *req = data;
181        int evil_server = 0;
182        char buffer[2048];
183        char *end1, *end2;
184        int st;
185       
186        if( req->inpa > 0 )
187                b_event_remove( req->inpa );
188       
189        if( req->ssl )
190        {
191                st = ssl_read( req->ssl, buffer, sizeof( buffer ) );
192                if( st < 0 )
193                {
194                        if( ssl_errno != SSL_AGAIN )
195                        {
196                                /* goto cleanup; */
197                               
198                                /* YAY! We have to deal with crappy Microsoft
199                                   servers that LOVE to send invalid TLS
200                                   packets that abort connections! \o/ */
201                               
202                                goto got_reply;
203                        }
204                }
205                else if( st == 0 )
206                {
207                        goto got_reply;
208                }
209        }
210        else
211        {
212                st = read( req->fd, buffer, sizeof( buffer ) );
213                if( st < 0 )
214                {
215                        if( !sockerr_again() )
216                        {
217                                req->status_string = g_strdup( strerror( errno ) );
218                                goto cleanup;
219                        }
220                }
221                else if( st == 0 )
222                {
223                        goto got_reply;
224                }
225        }
226       
227        if( st > 0 )
228        {
229                req->reply_headers = g_realloc( req->reply_headers, req->bytes_read + st + 1 );
230                memcpy( req->reply_headers + req->bytes_read, buffer, st );
231                req->bytes_read += st;
232        }
233       
234        /* There will be more! */
235        req->inpa = b_input_add( req->fd,
236                                 req->ssl ? ssl_getdirection( req->ssl ) : B_EV_IO_READ,
237                                 http_incoming_data, req );
238       
239        return FALSE;
240
241got_reply:
242        /* Maybe if the webserver is overloaded, or when there's bad SSL
243           support... */
244        if( req->bytes_read == 0 )
245        {
246                req->status_string = g_strdup( "Empty HTTP reply" );
247                goto cleanup;
248        }
249       
250        /* Zero termination is very convenient. */
251        req->reply_headers[req->bytes_read] = 0;
252       
253        /* Find the separation between headers and body, and keep stupid
254           webservers in mind. */
255        end1 = strstr( req->reply_headers, "\r\n\r\n" );
256        end2 = strstr( req->reply_headers, "\n\n" );
257       
258        if( end2 && end2 < end1 )
259        {
260                end1 = end2 + 1;
261                evil_server = 1;
262        }
263        else if( end1 )
264        {
265                end1 += 2;
266        }
267        else
268        {
269                req->status_string = g_strdup( "Malformed HTTP reply" );
270                goto cleanup;
271        }
272       
273        *end1 = 0;
274       
275        if( evil_server )
276                req->reply_body = end1 + 1;
277        else
278                req->reply_body = end1 + 2;
279       
280        req->body_size = req->reply_headers + req->bytes_read - req->reply_body;
281       
282        if( ( end1 = strchr( req->reply_headers, ' ' ) ) != NULL )
283        {
284                if( sscanf( end1 + 1, "%d", &req->status_code ) != 1 )
285                {
286                        req->status_string = g_strdup( "Can't parse status code" );
287                        req->status_code = -1;
288                }
289                else
290                {
291                        char *eol;
292                       
293                        if( evil_server )
294                                eol = strchr( end1, '\n' );
295                        else
296                                eol = strchr( end1, '\r' );
297                       
298                        req->status_string = g_strndup( end1 + 1, eol - end1 - 1 );
299                       
300                        /* Just to be sure... */
301                        if( ( eol = strchr( req->status_string, '\r' ) ) )
302                                *eol = 0;
303                        if( ( eol = strchr( req->status_string, '\n' ) ) )
304                                *eol = 0;
305                }
306        }
307        else
308        {
309                req->status_string = g_strdup( "Can't locate status code" );
310                req->status_code = -1;
311        }
312       
313        if( req->status_code == 301 || req->status_code == 302 )
314        {
315                char *loc, *new_request, *new_host;
316                int error = 0, new_port, new_proto;
317               
318                /* We might fill it again, so let's not leak any memory. */
319                g_free( req->status_string );
320                req->status_string = NULL;
321               
322                loc = strstr( req->reply_headers, "\nLocation: " );
323                if( loc == NULL ) /* We can't handle this redirect... */
324                {
325                        req->status_string = g_strdup( "Can't locate Location: header" );
326                        goto cleanup;
327                }
328               
329                loc += 11;
330                while( *loc == ' ' )
331                        loc ++;
332               
333                /* TODO/FIXME: Possibly have to handle relative redirections,
334                   and rewrite Host: headers. Not necessary for now, it's
335                   enough for passport authentication like this. */
336               
337                if( *loc == '/' )
338                {
339                        /* Just a different pathname... */
340                       
341                        /* Since we don't cache the servername, and since we
342                           don't need this yet anyway, I won't implement it. */
343                       
344                        req->status_string = g_strdup( "Can't handle recursive redirects" );
345                       
346                        goto cleanup;
347                }
348                else
349                {
350                        /* A whole URL */
351                        url_t *url;
352                        char *s;
353                       
354                        s = strstr( loc, "\r\n" );
355                        if( s == NULL )
356                                goto cleanup;
357                       
358                        url = g_new0( url_t, 1 );
359                        *s = 0;
360                       
361                        if( !url_set( url, loc ) )
362                        {
363                                req->status_string = g_strdup( "Malformed redirect URL" );
364                                g_free( url );
365                                goto cleanup;
366                        }
367                       
368                        /* Okay, this isn't fun! We have to rebuild the request... :-( */
369                        new_request = g_malloc( req->request_length + strlen( url->file ) );
370                       
371                        /* So, now I just allocated enough memory, so I'm
372                           going to use strcat(), whether you like it or not. :-) */
373                       
374                        sprintf( new_request, "GET %s HTTP/1.0", url->file );
375                       
376                        s = strstr( req->request, "\r\n" );
377                        if( s == NULL )
378                        {
379                                req->status_string = g_strdup( "Error while rebuilding request string" );
380                                g_free( new_request );
381                                g_free( url );
382                                goto cleanup;
383                        }
384                       
385                        strcat( new_request, s );
386                        new_host = g_strdup( url->host );
387                        new_port = url->port;
388                        new_proto = url->proto;
389                       
390                        g_free( url );
391                }
392               
393                if( req->ssl )
394                        ssl_disconnect( req->ssl );
395                else
396                        closesocket( req->fd );
397               
398                req->fd = -1;
399                req->ssl = NULL;
400               
401                if( new_proto == PROTO_HTTPS )
402                {
403                        req->ssl = ssl_connect( new_host, new_port, http_ssl_connected, req );
404                        if( req->ssl == NULL )
405                                error = 1;
406                }
407                else
408                {
409                        req->fd = proxy_connect( new_host, new_port, http_connected, req );
410                        if( req->fd < 0 )
411                                error = 1;
412                }
413                g_free( new_host );
414               
415                if( error )
416                {
417                        req->status_string = g_strdup( "Connection problem during redirect" );
418                        g_free( new_request );
419                        goto cleanup;
420                }
421               
422                g_free( req->request );
423                g_free( req->reply_headers );
424                req->request = new_request;
425                req->request_length = strlen( new_request );
426                req->bytes_read = req->bytes_written = req->inpa = 0;
427                req->reply_headers = req->reply_body = NULL;
428               
429                return FALSE;
430        }
431       
432        /* Assume that a closed connection means we're finished, this indeed
433           breaks with keep-alive connections and faulty connections. */
434        req->finished = 1;
435
436cleanup:
437        if( req->ssl )
438                ssl_disconnect( req->ssl );
439        else
440                closesocket( req->fd );
441       
442        req->func( req );
443        http_free( req );
444        return FALSE;
445}
446
447void http_free( struct http_request *req )
448{
449        g_free( req->request );
450        g_free( req->reply_headers );
451        g_free( req->status_string );
452        g_free( req );
453}
454
Note: See TracBrowser for help on using the repository browser.