See ChangeLog: Thu Jan 27 18:00:44 CET 2000 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
29 #ifndef HAVE_DOSISH_SYSTEM
30
31 #include <unistd.h>
32 #include <sys/types.h>
33 #include <sys/socket.h>
34 #include <sys/time.h>
35 #include <time.h>
36 #include <netinet/in.h>
37 #include <arpa/inet.h>
38 #include <netdb.h>
39
40 #include <gcrypt.h>
41
42 #include "util.h"
43 #include "iobuf.h"
44 #include "i18n.h"
45
46 #include "http.h"
47
48 #define MAX_LINELEN 20000  /* max. length of a HTTP line */
49 #define VALID_URI_CHARS "abcdefghijklmnopqrstuvwxyz"   \
50                         "ABCDEFGHIJKLMNOPQRSTUVWXYZ"   \
51                         "01234567890@"                 \
52                         "!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~"
53
54 #ifndef EAGAIN
55   #define EAGAIN  EWOULDBLOCK
56 #endif
57
58 static int parse_uri( PARSED_URI *ret_uri, const char *uri );
59 static void release_parsed_uri( PARSED_URI uri );
60 static int do_parse_uri( PARSED_URI uri, int only_local_part );
61 static int remove_escapes( byte *string );
62 static int insert_escapes( byte *buffer, const byte *string,
63                                          const byte *special );
64 static URI_TUPLE parse_tuple( byte *string );
65 static int send_request( HTTP_HD hd );
66 static byte *build_rel_path( PARSED_URI uri );
67 static int parse_response( HTTP_HD hd );
68
69 static int connect_server( const char *server, ushort port );
70 static int write_server( int sock, const char *data, size_t length );
71
72
73 int
74 http_open( HTTP_HD hd, HTTP_REQ_TYPE reqtype, const char *url,
75                                               unsigned int flags )
76 {
77     int rc;
78
79     if( flags || !(reqtype == HTTP_REQ_GET || reqtype == HTTP_REQ_POST) )
80         return GPGERR_INV_ARG;
81
82     /* initialize the handle */
83     memset( hd, 0, sizeof *hd );
84     hd->sock = -1;
85     hd->initialized = 1;
86     hd->req_type = reqtype;
87
88     rc = parse_uri( &hd->uri, url );
89     if( !rc ) {
90         rc = send_request( hd );
91         if( !rc ) {
92             hd->fp_write = iobuf_fdopen( hd->sock , "w" );
93             if( hd->fp_write )
94                 return 0;
95             rc = GPGERR_GENERAL;
96         }
97     }
98
99     if( !hd->fp_read && !hd->fp_write && hd->sock != -1 )
100         close( hd->sock );
101     iobuf_close( hd->fp_read );
102     iobuf_close( hd->fp_write);
103     release_parsed_uri( hd->uri );
104     hd->initialized = 0;
105
106     return rc;
107 }
108
109
110 void
111 http_start_data( HTTP_HD hd )
112 {
113     if( !hd->in_data ) {
114         iobuf_put( hd->fp_write, '\n' );
115         hd->in_data = 1;
116     }
117 }
118
119
120 int
121 http_wait_response( HTTP_HD hd, unsigned int *ret_status )
122 {
123     int rc;
124
125     http_start_data( hd ); /* make sure that we are in the data */
126     iobuf_flush( hd->fp_write );
127
128     hd->sock = dup( hd->sock );
129     if( hd->sock == -1 )
130         return GPGERR_GENERAL;
131     iobuf_close( hd->fp_write );
132     hd->fp_write = NULL;
133     shutdown( hd->sock, 1 );
134     hd->in_data = 0;
135
136     hd->fp_read = iobuf_fdopen( hd->sock , "r" );
137     if( !hd->fp_read )
138         return GPGERR_GENERAL;
139
140     rc = parse_response( hd );
141     if( !rc && ret_status )
142         *ret_status = hd->status_code;
143
144     return rc;
145 }
146
147
148 int
149 http_open_document( HTTP_HD hd, const char *document, unsigned int flags )
150 {
151     int rc;
152
153     if( flags )
154         return GPGERR_INV_ARG;
155
156     rc = http_open( hd, HTTP_REQ_GET, document, 0 );
157     if( rc )
158         return rc;
159
160     rc = http_wait_response( hd, NULL );
161     if( rc )
162         http_close( hd );
163
164     return rc;
165 }
166
167
168
169
170 void
171 http_close( HTTP_HD hd )
172 {
173     if( !hd || !hd->initialized )
174         return;
175     if( !hd->fp_read && !hd->fp_write && hd->sock != -1 )
176         close( hd->sock );
177     iobuf_close( hd->fp_read );
178     iobuf_close( hd->fp_write );
179     release_parsed_uri( hd->uri );
180     gcry_free( hd->buffer );
181     hd->initialized = 0;
182 }
183
184
185
186 /****************
187  * Parse an URI and put the result into the newly allocated ret_uri.
188  * The caller must always use release_parsed_uri to releases the
189  * resources (even on an error).
190  */
191 static int
192 parse_uri( PARSED_URI *ret_uri, const char *uri )
193 {
194    *ret_uri = gcry_xcalloc( 1, sizeof(**ret_uri) + strlen(uri) );
195    strcpy( (*ret_uri)->buffer, uri );
196    return do_parse_uri( *ret_uri, 0 );
197 }
198
199 static void
200 release_parsed_uri( PARSED_URI uri )
201 {
202     if( uri )
203     {
204         URI_TUPLE r, r2;
205
206         for( r = uri->query; r; r = r2 ) {
207             r2 = r->next;
208             gcry_free( r );
209         }
210         gcry_free( uri );
211     }
212 }
213
214 static int
215 do_parse_uri( PARSED_URI uri, int only_local_part )
216 {
217     URI_TUPLE *tail;
218     char *p, *p2, *p3;
219     int n;
220
221     p = uri->buffer;
222     n = strlen( uri->buffer );
223     /* initialize all fields to an empty string or an empty list */
224     uri->scheme = uri->host = uri->path = p + n;
225     uri->port = 0;
226     uri->params = uri->query = NULL;
227
228     /* a quick validity check */
229     if( strspn( p, VALID_URI_CHARS) != n )
230         return GPGERR_BAD_URI; /* invalid characters found */
231
232     if( !only_local_part ) {
233         /* find the scheme */
234         if( !(p2 = strchr( p, ':' ) ) || p2 == p )
235            return GPGERR_BAD_URI; /* No scheme */
236         *p2++ = 0;
237         strlwr( p );
238         uri->scheme = p;
239         if( !strcmp( uri->scheme, "http" ) )
240             ;
241         else if( !strcmp( uri->scheme, "x-hkp" ) ) /* same as HTTP */
242             ;
243         else
244             return GPGERR_INVALID_URI; /* Unsupported scheme */
245
246         p = p2;
247
248         /* find the hostname */
249         if( *p != '/' )
250             return GPGERR_INVALID_URI; /* does not start with a slash */
251
252         p++;
253         if( *p == '/' ) {  /* there seems to be a hostname */
254             p++;
255             if( (p2 = strchr(p, '/')) )
256                 *p2++ = 0;
257             strlwr( p );
258             uri->host = p;
259             if( (p3=strchr( p, ':' )) ) {
260                 *p3++ = 0;
261                 uri->port = atoi( p3 );
262             }
263             else
264                uri->port = 80;
265             uri->host = p;
266             if( (n = remove_escapes( uri->host )) < 0 )
267                 return GPGERR_BAD_URI;
268             if( n != strlen( p ) )
269                 return GPGERR_BAD_URI; /* hostname with a Nul in it */
270             p = p2 ? p2 : NULL;
271         }
272     } /* end global URI part */
273
274     /* parse the pathname part */
275     if( !p || !*p ) /* we don't have a path */
276         return 0; /* and this is okay */
277
278     /* todo: here we have to check params */
279
280     /* do we have a query part */
281     if( (p2 = strchr( p, '?' )) )
282         *p2++ = 0;
283
284     uri->path = p;
285     if( (n = remove_escapes( p )) < 0 )
286         return GPGERR_BAD_URI;
287     if( n != strlen( p ) )
288         return GPGERR_BAD_URI; /* path with a Nul in it */
289     p = p2 ? p2 : NULL;
290
291     if( !p || !*p ) /* we don't have a query string */
292         return 0;   /* okay */
293
294     /* now parse the query string */
295     tail = &uri->query;
296     for(;;) {
297         URI_TUPLE elem;
298
299         if( (p2 = strchr( p, '&' )) )
300             *p2++ = 0;
301         if( !(elem = parse_tuple( p )) )
302             return GPGERR_BAD_URI;
303         *tail = elem;
304         tail = &elem->next;
305
306         if( !p2 )
307            break; /* ready */
308         p = p2;
309     }
310
311     return 0;
312 }
313
314
315
316 /****************
317  * Remove all %xx escapes; this is done inplace.
318  * Returns: new length of the string.
319  */
320 static int
321 remove_escapes( byte *string )
322 {
323     int n = 0;
324     byte *p, *s;
325
326     for(p=s=string; *s ; s++ ) {
327         if( *s == '%' ) {
328             if( s[1] && s[2] && isxdigit(s[1]) && isxdigit(s[2]) ) {
329                 s++;
330                 *p  = *s >= '0' && *s <= '9' ? *s - '0' :
331                       *s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10 ;
332                 *p <<= 4;
333                 s++;
334                 *p |= *s >= '0' && *s <= '9' ? *s - '0' :
335                       *s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10 ;
336                 p++;
337                 n++;
338             }
339             else {
340                 *p++ = *s++;
341                 if( *s )
342                    *p++ = *s++;
343                 if( *s )
344                    *p++ = *s++;
345                 if( *s )
346                    *p = 0;
347                 return -1; /* bad URI */
348             }
349         }
350         else
351         {
352             *p++ = *s;
353             n++;
354         }
355     }
356     *p = 0; /* always keep a string terminator */
357     return n;
358 }
359
360
361 static int
362 insert_escapes( byte *buffer, const byte *string, const byte *special )
363 {
364     int n = 0;
365
366     for( ; *string; string++ ) {
367         if( strchr( VALID_URI_CHARS, *string )
368             && !strchr( special, *string ) )  {
369             if( buffer )
370                 *buffer++ = *string;
371             n++;
372         }
373         else {
374             if( buffer ) {
375                 sprintf( buffer, "%02X", *string );
376                 buffer += 3;
377             }
378             n += 3;
379         }
380     }
381     return n;
382 }
383
384
385
386
387
388 static URI_TUPLE
389 parse_tuple( byte *string )
390 {
391     byte *p = string;
392     byte *p2;
393     int n;
394     URI_TUPLE tuple;
395
396     if( (p2 = strchr( p, '=' )) )
397         *p2++ = 0;
398     if( (n = remove_escapes( p )) < 0 )
399         return NULL; /* bad URI */
400     if( n != strlen( p ) )
401        return NULL; /* name with a Nul in it */
402     tuple = gcry_xcalloc( 1, sizeof *tuple );
403     tuple->name = p;
404     if( !p2 )  {
405         /* we have only the name, so we assume an empty value string */
406         tuple->value = p + strlen(p);
407         tuple->valuelen = 0;
408     }
409     else { /* name and value */
410         if( (n = remove_escapes( p2 )) < 0 ) {
411             gcry_free( tuple );
412             return NULL; /* bad URI */
413         }
414         tuple->value = p2;
415         tuple->valuelen = n;
416     }
417     return tuple;
418 }
419
420
421 /****************
422  * Send a HTTP request to the server
423  * Returns 0 if the request was successful
424  */
425 static int
426 send_request( HTTP_HD hd )
427 {
428     const byte *server;
429     byte *request, *p;
430     ushort port;
431     int rc;
432
433     server = *hd->uri->host? hd->uri->host : "localhost";
434     port   = hd->uri->port?  hd->uri->port : 80;
435
436     hd->sock = connect_server( server, port );
437     if( hd->sock == -1 )
438         return GPGERR_NETWORK;
439
440     p = build_rel_path( hd->uri );
441     request = gcry_xmalloc( strlen(p) + 20 );
442     sprintf( request, "%s %s%s HTTP/1.0\r\n",
443                           hd->req_type == HTTP_REQ_GET ? "GET" :
444                           hd->req_type == HTTP_REQ_HEAD? "HEAD":
445                           hd->req_type == HTTP_REQ_POST? "POST": "OOPS",
446                                                   *p == '/'? "":"/", p );
447     gcry_free(p);
448
449     rc = write_server( hd->sock, request, strlen(request) );
450     gcry_free( request );
451
452     return rc;
453 }
454
455
456
457
458 /****************
459  * Build the relative path from the parsed URI.
460  * Minimal implementation.
461  */
462 static byte*
463 build_rel_path( PARSED_URI uri )
464 {
465     URI_TUPLE r;
466     byte *rel_path, *p;
467     int n;
468
469     /* count the needed space */
470     n = insert_escapes( NULL, uri->path, "%;?&" );
471     /* todo: build params */
472     for( r=uri->query; r; r = r->next ) {
473         n++; /* '?'/'&' */
474         n += insert_escapes( NULL, r->name, "%;?&=" );
475         n++; /* '='*/
476         n += insert_escapes( NULL, r->value, "%;?&=" );
477     }
478     n++;
479
480     /* now  allocate and copy */
481     p = rel_path = gcry_xmalloc( n );
482     n = insert_escapes( p, uri->path, "%;?&" );
483     p += n;
484     /* todo: add params */
485     for( r=uri->query; r; r = r->next ) {
486         *p++ = r == uri->query? '?':'&';
487         n = insert_escapes( p, r->name, "%;?&=" );
488         p += n;
489         *p++ = '=';
490         /* todo: use valuelen */
491         n = insert_escapes( p, r->value, "%;?&=" );
492         p += n;
493     }
494     *p = 0;
495     return rel_path;
496 }
497
498
499
500 /***********************
501  * Parse the response from a server.
502  * Returns: errorcode and sets some fileds in the handle
503  */
504 static int
505 parse_response( HTTP_HD hd )
506 {
507     byte *line, *p, *p2;
508     unsigned maxlen, len;
509
510     /* Wait for the status line */
511     do {
512         maxlen = MAX_LINELEN;
513         len = iobuf_read_line( hd->fp_read, &hd->buffer,
514                                             &hd->buffer_size, &maxlen );
515         line = hd->buffer;
516         if( !maxlen )
517             return -1; /* line has been truncated */
518         if( !len )
519             return -1; /* eof */
520     } while( !*line  );
521
522     if( (p = strchr( line, '/')) )
523         *p++ = 0;
524     if( !p || strcmp( line, "HTTP" ) )
525         return 0; /* assume http 0.9 */
526
527     if( (p2 = strpbrk( p, " \t" ) ) ) {
528         *p2++ = 0;
529         p2 += strspn( p2, " \t" );
530     }
531     if( !p2 )
532         return 0; /* assume http 0.9 */
533     p = p2;
534     /* todo: add HTTP version number check here */
535     if( (p2 = strpbrk( p, " \t" ) ) )
536         *p2++ = 0;
537     if( !isdigit(p[0]) || !isdigit(p[1]) || !isdigit(p[2]) || p[3] ) {
538          /* malformed HTTP statuscode - assume HTTP 0.9 */
539         hd->is_http_0_9 = 1;
540         hd->status_code = 200;
541         return 0;
542     }
543     hd->status_code = atoi( p );
544
545     /* skip all the header lines and wait for the empty line */
546     do {
547         maxlen = MAX_LINELEN;
548         len = iobuf_read_line( hd->fp_read, &hd->buffer,
549                                &hd->buffer_size, &maxlen );
550         line = hd->buffer;
551         /* we ignore truncated lines */
552         if( !len )
553             return -1; /* eof */
554         /* time lineendings */
555         if( (*line == '\r' && line[1] == '\n') || *line == '\n' )
556             *line = 0;
557     } while( len && *line  );
558
559     return 0;
560 }
561
562 #if 0
563 static int
564 start_server()
565 {
566     struct sockaddr_in mya;
567     struct sockaddr_in peer;
568     int fd, client;
569     fd_set rfds;
570     int addrlen;
571     int i;
572
573     if( (fd=socket(AF_INET,SOCK_STREAM, 0)) == -1 ) {
574         log_error("socket() failed: %s\n", strerror(errno));
575         return -1;
576     }
577     i = 1;
578     if( setsockopt( fd, SOL_SOCKET, SO_REUSEADDR, (byte*)&i, sizeof(i) ) )
579         log_info("setsockopt(SO_REUSEADDR) failed: %s\n", strerror(errno) );
580
581     mya.sin_family=AF_INET;
582     memset(&mya.sin_addr, 0, sizeof(mya.sin_addr));
583     mya.sin_port=htons(11371);
584
585     if( bind( fd, (struct sockaddr *)&mya, sizeof(mya)) ) {
586         log_error("bind to port 11371 failed: %s\n", strerror(errno) );
587         close( fd );
588         return -1;
589     }
590
591     if( listen( fd, 5 ) ) {
592         log_error("listen failed: %s\n", strerror(errno) );
593         close( fd );
594         return -1;
595     }
596
597     for(;;) {
598         FD_ZERO(&rfds);
599         FD_SET( fd, &rfds );
600
601         if( select( fd+1, &rfds, NULL, NULL, NULL) <= 0 )
602             continue; /* ignore any errors */
603
604         if( !FD_ISSET( fd, &rfds ) )
605             continue;
606
607         addrlen = sizeof peer;
608         client = accept( fd, (struct sockaddr *)&peer, &addrlen);
609         if( client == -1 )
610             continue; /* oops */
611
612         log_info("connect from %s\n", inet_ntoa( peer.sin_addr ) );
613
614         fflush(stdout);
615         fflush(stderr);
616         if( !fork() ) {
617             int c;
618             FILE *fp;
619
620             fp = fdopen( client , "r" );
621             while( (c=getc(fp)) != EOF )
622                 putchar(c);
623             fclose(fp);
624             exit(0);
625         }
626         close( client );
627     }
628
629
630     return 0;
631 }
632 #endif
633
634
635
636 static int
637 connect_server( const char *server, ushort port )
638 {
639     struct sockaddr_in addr;
640     struct hostent *host;
641     int sd;
642
643     addr.sin_family = AF_INET;
644     addr.sin_port = htons(port);
645     host = gethostbyname((char*)server);
646     if( !host )
647          return -1;
648
649     addr.sin_addr = *(struct in_addr*)host->h_addr;
650
651     sd = socket(AF_INET, SOCK_STREAM, 0);
652     if( sd == -1 )
653         return -1;
654
655     if( connect( sd, (struct sockaddr *)&addr, sizeof addr) == -1 ) {
656         close(sd);
657         return -1;
658     }
659
660     return sd;
661 }
662
663
664 static int
665 write_server( int sock, const char *data, size_t length )
666 {
667     int nleft, nwritten;
668
669     nleft = length;
670     while( nleft > 0 ) {
671         nwritten = write( sock, data, nleft );
672         if( nwritten == -1 ) {
673             if( errno == EINTR )
674                 continue;
675             if( errno == EAGAIN ) {
676                 struct timeval tv;
677
678                 tv.tv_sec =  0;
679                 tv.tv_usec = 50000;
680                 select(0, NULL, NULL, NULL, &tv);
681                 continue;
682             }
683             log_info("write failed: %s\n", strerror(errno));
684             return GPGERR_NETWORK;
685         }
686         nleft -=nwritten;
687         data += nwritten;
688     }
689
690     return 0;
691 }
692
693 #endif /* HAVE_DOSISH_SYSTEM */
694
695 /**** Test code ****/
696 #ifdef TEST
697
698 int
699 main(int argc, char **argv)
700 {
701     int rc;
702     PARSED_URI uri;
703     URI_TUPLE r;
704     struct http_context hd;
705     int c;
706
707     log_set_name("http-test");
708     if( argc == 1 ) {
709         start_server();
710         return 0;
711     }
712
713     if( argc != 2 ) {
714         fprintf(stderr,"usage: http-test uri\n");
715         return 1;
716     }
717     argc--; argv++;
718
719     rc = parse_uri( &uri, *argv );
720     if( rc ) {
721         log_error("`%s': %s\n", *argv, gpg_errstr(rc));
722         release_parsed_uri( uri );
723         return 1;
724     }
725
726     printf("Scheme: %s\n", uri->scheme );
727     printf("Host  : %s\n", uri->host );
728     printf("Port  : %u\n", uri->port );
729     printf("Path  : %s\n", uri->path );
730     for( r=uri->params; r; r = r->next ) {
731         printf("Params: %s=%s", r->name, r->value );
732         if( strlen( r->value ) != r->valuelen )
733             printf(" [real length=%d]", (int)r->valuelen );
734         putchar('\n');
735     }
736     for( r=uri->query; r; r = r->next ) {
737         printf("Query : %s=%s", r->name, r->value );
738         if( strlen( r->value ) != r->valuelen )
739             printf(" [real length=%d]", (int)r->valuelen );
740         putchar('\n');
741     }
742     release_parsed_uri( uri ); uri = NULL;
743
744     rc = http_open_document( &hd, *argv, 0 );
745     if( rc ) {
746         log_error("can't get `%s': %s\n", *argv, gpg_errstr(rc));
747         return 1;
748     }
749     log_info("open_http_document succeeded; status=%u\n", hd.status_code );
750     while( (c=iobuf_get( hd.fp_read)) != -1 )
751         putchar(c);
752     http_close( &hd );
753     return 0;
754 }
755 #endif /*TEST*/