1 /* http.c - HTTP protocol handler
2 * Copyright (C) 1999 Free Software Foundation, Inc.
4 * This file is part of GnuPG.
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.
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.
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
29 #ifndef HAVE_DOSISH_SYSTEM
32 #include <sys/types.h>
33 #include <sys/socket.h>
36 #include <netinet/in.h>
37 #include <arpa/inet.h>
46 #define MAX_LINELEN 20000 /* max. length of a HTTP line */
47 #define VALID_URI_CHARS "abcdefghijklmnopqrstuvwxyz" \
48 "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
50 "!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~"
53 #define EAGAIN EWOULDBLOCK
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 );
67 static int connect_server( const char *server, ushort port );
68 static int write_server( int sock, const char *data, size_t length );
72 http_open( HTTP_HD hd, HTTP_REQ_TYPE reqtype, const char *url,
77 if( flags || !(reqtype == HTTP_REQ_GET || reqtype == HTTP_REQ_POST) )
78 return G10ERR_INV_ARG;
80 /* initialize the handle */
81 memset( hd, 0, sizeof *hd );
84 hd->req_type = reqtype;
86 rc = parse_uri( &hd->uri, url );
88 rc = send_request( hd );
90 hd->fp_write = iobuf_fdopen( hd->sock , "w" );
97 if( !hd->fp_read && !hd->fp_write && hd->sock != -1 )
99 iobuf_close( hd->fp_read );
100 iobuf_close( hd->fp_write);
101 release_parsed_uri( hd->uri );
109 http_start_data( HTTP_HD hd )
112 iobuf_put( hd->fp_write, '\n' );
119 http_wait_response( HTTP_HD hd, unsigned int *ret_status )
123 http_start_data( hd ); /* make sure that we are in the data */
124 iobuf_flush( hd->fp_write );
126 hd->sock = dup( hd->sock );
128 return G10ERR_GENERAL;
129 iobuf_close( hd->fp_write );
131 shutdown( hd->sock, 1 );
134 hd->fp_read = iobuf_fdopen( hd->sock , "r" );
136 return G10ERR_GENERAL;
138 rc = parse_response( hd );
139 if( !rc && ret_status )
140 *ret_status = hd->status_code;
147 http_open_document( HTTP_HD hd, const char *document, unsigned int flags )
152 return G10ERR_INV_ARG;
154 rc = http_open( hd, HTTP_REQ_GET, document, 0 );
158 rc = http_wait_response( hd, NULL );
169 http_close( HTTP_HD hd )
171 if( !hd || !hd->initialized )
173 if( !hd->fp_read && !hd->fp_write && hd->sock != -1 )
175 iobuf_close( hd->fp_read );
176 iobuf_close( hd->fp_write );
177 release_parsed_uri( hd->uri );
178 m_free( hd->buffer );
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).
190 parse_uri( PARSED_URI *ret_uri, const char *uri )
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 );
198 release_parsed_uri( PARSED_URI uri )
204 for( r = uri->query; r; r = r2 ) {
213 do_parse_uri( PARSED_URI uri, int only_local_part )
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;
224 uri->params = uri->query = NULL;
226 /* a quick validity check */
227 if( strspn( p, VALID_URI_CHARS) != n )
228 return G10ERR_BAD_URI; /* invalid characters found */
230 if( !only_local_part ) {
231 /* find the scheme */
232 if( !(p2 = strchr( p, ':' ) ) || p2 == p )
233 return G10ERR_BAD_URI; /* No scheme */
237 if( !strcmp( uri->scheme, "http" ) )
239 else if( !strcmp( uri->scheme, "x-hkp" ) ) /* same as HTTP */
242 return G10ERR_INVALID_URI; /* Unsupported scheme */
246 /* find the hostname */
248 return G10ERR_INVALID_URI; /* does not start with a slash */
251 if( *p == '/' ) { /* there seems to be a hostname */
253 if( (p2 = strchr(p, '/')) )
257 if( (p3=strchr( p, ':' )) ) {
259 uri->port = atoi( p3 );
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 */
270 } /* end global URI part */
272 /* parse the pathname part */
273 if( !p || !*p ) /* we don't have a path */
274 return 0; /* and this is okay */
276 /* todo: here we have to check params */
278 /* do we have a query part */
279 if( (p2 = strchr( 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 */
289 if( !p || !*p ) /* we don't have a query string */
292 /* now parse the query string */
297 if( (p2 = strchr( p, '&' )) )
299 if( !(elem = parse_tuple( p )) )
300 return G10ERR_BAD_URI;
315 * Remove all %xx escapes; this is done inplace.
316 * Returns: new length of the string.
319 remove_escapes( byte *string )
324 for(p=s=string; *s ; s++ ) {
326 if( s[1] && s[2] && isxdigit(s[1]) && isxdigit(s[2]) ) {
328 *p = *s >= '0' && *s <= '9' ? *s - '0' :
329 *s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10 ;
332 *p |= *s >= '0' && *s <= '9' ? *s - '0' :
333 *s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10 ;
345 return -1; /* bad URI */
354 *p = 0; /* always keep a string terminator */
360 insert_escapes( byte *buffer, const byte *string, const byte *special )
364 for( ; *string; string++ ) {
365 if( strchr( VALID_URI_CHARS, *string )
366 && !strchr( special, *string ) ) {
373 sprintf( buffer, "%02X", *string );
387 parse_tuple( byte *string )
394 if( (p2 = strchr( p, '=' )) )
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 );
403 /* we have only the name, so we assume an empty value string */
404 tuple->value = p + strlen(p);
407 else { /* name and value */
408 if( (n = remove_escapes( p2 )) < 0 ) {
410 return NULL; /* bad URI */
420 * Send a HTTP request to the server
421 * Returns 0 if the request was successful
424 send_request( HTTP_HD hd )
431 server = *hd->uri->host? hd->uri->host : "localhost";
432 port = hd->uri->port? hd->uri->port : 80;
434 hd->sock = connect_server( server, port );
436 return G10ERR_NETWORK;
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 );
447 rc = write_server( hd->sock, request, strlen(request) );
457 * Build the relative path from the parsed URI.
458 * Minimal implementation.
461 build_rel_path( PARSED_URI uri )
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 ) {
472 n += insert_escapes( NULL, r->name, "%;?&=" );
474 n += insert_escapes( NULL, r->value, "%;?&=" );
478 /* now allocate and copy */
479 p = rel_path = m_alloc( n );
480 n = insert_escapes( p, uri->path, "%;?&" );
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, "%;?&=" );
488 /* todo: use valuelen */
489 n = insert_escapes( p, r->value, "%;?&=" );
498 /***********************
499 * Parse the response from a server.
500 * Returns: errorcode and sets some fileds in the handle
503 parse_response( HTTP_HD hd )
506 unsigned maxlen, len;
508 /* Wait for the status line */
510 maxlen = MAX_LINELEN;
511 len = iobuf_read_line( hd->fp_read, &hd->buffer,
512 &hd->buffer_size, &maxlen );
515 return -1; /* line has been truncated */
520 if( (p = strchr( line, '/')) )
522 if( !p || strcmp( line, "HTTP" ) )
523 return 0; /* assume http 0.9 */
525 if( (p2 = strpbrk( p, " \t" ) ) ) {
527 p2 += strspn( p2, " \t" );
530 return 0; /* assume http 0.9 */
532 /* todo: add HTTP version number check here */
533 if( (p2 = strpbrk( p, " \t" ) ) )
535 if( !isdigit(p[0]) || !isdigit(p[1]) || !isdigit(p[2]) || p[3] ) {
536 /* malformed HTTP statuscode - assume HTTP 0.9 */
538 hd->status_code = 200;
541 hd->status_code = atoi( p );
543 /* skip all the header lines and wait for the empty line */
545 maxlen = MAX_LINELEN;
546 len = iobuf_read_line( hd->fp_read, &hd->buffer,
547 &hd->buffer_size, &maxlen );
549 /* we ignore truncated lines */
552 /* time lineendings */
553 if( (*line == '\r' && line[1] == '\n') || *line == '\n' )
555 } while( len && *line );
564 struct sockaddr_in mya;
565 struct sockaddr_in peer;
571 if( (fd=socket(AF_INET,SOCK_STREAM, 0)) == -1 ) {
572 log_error("socket() failed: %s\n", strerror(errno));
576 if( setsockopt( fd, SOL_SOCKET, SO_REUSEADDR, (byte*)&i, sizeof(i) ) )
577 log_info("setsockopt(SO_REUSEADDR) failed: %s\n", strerror(errno) );
579 mya.sin_family=AF_INET;
580 memset(&mya.sin_addr, 0, sizeof(mya.sin_addr));
581 mya.sin_port=htons(11371);
583 if( bind( fd, (struct sockaddr *)&mya, sizeof(mya)) ) {
584 log_error("bind to port 11371 failed: %s\n", strerror(errno) );
589 if( listen( fd, 5 ) ) {
590 log_error("listen failed: %s\n", strerror(errno) );
599 if( select( fd+1, &rfds, NULL, NULL, NULL) <= 0 )
600 continue; /* ignore any errors */
602 if( !FD_ISSET( fd, &rfds ) )
605 addrlen = sizeof peer;
606 client = accept( fd, (struct sockaddr *)&peer, &addrlen);
610 log_info("connect from %s\n", inet_ntoa( peer.sin_addr ) );
618 fp = fdopen( client , "r" );
619 while( (c=getc(fp)) != EOF )
635 connect_server( const char *server, ushort port )
637 struct sockaddr_in addr;
638 struct hostent *host;
641 addr.sin_family = AF_INET;
642 addr.sin_port = htons(port);
643 host = gethostbyname((char*)server);
647 addr.sin_addr = *(struct in_addr*)host->h_addr;
649 sd = socket(AF_INET, SOCK_STREAM, 0);
653 if( connect( sd, (struct sockaddr *)&addr, sizeof addr) == -1 ) {
663 write_server( int sock, const char *data, size_t length )
669 nwritten = write( sock, data, nleft );
670 if( nwritten == -1 ) {
673 if( errno == EAGAIN ) {
678 select(0, NULL, NULL, NULL, &tv);
681 log_info("write failed: %s\n", strerror(errno));
682 return G10ERR_NETWORK;
691 #endif /* HAVE_DOSISH_SYSTEM */
693 /**** Test code ****/
697 main(int argc, char **argv)
702 struct http_context hd;
705 log_set_name("http-test");
712 fprintf(stderr,"usage: http-test uri\n");
717 rc = parse_uri( &uri, *argv );
719 log_error("`%s': %s\n", *argv, g10_errstr(rc));
720 release_parsed_uri( uri );
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 );
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 );
740 release_parsed_uri( uri ); uri = NULL;
742 rc = http_open_document( &hd, *argv, 0 );
744 log_error("can't get `%s': %s\n", *argv, g10_errstr(rc));
747 log_info("open_http_document succeeded; status=%u\n", hd.status_code );
748 while( (c=iobuf_get( hd.fp_read)) != -1 )