Files
2017-09-01 00:20:11 +02:00

763 lines
22 KiB
C

/* http_get - fetch the contents of an http URL
**
** Originally based on a simple version by Al Globus <globus@nas.nasa.gov>.
** Debugged and prettified by Jef Poskanzer <jef@mail.acme.com>. Also includes
** ifdefs to handle https via OpenSSL.
*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#ifdef USE_SSL
#include <openssl/ssl.h>
#include <openssl/err.h>
#endif
#include <clib/exec_protos.h>
#include <clib/amitcp_protos.h>
#define alarm(x) ;
int open_libs();
extern struct Library *SocketBase;
/* Forwards. */
static void usage( void );
static int getURL( char* u, char* referrer, char* user_agent, char* auth_token, int ncookies, char** cookies, char* header_name, char* header_value );
static int getURLbyParts( int protocol, char* host, unsigned short port, char* file, char* referrer, char* user_agent, char* auth_token, int ncookies, char** cookies, char* header_name, char* header_value );
static int open_client_socket( char* hostname, unsigned short port );
static void show_error( char* cause );
static void sigcatch( int sig );
static int b64_encode( unsigned char* ptr, int len, char* space, int size );
/* Globals. */
static char* argv0;
static int verbose;
static int timeout;
static char* url;
/* Protocol symbols. */
#define PROTO_HTTP 0
#ifdef USE_SSL
#define PROTO_HTTPS 1
#endif
/* Header FSM states. */
#define HDST_LINE1_PROTOCOL 0
#define HDST_LINE1_WHITESPACE 1
#define HDST_LINE1_STATUS 2
#define HDST_BOL 10
#define HDST_TEXT 11
#define HDST_LF 12
#define HDST_CR 13
#define HDST_CRLF 14
#define HDST_CRLFCR 15
#define MAX_COOKIES 20
int
main( int argc, char** argv )
{
int argn;
char* referrer;
char* user_agent;
char* auth_token;
int ncookies;
char* cookies[MAX_COOKIES];
char* header_name;
char* header_value;
int status;
open_libs();
argv0 = argv[0];
argn = 1;
timeout = 60;
referrer = (char*) 0;
user_agent = "http_get";
auth_token = (char*) 0;
ncookies = 0;
header_name = (char*) 0;
header_value = (char*) 0;
verbose = 0;
while ( argn < argc && argv[argn][0] == '-' && argv[argn][1] != '\0' )
{
if ( strcmp( argv[argn], "-t" ) == 0 && argn + 1 < argc )
{
++argn;
timeout = atoi( argv[argn] );
}
else if ( strcmp( argv[argn], "-r" ) == 0 && argn + 1 < argc )
{
++argn;
referrer = argv[argn];
}
else if ( strcmp( argv[argn], "-u" ) == 0 && argn + 1 < argc )
{
++argn;
user_agent = argv[argn];
}
else if ( strcmp( argv[argn], "-a" ) == 0 && argn + 1 < argc )
{
++argn;
auth_token = argv[argn];
}
else if ( strcmp( argv[argn], "-c" ) == 0 && argn + 1 < argc )
{
if ( ncookies >= MAX_COOKIES )
{
(void) fprintf( stderr, "%s: too many cookies\n", argv0 );
exit( 1 );
}
++argn;
cookies[ncookies++] = argv[argn];
}
else if ( strcmp( argv[argn], "-h" ) == 0 && argn + 2 < argc )
{
++argn;
header_name = argv[argn];
++argn;
header_value = argv[argn];
}
else if ( strcmp( argv[argn], "-v" ) == 0 )
verbose = 1;
else
usage();
++argn;
}
if ( argn >= argc )
usage();
url = argv[argn];
++argn;
if ( argn != argc )
usage();
(void) signal( SIGALRM, sigcatch );
status = getURL( url, referrer, user_agent, auth_token, ncookies, cookies, header_name, header_value );
if ( status == 200 )
exit( 0 );
else if ( status == 0 )
exit( 1 );
else
exit( status );
}
static void
usage( void )
{
(void) fprintf( stderr, "usage: %s [-c cookie] [-t timeout] [-r referrer] [-u user-agent] [-a username:password] [-h header value] [-v] url\n", argv0 );
exit( 1 );
}
/* URL must be of the form http://host-name[:port]/file-name */
static int
getURL( char* u, char* referrer, char* user_agent, char* auth_token, int ncookies, char** cookies, char* header_name, char* header_value )
{
char* s;
int protocol;
char host[2000];
int host_len;
unsigned short port;
char* file = (char*) 0;
char* http = "http://";
int http_len = strlen( http );
#ifdef USE_SSL
char* https = "https://";
int https_len = strlen( https );
#endif /* USE_SSL */
int proto_len;
if ( u == (char*) 0 )
{
(void) fprintf( stderr, "%s: null URL\n", argv0 );
exit( 1 );
}
if ( strncmp( http, u, http_len ) == 0 )
{
proto_len = http_len;
protocol = PROTO_HTTP;
}
#ifdef USE_SSL
else if ( strncmp( https, u, https_len ) == 0 )
{
proto_len = https_len;
protocol = PROTO_HTTPS;
}
#endif /* USE_SSL */
else
{
(void) fprintf( stderr, "%s: non-http URL\n", argv0 );
exit( 1 );
}
/* Get the host name. */
for ( s = u + proto_len; *s != '\0' && *s != ':' && *s != '/'; ++s )
;
host_len = s - u;
host_len -= proto_len;
strncpy( host, u + proto_len, host_len );
host[host_len] = '\0';
/* Get port number. */
if ( *s == ':' )
{
port = (unsigned short) atoi( ++s );
while ( *s != '\0' && *s != '/' )
++s;
}
else
{
#ifdef USE_SSL
if ( protocol == PROTO_HTTPS )
port = 443;
else
#endif
port = 80;
}
/* Get the file name. */
if ( *s == '\0' )
file = "/";
else
file = s;
return getURLbyParts( protocol, host, port, file, referrer, user_agent, auth_token, ncookies, cookies, header_name, header_value );
}
static int
getURLbyParts( int protocol, char* host, unsigned short port, char* file, char* referrer, char* user_agent, char* auth_token, int ncookies, char** cookies, char* header_name, char* header_value )
{
int sockfd;
#ifdef USE_SSL
SSL_CTX* ssl_ctx = (SSL_CTX*) 0;
SSL* ssl = (SSL*) 0;
#endif
char buf[20000];
int i, bytes, header_state, status;
int b = 0;
alarm( timeout );
sockfd = open_client_socket( host, port );
#ifdef USE_SSL
if ( protocol == PROTO_HTTPS )
{
/* Make SSL connection. */
int r;
SSL_load_error_strings();
SSLeay_add_ssl_algorithms();
ssl_ctx = SSL_CTX_new( SSLv23_client_method() );
ssl = SSL_new( ssl_ctx );
SSL_set_fd( ssl, sockfd );
r = SSL_connect( ssl );
if ( r <= 0 )
{
(void) fprintf(
stderr, "%s: %s - SSL connection failed - %d\n",
argv0, url, r );
ERR_print_errors_fp( stderr );
exit( 1 );
}
}
#endif
/* Build request buffer, starting with the GET. */
alarm( timeout );
bytes = snprintf( buf, sizeof(buf), "GET %s HTTP/1.0\r\n", file );
/* HTTP/1.1 host header - some servers want it even in HTTP/1.0. */
bytes += snprintf( &buf[bytes], sizeof(buf) - bytes, "Host: %s\r\n", host );
if ( referrer != (char*) 0 )
{
/* We generate both the standard Referer and the correctly
** spelled Referrer.
*/
bytes += snprintf( &buf[bytes], sizeof(buf) - bytes, "Referer: %s\r\n", referrer );
bytes += snprintf( &buf[bytes], sizeof(buf) - bytes, "Referrer: %s\r\n", referrer );
}
/* User-agent. */
bytes += snprintf( &buf[bytes], sizeof(buf) - bytes, "User-Agent: %s\r\n", user_agent );
/* Fixed headers. */
bytes += snprintf( &buf[bytes], sizeof(buf) - bytes, "Accept: */*\r\n" );
bytes += snprintf( &buf[bytes], sizeof(buf) - bytes, "Accept-Language: en\r\n" );
bytes += snprintf( &buf[bytes], sizeof(buf) - bytes, "Accept-Charset: iso-8859-1,*,utf-8\r\n" );
if ( auth_token != (char*) 0 )
{
/* Basic Auth info. */
char token_buf[1000];
token_buf[b64_encode( (unsigned char*) auth_token, strlen( auth_token ), token_buf, sizeof(token_buf) )] = '\0';
bytes += snprintf( &buf[bytes], sizeof(buf) - bytes, "Authorization: Basic %s\r\n", token_buf );
}
/* Cookies. */
for ( i = 0; i < ncookies; ++i )
bytes += snprintf( &buf[bytes], sizeof(buf) - bytes, "Cookie: %s\r\n", cookies[i] );
/* Optional extra header. */
if ( header_name != (char*) 0 )
bytes += snprintf( &buf[bytes], sizeof(buf) - bytes, "%s: %s\r\n", header_name, header_value );
/* Blank line. */
bytes += snprintf( &buf[bytes], sizeof(buf) - bytes, "\r\n" );
/* Now actually send it. */
#ifdef USE_SSL
if ( protocol == PROTO_HTTPS )
(void) SSL_write( ssl, buf, bytes );
else
#endif
(void) Send( sockfd, buf, bytes, 0 );
/* Get lines until a blank one. */
alarm( timeout );
header_state = HDST_LINE1_PROTOCOL;
status = 0;
for (;;)
{
#ifdef USE_SSL
if ( protocol == PROTO_HTTPS )
bytes = SSL_read( ssl, buf, sizeof(buf) );
else
#endif
bytes = Recv( sockfd, buf, sizeof(buf), 0 );
if ( bytes <= 0 )
break;
for ( b = 0; b < bytes; ++b )
{
if ( verbose )
(void) write( 1, &buf[b], 1 );
switch ( header_state )
{
case HDST_LINE1_PROTOCOL:
switch ( buf[b] )
{
case ' ':
case '\t':
header_state = HDST_LINE1_WHITESPACE; ;
break;
case '\n':
header_state = HDST_LF ;
break;
case '\r':
header_state = HDST_CR;
break;
}
break;
case HDST_LINE1_WHITESPACE:
switch ( buf[b] )
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
status = buf[b] - '0';
header_state = HDST_LINE1_STATUS;
break;
case '\n':
header_state = HDST_LF ;
break;
case '\r':
header_state = HDST_CR;
break;
default:
header_state = HDST_TEXT;
break;
}
break;
case HDST_LINE1_STATUS:
switch ( buf[b] )
{
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
status = status * 10 + buf[b] - '0';
break;
case '\n':
header_state = HDST_LF ;
break;
case '\r':
header_state = HDST_CR;
break;
default:
header_state = HDST_TEXT;
break;
}
break;
case HDST_BOL:
switch ( buf[b] )
{
case '\n':
header_state = HDST_LF;
break;
case '\r':
header_state = HDST_CR;
break;
default:
header_state = HDST_TEXT;
break;
}
break;
case HDST_TEXT:
switch ( buf[b] )
{
case '\n':
header_state = HDST_LF;
break;
case '\r':
header_state = HDST_CR;
break;
}
break;
case HDST_LF:
switch ( buf[b] )
{
case '\n':
goto end_of_headers;
case '\r':
header_state = HDST_CR;
break;
default:
header_state = HDST_TEXT;
break;
}
break;
case HDST_CR:
switch ( buf[b] )
{
case '\n':
header_state = HDST_CRLF;
break;
case '\r':
goto end_of_headers;
default:
header_state = HDST_TEXT;
break;
}
break;
case HDST_CRLF:
switch ( buf[b] )
{
case '\n':
goto end_of_headers;
case '\r':
header_state = HDST_CRLFCR;
break;
default:
header_state = HDST_TEXT;
break;
}
break;
case HDST_CRLFCR:
switch ( buf[b] )
{
case '\n':
case '\r':
goto end_of_headers;
default:
header_state = HDST_TEXT;
break;
}
break;
}
}
}
end_of_headers:
/* Dump out the rest of the headers buffer. */
if ( bytes > 0 )
{
++b;
(void) write( 1, &buf[b], bytes - b );
}
/* Copy the data. */
for (;;)
{
alarm( timeout );
#ifdef USE_SSL
if ( protocol == PROTO_HTTPS )
bytes = SSL_read( ssl, buf, sizeof(buf) );
else
#endif
bytes = Recv( sockfd, buf, sizeof(buf), 0 );
if ( bytes == 0 )
break;
if ( bytes < 0 )
show_error( "read" );
(void) write( 1, buf, bytes );
}
#ifdef USE_SSL
if ( protocol == PROTO_HTTPS )
{
SSL_free( ssl );
SSL_CTX_free( ssl_ctx );
}
#endif
(void) CloseSocket( sockfd );
return status;
}
#if defined(AF_INET6) && defined(IN6_IS_ADDR_V4MAPPED)
#define USE_IPV6
#endif
static int
open_client_socket( char* hostname, unsigned short port )
{
#ifdef USE_IPV6
struct addrinfo hints;
char portstr[10];
int gaierr;
struct addrinfo* ai;
struct addrinfo* ai2;
struct addrinfo* aiv4;
struct addrinfo* aiv6;
struct sockaddr_in6 sa_in;
#else /* USE_IPV6 */
struct hostent *he;
struct sockaddr_in sa_in;
#endif /* USE_IPV6 */
int sa_len, sock_family, sock_type, sock_protocol;
int sockfd;
(void) memset( (void*) &sa_in, 0, sizeof(sa_in) );
#ifdef USE_IPV6
(void) memset( &hints, 0, sizeof(hints) );
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
(void) snprintf( portstr, sizeof(portstr), "%d", (int) port );
if ( (gaierr = getaddrinfo( hostname, portstr, &hints, &ai )) != 0 )
{
(void) fprintf(
stderr, "%s: getaddrinfo %s - %s\n", argv0, hostname,
gai_strerror( gaierr ) );
exit( 1 );
}
/* Find the first IPv4 and IPv6 entries. */
aiv4 = (struct addrinfo*) 0;
aiv6 = (struct addrinfo*) 0;
for ( ai2 = ai; ai2 != (struct addrinfo*) 0; ai2 = ai2->ai_next )
{
switch ( ai2->ai_family )
{
case AF_INET:
if ( aiv4 == (struct addrinfo*) 0 )
aiv4 = ai2;
break;
case AF_INET6:
if ( aiv6 == (struct addrinfo*) 0 )
aiv6 = ai2;
break;
}
}
/* If there's an IPv4 address, use that, otherwise try IPv6. */
if ( aiv4 != (struct addrinfo*) 0 )
{
if ( sizeof(sa_in) < aiv4->ai_addrlen )
{
(void) fprintf(
stderr, "%s - sockaddr too small (%lu < %lu)\n",
hostname, (unsigned long) sizeof(sa_in),
(unsigned long) aiv4->ai_addrlen );
exit( 1 );
}
sock_family = aiv4->ai_family;
sock_type = aiv4->ai_socktype;
sock_protocol = aiv4->ai_protocol;
sa_len = aiv4->ai_addrlen;
(void) memmove( &sa_in, aiv4->ai_addr, sa_len );
goto ok;
}
if ( aiv6 != (struct addrinfo*) 0 )
{
if ( sizeof(sa_in) < aiv6->ai_addrlen )
{
(void) fprintf(
stderr, "%s - sockaddr too small (%lu < %lu)\n",
hostname, (unsigned long) sizeof(sa_in),
(unsigned long) aiv6->ai_addrlen );
exit( 1 );
}
sock_family = aiv6->ai_family;
sock_type = aiv6->ai_socktype;
sock_protocol = aiv6->ai_protocol;
sa_len = aiv6->ai_addrlen;
(void) memmove( &sa_in, aiv6->ai_addr, sa_len );
goto ok;
}
(void) fprintf(
stderr, "%s: no valid address found for host %s\n", argv0, hostname );
exit( 1 );
ok:
freeaddrinfo( ai );
#else /* USE_IPV6 */
he = gethostbyname( hostname ); // Socket Operation
if ( he == (struct hostent*) 0 )
{
(void) fprintf( stderr, "%s: unknown host - %s\n", argv0, hostname );
exit( 1 );
}
sock_family = sa_in.sin_family = he->h_addrtype;
sock_type = SOCK_STREAM;
sock_protocol = 0;
sa_len = sizeof(sa_in);
(void) memmove( &sa_in.sin_addr, he->h_addr, he->h_length );
sa_in.sin_port = htons( port );
#endif /* USE_IPV6 */
sockfd = socket( sock_family, sock_type, sock_protocol ); // Socket Operation
if ( sockfd < 0 )
show_error( "socket" );
if ( connect( sockfd, (struct sockaddr*) &sa_in, sa_len ) < 0 ) // Socket Operation
show_error( "connect" );
return sockfd;
}
static void
show_error( char* cause )
{
char buf[5000];
(void) sprintf( buf, "%s: %s - %s", argv0, url, cause );
perror( buf );
exit( 1 );
}
static void
sigcatch( int sig )
{
(void) fprintf( stderr, "%s: %s - timed out\n", argv0, url );
exit( 1 );
}
/* Base-64 encoding. This encodes binary data as printable ASCII characters.
** Three 8-bit binary bytes are turned into four 6-bit values, like so:
**
** [11111111] [22222222] [33333333]
**
** [111111] [112222] [222233] [333333]
**
** Then the 6-bit values are represented using the characters "A-Za-z0-9+/".
*/
static char b64_encode_table[64] = {
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', /* 0-7 */
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', /* 8-15 */
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', /* 16-23 */
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', /* 24-31 */
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', /* 32-39 */
'o', 'p', 'q', 'r', 's', 't', 'u', 'v', /* 40-47 */
'w', 'x', 'y', 'z', '0', '1', '2', '3', /* 48-55 */
'4', '5', '6', '7', '8', '9', '+', '/' /* 56-63 */
};
static int b64_decode_table[256] = {
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 00-0F */
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 10-1F */
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,62,-1,-1,-1,63, /* 20-2F */
52,53,54,55,56,57,58,59,60,61,-1,-1,-1,-1,-1,-1, /* 30-3F */
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13,14, /* 40-4F */
15,16,17,18,19,20,21,22,23,24,25,-1,-1,-1,-1,-1, /* 50-5F */
-1,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40, /* 60-6F */
41,42,43,44,45,46,47,48,49,50,51,-1,-1,-1,-1,-1, /* 70-7F */
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 80-8F */
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* 90-9F */
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* A0-AF */
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* B0-BF */
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* C0-CF */
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* D0-DF */
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1, /* E0-EF */
-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 /* F0-FF */
};
/* Do base-64 encoding on a hunk of bytes. Return the actual number of
** bytes generated. Base-64 encoding takes up 4/3 the space of the original,
** plus a bit for end-padding. 3/2+5 gives a safe margin.
*/
static int
b64_encode( unsigned char* ptr, int len, char* space, int size )
{
int ptr_idx, space_idx, phase;
char c;
space_idx = 0;
phase = 0;
for ( ptr_idx = 0; ptr_idx < len; ++ptr_idx )
{
switch ( phase )
{
case 0:
c = b64_encode_table[ptr[ptr_idx] >> 2];
if ( space_idx < size )
space[space_idx++] = c;
c = b64_encode_table[( ptr[ptr_idx] & 0x3 ) << 4];
if ( space_idx < size )
space[space_idx++] = c;
++phase;
break;
case 1:
space[space_idx - 1] =
b64_encode_table[
b64_decode_table[(int) ((unsigned char) space[space_idx - 1])] |
( ptr[ptr_idx] >> 4 ) ];
c = b64_encode_table[( ptr[ptr_idx] & 0xf ) << 2];
if ( space_idx < size )
space[space_idx++] = c;
++phase;
break;
case 2:
space[space_idx - 1] =
b64_encode_table[
b64_decode_table[(int) ((unsigned char) space[space_idx - 1])] |
( ptr[ptr_idx] >> 6 ) ];
c = b64_encode_table[ptr[ptr_idx] & 0x3f];
if ( space_idx < size )
space[space_idx++] = c;
phase = 0;
break;
}
}
/* Pad with ='s. */
while ( phase++ < 3 )
if ( space_idx < size )
space[space_idx++] = '=';
return space_idx;
}