source: lib/http_client.c @ 7885d0f

Last change on this file since 7885d0f was 7885d0f, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-07-15T23:23:04Z

Don't be a dumbass and stop following redirects if there doesn't seem to
be an end.

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