/* http_get - fetch the contents of an http URL ** ** Originally based on a simple version by Al Globus . ** Debugged and prettified by Jef Poskanzer . Also includes ** ifdefs to handle https via OpenSSL. */ #include #include #include #include #include #include #include #include #include #ifdef USE_SSL #include #include #endif #include #include #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; }