source: lib/http_client.c @ f01bc6f

Last change on this file since f01bc6f was 4346c3f4, checked in by Wilmer van der Gaast <wilmer@…>, at 2010-07-16T23:31:55Z

Merging mainline.

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