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