source: lib/http_client.c @ 06b5893

Last change on this file since 06b5893 was bd31661, checked in by Wilmer van der Gaast <wilmer@…>, at 2011-12-02T10:32:54Z

Somewhat improve debug logging of HTTP/SOAP stuff.

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