xref: /freebsd/libexec/phttpget/phttpget.c (revision a6fe717c)
16703731dSKyle Evans /*-
24d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
36703731dSKyle Evans  *
46703731dSKyle Evans  * Copyright 2005 Colin Percival
56703731dSKyle Evans  * All rights reserved
66703731dSKyle Evans  *
76703731dSKyle Evans  * Redistribution and use in source and binary forms, with or without
86703731dSKyle Evans  * modification, are permitted providing that the following conditions
96703731dSKyle Evans  * are met:
106703731dSKyle Evans  * 1. Redistributions of source code must retain the above copyright
116703731dSKyle Evans  *    notice, this list of conditions and the following disclaimer.
126703731dSKyle Evans  * 2. Redistributions in binary form must reproduce the above copyright
136703731dSKyle Evans  *    notice, this list of conditions and the following disclaimer in the
146703731dSKyle Evans  *    documentation and/or other materials provided with the distribution.
156703731dSKyle Evans  *
166703731dSKyle Evans  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
176703731dSKyle Evans  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
186703731dSKyle Evans  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
196703731dSKyle Evans  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
206703731dSKyle Evans  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
216703731dSKyle Evans  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
226703731dSKyle Evans  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
236703731dSKyle Evans  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
246703731dSKyle Evans  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
256703731dSKyle Evans  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
266703731dSKyle Evans  * POSSIBILITY OF SUCH DAMAGE.
276703731dSKyle Evans  */
286703731dSKyle Evans 
296703731dSKyle Evans #include <sys/types.h>
306703731dSKyle Evans #include <sys/time.h>
316703731dSKyle Evans #include <sys/socket.h>
326703731dSKyle Evans 
336703731dSKyle Evans #include <ctype.h>
346703731dSKyle Evans #include <err.h>
356703731dSKyle Evans #include <errno.h>
366703731dSKyle Evans #include <fcntl.h>
376703731dSKyle Evans #include <limits.h>
386703731dSKyle Evans #include <netdb.h>
396703731dSKyle Evans #include <stdint.h>
406703731dSKyle Evans #include <stdio.h>
416703731dSKyle Evans #include <stdlib.h>
426703731dSKyle Evans #include <string.h>
436703731dSKyle Evans #include <sysexits.h>
446703731dSKyle Evans #include <unistd.h>
456703731dSKyle Evans 
466703731dSKyle Evans static const char *	env_HTTP_PROXY;
476703731dSKyle Evans static char *		env_HTTP_PROXY_AUTH;
486703731dSKyle Evans static const char *	env_HTTP_USER_AGENT;
496703731dSKyle Evans static char *		env_HTTP_TIMEOUT;
506703731dSKyle Evans static const char *	proxyport;
516703731dSKyle Evans static char *		proxyauth;
526703731dSKyle Evans 
536703731dSKyle Evans static struct timeval	timo = { 15, 0};
546703731dSKyle Evans 
556703731dSKyle Evans static void
usage(void)566703731dSKyle Evans usage(void)
576703731dSKyle Evans {
586703731dSKyle Evans 
596703731dSKyle Evans 	fprintf(stderr, "usage: phttpget server [file ...]\n");
606703731dSKyle Evans 	exit(EX_USAGE);
616703731dSKyle Evans }
626703731dSKyle Evans 
636703731dSKyle Evans /*
646703731dSKyle Evans  * Base64 encode a string; the string returned, if non-NULL, is
656703731dSKyle Evans  * allocated using malloc() and must be freed by the caller.
666703731dSKyle Evans  */
676703731dSKyle Evans static char *
b64enc(const char * ptext)686703731dSKyle Evans b64enc(const char *ptext)
696703731dSKyle Evans {
706703731dSKyle Evans 	static const char base64[] =
716703731dSKyle Evans 	    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
726703731dSKyle Evans 	    "abcdefghijklmnopqrstuvwxyz"
736703731dSKyle Evans 	    "0123456789+/";
746703731dSKyle Evans 	const char *pt;
756703731dSKyle Evans 	char *ctext, *pc;
766703731dSKyle Evans 	size_t ptlen, ctlen;
776703731dSKyle Evans 	uint32_t t;
786703731dSKyle Evans 	unsigned int j;
796703731dSKyle Evans 
806703731dSKyle Evans 	/*
816703731dSKyle Evans 	 * Encoded length is 4 characters per 3-byte block or partial
826703731dSKyle Evans 	 * block of plaintext, plus one byte for the terminating NUL
836703731dSKyle Evans 	 */
846703731dSKyle Evans 	ptlen = strlen(ptext);
856703731dSKyle Evans 	if (ptlen > ((SIZE_MAX - 1) / 4) * 3 - 2)
866703731dSKyle Evans 		return NULL;	/* Possible integer overflow */
876703731dSKyle Evans 	ctlen = 4 * ((ptlen + 2) / 3) + 1;
886703731dSKyle Evans 	if ((ctext = malloc(ctlen)) == NULL)
896703731dSKyle Evans 		return NULL;
906703731dSKyle Evans 	ctext[ctlen - 1] = 0;
916703731dSKyle Evans 
926703731dSKyle Evans 	/*
936703731dSKyle Evans 	 * Scan through ptext, reading up to 3 bytes from ptext and
946703731dSKyle Evans 	 * writing 4 bytes to ctext, until we run out of input.
956703731dSKyle Evans 	 */
966703731dSKyle Evans 	for (pt = ptext, pc = ctext; ptlen; ptlen -= 3, pc += 4) {
976703731dSKyle Evans 		/* Read 3 bytes */
986703731dSKyle Evans 		for (t = j = 0; j < 3; j++) {
996703731dSKyle Evans 			t <<= 8;
1006703731dSKyle Evans 			if (j < ptlen)
1016703731dSKyle Evans 				t += *pt++;
1026703731dSKyle Evans 		}
1036703731dSKyle Evans 
1046703731dSKyle Evans 		/* Write 4 bytes */
1056703731dSKyle Evans 		for (j = 0; j < 4; j++) {
1066703731dSKyle Evans 			if (j <= ptlen + 1)
1076703731dSKyle Evans 				pc[j] = base64[(t >> 18) & 0x3f];
1086703731dSKyle Evans 			else
1096703731dSKyle Evans 				pc[j] = '=';
1106703731dSKyle Evans 			t <<= 6;
1116703731dSKyle Evans 		}
1126703731dSKyle Evans 
1136703731dSKyle Evans 		/* If we're done, exit the loop */
1146703731dSKyle Evans 		if (ptlen <= 3)
1156703731dSKyle Evans 			break;
1166703731dSKyle Evans 	}
1176703731dSKyle Evans 
1186703731dSKyle Evans 	return (ctext);
1196703731dSKyle Evans }
1206703731dSKyle Evans 
1216703731dSKyle Evans static void
readenv(void)1226703731dSKyle Evans readenv(void)
1236703731dSKyle Evans {
1246703731dSKyle Evans 	char *proxy_auth_userpass, *proxy_auth_userpass64, *p;
1256703731dSKyle Evans 	char *proxy_auth_user = NULL;
1266703731dSKyle Evans 	char *proxy_auth_pass = NULL;
1276703731dSKyle Evans 	long http_timeout;
1286703731dSKyle Evans 
1296703731dSKyle Evans 	env_HTTP_PROXY = getenv("HTTP_PROXY");
1306703731dSKyle Evans 	if (env_HTTP_PROXY == NULL)
1316703731dSKyle Evans 		env_HTTP_PROXY = getenv("http_proxy");
1326703731dSKyle Evans 	if (env_HTTP_PROXY != NULL) {
1336703731dSKyle Evans 		if (strncmp(env_HTTP_PROXY, "http://", 7) == 0)
1346703731dSKyle Evans 			env_HTTP_PROXY += 7;
1356703731dSKyle Evans 		p = strchr(env_HTTP_PROXY, '/');
1366703731dSKyle Evans 		if (p != NULL)
1376703731dSKyle Evans 			*p = 0;
1386703731dSKyle Evans 		p = strchr(env_HTTP_PROXY, ':');
1396703731dSKyle Evans 		if (p != NULL) {
1406703731dSKyle Evans 			*p = 0;
1416703731dSKyle Evans 			proxyport = p + 1;
1426703731dSKyle Evans 		} else
1436703731dSKyle Evans 			proxyport = "3128";
1446703731dSKyle Evans 	}
1456703731dSKyle Evans 
1466703731dSKyle Evans 	env_HTTP_PROXY_AUTH = getenv("HTTP_PROXY_AUTH");
1476703731dSKyle Evans 	if ((env_HTTP_PROXY != NULL) &&
1486703731dSKyle Evans 	    (env_HTTP_PROXY_AUTH != NULL) &&
1496703731dSKyle Evans 	    (strncasecmp(env_HTTP_PROXY_AUTH, "basic:" , 6) == 0)) {
1506703731dSKyle Evans 		/* Ignore authentication scheme */
1516703731dSKyle Evans 		(void) strsep(&env_HTTP_PROXY_AUTH, ":");
1526703731dSKyle Evans 
1536703731dSKyle Evans 		/* Ignore realm */
1546703731dSKyle Evans 		(void) strsep(&env_HTTP_PROXY_AUTH, ":");
1556703731dSKyle Evans 
1566703731dSKyle Evans 		/* Obtain username and password */
1576703731dSKyle Evans 		proxy_auth_user = strsep(&env_HTTP_PROXY_AUTH, ":");
1586703731dSKyle Evans 		proxy_auth_pass = env_HTTP_PROXY_AUTH;
1596703731dSKyle Evans 	}
1606703731dSKyle Evans 
1616703731dSKyle Evans 	if ((proxy_auth_user != NULL) && (proxy_auth_pass != NULL)) {
1626703731dSKyle Evans 		asprintf(&proxy_auth_userpass, "%s:%s",
1636703731dSKyle Evans 		    proxy_auth_user, proxy_auth_pass);
1646703731dSKyle Evans 		if (proxy_auth_userpass == NULL)
1656703731dSKyle Evans 			err(1, "asprintf");
1666703731dSKyle Evans 
1676703731dSKyle Evans 		proxy_auth_userpass64 = b64enc(proxy_auth_userpass);
1686703731dSKyle Evans 		if (proxy_auth_userpass64 == NULL)
1696703731dSKyle Evans 			err(1, "malloc");
1706703731dSKyle Evans 
1716703731dSKyle Evans 		asprintf(&proxyauth, "Proxy-Authorization: Basic %s\r\n",
1726703731dSKyle Evans 		    proxy_auth_userpass64);
1736703731dSKyle Evans 		if (proxyauth == NULL)
1746703731dSKyle Evans 			err(1, "asprintf");
1756703731dSKyle Evans 
1766703731dSKyle Evans 		free(proxy_auth_userpass);
1776703731dSKyle Evans 		free(proxy_auth_userpass64);
1786703731dSKyle Evans 	} else
1796703731dSKyle Evans 		proxyauth = NULL;
1806703731dSKyle Evans 
1816703731dSKyle Evans 	env_HTTP_USER_AGENT = getenv("HTTP_USER_AGENT");
1826703731dSKyle Evans 	if (env_HTTP_USER_AGENT == NULL)
1836703731dSKyle Evans 		env_HTTP_USER_AGENT = "phttpget/0.1";
1846703731dSKyle Evans 
1856703731dSKyle Evans 	env_HTTP_TIMEOUT = getenv("HTTP_TIMEOUT");
1866703731dSKyle Evans 	if (env_HTTP_TIMEOUT != NULL) {
1876703731dSKyle Evans 		http_timeout = strtol(env_HTTP_TIMEOUT, &p, 10);
1886703731dSKyle Evans 		if ((*env_HTTP_TIMEOUT == '\0') || (*p != '\0') ||
1896703731dSKyle Evans 		    (http_timeout < 0))
1906703731dSKyle Evans 			warnx("HTTP_TIMEOUT (%s) is not a positive integer",
1916703731dSKyle Evans 			    env_HTTP_TIMEOUT);
1926703731dSKyle Evans 		else
1936703731dSKyle Evans 			timo.tv_sec = http_timeout;
1946703731dSKyle Evans 	}
1956703731dSKyle Evans }
1966703731dSKyle Evans 
1976703731dSKyle Evans static int
makerequest(char ** buf,char * path,char * server,int connclose)1986703731dSKyle Evans makerequest(char ** buf, char * path, char * server, int connclose)
1996703731dSKyle Evans {
2006703731dSKyle Evans 	int buflen;
2016703731dSKyle Evans 
2026703731dSKyle Evans 	buflen = asprintf(buf,
2036703731dSKyle Evans 	    "GET %s%s/%s HTTP/1.1\r\n"
2046703731dSKyle Evans 	    "Host: %s\r\n"
2056703731dSKyle Evans 	    "User-Agent: %s\r\n"
2066703731dSKyle Evans 	    "%s"
2076703731dSKyle Evans 	    "%s"
2086703731dSKyle Evans 	    "\r\n",
2096703731dSKyle Evans 	    env_HTTP_PROXY ? "http://" : "",
2106703731dSKyle Evans 	    env_HTTP_PROXY ? server : "",
2116703731dSKyle Evans 	    path, server, env_HTTP_USER_AGENT,
2126703731dSKyle Evans 	    proxyauth ? proxyauth : "",
2136703731dSKyle Evans 	    connclose ? "Connection: Close\r\n" : "Connection: Keep-Alive\r\n");
2146703731dSKyle Evans 	if (buflen == -1)
2156703731dSKyle Evans 		err(1, "asprintf");
2166703731dSKyle Evans 	return(buflen);
2176703731dSKyle Evans }
2186703731dSKyle Evans 
2196703731dSKyle Evans static int
readln(int sd,char * resbuf,int * resbuflen,int * resbufpos)2206703731dSKyle Evans readln(int sd, char * resbuf, int * resbuflen, int * resbufpos)
2216703731dSKyle Evans {
2226703731dSKyle Evans 	ssize_t len;
2236703731dSKyle Evans 
2246703731dSKyle Evans 	while (strnstr(resbuf + *resbufpos, "\r\n",
2256703731dSKyle Evans 	    *resbuflen - *resbufpos) == NULL) {
2266703731dSKyle Evans 		/* Move buffered data to the start of the buffer */
2276703731dSKyle Evans 		if (*resbufpos != 0) {
2286703731dSKyle Evans 			memmove(resbuf, resbuf + *resbufpos,
2296703731dSKyle Evans 			    *resbuflen - *resbufpos);
2306703731dSKyle Evans 			*resbuflen -= *resbufpos;
2316703731dSKyle Evans 			*resbufpos = 0;
2326703731dSKyle Evans 		}
2336703731dSKyle Evans 
2346703731dSKyle Evans 		/* If the buffer is full, complain */
2356703731dSKyle Evans 		if (*resbuflen == BUFSIZ)
2366703731dSKyle Evans 			return -1;
2376703731dSKyle Evans 
2386703731dSKyle Evans 		/* Read more data into the buffer */
2396703731dSKyle Evans 		len = recv(sd, resbuf + *resbuflen, BUFSIZ - *resbuflen, 0);
2406703731dSKyle Evans 		if ((len == 0) ||
2416703731dSKyle Evans 		    ((len == -1) && (errno != EINTR)))
2426703731dSKyle Evans 			return -1;
2436703731dSKyle Evans 
2446703731dSKyle Evans 		if (len != -1)
2456703731dSKyle Evans 			*resbuflen += len;
2466703731dSKyle Evans 	}
2476703731dSKyle Evans 
2486703731dSKyle Evans 	return 0;
2496703731dSKyle Evans }
2506703731dSKyle Evans 
2516703731dSKyle Evans static int
copybytes(int sd,int fd,off_t copylen,char * resbuf,int * resbuflen,int * resbufpos)2526703731dSKyle Evans copybytes(int sd, int fd, off_t copylen, char * resbuf, int * resbuflen,
2536703731dSKyle Evans     int * resbufpos)
2546703731dSKyle Evans {
2556703731dSKyle Evans 	ssize_t len;
2566703731dSKyle Evans 
2576703731dSKyle Evans 	while (copylen) {
2586703731dSKyle Evans 		/* Write data from resbuf to fd */
2596703731dSKyle Evans 		len = *resbuflen - *resbufpos;
2606703731dSKyle Evans 		if (copylen < len)
2616703731dSKyle Evans 			len = copylen;
2626703731dSKyle Evans 		if (len > 0) {
2636703731dSKyle Evans 			if (fd != -1)
2646703731dSKyle Evans 				len = write(fd, resbuf + *resbufpos, len);
2656703731dSKyle Evans 			if (len == -1)
2666703731dSKyle Evans 				err(1, "write");
2676703731dSKyle Evans 			*resbufpos += len;
2686703731dSKyle Evans 			copylen -= len;
2696703731dSKyle Evans 			continue;
2706703731dSKyle Evans 		}
2716703731dSKyle Evans 
2726703731dSKyle Evans 		/* Read more data into buffer */
2736703731dSKyle Evans 		len = recv(sd, resbuf, BUFSIZ, 0);
2746703731dSKyle Evans 		if (len == -1) {
2756703731dSKyle Evans 			if (errno == EINTR)
2766703731dSKyle Evans 				continue;
2776703731dSKyle Evans 			return -1;
2786703731dSKyle Evans 		} else if (len == 0) {
2796703731dSKyle Evans 			return -2;
2806703731dSKyle Evans 		} else {
2816703731dSKyle Evans 			*resbuflen = len;
2826703731dSKyle Evans 			*resbufpos = 0;
2836703731dSKyle Evans 		}
2846703731dSKyle Evans 	}
2856703731dSKyle Evans 
2866703731dSKyle Evans 	return 0;
2876703731dSKyle Evans }
2886703731dSKyle Evans 
2896703731dSKyle Evans int
main(int argc,char * argv[])2906703731dSKyle Evans main(int argc, char *argv[])
2916703731dSKyle Evans {
2926703731dSKyle Evans 	struct addrinfo hints;	/* Hints to getaddrinfo */
2936703731dSKyle Evans 	struct addrinfo *res;	/* Pointer to server address being used */
2946703731dSKyle Evans 	struct addrinfo *res0;	/* Pointer to server addresses */
2956703731dSKyle Evans 	char * resbuf = NULL;	/* Response buffer */
2966703731dSKyle Evans 	int resbufpos = 0;	/* Response buffer position */
2976703731dSKyle Evans 	int resbuflen = 0;	/* Response buffer length */
2986703731dSKyle Evans 	char * eolp;		/* Pointer to "\r\n" within resbuf */
2996703731dSKyle Evans 	char * hln;		/* Pointer within header line */
3006703731dSKyle Evans 	char * servername;	/* Name of server */
3016703731dSKyle Evans 	char * fname = NULL;	/* Name of downloaded file */
3026703731dSKyle Evans 	char * reqbuf = NULL;	/* Request buffer */
3036703731dSKyle Evans 	int reqbufpos = 0;	/* Request buffer position */
3046703731dSKyle Evans 	int reqbuflen = 0;	/* Request buffer length */
3056703731dSKyle Evans 	ssize_t len;		/* Length sent or received */
3066703731dSKyle Evans 	int nreq = 0;		/* Number of next request to send */
3076703731dSKyle Evans 	int nres = 0;		/* Number of next reply to receive */
3086703731dSKyle Evans 	int pipelined = 0;	/* != 0 if connection in pipelined mode. */
3096703731dSKyle Evans 	int keepalive;		/* != 0 if HTTP/1.0 keep-alive rcvd. */
3106703731dSKyle Evans 	int sd = -1;		/* Socket descriptor */
3116703731dSKyle Evans 	int sdflags = 0;	/* Flags on the socket sd */
3126703731dSKyle Evans 	int fd = -1;		/* Descriptor for download target file */
3136703731dSKyle Evans 	int error;		/* Error code */
3146703731dSKyle Evans 	int statuscode;		/* HTTP Status code */
3156703731dSKyle Evans 	off_t contentlength;	/* Value from Content-Length header */
3166703731dSKyle Evans 	int chunked;		/* != if transfer-encoding is chunked */
3176703731dSKyle Evans 	off_t clen;		/* Chunk length */
3186703731dSKyle Evans 	int firstreq = 0;	/* # of first request for this connection */
3196703731dSKyle Evans 	int val;		/* Value used for setsockopt call */
3206703731dSKyle Evans 
3216703731dSKyle Evans 	/* Check that the arguments are sensible */
3226703731dSKyle Evans 	if (argc < 2)
3236703731dSKyle Evans 		usage();
3246703731dSKyle Evans 
3256703731dSKyle Evans 	/* Read important environment variables */
3266703731dSKyle Evans 	readenv();
3276703731dSKyle Evans 
3286703731dSKyle Evans 	/* Get server name and adjust arg[cv] to point at file names */
3296703731dSKyle Evans 	servername = argv[1];
3306703731dSKyle Evans 	argv += 2;
3316703731dSKyle Evans 	argc -= 2;
3326703731dSKyle Evans 
3336703731dSKyle Evans 	/* Allocate response buffer */
3346703731dSKyle Evans 	resbuf = malloc(BUFSIZ);
3356703731dSKyle Evans 	if (resbuf == NULL)
3366703731dSKyle Evans 		err(1, "malloc");
3376703731dSKyle Evans 
3386703731dSKyle Evans 	/* Look up server */
3396703731dSKyle Evans 	memset(&hints, 0, sizeof(hints));
3406703731dSKyle Evans 	hints.ai_family = PF_UNSPEC;
3416703731dSKyle Evans 	hints.ai_socktype = SOCK_STREAM;
3426703731dSKyle Evans 	error = getaddrinfo(env_HTTP_PROXY ? env_HTTP_PROXY : servername,
3436703731dSKyle Evans 	    env_HTTP_PROXY ? proxyport : "http", &hints, &res0);
3446703731dSKyle Evans 	if (error)
3456703731dSKyle Evans 		errx(1, "host = %s, port = %s: %s",
3466703731dSKyle Evans 		    env_HTTP_PROXY ? env_HTTP_PROXY : servername,
3476703731dSKyle Evans 		    env_HTTP_PROXY ? proxyport : "http",
3486703731dSKyle Evans 		    gai_strerror(error));
3496703731dSKyle Evans 	if (res0 == NULL)
3506703731dSKyle Evans 		errx(1, "could not look up %s", servername);
3516703731dSKyle Evans 	res = res0;
3526703731dSKyle Evans 
3536703731dSKyle Evans 	/* Do the fetching */
3546703731dSKyle Evans 	while (nres < argc) {
3556703731dSKyle Evans 		/* Make sure we have a connected socket */
3566703731dSKyle Evans 		for (; sd == -1; res = res->ai_next) {
3576703731dSKyle Evans 			/* No addresses left to try :-( */
3586703731dSKyle Evans 			if (res == NULL)
3596703731dSKyle Evans 				errx(1, "Could not connect to %s", servername);
3606703731dSKyle Evans 
3616703731dSKyle Evans 			/* Create a socket... */
3626703731dSKyle Evans 			sd = socket(res->ai_family, res->ai_socktype,
3636703731dSKyle Evans 			    res->ai_protocol);
3646703731dSKyle Evans 			if (sd == -1)
3656703731dSKyle Evans 				continue;
3666703731dSKyle Evans 
3676703731dSKyle Evans 			/* ... set 15-second timeouts ... */
3686703731dSKyle Evans 			setsockopt(sd, SOL_SOCKET, SO_SNDTIMEO,
3696703731dSKyle Evans 			    (void *)&timo, (socklen_t)sizeof(timo));
3706703731dSKyle Evans 			setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO,
3716703731dSKyle Evans 			    (void *)&timo, (socklen_t)sizeof(timo));
3726703731dSKyle Evans 
3736703731dSKyle Evans 			/* ... disable SIGPIPE generation ... */
3746703731dSKyle Evans 			val = 1;
3756703731dSKyle Evans 			setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE,
3766703731dSKyle Evans 			    (void *)&val, sizeof(int));
3776703731dSKyle Evans 
3786703731dSKyle Evans 			/* ... and connect to the server. */
3796703731dSKyle Evans 			if(connect(sd, res->ai_addr, res->ai_addrlen)) {
3806703731dSKyle Evans 				close(sd);
3816703731dSKyle Evans 				sd = -1;
3826703731dSKyle Evans 				continue;
3836703731dSKyle Evans 			}
3846703731dSKyle Evans 
3856703731dSKyle Evans 			firstreq = nres;
3866703731dSKyle Evans 		}
3876703731dSKyle Evans 
3886703731dSKyle Evans 		/*
3896703731dSKyle Evans 		 * If in pipelined HTTP mode, put socket into non-blocking
3906703731dSKyle Evans 		 * mode, since we're probably going to want to try to send
3916703731dSKyle Evans 		 * several HTTP requests.
3926703731dSKyle Evans 		 */
3936703731dSKyle Evans 		if (pipelined) {
3946703731dSKyle Evans 			sdflags = fcntl(sd, F_GETFL);
3956703731dSKyle Evans 			if (fcntl(sd, F_SETFL, sdflags | O_NONBLOCK) == -1)
3966703731dSKyle Evans 				err(1, "fcntl");
3976703731dSKyle Evans 		}
3986703731dSKyle Evans 
3996703731dSKyle Evans 		/* Construct requests and/or send them without blocking */
4006703731dSKyle Evans 		while ((nreq < argc) && ((reqbuf == NULL) || pipelined)) {
4016703731dSKyle Evans 			/* If not in the middle of a request, make one */
4026703731dSKyle Evans 			if (reqbuf == NULL) {
4036703731dSKyle Evans 				reqbuflen = makerequest(&reqbuf, argv[nreq],
4046703731dSKyle Evans 				    servername, (nreq == argc - 1));
4056703731dSKyle Evans 				reqbufpos = 0;
4066703731dSKyle Evans 			}
4076703731dSKyle Evans 
4086703731dSKyle Evans 			/* If in pipelined mode, try to send the request */
4096703731dSKyle Evans 			if (pipelined) {
4106703731dSKyle Evans 				while (reqbufpos < reqbuflen) {
4116703731dSKyle Evans 					len = send(sd, reqbuf + reqbufpos,
4126703731dSKyle Evans 					    reqbuflen - reqbufpos, 0);
4136703731dSKyle Evans 					if (len == -1)
4146703731dSKyle Evans 						break;
4156703731dSKyle Evans 					reqbufpos += len;
4166703731dSKyle Evans 				}
4176703731dSKyle Evans 				if (reqbufpos < reqbuflen) {
4186703731dSKyle Evans 					if (errno != EAGAIN)
4196703731dSKyle Evans 						goto conndied;
4206703731dSKyle Evans 					break;
4216703731dSKyle Evans 				} else {
4226703731dSKyle Evans 					free(reqbuf);
4236703731dSKyle Evans 					reqbuf = NULL;
4246703731dSKyle Evans 					nreq++;
4256703731dSKyle Evans 				}
4266703731dSKyle Evans 			}
4276703731dSKyle Evans 		}
4286703731dSKyle Evans 
4296703731dSKyle Evans 		/* Put connection back into blocking mode */
4306703731dSKyle Evans 		if (pipelined) {
4316703731dSKyle Evans 			if (fcntl(sd, F_SETFL, sdflags) == -1)
4326703731dSKyle Evans 				err(1, "fcntl");
4336703731dSKyle Evans 		}
4346703731dSKyle Evans 
4356703731dSKyle Evans 		/* Do we need to blocking-send a request? */
4366703731dSKyle Evans 		if (nres == nreq) {
4376703731dSKyle Evans 			while (reqbufpos < reqbuflen) {
4386703731dSKyle Evans 				len = send(sd, reqbuf + reqbufpos,
4396703731dSKyle Evans 				    reqbuflen - reqbufpos, 0);
4406703731dSKyle Evans 				if (len == -1)
4416703731dSKyle Evans 					goto conndied;
4426703731dSKyle Evans 				reqbufpos += len;
4436703731dSKyle Evans 			}
4446703731dSKyle Evans 			free(reqbuf);
4456703731dSKyle Evans 			reqbuf = NULL;
4466703731dSKyle Evans 			nreq++;
4476703731dSKyle Evans 		}
4486703731dSKyle Evans 
4496703731dSKyle Evans 		/* Scan through the response processing headers. */
4506703731dSKyle Evans 		statuscode = 0;
4516703731dSKyle Evans 		contentlength = -1;
4526703731dSKyle Evans 		chunked = 0;
4536703731dSKyle Evans 		keepalive = 0;
4546703731dSKyle Evans 		do {
4556703731dSKyle Evans 			/* Get a header line */
4566703731dSKyle Evans 			error = readln(sd, resbuf, &resbuflen, &resbufpos);
4576703731dSKyle Evans 			if (error)
4586703731dSKyle Evans 				goto conndied;
4596703731dSKyle Evans 			hln = resbuf + resbufpos;
4606703731dSKyle Evans 			eolp = strnstr(hln, "\r\n", resbuflen - resbufpos);
4616703731dSKyle Evans 			resbufpos = (eolp - resbuf) + 2;
4626703731dSKyle Evans 			*eolp = '\0';
4636703731dSKyle Evans 
4646703731dSKyle Evans 			/* Make sure it doesn't contain a NUL character */
4656703731dSKyle Evans 			if (strchr(hln, '\0') != eolp)
4666703731dSKyle Evans 				goto conndied;
4676703731dSKyle Evans 
4686703731dSKyle Evans 			if (statuscode == 0) {
4696703731dSKyle Evans 				/* The first line MUST be HTTP/1.x xxx ... */
4706703731dSKyle Evans 				if ((strncmp(hln, "HTTP/1.", 7) != 0) ||
4716703731dSKyle Evans 				    ! isdigit(hln[7]))
4726703731dSKyle Evans 					goto conndied;
4736703731dSKyle Evans 
4746703731dSKyle Evans 				/*
4756703731dSKyle Evans 				 * If the minor version number isn't zero,
4766703731dSKyle Evans 				 * then we can assume that pipelining our
4776703731dSKyle Evans 				 * requests is OK -- as long as we don't
4786703731dSKyle Evans 				 * see a "Connection: close" line later
4796703731dSKyle Evans 				 * and we either have a Content-Length or
4806703731dSKyle Evans 				 * Transfer-Encoding: chunked header to
4816703731dSKyle Evans 				 * tell us the length.
4826703731dSKyle Evans 				 */
4836703731dSKyle Evans 				if (hln[7] != '0')
4846703731dSKyle Evans 					pipelined = 1;
4856703731dSKyle Evans 
4866703731dSKyle Evans 				/* Skip over the minor version number */
4876703731dSKyle Evans 				hln = strchr(hln + 7, ' ');
4886703731dSKyle Evans 				if (hln == NULL)
4896703731dSKyle Evans 					goto conndied;
4906703731dSKyle Evans 				else
4916703731dSKyle Evans 					hln++;
4926703731dSKyle Evans 
4936703731dSKyle Evans 				/* Read the status code */
4946703731dSKyle Evans 				while (isdigit(*hln)) {
4956703731dSKyle Evans 					statuscode = statuscode * 10 +
4966703731dSKyle Evans 					    *hln - '0';
4976703731dSKyle Evans 					hln++;
4986703731dSKyle Evans 				}
4996703731dSKyle Evans 
5006703731dSKyle Evans 				if (statuscode < 100 || statuscode > 599)
5016703731dSKyle Evans 					goto conndied;
5026703731dSKyle Evans 
5036703731dSKyle Evans 				/* Ignore the rest of the line */
5046703731dSKyle Evans 				continue;
5056703731dSKyle Evans 			}
5066703731dSKyle Evans 
5076703731dSKyle Evans 			/*
5086703731dSKyle Evans 			 * Check for "Connection: close" or
5096703731dSKyle Evans 			 * "Connection: Keep-Alive" header
5106703731dSKyle Evans 			 */
5116703731dSKyle Evans 			if (strncasecmp(hln, "Connection:", 11) == 0) {
5126703731dSKyle Evans 				hln += 11;
5136703731dSKyle Evans 				if (strcasestr(hln, "close") != NULL)
5146703731dSKyle Evans 					pipelined = 0;
5156703731dSKyle Evans 				if (strcasestr(hln, "Keep-Alive") != NULL)
5166703731dSKyle Evans 					keepalive = 1;
5176703731dSKyle Evans 
5186703731dSKyle Evans 				/* Next header... */
5196703731dSKyle Evans 				continue;
5206703731dSKyle Evans 			}
5216703731dSKyle Evans 
5226703731dSKyle Evans 			/* Check for "Content-Length:" header */
5236703731dSKyle Evans 			if (strncasecmp(hln, "Content-Length:", 15) == 0) {
5246703731dSKyle Evans 				hln += 15;
5256703731dSKyle Evans 				contentlength = 0;
5266703731dSKyle Evans 
5276703731dSKyle Evans 				/* Find the start of the length */
5286703731dSKyle Evans 				while (!isdigit(*hln) && (*hln != '\0'))
5296703731dSKyle Evans 					hln++;
5306703731dSKyle Evans 
5316703731dSKyle Evans 				/* Compute the length */
5326703731dSKyle Evans 				while (isdigit(*hln)) {
5336703731dSKyle Evans 					if (contentlength >= OFF_MAX / 10) {
5346703731dSKyle Evans 						/* Nasty people... */
5356703731dSKyle Evans 						goto conndied;
5366703731dSKyle Evans 					}
5376703731dSKyle Evans 					contentlength = contentlength * 10 +
5386703731dSKyle Evans 					    *hln - '0';
5396703731dSKyle Evans 					hln++;
5406703731dSKyle Evans 				}
5416703731dSKyle Evans 
5426703731dSKyle Evans 				/* Next header... */
5436703731dSKyle Evans 				continue;
5446703731dSKyle Evans 			}
5456703731dSKyle Evans 
5466703731dSKyle Evans 			/* Check for "Transfer-Encoding: chunked" header */
5476703731dSKyle Evans 			if (strncasecmp(hln, "Transfer-Encoding:", 18) == 0) {
5486703731dSKyle Evans 				hln += 18;
5496703731dSKyle Evans 				if (strcasestr(hln, "chunked") != NULL)
5506703731dSKyle Evans 					chunked = 1;
5516703731dSKyle Evans 
5526703731dSKyle Evans 				/* Next header... */
5536703731dSKyle Evans 				continue;
5546703731dSKyle Evans 			}
5556703731dSKyle Evans 
5566703731dSKyle Evans 			/* We blithely ignore any other header lines */
5576703731dSKyle Evans 
5586703731dSKyle Evans 			/* No more header lines */
5596703731dSKyle Evans 			if (strlen(hln) == 0) {
5606703731dSKyle Evans 				/*
5616703731dSKyle Evans 				 * If the status code was 1xx, then there will
5626703731dSKyle Evans 				 * be a real header later.  Servers may emit
5636703731dSKyle Evans 				 * 1xx header blocks at will, but since we
5646703731dSKyle Evans 				 * don't expect one, we should just ignore it.
5656703731dSKyle Evans 				 */
5666703731dSKyle Evans 				if (100 <= statuscode && statuscode <= 199) {
5676703731dSKyle Evans 					statuscode = 0;
5686703731dSKyle Evans 					continue;
5696703731dSKyle Evans 				}
5706703731dSKyle Evans 
5716703731dSKyle Evans 				/* End of header; message body follows */
5726703731dSKyle Evans 				break;
5736703731dSKyle Evans 			}
5746703731dSKyle Evans 		} while (1);
5756703731dSKyle Evans 
5766703731dSKyle Evans 		/* No message body for 204 or 304 */
5776703731dSKyle Evans 		if (statuscode == 204 || statuscode == 304) {
5786703731dSKyle Evans 			nres++;
5796703731dSKyle Evans 			continue;
5806703731dSKyle Evans 		}
5816703731dSKyle Evans 
5826703731dSKyle Evans 		/*
5836703731dSKyle Evans 		 * There should be a message body coming, but we only want
5846703731dSKyle Evans 		 * to send it to a file if the status code is 200
5856703731dSKyle Evans 		 */
5866703731dSKyle Evans 		if (statuscode == 200) {
5876703731dSKyle Evans 			/* Generate a file name for the download */
5886703731dSKyle Evans 			fname = strrchr(argv[nres], '/');
5896703731dSKyle Evans 			if (fname == NULL)
5906703731dSKyle Evans 				fname = argv[nres];
5916703731dSKyle Evans 			else
5926703731dSKyle Evans 				fname++;
5936703731dSKyle Evans 			if (strlen(fname) == 0)
5946703731dSKyle Evans 				errx(1, "Cannot obtain file name from %s\n",
5956703731dSKyle Evans 				    argv[nres]);
5966703731dSKyle Evans 
5976703731dSKyle Evans 			fd = open(fname, O_CREAT | O_TRUNC | O_WRONLY, 0644);
5986703731dSKyle Evans 			if (fd == -1)
5996703731dSKyle Evans 				errx(1, "open(%s)", fname);
6006703731dSKyle Evans 		}
6016703731dSKyle Evans 
6026703731dSKyle Evans 		/* Read the message and send data to fd if appropriate */
6036703731dSKyle Evans 		if (chunked) {
6046703731dSKyle Evans 			/* Handle a chunked-encoded entity */
6056703731dSKyle Evans 
6066703731dSKyle Evans 			/* Read chunks */
6076703731dSKyle Evans 			do {
6086703731dSKyle Evans 				error = readln(sd, resbuf, &resbuflen,
6096703731dSKyle Evans 				    &resbufpos);
6106703731dSKyle Evans 				if (error)
6116703731dSKyle Evans 					goto conndied;
6126703731dSKyle Evans 				hln = resbuf + resbufpos;
6136703731dSKyle Evans 				eolp = strstr(hln, "\r\n");
6146703731dSKyle Evans 				resbufpos = (eolp - resbuf) + 2;
6156703731dSKyle Evans 
6166703731dSKyle Evans 				clen = 0;
6176703731dSKyle Evans 				while (isxdigit(*hln)) {
6186703731dSKyle Evans 					if (clen >= OFF_MAX / 16) {
6196703731dSKyle Evans 						/* Nasty people... */
6206703731dSKyle Evans 						goto conndied;
6216703731dSKyle Evans 					}
6226703731dSKyle Evans 					if (isdigit(*hln))
6236703731dSKyle Evans 						clen = clen * 16 + *hln - '0';
6246703731dSKyle Evans 					else
6256703731dSKyle Evans 						clen = clen * 16 + 10 +
6266703731dSKyle Evans 						    tolower(*hln) - 'a';
6276703731dSKyle Evans 					hln++;
6286703731dSKyle Evans 				}
6296703731dSKyle Evans 
6306703731dSKyle Evans 				error = copybytes(sd, fd, clen, resbuf,
6316703731dSKyle Evans 				    &resbuflen, &resbufpos);
6326703731dSKyle Evans 				if (error) {
6336703731dSKyle Evans 					goto conndied;
6346703731dSKyle Evans 				}
6356703731dSKyle Evans 			} while (clen != 0);
6366703731dSKyle Evans 
6376703731dSKyle Evans 			/* Read trailer and final CRLF */
6386703731dSKyle Evans 			do {
6396703731dSKyle Evans 				error = readln(sd, resbuf, &resbuflen,
6406703731dSKyle Evans 				    &resbufpos);
6416703731dSKyle Evans 				if (error)
6426703731dSKyle Evans 					goto conndied;
6436703731dSKyle Evans 				hln = resbuf + resbufpos;
6446703731dSKyle Evans 				eolp = strstr(hln, "\r\n");
6456703731dSKyle Evans 				resbufpos = (eolp - resbuf) + 2;
6466703731dSKyle Evans 			} while (hln != eolp);
6476703731dSKyle Evans 		} else if (contentlength != -1) {
6486703731dSKyle Evans 			error = copybytes(sd, fd, contentlength, resbuf,
6496703731dSKyle Evans 			    &resbuflen, &resbufpos);
6506703731dSKyle Evans 			if (error)
6516703731dSKyle Evans 				goto conndied;
6526703731dSKyle Evans 		} else {
6536703731dSKyle Evans 			/*
6546703731dSKyle Evans 			 * Not chunked, and no content length header.
6556703731dSKyle Evans 			 * Read everything until the server closes the
6566703731dSKyle Evans 			 * socket.
6576703731dSKyle Evans 			 */
6586703731dSKyle Evans 			error = copybytes(sd, fd, OFF_MAX, resbuf,
6596703731dSKyle Evans 			    &resbuflen, &resbufpos);
6606703731dSKyle Evans 			if (error == -1)
6616703731dSKyle Evans 				goto conndied;
6626703731dSKyle Evans 			pipelined = 0;
6636703731dSKyle Evans 		}
6646703731dSKyle Evans 
6656703731dSKyle Evans 		if (fd != -1) {
6666703731dSKyle Evans 			close(fd);
6676703731dSKyle Evans 			fd = -1;
6686703731dSKyle Evans 		}
6696703731dSKyle Evans 
6706703731dSKyle Evans 		fprintf(stderr, "http://%s/%s: %d ", servername, argv[nres],
6716703731dSKyle Evans 		    statuscode);
6726703731dSKyle Evans 		if (statuscode == 200)
6736703731dSKyle Evans 			fprintf(stderr, "OK\n");
6746703731dSKyle Evans 		else if (statuscode < 300)
6756703731dSKyle Evans 			fprintf(stderr, "Successful (ignored)\n");
6766703731dSKyle Evans 		else if (statuscode < 400)
6776703731dSKyle Evans 			fprintf(stderr, "Redirection (ignored)\n");
6786703731dSKyle Evans 		else
6796703731dSKyle Evans 			fprintf(stderr, "Error (ignored)\n");
6806703731dSKyle Evans 
6816703731dSKyle Evans 		/* We've finished this file! */
6826703731dSKyle Evans 		nres++;
6836703731dSKyle Evans 
6846703731dSKyle Evans 		/*
6856703731dSKyle Evans 		 * If necessary, clean up this connection so that we
6866703731dSKyle Evans 		 * can start a new one.
6876703731dSKyle Evans 		 */
6886703731dSKyle Evans 		if (pipelined == 0 && keepalive == 0)
6896703731dSKyle Evans 			goto cleanupconn;
6906703731dSKyle Evans 		continue;
6916703731dSKyle Evans 
6926703731dSKyle Evans conndied:
6936703731dSKyle Evans 		/*
6946703731dSKyle Evans 		 * Something went wrong -- our connection died, the server
6956703731dSKyle Evans 		 * sent us garbage, etc.  If this happened on the first
6966703731dSKyle Evans 		 * request we sent over this connection, give up.  Otherwise,
6976703731dSKyle Evans 		 * close this connection, open a new one, and reissue the
6986703731dSKyle Evans 		 * request.
6996703731dSKyle Evans 		 */
7006703731dSKyle Evans 		if (nres == firstreq)
7016703731dSKyle Evans 			errx(1, "Connection failure");
7026703731dSKyle Evans 
7036703731dSKyle Evans cleanupconn:
7046703731dSKyle Evans 		/*
7056703731dSKyle Evans 		 * Clean up our connection and keep on going
7066703731dSKyle Evans 		 */
7076703731dSKyle Evans 		shutdown(sd, SHUT_RDWR);
7086703731dSKyle Evans 		close(sd);
7096703731dSKyle Evans 		sd = -1;
7106703731dSKyle Evans 		if (fd != -1) {
7116703731dSKyle Evans 			close(fd);
7126703731dSKyle Evans 			fd = -1;
7136703731dSKyle Evans 		}
7146703731dSKyle Evans 		if (reqbuf != NULL) {
7156703731dSKyle Evans 			free(reqbuf);
7166703731dSKyle Evans 			reqbuf = NULL;
7176703731dSKyle Evans 		}
7186703731dSKyle Evans 		nreq = nres;
7196703731dSKyle Evans 		res = res0;
7206703731dSKyle Evans 		pipelined = 0;
7216703731dSKyle Evans 		resbufpos = resbuflen = 0;
7226703731dSKyle Evans 		continue;
7236703731dSKyle Evans 	}
7246703731dSKyle Evans 
7256703731dSKyle Evans 	free(resbuf);
7266703731dSKyle Evans 	freeaddrinfo(res0);
7276703731dSKyle Evans 
7286703731dSKyle Evans 	return 0;
7296703731dSKyle Evans }
730