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
RevLine 
[8a9afe4]1  /********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2005 Wilmer van der Gaast and others                *
5  \********************************************************************/
6
[52b3a99]7/* HTTP(S) module                                                       */
[8a9afe4]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>
[52b3a99]27#include <stdio.h>
[8a9afe4]28
29#include "http_client.h"
[52b3a99]30#include "url.h"
[e4d6271]31#include "sock.h"
[8a9afe4]32
33
[ba9edaa]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 );
[8a9afe4]37
38
[f3e1e92]39void *http_dorequest( char *host, int port, int ssl, char *request, http_input_function func, gpointer data )
[8a9afe4]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        {
[fb98634]61                http_free( req );
62                return NULL;
[8a9afe4]63        }
64       
[52b3a99]65        req->func = func;
66        req->data = data;
[8a9afe4]67        req->request = g_strdup( request );
68        req->request_length = strlen( request );
[7885d0f]69        req->redir_ttl = 3;
[8a9afe4]70       
71        return( req );
72}
73
[0790644]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"
[1b5ab36]94                                   "User-Agent: BitlBee " BITLBEE_VERSION " " ARCH "/" CPU "\r\n"
[0790644]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 );
[266fe2f]102        return ret;
[0790644]103}
104
[8a9afe4]105/* This one is actually pretty simple... Might get more calls if we can't write
106   the whole request at once. */
[ba9edaa]107static gboolean http_connected( gpointer data, int source, b_input_condition cond )
[8a9afe4]108{
109        struct http_request *req = data;
110        int st;
111       
112        if( source < 0 )
113                goto error;
114       
115        if( req->inpa > 0 )
[ba9edaa]116                b_event_remove( req->inpa );
[8a9afe4]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                        {
[52b3a99]141                                closesocket( req->fd );
[8a9afe4]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 )
[ba9edaa]151                req->inpa = b_input_add( source,
152                                         req->ssl ? ssl_getdirection( req->ssl ) : GAIM_INPUT_WRITE,
153                                         http_connected, req );
[8a9afe4]154        else
[ba9edaa]155                req->inpa = b_input_add( source, GAIM_INPUT_READ, http_incoming_data, req );
[8a9afe4]156       
[ba9edaa]157        return FALSE;
[8a9afe4]158       
159error:
[7deb447]160        req->status_string = g_strdup( "Error while writing HTTP request" );
161       
[8a9afe4]162        req->func( req );
[fb98634]163        http_free( req );
[ba9edaa]164        return FALSE;
[8a9afe4]165}
166
[ba9edaa]167static gboolean http_ssl_connected( gpointer data, void *source, b_input_condition cond )
[8a9afe4]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
[ba9edaa]179static gboolean http_incoming_data( gpointer data, int source, b_input_condition cond )
[8a9afe4]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 )
[ba9edaa]188                b_event_remove( req->inpa );
[8a9afe4]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                        {
[ad8b8a3]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;
[8a9afe4]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                        {
[7deb447]218                                req->status_string = g_strdup( strerror( errno ) );
[8a9afe4]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 );
[52b3a99]232                req->bytes_read += st;
[8a9afe4]233        }
234       
235        /* There will be more! */
[ba9edaa]236        req->inpa = b_input_add( req->fd,
237                                 req->ssl ? ssl_getdirection( req->ssl ) : GAIM_INPUT_READ,
238                                 http_incoming_data, req );
[8a9afe4]239       
[ba9edaa]240        return FALSE;
[8a9afe4]241
242got_reply:
[0602496]243        /* Maybe if the webserver is overloaded, or when there's bad SSL
244           support... */
245        if( req->bytes_read == 0 )
[7deb447]246        {
247                req->status_string = g_strdup( "Empty HTTP reply" );
[0602496]248                goto cleanup;
[7deb447]249        }
[0602496]250       
[8a9afe4]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        {
[52b3a99]261                end1 = end2 + 1;
[8a9afe4]262                evil_server = 1;
263        }
[0eec386]264        else if( end1 )
[52b3a99]265        {
266                end1 += 2;
267        }
[0eec386]268        else
[8a9afe4]269        {
[7deb447]270                req->status_string = g_strdup( "Malformed HTTP reply" );
[0eec386]271                goto cleanup;
[52b3a99]272        }
273       
[0eec386]274        *end1 = 0;
275       
276        if( evil_server )
277                req->reply_body = end1 + 1;
278        else
279                req->reply_body = end1 + 2;
280       
[41e5202]281        req->body_size = req->reply_headers + req->bytes_read - req->reply_body;
[0eec386]282       
[52b3a99]283        if( ( end1 = strchr( req->reply_headers, ' ' ) ) != NULL )
284        {
285                if( sscanf( end1 + 1, "%d", &req->status_code ) != 1 )
[7deb447]286                {
287                        req->status_string = g_strdup( "Can't parse status code" );
[52b3a99]288                        req->status_code = -1;
[7deb447]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                }
[52b3a99]307        }
308        else
309        {
[7deb447]310                req->status_string = g_strdup( "Can't locate status code" );
[52b3a99]311                req->status_code = -1;
312        }
313       
[7885d0f]314        if( ( req->status_code == 301 || req->status_code == 302 ) && req->redir_ttl-- > 0 )
[52b3a99]315        {
316                char *loc, *new_request, *new_host;
317                int error = 0, new_port, new_proto;
318               
[7deb447]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               
[52b3a99]323                loc = strstr( req->reply_headers, "\nLocation: " );
324                if( loc == NULL ) /* We can't handle this redirect... */
[7deb447]325                {
326                        req->status_string = g_strdup( "Can't locate Location: header" );
[52b3a99]327                        goto cleanup;
[7deb447]328                }
[52b3a99]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                       
[7deb447]345                        req->status_string = g_strdup( "Can't handle recursive redirects" );
346                       
[52b3a99]347                        goto cleanup;
348                }
[8a9afe4]349                else
[52b3a99]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                        {
[7deb447]364                                req->status_string = g_strdup( "Malformed redirect URL" );
[52b3a99]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                       
[fe23720]375                        sprintf( new_request, "GET %s HTTP/1.0", url->file );
[52b3a99]376                       
377                        s = strstr( req->request, "\r\n" );
378                        if( s == NULL )
379                        {
[7deb447]380                                req->status_string = g_strdup( "Error while rebuilding request string" );
[52b3a99]381                                g_free( new_request );
382                                g_free( url );
383                                goto cleanup;
384                        }
385                       
[fe23720]386                        strcat( new_request, s );
[52b3a99]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;
[7deb447]400                req->ssl = NULL;
[52b3a99]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                }
[2db811a]414                g_free( new_host );
[52b3a99]415               
416                if( error )
417                {
[7deb447]418                        req->status_string = g_strdup( "Connection problem during redirect" );
[52b3a99]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               
[ba9edaa]430                return FALSE;
[8a9afe4]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
[52b3a99]441                closesocket( req->fd );
[8a9afe4]442       
443        req->func( req );
[fb98634]444        http_free( req );
445        return FALSE;
446}
447
448void http_free( struct http_request *req )
449{
[8a9afe4]450        g_free( req->request );
451        g_free( req->reply_headers );
[7deb447]452        g_free( req->status_string );
[8a9afe4]453        g_free( req );
454}
[fb98634]455
Note: See TracBrowser for help on using the repository browser.