See ChangeLog: Sat Jan 16 21:25:17 CET 1999 Werner Koch
[gnupg.git] / util / http.c
1 /* http.c  -  HTTP protocol handler
2  *      Copyright (C) 1999 Free Software Foundation, Inc.
3  *
4  * This file is part of GnuPG.
5  *
6  * GnuPG is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * GnuPG is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
19  */
20
21 #include <config.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <stdarg.h>
25 #include <string.h>
26 #include <ctype.h>
27 #include <errno.h>
28 #include <unistd.h>
29 #include <sys/types.h>
30 #include <sys/socket.h>
31 #include <sys/time.h>
32 #include <arpa/inet.h>
33 #include <netinet/in.h>
34 #include <netdb.h>
35
36 #include "util.h"
37 #include "iobuf.h"
38 #include "i18n.h"
39
40 #include "http.h"
41
42 #define MAX_LINELEN 20000  /* max. length of a HTTP line */
43 #define VALID_URI_CHARS "abcdefghijklmnopqrstuvwxyz"   \
44                         "ABCDEFGHIJKLMNOPQRSTUVWXYZ"   \
45                         "01234567890@"                 \
46                         "!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~"
47
48 #ifndef EAGAIN
49   #define EAGAIN  EWOULDBLOCK
50 #endif
51
52 static int parse_uri( PARSED_URI *ret_uri, const char *uri );
53 static void release_parsed_uri( PARSED_URI uri );
54 static int do_parse_uri( PARSED_URI uri, int only_local_part );
55 static int remove_escapes( byte *string );
56 static int insert_escapes( byte *buffer, const byte *string,
57                                          const byte *special );
58 static URI_TUPLE parse_tuple( byte *string );
59 static int send_request( HTTP_HD hd );
60 static byte *build_rel_path( PARSED_URI uri );
61 static int parse_response( HTTP_HD hd );
62
63 static int connect_server( const char *server, ushort port );
64 static int write_server( int socket, const char *data, size_t length );
65
66
67
68 int
69 open_http_document( HTTP_HD hd, const char *document, unsigned int flags )
70 {
71     int rc;
72
73     if( flags )
74         return G10ERR_INV_ARG;
75
76     /* initialize the handle */
77     memset( hd, 0, sizeof *hd );
78     hd->socket = -1;
79
80     rc = parse_uri( &hd->uri, document );
81     if( rc )
82         goto failure;
83
84     rc = send_request( hd );
85     if( rc )
86         goto failure;
87
88     hd->fp_read = iobuf_fdopen( hd->socket , "r" );
89     if( !hd->fp_read )
90         goto failure;
91
92     rc = parse_response( hd );
93     if( rc == -1 ) { /* no response from server */
94         /* Hmmm, should we set some errro variable or map errors */
95         goto failure;
96     }
97
98     if( !rc )
99         hd->is_http_0_9 = 1;
100     else
101         hd->status_code = rc ;
102
103     hd->initialized = 1;
104     return 0;
105
106   failure:
107     if( !hd->fp_read && !hd->fp_write )
108         close( hd->socket );
109     iobuf_close( hd->fp_read );
110     iobuf_close( hd->fp_write);
111     release_parsed_uri( hd->uri );
112
113     return rc;
114 }
115
116 void
117 close_http_document( HTTP_HD hd )
118 {
119     if( !hd || !hd->initialized )
120         return;
121     if( !hd->fp_read && !hd->fp_write )
122         close( hd->socket );
123     iobuf_close( hd->fp_read );
124     iobuf_close( hd->fp_write );
125     release_parsed_uri( hd->uri );
126     m_free( hd->buffer );
127     hd->initialized = 0;
128 }
129
130
131
132 /****************
133  * Parse an URI and put the result into the newly allocated ret_uri.
134  * The caller must always use release_parsed_uri to releases the
135  * resources (even on an error).
136  */
137 static int
138 parse_uri( PARSED_URI *ret_uri, const char *uri )
139 {
140    *ret_uri = m_alloc_clear( sizeof(**ret_uri) + strlen(uri) );
141    strcpy( (*ret_uri)->buffer, uri );
142    return do_parse_uri( *ret_uri, 0 );
143 }
144
145 static void
146 release_parsed_uri( PARSED_URI uri )
147 {
148     if( uri )
149     {
150         URI_TUPLE r, r2;
151
152         for( r = uri->query; r; r = r2 ) {
153             r2 = r->next;
154             m_free( r );
155         }
156         m_free( uri );
157     }
158 }
159
160 static int
161 do_parse_uri( PARSED_URI uri, int only_local_part )
162 {
163     URI_TUPLE *tail;
164     char *p, *p2, *p3;
165     int n;
166
167     p = uri->buffer;
168     n = strlen( uri->buffer );
169     /* initialize all fields to an empty string or an empty list */
170     uri->scheme = uri->host = uri->path = p + n;
171     uri->port = 0;
172     uri->params = uri->query = NULL;
173
174     /* a quick validity check */
175     if( strspn( p, VALID_URI_CHARS) != n )
176         return G10ERR_BAD_URI; /* invalid characters found */
177
178     if( !only_local_part ) {
179         /* find the scheme */
180         if( !(p2 = strchr( p, ':' ) ) || p2 == p )
181            return G10ERR_BAD_URI; /* No scheme */
182         *p2++ = 0;
183         strlwr( p );
184         uri->scheme = p;
185         if( !strcmp( uri->scheme, "http" ) )
186             ;
187         else if( !strcmp( uri->scheme, "x-hkp" ) ) /* same as HTTP */
188             ;
189         else
190             return G10ERR_INVALID_URI; /* Unsupported scheme */
191
192         p = p2;
193
194         /* find the hostname */
195         if( *p != '/' )
196             return G10ERR_INVALID_URI; /* does not start with a slash */
197
198         p++;
199         if( *p == '/' ) {  /* there seems to be a hostname */
200             p++;
201             if( (p2 = strchr(p, '/')) )
202                 *p2++ = 0;
203             strlwr( p );
204             uri->host = p;
205             if( (p3=strchr( p, ':' )) ) {
206                 *p3++ = 0;
207                 uri->port = atoi( p3 );
208             }
209             else
210                uri->port = 80;
211             uri->host = p;
212             if( (n = remove_escapes( uri->host )) < 0 )
213                 return G10ERR_BAD_URI;
214             if( n != strlen( p ) )
215                 return G10ERR_BAD_URI; /* hostname with a Nul in it */
216             p = p2 ? p2 : NULL;
217         }
218     } /* end global URI part */
219
220     /* parse the pathname part */
221     if( !p || !*p ) /* we don't have a path */
222         return 0; /* and this is okay */
223
224     /* fixme: here we have to check params */
225
226     /* do we have a query part */
227     if( (p2 = strchr( p, '?' )) )
228         *p2++ = 0;
229
230     uri->path = p;
231     if( (n = remove_escapes( p )) < 0 )
232         return G10ERR_BAD_URI;
233     if( n != strlen( p ) )
234         return G10ERR_BAD_URI; /* path with a Nul in it */
235     p = p2 ? p2 : NULL;
236
237     if( !p || !*p ) /* we don't have a query string */
238         return 0;   /* okay */
239
240     /* now parse the query string */
241     tail = &uri->query;
242     for(;;) {
243         URI_TUPLE elem;
244
245         if( (p2 = strchr( p, '&' )) )
246             *p2++ = 0;
247         if( !(elem = parse_tuple( p )) )
248             return G10ERR_BAD_URI;
249         *tail = elem;
250         tail = &elem->next;
251
252         if( !p2 )
253            break; /* ready */
254         p = p2;
255     }
256
257     return 0;
258 }
259
260
261
262 /****************
263  * Remove all %xx escapes; this is done inplace.
264  * Returns: new length of the string.
265  */
266 static int
267 remove_escapes( byte *string )
268 {
269     int n = 0;
270     byte *p, *s;
271
272     for(p=s=string; *s ; s++ ) {
273         if( *s == '%' ) {
274             if( s[1] && s[2] && isxdigit(s[1]) && isxdigit(s[2]) ) {
275                 s++;
276                 *p  = *s >= '0' && *s <= '9' ? *s - '0' :
277                       *s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10 ;
278                 *p <<= 4;
279                 s++;
280                 *p |= *s >= '0' && *s <= '9' ? *s - '0' :
281                       *s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10 ;
282                 p++;
283                 n++;
284             }
285             else {
286                 *p++ = *s++;
287                 if( *s )
288                    *p++ = *s++;
289                 if( *s )
290                    *p++ = *s++;
291                 if( *s )
292                    *p = 0;
293                 return -1; /* bad URI */
294             }
295         }
296         else
297         {
298             *p++ = *s;
299             n++;
300         }
301     }
302     *p = 0; /* always keep a string terminator */
303     return n;
304 }
305
306
307 static int
308 insert_escapes( byte *buffer, const byte *string, const byte *special )
309 {
310     int n = 0;
311
312     for( ; *string; string++ ) {
313         if( strchr( VALID_URI_CHARS, *string )
314             && !strchr( special, *string ) )  {
315             if( buffer )
316                 *buffer++ = *string;
317             n++;
318         }
319         else {
320             if( buffer ) {
321                 sprintf( buffer, "%02X", *string );
322                 buffer += 3;
323             }
324             n += 3;
325         }
326     }
327     return n;
328 }
329
330
331
332
333
334 static URI_TUPLE
335 parse_tuple( byte *string )
336 {
337     byte *p = string;
338     byte *p2;
339     int n;
340     URI_TUPLE tuple;
341
342     if( (p2 = strchr( p, '=' )) )
343         *p2++ = 0;
344     if( (n = remove_escapes( p )) < 0 )
345         return NULL; /* bad URI */
346     if( n != strlen( p ) )
347        return NULL; /* name with a Nul in it */
348     tuple = m_alloc_clear( sizeof *tuple );
349     tuple->name = p;
350     if( !p2 )  {
351         /* we have only the name, so we assume an empty value string */
352         tuple->value = p + strlen(p);
353         tuple->valuelen = 0;
354     }
355     else { /* name and value */
356         if( (n = remove_escapes( p2 )) < 0 ) {
357             m_free( tuple );
358             return NULL; /* bad URI */
359         }
360         tuple->value = p2;
361         tuple->valuelen = n;
362     }
363     return tuple;
364 }
365
366
367 /****************
368  * Send a HTTP request to the server
369  * Returns 0 if the request was successful
370  */
371 static int
372 send_request( HTTP_HD hd )
373 {
374     const byte *server;
375     byte *request, *p;
376     ushort port;
377     int rc;
378
379     server = *hd->uri->host? hd->uri->host : "localhost";
380     port   = hd->uri->port?  hd->uri->port : 80;
381
382     hd->socket = connect_server( server, port );
383     if( hd->socket == -1 )
384         return G10ERR_NETWORK;
385
386     p = build_rel_path( hd->uri );
387     request = m_alloc( strlen(p) + 20 );
388     sprintf( request, "GET %s%s HTTP/1.0\r\n\r\n", *p == '/'? "":"/", p );
389     m_free(p);
390
391     rc = write_server( hd->socket, request, strlen(request) );
392     m_free( request );
393     shutdown( hd->socket, 1 );
394
395     return rc;
396 }
397
398
399
400
401 /****************
402  * Build the relative path from the parsed URI.
403  * Minimal implementation.
404  */
405 static byte*
406 build_rel_path( PARSED_URI uri )
407 {
408     URI_TUPLE r;
409     byte *rel_path, *p;
410     int n;
411
412     /* count the needed space */
413     n = insert_escapes( NULL, uri->path, "%;?&" );
414     /* fixme: add params */
415     for( r=uri->query; r; r = r->next ) {
416         n++; /* '?'/'&' */
417         n += insert_escapes( NULL, r->name, "%;?&=" );
418         n++; /* '='*/
419         n += insert_escapes( NULL, r->value, "%;?&=" );
420     }
421     n++;
422
423     /* now  allocate and copy */
424     p = rel_path = m_alloc( n );
425     n = insert_escapes( p, uri->path, "%;?&" );
426     p += n;
427     /* fixme: add params */
428     for( r=uri->query; r; r = r->next ) {
429         *p++ = r == uri->query? '?':'&';
430         n = insert_escapes( p, r->name, "%;?&=" );
431         p += n;
432         *p++ = '=';
433         /* fixme: use valuelen */
434         n = insert_escapes( p, r->value, "%;?&=" );
435         p += n;
436     }
437     *p = 0;
438     return rel_path;
439 }
440
441
442
443 /***********************
444  * Parse the response from a server.
445  * Returns: -1  for no response or error
446  *           0  for a http 0.9 response
447  *         nnn  http 1.0 status code
448  */
449 static int
450 parse_response( HTTP_HD hd )
451 {
452     byte *line, *p, *p2;
453     int status;
454     unsigned maxlen, len;
455
456     /* Wait for the status line */
457     do {
458         maxlen = MAX_LINELEN;
459         len = iobuf_read_line( hd->fp_read, &hd->buffer,
460                                             &hd->buffer_size, &maxlen );
461         line = hd->buffer;
462         if( !maxlen )
463             return -1; /* line has been truncated */
464         if( !len )
465             return -1; /* eof */
466     } while( !*line  );
467
468     if( (p = strchr( line, '/')) )
469         *p++ = 0;
470     if( !p || strcmp( line, "HTTP" ) )
471         return 0; /* assume http 0.9 */
472
473     if( (p2 = strpbrk( p, " \t" ) ) ) {
474         *p2++ = 0;
475         p2 += strspn( p2, " \t" );
476     }
477     if( !p2 )
478         return 0; /* assume http 0.9 */
479     p = p2;
480     /* fixme: add HTTP version number check here */
481     if( (p2 = strpbrk( p, " \t" ) ) )
482         *p2++ = 0;
483     if( !isdigit(p[0]) || !isdigit(p[1]) || !isdigit(p[2]) || p[3] )
484         return 0; /* malformed HTTP statuscode - assume HTTP 0.9 */
485     status = atoi( p );
486
487     /* skip all the header lines and wait for the empty line */
488     do {
489         maxlen = MAX_LINELEN;
490         len = iobuf_read_line( hd->fp_read, &hd->buffer,
491                                &hd->buffer_size, &maxlen );
492         line = hd->buffer;
493         /* we ignore truncated lines */
494         if( !len )
495             return -1; /* eof */
496         /* time lineendings */
497         if( (*line == '\r' && line[1] == '\n') || *line == '\n' )
498             *line = 0;
499     } while( len && *line  );
500
501     return status;
502 }
503
504
505
506
507
508
509
510
511
512
513 static int
514 connect_server( const char *server, ushort port )
515 {
516     struct sockaddr_in addr;
517     struct hostent *host;
518     int sd;
519
520     addr.sin_family = AF_INET;
521     addr.sin_port = htons(port);
522     host = gethostbyname((char*)server);
523     if( !host )
524          return -1;
525
526     addr.sin_addr = *(struct in_addr*)host->h_addr;
527
528     sd = socket(AF_INET, SOCK_STREAM, 0);
529     if( sd == -1 )
530         return -1;
531
532     if( connect( sd, (struct sockaddr *)&addr, sizeof addr) == -1 ) {
533         close(sd);
534         return -1;
535     }
536
537     return sd;
538 }
539
540
541 static int
542 write_server( int socket, const char *data, size_t length )
543 {
544     int nleft, nwritten;
545
546     nleft = length;
547     while( nleft > 0 ) {
548         nwritten = write( socket, data, nleft );
549         if( nwritten == -1 ) {
550             if( errno == EINTR )
551                 continue;
552             if( errno == EAGAIN ) {
553                 struct timeval tv;
554
555                 tv.tv_sec =  0;
556                 tv.tv_usec = 50000;
557                 select(0, NULL, NULL, NULL, &tv);
558                 continue;
559             }
560             return G10ERR_NETWORK;
561         }
562         nleft -=nwritten;
563         data += nwritten;
564     }
565
566     return 0;
567 }
568
569
570 /**** Test code ****/
571 #ifdef TEST
572
573 int
574 main(int argc, char **argv)
575 {
576     int rc;
577     PARSED_URI uri;
578     URI_TUPLE r;
579     struct http_context hd;
580     int c;
581
582     log_set_name("http-test");
583     if( argc != 2 ) {
584         fprintf(stderr,"usage: http-test uri\n");
585         return 1;
586     }
587     argc--; argv++;
588
589     rc = parse_uri( &uri, *argv );
590     if( rc ) {
591         log_error("`%s': %s\n", *argv, g10_errstr(rc));
592         release_parsed_uri( uri );
593         return 1;
594     }
595
596     printf("Scheme: %s\n", uri->scheme );
597     printf("Host  : %s\n", uri->host );
598     printf("Port  : %u\n", uri->port );
599     printf("Path  : %s\n", uri->path );
600     for( r=uri->params; r; r = r->next ) {
601         printf("Params: %s=%s", r->name, r->value );
602         if( strlen( r->value ) != r->valuelen )
603             printf(" [real length=%d]", (int)r->valuelen );
604         putchar('\n');
605     }
606     for( r=uri->query; r; r = r->next ) {
607         printf("Query : %s=%s", r->name, r->value );
608         if( strlen( r->value ) != r->valuelen )
609             printf(" [real length=%d]", (int)r->valuelen );
610         putchar('\n');
611     }
612     release_parsed_uri( uri ); uri = NULL;
613
614     rc = open_http_document( &hd, *argv, 0 );
615     if( rc ) {
616         log_error("can't get `%s': %s\n", *argv, g10_errstr(rc));
617         return 1;
618     }
619     log_info("open_http_document succeeded; status=%u\n", hd.status_code );
620     while( (c=iobuf_get( hd.fp_read)) != -1 )
621         putchar(c);
622     close_http_document( &hd );
623     return 0;
624 }
625 #endif /*TEST*/