source: lib/http_client.c @ deff040

Last change on this file since deff040 was df1694b, checked in by Wilmer van der Gaast <wilmer@…>, at 2006-06-25T12:15:42Z

Moving all generic files to lib/ instead of having some in / and some in
protocols/, and adding RC4 code.

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