source: protocols/http_client.c @ 0790644

Last change on this file since 0790644 was 0790644, checked in by Wilmer van der Gaast <wilmer@…>, at 2006-05-17T13:15:20Z

Added http_dorequest_url().

  • Property mode set to 100644
File size: 9.1 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
34static void http_connected( gpointer data, int source, GaimInputCondition cond );
35static void http_ssl_connected( gpointer data, void *source, GaimInputCondition cond );
36static void http_incoming_data( gpointer data, int source, GaimInputCondition cond );
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"
93                                   "User-Agent: BitlBee " BITLBEE_VERSION "\r\n"
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 );
101        return NULL;
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. */
106static void http_connected( gpointer data, int source, GaimInputCondition cond )
107{
108        struct http_request *req = data;
109        int st;
110       
111        if( source < 0 )
112                goto error;
113       
114        if( req->inpa > 0 )
115                gaim_input_remove( req->inpa );
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 )
150                req->inpa = gaim_input_add( source,
151                                            req->ssl ? ssl_getdirection( req->ssl ) : GAIM_INPUT_WRITE,
152                                            http_connected, req );
153        else
154                req->inpa = gaim_input_add( source, GAIM_INPUT_READ, http_incoming_data, req );
155       
156        return;
157       
158error:
159        req->func( req );
160       
161        g_free( req->request );
162        g_free( req );
163       
164        return;
165}
166
167static void http_ssl_connected( gpointer data, void *source, GaimInputCondition cond )
168{
169        struct http_request *req = data;
170       
171        if( source == NULL )
172                return http_connected( data, -1, cond );
173       
174        req->fd = ssl_getfd( source );
175       
176        return http_connected( data, req->fd, cond );
177}
178
179static void http_incoming_data( gpointer data, int source, GaimInputCondition cond )
180{
181        struct http_request *req = data;
182        int evil_server = 0;
183        char buffer[2048];
184        char *end1, *end2;
185        int st;
186       
187        if( req->inpa > 0 )
188                gaim_input_remove( req->inpa );
189       
190        if( req->ssl )
191        {
192                st = ssl_read( req->ssl, buffer, sizeof( buffer ) );
193                if( st < 0 )
194                {
195                        if( ssl_errno != SSL_AGAIN )
196                        {
[ad8b8a3]197                                /* goto cleanup; */
198                               
199                                /* YAY! We have to deal with crappy Microsoft
200                                   servers that LOVE to send invalid TLS
201                                   packets that abort connections! \o/ */
202                               
203                                goto got_reply;
[8a9afe4]204                        }
205                }
206                else if( st == 0 )
207                {
208                        goto got_reply;
209                }
210        }
211        else
212        {
213                st = read( req->fd, buffer, sizeof( buffer ) );
214                if( st < 0 )
215                {
216                        if( !sockerr_again() )
217                        {
218                                goto cleanup;
219                        }
220                }
221                else if( st == 0 )
222                {
223                        goto got_reply;
224                }
225        }
226       
227        if( st > 0 )
228        {
229                req->reply_headers = g_realloc( req->reply_headers, req->bytes_read + st + 1 );
230                memcpy( req->reply_headers + req->bytes_read, buffer, st );
[52b3a99]231                req->bytes_read += st;
[8a9afe4]232        }
233       
234        /* There will be more! */
235        req->inpa = gaim_input_add( req->fd,
236                                    req->ssl ? ssl_getdirection( req->ssl ) : GAIM_INPUT_READ,
237                                    http_incoming_data, req );
238       
239        return;
240
241got_reply:
242        /* Zero termination is very convenient. */
243        req->reply_headers[req->bytes_read] = 0;
244       
245        /* Find the separation between headers and body, and keep stupid
246           webservers in mind. */
247        end1 = strstr( req->reply_headers, "\r\n\r\n" );
248        end2 = strstr( req->reply_headers, "\n\n" );
249       
250        if( end2 && end2 < end1 )
251        {
[52b3a99]252                end1 = end2 + 1;
[8a9afe4]253                evil_server = 1;
254        }
[52b3a99]255        else
256        {
257                end1 += 2;
258        }
[8a9afe4]259       
260        if( end1 )
261        {
262                *end1 = 0;
263               
264                if( evil_server )
[52b3a99]265                        req->reply_body = end1 + 1;
266                else
[8a9afe4]267                        req->reply_body = end1 + 2;
[52b3a99]268        }
269       
270        if( ( end1 = strchr( req->reply_headers, ' ' ) ) != NULL )
271        {
272                if( sscanf( end1 + 1, "%d", &req->status_code ) != 1 )
273                        req->status_code = -1;
274        }
275        else
276        {
277                req->status_code = -1;
278        }
279       
280        if( req->status_code == 301 || req->status_code == 302 )
281        {
282                char *loc, *new_request, *new_host;
283                int error = 0, new_port, new_proto;
284               
285                loc = strstr( req->reply_headers, "\nLocation: " );
286                if( loc == NULL ) /* We can't handle this redirect... */
287                        goto cleanup;
288               
289                loc += 11;
290                while( *loc == ' ' )
291                        loc ++;
292               
293                /* TODO/FIXME: Possibly have to handle relative redirections,
294                   and rewrite Host: headers. Not necessary for now, it's
295                   enough for passport authentication like this. */
296               
297                if( *loc == '/' )
298                {
299                        /* Just a different pathname... */
300                       
301                        /* Since we don't cache the servername, and since we
302                           don't need this yet anyway, I won't implement it. */
303                       
304                        goto cleanup;
305                }
[8a9afe4]306                else
[52b3a99]307                {
308                        /* A whole URL */
309                        url_t *url;
310                        char *s;
311                       
312                        s = strstr( loc, "\r\n" );
313                        if( s == NULL )
314                                goto cleanup;
315                       
316                        url = g_new0( url_t, 1 );
317                        *s = 0;
318                       
319                        if( !url_set( url, loc ) )
320                        {
321                                g_free( url );
322                                goto cleanup;
323                        }
324                       
325                        /* Okay, this isn't fun! We have to rebuild the request... :-( */
326                        new_request = g_malloc( req->request_length + strlen( url->file ) );
327                       
328                        /* So, now I just allocated enough memory, so I'm
329                           going to use strcat(), whether you like it or not. :-) */
330                       
331                        /* First, find the GET/POST/whatever from the original request. */
332                        s = strchr( req->request, ' ' );
333                        if( s == NULL )
334                        {
335                                g_free( new_request );
336                                g_free( url );
337                                goto cleanup;
338                        }
339                       
340                        *s = 0;
341                        sprintf( new_request, "%s %s HTTP/1.0\r\n", req->request, url->file );
342                        *s = ' ';
343                       
344                        s = strstr( req->request, "\r\n" );
345                        if( s == NULL )
346                        {
347                                g_free( new_request );
348                                g_free( url );
349                                goto cleanup;
350                        }
351                       
352                        strcat( new_request, s + 2 );
353                        new_host = g_strdup( url->host );
354                        new_port = url->port;
355                        new_proto = url->proto;
356                       
357                        g_free( url );
358                }
359               
360                if( req->ssl )
361                        ssl_disconnect( req->ssl );
362                else
363                        closesocket( req->fd );
364               
365                req->fd = -1;
366                req->ssl = 0;
367               
368                if( new_proto == PROTO_HTTPS )
369                {
370                        req->ssl = ssl_connect( new_host, new_port, http_ssl_connected, req );
371                        if( req->ssl == NULL )
372                                error = 1;
373                }
374                else
375                {
376                        req->fd = proxy_connect( new_host, new_port, http_connected, req );
377                        if( req->fd < 0 )
378                                error = 1;
379                }
[2db811a]380                g_free( new_host );
[52b3a99]381               
382                if( error )
383                {
384                        g_free( new_request );
385                        goto cleanup;
386                }
387               
388                g_free( req->request );
389                g_free( req->reply_headers );
390                req->request = new_request;
391                req->request_length = strlen( new_request );
392                req->bytes_read = req->bytes_written = req->inpa = 0;
393                req->reply_headers = req->reply_body = NULL;
394               
395                return;
[8a9afe4]396        }
397       
398        /* Assume that a closed connection means we're finished, this indeed
399           breaks with keep-alive connections and faulty connections. */
400        req->finished = 1;
401
402cleanup:
403        if( req->ssl )
404                ssl_disconnect( req->ssl );
405        else
[52b3a99]406                closesocket( req->fd );
[8a9afe4]407       
408        req->func( req );
409       
410        g_free( req->request );
411        g_free( req->reply_headers );
412        g_free( req );
413}
Note: See TracBrowser for help on using the repository browser.