#include <sys/types.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <netdb.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
static const char * env_HTTP_PROXY;
static char * env_HTTP_PROXY_AUTH;
static const char * env_HTTP_USER_AGENT;
static char * env_HTTP_TIMEOUT;
static const char * proxyport;
static char * proxyauth;
static struct timeval timo = { 15, 0};
static void
usage(void)
{
fprintf(stderr, "usage: phttpget server [file ...]\n");
exit(EX_USAGE);
}
static char *
b64enc(const char *ptext)
{
static const char base64[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
const char *pt;
char *ctext, *pc;
size_t ptlen, ctlen;
uint32_t t;
unsigned int j;
ptlen = strlen(ptext);
if (ptlen > ((SIZE_MAX - 1) / 4) * 3 - 2)
return NULL;
ctlen = 4 * ((ptlen + 2) / 3) + 1;
if ((ctext = malloc(ctlen)) == NULL)
return NULL;
ctext[ctlen - 1] = 0;
for (pt = ptext, pc = ctext; ptlen; ptlen -= 3, pc += 4) {
for (t = j = 0; j < 3; j++) {
t <<= 8;
if (j < ptlen)
t += *pt++;
}
for (j = 0; j < 4; j++) {
if (j <= ptlen + 1)
pc[j] = base64[(t >> 18) & 0x3f];
else
pc[j] = '=';
t <<= 6;
}
if (ptlen <= 3)
break;
}
return (ctext);
}
static void
readenv(void)
{
char *proxy_auth_userpass, *proxy_auth_userpass64, *p;
char *proxy_auth_user = NULL;
char *proxy_auth_pass = NULL;
long http_timeout;
env_HTTP_PROXY = getenv("HTTP_PROXY");
if (env_HTTP_PROXY == NULL)
env_HTTP_PROXY = getenv("http_proxy");
if (env_HTTP_PROXY != NULL) {
if (strncmp(env_HTTP_PROXY, "http://", 7) == 0)
env_HTTP_PROXY += 7;
p = strchr(env_HTTP_PROXY, '/');
if (p != NULL)
*p = 0;
p = strchr(env_HTTP_PROXY, ':');
if (p != NULL) {
*p = 0;
proxyport = p + 1;
} else
proxyport = "3128";
}
env_HTTP_PROXY_AUTH = getenv("HTTP_PROXY_AUTH");
if ((env_HTTP_PROXY != NULL) &&
(env_HTTP_PROXY_AUTH != NULL) &&
(strncasecmp(env_HTTP_PROXY_AUTH, "basic:" , 6) == 0)) {
(void) strsep(&env_HTTP_PROXY_AUTH, ":");
(void) strsep(&env_HTTP_PROXY_AUTH, ":");
proxy_auth_user = strsep(&env_HTTP_PROXY_AUTH, ":");
proxy_auth_pass = env_HTTP_PROXY_AUTH;
}
if ((proxy_auth_user != NULL) && (proxy_auth_pass != NULL)) {
asprintf(&proxy_auth_userpass, "%s:%s",
proxy_auth_user, proxy_auth_pass);
if (proxy_auth_userpass == NULL)
err(1, "asprintf");
proxy_auth_userpass64 = b64enc(proxy_auth_userpass);
if (proxy_auth_userpass64 == NULL)
err(1, "malloc");
asprintf(&proxyauth, "Proxy-Authorization: Basic %s\r\n",
proxy_auth_userpass64);
if (proxyauth == NULL)
err(1, "asprintf");
free(proxy_auth_userpass);
free(proxy_auth_userpass64);
} else
proxyauth = NULL;
env_HTTP_USER_AGENT = getenv("HTTP_USER_AGENT");
if (env_HTTP_USER_AGENT == NULL)
env_HTTP_USER_AGENT = "phttpget/0.1";
env_HTTP_TIMEOUT = getenv("HTTP_TIMEOUT");
if (env_HTTP_TIMEOUT != NULL) {
http_timeout = strtol(env_HTTP_TIMEOUT, &p, 10);
if ((*env_HTTP_TIMEOUT == '\0') || (*p != '\0') ||
(http_timeout < 0))
warnx("HTTP_TIMEOUT (%s) is not a positive integer",
env_HTTP_TIMEOUT);
else
timo.tv_sec = http_timeout;
}
}
static int
makerequest(char ** buf, char * path, char * server, int connclose)
{
int buflen;
buflen = asprintf(buf,
"GET %s%s/%s HTTP/1.1\r\n"
"Host: %s\r\n"
"User-Agent: %s\r\n"
"%s"
"%s"
"\r\n",
env_HTTP_PROXY ? "http://" : "",
env_HTTP_PROXY ? server : "",
path, server, env_HTTP_USER_AGENT,
proxyauth ? proxyauth : "",
connclose ? "Connection: Close\r\n" : "Connection: Keep-Alive\r\n");
if (buflen == -1)
err(1, "asprintf");
return(buflen);
}
static int
readln(int sd, char * resbuf, int * resbuflen, int * resbufpos)
{
ssize_t len;
while (strnstr(resbuf + *resbufpos, "\r\n",
*resbuflen - *resbufpos) == NULL) {
if (*resbufpos != 0) {
memmove(resbuf, resbuf + *resbufpos,
*resbuflen - *resbufpos);
*resbuflen -= *resbufpos;
*resbufpos = 0;
}
if (*resbuflen == BUFSIZ)
return -1;
len = recv(sd, resbuf + *resbuflen, BUFSIZ - *resbuflen, 0);
if ((len == 0) ||
((len == -1) && (errno != EINTR)))
return -1;
if (len != -1)
*resbuflen += len;
}
return 0;
}
static int
copybytes(int sd, int fd, off_t copylen, char * resbuf, int * resbuflen,
int * resbufpos)
{
ssize_t len;
while (copylen) {
len = *resbuflen - *resbufpos;
if (copylen < len)
len = copylen;
if (len > 0) {
if (fd != -1)
len = write(fd, resbuf + *resbufpos, len);
if (len == -1)
err(1, "write");
*resbufpos += len;
copylen -= len;
continue;
}
len = recv(sd, resbuf, BUFSIZ, 0);
if (len == -1) {
if (errno == EINTR)
continue;
return -1;
} else if (len == 0) {
return -2;
} else {
*resbuflen = len;
*resbufpos = 0;
}
}
return 0;
}
int
main(int argc, char *argv[])
{
struct addrinfo hints;
struct addrinfo *res;
struct addrinfo *res0;
char * resbuf = NULL;
int resbufpos = 0;
int resbuflen = 0;
char * eolp;
char * hln;
char * servername;
char * fname = NULL;
char * reqbuf = NULL;
int reqbufpos = 0;
int reqbuflen = 0;
ssize_t len;
int nreq = 0;
int nres = 0;
int pipelined = 0;
int keepalive;
int sd = -1;
int sdflags = 0;
int fd = -1;
int error;
int statuscode;
off_t contentlength;
int chunked;
off_t clen;
int firstreq = 0;
int val;
if (argc < 2)
usage();
readenv();
servername = argv[1];
argv += 2;
argc -= 2;
resbuf = malloc(BUFSIZ);
if (resbuf == NULL)
err(1, "malloc");
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
error = getaddrinfo(env_HTTP_PROXY ? env_HTTP_PROXY : servername,
env_HTTP_PROXY ? proxyport : "http", &hints, &res0);
if (error)
errx(1, "host = %s, port = %s: %s",
env_HTTP_PROXY ? env_HTTP_PROXY : servername,
env_HTTP_PROXY ? proxyport : "http",
gai_strerror(error));
if (res0 == NULL)
errx(1, "could not look up %s", servername);
res = res0;
while (nres < argc) {
for (; sd == -1; res = res->ai_next) {
if (res == NULL)
errx(1, "Could not connect to %s", servername);
sd = socket(res->ai_family, res->ai_socktype,
res->ai_protocol);
if (sd == -1)
continue;
setsockopt(sd, SOL_SOCKET, SO_SNDTIMEO,
(void *)&timo, (socklen_t)sizeof(timo));
setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO,
(void *)&timo, (socklen_t)sizeof(timo));
val = 1;
setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE,
(void *)&val, sizeof(int));
if(connect(sd, res->ai_addr, res->ai_addrlen)) {
close(sd);
sd = -1;
continue;
}
firstreq = nres;
}
if (pipelined) {
sdflags = fcntl(sd, F_GETFL);
if (fcntl(sd, F_SETFL, sdflags | O_NONBLOCK) == -1)
err(1, "fcntl");
}
while ((nreq < argc) && ((reqbuf == NULL) || pipelined)) {
if (reqbuf == NULL) {
reqbuflen = makerequest(&reqbuf, argv[nreq],
servername, (nreq == argc - 1));
reqbufpos = 0;
}
if (pipelined) {
while (reqbufpos < reqbuflen) {
len = send(sd, reqbuf + reqbufpos,
reqbuflen - reqbufpos, 0);
if (len == -1)
break;
reqbufpos += len;
}
if (reqbufpos < reqbuflen) {
if (errno != EAGAIN)
goto conndied;
break;
} else {
free(reqbuf);
reqbuf = NULL;
nreq++;
}
}
}
if (pipelined) {
if (fcntl(sd, F_SETFL, sdflags) == -1)
err(1, "fcntl");
}
if (nres == nreq) {
while (reqbufpos < reqbuflen) {
len = send(sd, reqbuf + reqbufpos,
reqbuflen - reqbufpos, 0);
if (len == -1)
goto conndied;
reqbufpos += len;
}
free(reqbuf);
reqbuf = NULL;
nreq++;
}
statuscode = 0;
contentlength = -1;
chunked = 0;
keepalive = 0;
do {
error = readln(sd, resbuf, &resbuflen, &resbufpos);
if (error)
goto conndied;
hln = resbuf + resbufpos;
eolp = strnstr(hln, "\r\n", resbuflen - resbufpos);
resbufpos = (eolp - resbuf) + 2;
*eolp = '\0';
if (strchr(hln, '\0') != eolp)
goto conndied;
if (statuscode == 0) {
if ((strncmp(hln, "HTTP/1.", 7) != 0) ||
! isdigit(hln[7]))
goto conndied;
if (hln[7] != '0')
pipelined = 1;
hln = strchr(hln + 7, ' ');
if (hln == NULL)
goto conndied;
else
hln++;
while (isdigit(*hln)) {
statuscode = statuscode * 10 +
*hln - '0';
hln++;
}
if (statuscode < 100 || statuscode > 599)
goto conndied;
continue;
}
if (strncasecmp(hln, "Connection:", 11) == 0) {
hln += 11;
if (strcasestr(hln, "close") != NULL)
pipelined = 0;
if (strcasestr(hln, "Keep-Alive") != NULL)
keepalive = 1;
continue;
}
if (strncasecmp(hln, "Content-Length:", 15) == 0) {
hln += 15;
contentlength = 0;
while (!isdigit(*hln) && (*hln != '\0'))
hln++;
while (isdigit(*hln)) {
if (contentlength >= OFF_MAX / 10) {
goto conndied;
}
contentlength = contentlength * 10 +
*hln - '0';
hln++;
}
continue;
}
if (strncasecmp(hln, "Transfer-Encoding:", 18) == 0) {
hln += 18;
if (strcasestr(hln, "chunked") != NULL)
chunked = 1;
continue;
}
if (strlen(hln) == 0) {
if (100 <= statuscode && statuscode <= 199) {
statuscode = 0;
continue;
}
break;
}
} while (1);
if (statuscode == 204 || statuscode == 304) {
nres++;
continue;
}
if (statuscode == 200) {
fname = strrchr(argv[nres], '/');
if (fname == NULL)
fname = argv[nres];
else
fname++;
if (strlen(fname) == 0)
errx(1, "Cannot obtain file name from %s\n",
argv[nres]);
fd = open(fname, O_CREAT | O_TRUNC | O_WRONLY, 0644);
if (fd == -1)
errx(1, "open(%s)", fname);
}
if (chunked) {
do {
error = readln(sd, resbuf, &resbuflen,
&resbufpos);
if (error)
goto conndied;
hln = resbuf + resbufpos;
eolp = strstr(hln, "\r\n");
resbufpos = (eolp - resbuf) + 2;
clen = 0;
while (isxdigit(*hln)) {
if (clen >= OFF_MAX / 16) {
goto conndied;
}
if (isdigit(*hln))
clen = clen * 16 + *hln - '0';
else
clen = clen * 16 + 10 +
tolower(*hln) - 'a';
hln++;
}
error = copybytes(sd, fd, clen, resbuf,
&resbuflen, &resbufpos);
if (error) {
goto conndied;
}
} while (clen != 0);
do {
error = readln(sd, resbuf, &resbuflen,
&resbufpos);
if (error)
goto conndied;
hln = resbuf + resbufpos;
eolp = strstr(hln, "\r\n");
resbufpos = (eolp - resbuf) + 2;
} while (hln != eolp);
} else if (contentlength != -1) {
error = copybytes(sd, fd, contentlength, resbuf,
&resbuflen, &resbufpos);
if (error)
goto conndied;
} else {
error = copybytes(sd, fd, OFF_MAX, resbuf,
&resbuflen, &resbufpos);
if (error == -1)
goto conndied;
pipelined = 0;
}
if (fd != -1) {
close(fd);
fd = -1;
}
fprintf(stderr, "http://%s/%s: %d ", servername, argv[nres],
statuscode);
if (statuscode == 200)
fprintf(stderr, "OK\n");
else if (statuscode < 300)
fprintf(stderr, "Successful (ignored)\n");
else if (statuscode < 400)
fprintf(stderr, "Redirection (ignored)\n");
else
fprintf(stderr, "Error (ignored)\n");
nres++;
if (pipelined == 0 && keepalive == 0)
goto cleanupconn;
continue;
conndied:
if (nres == firstreq)
errx(1, "Connection failure");
cleanupconn:
shutdown(sd, SHUT_RDWR);
close(sd);
sd = -1;
if (fd != -1) {
close(fd);
fd = -1;
}
if (reqbuf != NULL) {
free(reqbuf);
reqbuf = NULL;
}
nreq = nres;
res = res0;
pipelined = 0;
resbufpos = resbuflen = 0;
continue;
}
free(resbuf);
freeaddrinfo(res0);
return 0;
}