source: protocols/http_client.c @ ac83732

Last change on this file since ac83732 was ba9edaa, checked in by Wilmer van der Gaast <wilmer@…>, at 2006-05-10T17:34:46Z

Moved everything to the BitlBee event handling API.

  • Property mode set to 100644
File size: 8.5 KB
Line 
1  /********************************************************************\
2  * BitlBee -- An IRC to other IM-networks gateway                     *
3  *                                                                    *
4  * Copyright 2002-2005 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 );
37
38
39void *http_dorequest( char *host, int port, int ssl, char *request, http_input_function func, gpointer data )
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       
65        req->func = func;
66        req->data = data;
67        req->request = g_strdup( request );
68        req->request_length = strlen( request );
69       
70        return( req );
71}
72
73/* This one is actually pretty simple... Might get more calls if we can't write
74   the whole request at once. */
75static gboolean http_connected( gpointer data, int source, b_input_condition cond )
76{
77        struct http_request *req = data;
78        int st;
79       
80        if( source < 0 )
81                goto error;
82       
83        if( req->inpa > 0 )
84                b_event_remove( req->inpa );
85       
86        sock_make_nonblocking( req->fd );
87       
88        if( req->ssl )
89        {
90                st = ssl_write( req->ssl, req->request + req->bytes_written,
91                                req->request_length - req->bytes_written );
92                if( st < 0 )
93                {
94                        if( ssl_errno != SSL_AGAIN )
95                        {
96                                ssl_disconnect( req->ssl );
97                                goto error;
98                        }
99                }
100        }
101        else
102        {
103                st = write( source, req->request + req->bytes_written,
104                                    req->request_length - req->bytes_written );
105                if( st < 0 )
106                {
107                        if( !sockerr_again() )
108                        {
109                                closesocket( req->fd );
110                                goto error;
111                        }
112                }
113        }
114       
115        if( st > 0 )
116                req->bytes_written += st;
117       
118        if( req->bytes_written < req->request_length )
119                req->inpa = b_input_add( source,
120                                         req->ssl ? ssl_getdirection( req->ssl ) : GAIM_INPUT_WRITE,
121                                         http_connected, req );
122        else
123                req->inpa = b_input_add( source, GAIM_INPUT_READ, http_incoming_data, req );
124       
125        return FALSE;
126       
127error:
128        req->func( req );
129       
130        g_free( req->request );
131        g_free( req );
132       
133        return FALSE;
134}
135
136static gboolean http_ssl_connected( gpointer data, void *source, b_input_condition cond )
137{
138        struct http_request *req = data;
139       
140        if( source == NULL )
141                return http_connected( data, -1, cond );
142       
143        req->fd = ssl_getfd( source );
144       
145        return http_connected( data, req->fd, cond );
146}
147
148static gboolean http_incoming_data( gpointer data, int source, b_input_condition cond )
149{
150        struct http_request *req = data;
151        int evil_server = 0;
152        char buffer[2048];
153        char *end1, *end2;
154        int st;
155       
156        if( req->inpa > 0 )
157                b_event_remove( req->inpa );
158       
159        if( req->ssl )
160        {
161                st = ssl_read( req->ssl, buffer, sizeof( buffer ) );
162                if( st < 0 )
163                {
164                        if( ssl_errno != SSL_AGAIN )
165                        {
166                                /* goto cleanup; */
167                               
168                                /* YAY! We have to deal with crappy Microsoft
169                                   servers that LOVE to send invalid TLS
170                                   packets that abort connections! \o/ */
171                               
172                                goto got_reply;
173                        }
174                }
175                else if( st == 0 )
176                {
177                        goto got_reply;
178                }
179        }
180        else
181        {
182                st = read( req->fd, buffer, sizeof( buffer ) );
183                if( st < 0 )
184                {
185                        if( !sockerr_again() )
186                        {
187                                goto cleanup;
188                        }
189                }
190                else if( st == 0 )
191                {
192                        goto got_reply;
193                }
194        }
195       
196        if( st > 0 )
197        {
198                req->reply_headers = g_realloc( req->reply_headers, req->bytes_read + st + 1 );
199                memcpy( req->reply_headers + req->bytes_read, buffer, st );
200                req->bytes_read += st;
201        }
202       
203        /* There will be more! */
204        req->inpa = b_input_add( req->fd,
205                                 req->ssl ? ssl_getdirection( req->ssl ) : GAIM_INPUT_READ,
206                                 http_incoming_data, req );
207       
208        return FALSE;
209
210got_reply:
211        /* Zero termination is very convenient. */
212        req->reply_headers[req->bytes_read] = 0;
213       
214        /* Find the separation between headers and body, and keep stupid
215           webservers in mind. */
216        end1 = strstr( req->reply_headers, "\r\n\r\n" );
217        end2 = strstr( req->reply_headers, "\n\n" );
218       
219        if( end2 && end2 < end1 )
220        {
221                end1 = end2 + 1;
222                evil_server = 1;
223        }
224        else
225        {
226                end1 += 2;
227        }
228       
229        if( end1 )
230        {
231                *end1 = 0;
232               
233                if( evil_server )
234                        req->reply_body = end1 + 1;
235                else
236                        req->reply_body = end1 + 2;
237        }
238       
239        if( ( end1 = strchr( req->reply_headers, ' ' ) ) != NULL )
240        {
241                if( sscanf( end1 + 1, "%d", &req->status_code ) != 1 )
242                        req->status_code = -1;
243        }
244        else
245        {
246                req->status_code = -1;
247        }
248       
249        if( req->status_code == 301 || req->status_code == 302 )
250        {
251                char *loc, *new_request, *new_host;
252                int error = 0, new_port, new_proto;
253               
254                loc = strstr( req->reply_headers, "\nLocation: " );
255                if( loc == NULL ) /* We can't handle this redirect... */
256                        goto cleanup;
257               
258                loc += 11;
259                while( *loc == ' ' )
260                        loc ++;
261               
262                /* TODO/FIXME: Possibly have to handle relative redirections,
263                   and rewrite Host: headers. Not necessary for now, it's
264                   enough for passport authentication like this. */
265               
266                if( *loc == '/' )
267                {
268                        /* Just a different pathname... */
269                       
270                        /* Since we don't cache the servername, and since we
271                           don't need this yet anyway, I won't implement it. */
272                       
273                        goto cleanup;
274                }
275                else
276                {
277                        /* A whole URL */
278                        url_t *url;
279                        char *s;
280                       
281                        s = strstr( loc, "\r\n" );
282                        if( s == NULL )
283                                goto cleanup;
284                       
285                        url = g_new0( url_t, 1 );
286                        *s = 0;
287                       
288                        if( !url_set( url, loc ) )
289                        {
290                                g_free( url );
291                                goto cleanup;
292                        }
293                       
294                        /* Okay, this isn't fun! We have to rebuild the request... :-( */
295                        new_request = g_malloc( req->request_length + strlen( url->file ) );
296                       
297                        /* So, now I just allocated enough memory, so I'm
298                           going to use strcat(), whether you like it or not. :-) */
299                       
300                        /* First, find the GET/POST/whatever from the original request. */
301                        s = strchr( req->request, ' ' );
302                        if( s == NULL )
303                        {
304                                g_free( new_request );
305                                g_free( url );
306                                goto cleanup;
307                        }
308                       
309                        *s = 0;
310                        sprintf( new_request, "%s %s HTTP/1.0\r\n", req->request, url->file );
311                        *s = ' ';
312                       
313                        s = strstr( req->request, "\r\n" );
314                        if( s == NULL )
315                        {
316                                g_free( new_request );
317                                g_free( url );
318                                goto cleanup;
319                        }
320                       
321                        strcat( new_request, s + 2 );
322                        new_host = g_strdup( url->host );
323                        new_port = url->port;
324                        new_proto = url->proto;
325                       
326                        g_free( url );
327                }
328               
329                if( req->ssl )
330                        ssl_disconnect( req->ssl );
331                else
332                        closesocket( req->fd );
333               
334                req->fd = -1;
335                req->ssl = 0;
336               
337                if( new_proto == PROTO_HTTPS )
338                {
339                        req->ssl = ssl_connect( new_host, new_port, http_ssl_connected, req );
340                        if( req->ssl == NULL )
341                                error = 1;
342                }
343                else
344                {
345                        req->fd = proxy_connect( new_host, new_port, http_connected, req );
346                        if( req->fd < 0 )
347                                error = 1;
348                }
349                g_free( new_host );
350               
351                if( error )
352                {
353                        g_free( new_request );
354                        goto cleanup;
355                }
356               
357                g_free( req->request );
358                g_free( req->reply_headers );
359                req->request = new_request;
360                req->request_length = strlen( new_request );
361                req->bytes_read = req->bytes_written = req->inpa = 0;
362                req->reply_headers = req->reply_body = NULL;
363               
364                return FALSE;
365        }
366       
367        /* Assume that a closed connection means we're finished, this indeed
368           breaks with keep-alive connections and faulty connections. */
369        req->finished = 1;
370
371cleanup:
372        if( req->ssl )
373                ssl_disconnect( req->ssl );
374        else
375                closesocket( req->fd );
376       
377        req->func( req );
378       
379        g_free( req->request );
380        g_free( req->reply_headers );
381        g_free( req );
382       
383        return FALSE;
384}
Note: See TracBrowser for help on using the repository browser.