WebGet & WebPost Utilities
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

823 lines
21 KiB

/**
* @PROJECT WebGet Downloader
* @COPYRIGHT See COPYING in the top level directory
* @FILE libweb.c
* @PURPOSE Common Web Library
* @DEVELOPERS Al Globus <globus@nas.nasa.gov>
* Rafal Kupiec <belliash@asiotec.eu.org>
* Jef Poskanzer <jef@mail.acme.com>
*/
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <cyassl/openssl/ssl.h>
#include <cyassl/openssl/err.h>
#include <cyassl/error.h>
#ifdef CROSS_BUILD
#include <cyassl/cyassl_error.h>
#endif
#include "libweb.h"
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;
}
}
while(phase++ < 3) {
if(space_idx < size) {
space[space_idx++] = '=';
}
}
return space_idx;
}
void check(void* ptr) {
if(ptr == (void*) 0) {
(void) fprintf(stderr, "%s: out of memory\n", argv0);
exit(1);
}
}
off_t file_bytes(const char* filename) {
struct stat sb;
if(stat(filename, &sb) < 0) {
perror(filename);
exit(1);
}
return sb.st_size;
}
int file_copy(const char* filename, char* buf) {
int fd;
struct stat sb;
off_t bytes;
fd = open(filename, O_RDONLY);
if(fd == -1) {
perror(filename);
exit(-1);
}
if(fstat(fd, &sb) != 0) {
perror(filename);
exit(-1);
}
bytes = sb.st_size;
if(read(fd, buf, bytes) != bytes) {
perror(filename);
exit(-1);
}
(void) close(fd);
return bytes;
}
int getURL(char* url, char* referer, 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 );
char* https = "https://";
int https_len = strlen( https );
int proto_len;
if(url == (char*) 0) {
(void) fprintf(stderr, "%s: null URL\n", argv0);
exit(1);
}
if(strncmp(http, url, http_len) == 0) {
proto_len = http_len;
protocol = PROTO_HTTP;
} else if(strncmp(https, url, https_len) == 0) {
proto_len = https_len;
protocol = PROTO_HTTPS;
} else {
(void) fprintf(stderr, "%s: non-http URL\n", argv0);
exit(1);
}
for(s = url + proto_len; *s != '\0' && *s != ':' && *s != '/'; ++s);
host_len = s - url;
host_len -= proto_len;
strncpy(host, url + proto_len, host_len);
host[host_len] = '\0';
if(*s == ':') {
port = (unsigned short) atoi(++s);
while(*s != '\0' && *s != '/')
++s;
} else {
if(protocol == PROTO_HTTPS) {
port = 443;
} else {
port = 80;
}
}
if(*s == '\0') {
file = "/";
} else {
file = s;
}
return getURLbyParts(protocol, host, port, file, referer, user_agent, auth_token, ncookies, cookies, header_name, header_value);
}
int getURLbyParts(int protocol, char* host, unsigned short port, char* file, char* referer, char* user_agent, char* auth_token, int ncookies, char** cookies, char* header_name, char* header_value) {
int sockfd;
SSL_CTX* ssl_ctx = (SSL_CTX*) 0;
SSL* ssl = (SSL*) 0;
char buf[20000];
int i, bytes, b, header_state, status;
(void) alarm(timeout);
sockfd = open_client_socket(host, port);
if(protocol == PROTO_HTTPS) {
int r;
ssl_ctx = SSL_CTX_new(TLSv1_client_method());
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, 0);
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);
exit(1);
}
}
(void) alarm(timeout);
bytes = snprintf(buf, sizeof(buf), "GET %s HTTP/1.0\r\n", file);
bytes += snprintf(&buf[bytes], sizeof(buf) - bytes, "Host: %s\r\n", host);
if(referer != (char*) 0) {
bytes += snprintf(&buf[bytes], sizeof(buf) - bytes, "Referer: %s\r\n", referer);
}
bytes += snprintf(&buf[bytes], sizeof(buf) - bytes, "User-Agent: %s\r\n", user_agent);
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) {
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);
}
for(i = 0; i < ncookies; ++i) {
bytes += snprintf(&buf[bytes], sizeof(buf) - bytes, "Cookie: %s\r\n", cookies[i]);
}
if(header_name != (char*) 0) {
bytes += snprintf(&buf[bytes], sizeof(buf) - bytes, "%s: %s\r\n", header_name, header_value);
}
bytes += snprintf(&buf[bytes], sizeof(buf) - bytes, "\r\n");
if (protocol == PROTO_HTTPS)
(void) SSL_write(ssl, buf, bytes);
else
(void) write(sockfd, buf, bytes);
(void) alarm(timeout);
header_state = HDST_LINE1_PROTOCOL;
status = 0;
for(;;) {
if(protocol == PROTO_HTTPS) {
bytes = SSL_read(ssl, buf, sizeof(buf));
} else {
bytes = read(sockfd, buf, sizeof(buf));
}
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:
if(bytes > 0) {
++b;
(void) write(1, &buf[b], bytes - b);
}
for(;;) {
(void) alarm(timeout);
if(protocol == PROTO_HTTPS) {
bytes = SSL_read(ssl, buf, sizeof(buf));
} else {
bytes = read(sockfd, buf, sizeof(buf));
}
if(bytes == 0) {
break;
}
if(bytes < 0) {
show_error("read");
}
(void) write(1, buf, bytes);
}
if(protocol == PROTO_HTTPS) {
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
}
(void) close(sockfd);
return status;
}
void* malloc_check(size_t size) {
void* ptr = malloc(size);
check(ptr);
return ptr;
}
int open_client_socket(char* hostname, unsigned short port) {
struct addrinfo hints;
char portstr[10];
int gaierr;
struct addrinfo* ai;
struct addrinfo* ai2;
struct addrinfo* aiv4;
struct addrinfo* aiv6;
struct sockaddr_in6 sa;
int sa_len, sock_family, sock_type, sock_protocol;
int sockfd;
(void) memset((void*) &sa, 0, sizeof(sa));
(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);
}
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(aiv4 != (struct addrinfo*) 0) {
if(sizeof(sa) < aiv4->ai_addrlen) {
(void) fprintf(stderr, "%s - sockaddr too small (%lu < %lu)\n", hostname, (unsigned long) sizeof(sa), (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, aiv4->ai_addr, sa_len);
goto ok;
}
if(aiv6 != (struct addrinfo*) 0) {
if(sizeof(sa) < aiv6->ai_addrlen) {
(void) fprintf(stderr, "%s - sockaddr too small (%lu < %lu)\n", hostname, (unsigned long) sizeof(sa), (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, 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);
sockfd = socket(sock_family, sock_type, sock_protocol);
if(sockfd < 0) {
show_error("socket");
}
if(connect(sockfd, (struct sockaddr*) &sa, sa_len) < 0) {
show_error("connect");
}
return sockfd;
}
void postURL(char* url, char* referer, char* user_agent, char* auth_token, int ncookies, char** cookies, char* header_name, char* header_value, char** args, int argc) {
char* s;
int protocol;
char host[2000];
int host_len;
unsigned short port;
char* file = 0;
char* http = "http://";
int http_len = strlen(http);
char* https = "https://";
int https_len = strlen(https);
int proto_len;
if(url == (char*) 0) {
(void) fprintf(stderr, "%s: null URL\n", argv0);
exit(1);
}
if(strncmp(http, url, http_len) == 0) {
proto_len = http_len;
protocol = PROTO_HTTP;
} else if(strncmp(https, url, https_len) == 0) {
proto_len = https_len;
protocol = PROTO_HTTPS;
} else {
(void) fprintf(stderr, "%s: non-http URL\n", argv0);
exit(1);
}
for(s = url + proto_len; *s != '\0' && *s != ':' && *s != '/'; ++s);
host_len = s - url;
host_len -= proto_len;
strncpy(host, url + proto_len, host_len);
host[host_len] = '\0';
if(*s == ':') {
port = (unsigned short) atoi(++s);
while(*s != '\0' && *s != '/') {
++s;
}
} else {
if(protocol == PROTO_HTTPS) {
port = 443;
} else {
port = 80;
}
}
if(*s == '\0') {
file = "/";
} else {
file = s;
}
postURLbyParts(protocol, host, port, file, referer, user_agent, auth_token, ncookies, cookies, header_name, header_value, args, argc);
}
void postURLbyParts(int protocol, char* host, unsigned short port, char* file, char* referer, char* user_agent, char* auth_token, int ncookies, char** cookies, char* header_name, char* header_value, char** args, int argc) {
int sockfd;
SSL_CTX* ssl_ctx = (SSL_CTX*) 0;
SSL* ssl = (SSL*) 0;
char head_buf[20000];
int max_arg, total_bytes;
int multipart, next_arg_is_file;
static const char* const sep = "http_post-content-separator";
char* data_buf;
char* enc_buf;
int head_bytes, data_bytes, i, header_state;
char* eq;
(void) alarm(timeout);
sockfd = open_client_socket(host, port);
if(protocol == PROTO_HTTPS) {
int r;
ssl_ctx = SSL_CTX_new(TLSv1_client_method());
SSL_CTX_set_verify(ssl_ctx, SSL_VERIFY_NONE, 0);
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);
exit(1);
}
}
multipart = 0;
total_bytes = max_arg = 0;
next_arg_is_file = 0;
for(i = 0; i < argc ; ++i) {
int l = strlen(args[i]);
if(strcmp(args[i], "-f") == 0) {
multipart = 1;
next_arg_is_file = 1;
continue;
}
total_bytes += l;
if(l > max_arg) {
max_arg = l;
}
if(next_arg_is_file) {
eq = strchr(args[i], '=');
if(eq == (char*) 0) {
(void) fprintf(stderr, "%s: missing filename\n", argv0);
exit(1);
} else {
++eq;
total_bytes += file_bytes(eq);
}
next_arg_is_file = 0;
}
}
if(multipart) {
for(i = 0; i < argc ; ++i) {
total_bytes += strlen(sep) * 2 + 100;
}
total_bytes += strlen(sep) * 2;
} else {
total_bytes *= 4;
enc_buf = (char*) malloc_check(max_arg * 4);
}
data_buf = (char*) malloc_check(total_bytes);
if(multipart) {
next_arg_is_file = 0;
data_bytes = 0;
for(i = 0; i < argc; ++i) {
if(strcmp(args[i], "-f") == 0) {
next_arg_is_file = 1;
continue;
}
eq = strchr(args[i], '=');
if(eq == (char*) 0) {
data_bytes += sprintf(&data_buf[data_bytes], "--%s\r\nContent-Disposition: form-data\r\n\r\n%s\r\n", sep, args[i]);
} else {
*eq++ = '\0';
if(next_arg_is_file) {
data_bytes += sprintf(&data_buf[data_bytes], "--%s\r\nContent-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n\r\n", sep, args[i], eq);
data_bytes += file_copy(eq, &data_buf[data_bytes]);
data_bytes += sprintf(&data_buf[data_bytes], "\r\n");
next_arg_is_file = 0;
} else {
data_bytes += sprintf(&data_buf[data_bytes], "--%s\r\nContent-Disposition: form-data; name=\"%s\"\r\n\r\n%s\r\n", sep, args[i], eq);
}
}
}
data_bytes += sprintf(&data_buf[data_bytes], "--%s--\r\n", sep);
} else {
data_bytes = 0;
for(i = 0; i < argc ; ++i) {
if(data_bytes > 0) {
data_bytes += sprintf(&data_buf[data_bytes], "&");
}
eq = strchr(args[i], '=');
if(eq == (char*) 0) {
strencode(enc_buf, args[i]);
data_bytes += sprintf(&data_buf[data_bytes], "%s", enc_buf);
} else {
*eq++ = '\0';
strencode(enc_buf, args[i]);
data_bytes += sprintf(&data_buf[data_bytes], "%s=", enc_buf);
strencode(enc_buf, eq);
data_bytes += sprintf(&data_buf[data_bytes], "%s", enc_buf);
}
}
}
(void) alarm(timeout);
head_bytes = snprintf(head_buf, sizeof(head_buf), "POST %s HTTP/1.0\r\n", file);
head_bytes += snprintf(&head_buf[head_bytes], sizeof(head_buf) - head_bytes, "Host: %s\r\n", host);
head_bytes += snprintf(&head_buf[head_bytes], sizeof(head_buf) - head_bytes, "Content-Length: %d\r\n", data_bytes);
if(referer != (char*) 0) {
head_bytes += snprintf(&head_buf[head_bytes], sizeof(head_buf) - head_bytes, "Referer: %s\r\n", referer);
}
head_bytes += snprintf(&head_buf[head_bytes], sizeof(head_buf) - head_bytes, "User-Agent: %s\r\n", user_agent);
if(multipart) {
head_bytes += snprintf(&head_buf[head_bytes], sizeof(head_buf) - head_bytes, "Content-type: multipart/form-data; boundary=\"%s\"\r\n", sep);
} else {
head_bytes += snprintf(&head_buf[head_bytes], sizeof(head_buf) - head_bytes, "Content-type: application/x-www-form-urlencoded\r\n");
}
head_bytes += snprintf(&head_buf[head_bytes], sizeof(head_buf) - head_bytes, "Accept: */*\r\n");
head_bytes += snprintf(&head_buf[head_bytes], sizeof(head_buf) - head_bytes, "Accept-Language: en\r\n");
head_bytes += snprintf(&head_buf[head_bytes], sizeof(head_buf) - head_bytes, "Accept-Charset: iso-8859-1,*,utf-8\r\n");
if(auth_token != (char*) 0) {
char token_buf[1000];
token_buf[b64_encode((unsigned char*) auth_token, strlen(auth_token), token_buf, sizeof(token_buf))] = '\0';
head_bytes += snprintf(&head_buf[head_bytes], sizeof(head_buf) - head_bytes, "Authorization: Basic %s\r\n", token_buf);
}
for(i = 0; i < ncookies; ++i) {
head_bytes += snprintf(&head_buf[head_bytes], sizeof(head_buf) - head_bytes, "Cookie: %s\r\n", cookies[i]);
}
if(header_name != (char*) 0) {
head_bytes += snprintf(&head_buf[head_bytes], sizeof(head_buf) - head_bytes, "%s: %s\r\n", header_name, header_value);
}
head_bytes += snprintf(&head_buf[head_bytes], sizeof(head_buf) - head_bytes, "\r\n");
if(protocol == PROTO_HTTPS) {
(void) SSL_write(ssl, head_buf, head_bytes);
} else {
(void) write(sockfd, head_buf, head_bytes);
}
if(protocol == PROTO_HTTPS) {
(void) SSL_write(ssl, data_buf, data_bytes);
} else {
(void) write(sockfd, data_buf, data_bytes);
}
(void) alarm(timeout);
header_state = HDST_BOL;
for(;;) {
if(protocol == PROTO_HTTPS) {
head_bytes = SSL_read(ssl, head_buf, sizeof(head_buf));
} else {
head_bytes = read(sockfd, head_buf, sizeof(head_buf));
}
if(head_bytes <= 0) {
break;
}
for(i = 0; i < head_bytes; ++i) {
if(verbose) {
(void) write(1, &head_buf[i], 1);
}
switch(header_state) {
case HDST_BOL:
switch(head_buf[i]) {
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(head_buf[i]) {
case '\n':
header_state = HDST_LF;
break;
case '\r':
header_state = HDST_CR;
break;
}
break;
case HDST_LF:
switch(head_buf[i]) {
case '\n':
goto end_of_headers;
case '\r':
header_state = HDST_CR;
break;
default:
header_state = HDST_TEXT;
break;
}
break;
case HDST_CR:
switch(head_buf[i]) {
case '\n':
header_state = HDST_CRLF;
break;
case '\r':
goto end_of_headers;
default:
header_state = HDST_TEXT;
break;
}
break;
case HDST_CRLF:
switch(head_buf[i]) {
case '\n':
goto end_of_headers;
case '\r':
header_state = HDST_CRLFCR;
break;
default:
header_state = HDST_TEXT;
break;
}
break;
case HDST_CRLFCR:
switch(head_buf[i]) {
case '\n':
case '\r':
goto end_of_headers;
default:
header_state = HDST_TEXT;
break;
}
break;
}
}
}
end_of_headers:
if(head_bytes > 0) {
++i;
(void) write(1, &head_buf[i], head_bytes - i);
}
for(;;) {
(void) alarm(timeout);
if(protocol == PROTO_HTTPS) {
head_bytes = SSL_read(ssl, head_buf, sizeof(head_buf));
} else {
head_bytes = read(sockfd, head_buf, sizeof(head_buf));
}
if(head_bytes == 0) {
break;
}
if(head_bytes < 0) {
show_error("read");
}
(void) write(1, head_buf, head_bytes);
}
if(protocol == PROTO_HTTPS) {
SSL_free(ssl);
SSL_CTX_free(ssl_ctx);
}
(void) close(sockfd);
}
void show_error(char* cause) {
char buf[5000];
(void) sprintf(buf, "%s: %s - %s", argv0, url, cause);
perror(buf);
exit(1);
}
void sigcatch(int sig) {
(void) fprintf(stderr, "%s: %s - timed out\n", argv0, url);
exit(1);
}
void strencode(char* to, char* from) {
int tolen;
for(tolen = 0; *from != '\0'; ++from) {
if(isalnum(*from) || strchr("/_.", *from) != (char*) 0) {
*to = *from;
++to;
++tolen;
} else {
(void) sprintf(to, "%%%02x", (int) *from & 0xff);
to += 3;
tolen += 3;
}
}
*to = '\0';
}
void usage() {
(void) fprintf(stderr, "usage: %s [-c cookie] [-t timeout] [-r referer] [-u user-agent] [-a username:password] [-h header value] [-v] url\n", argv0);
exit(1);
}