source: lib/http_client.c @ 486ddb5

Last change on this file since 486ddb5 was 486ddb5, checked in by Wilmer van der Gaast <wilmer@…>, at 2011-12-19T14:50:58Z

Initial merge of tls_verify patch from AopicieR.

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