143809Sjkh/*-
243809Sjkh * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
343809Sjkh *
487047Sru * Copyright 2005 Colin Percival
543809Sjkh * All rights reserved
643809Sjkh *
743809Sjkh * Redistribution and use in source and binary forms, with or without
843809Sjkh * modification, are permitted providing that the following conditions
943809Sjkh * are met:
1059674Ssheldonh * 1. Redistributions of source code must retain the above copyright
1159674Ssheldonh *    notice, this list of conditions and the following disclaimer.
1259674Ssheldonh * 2. Redistributions in binary form must reproduce the above copyright
1354949Ssheldonh *    notice, this list of conditions and the following disclaimer in the
1443809Sjkh *    documentation and/or other materials provided with the distribution.
1543809Sjkh *
1650472Speter * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1743809Sjkh * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
1843809Sjkh * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1948290Sjseger * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
2043809Sjkh * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2143809Sjkh * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22109233Smtm * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23119170Smtm * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
2498188Sgordon * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
2543809Sjkh * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
2648785Siwasaki * POSSIBILITY OF SUCH DAMAGE.
2748785Siwasaki */
2848785Siwasaki
29131338Simp#include <sys/cdefs.h>
30112354Scjc__FBSDID("$FreeBSD: stable/11/usr.sbin/portsnap/phttpget/phttpget.c 330449 2018-03-05 07:26:05Z eadler $");
31112354Scjc
32112354Scjc#include <sys/types.h>
3343809Sjkh#include <sys/time.h>
3443809Sjkh#include <sys/socket.h>
3567793Ssanpei
3643809Sjkh#include <ctype.h>
37107655Simp#include <err.h>
3858979Siwasaki#include <errno.h>
3984537Ssheldonh#include <fcntl.h>
4075181Sbmah#include <limits.h>
41127345Sbrooks#include <netdb.h>
42127345Sbrooks#include <stdint.h>
43137477Skeramida#include <stdio.h>
44127345Sbrooks#include <stdlib.h>
45127345Sbrooks#include <string.h>
46137451Skeramida#include <sysexits.h>
47127345Sbrooks#include <unistd.h>
4894407Speter
4979825Sroamstatic const char *	env_HTTP_PROXY;
5043809Sjkhstatic char *		env_HTTP_PROXY_AUTH;
51120195Sdougbstatic const char *	env_HTTP_USER_AGENT;
52120195Sdougbstatic char *		env_HTTP_TIMEOUT;
53120195Sdougbstatic const char *	proxyport;
54120195Sdougbstatic char *		proxyauth;
55132356Ssimon
56132356Ssimonstatic struct timeval	timo = { 15, 0};
57125388Sdes
58125388Sdesstatic void
59120195Sdougbusage(void)
60136730Skeramida{
6187047Sru
6276946Sdd	fprintf(stderr, "usage: phttpget server [file ...]\n");
63108018Smckusick	exit(EX_USAGE);
64115585Sgordon}
6588676Ssheldonh
6688676Ssheldonh/*
6743809Sjkh * Base64 encode a string; the string returned, if non-NULL, is
6843809Sjkh * allocated using malloc() and must be freed by the caller.
6943809Sjkh */
7043809Sjkhstatic char *
7143809Sjkhb64enc(const char *ptext)
7264749Sjhb{
7348880Sjkh	static const char base64[] =
7443809Sjkh	    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
75115950Smtm	    "abcdefghijklmnopqrstuvwxyz"
76115950Smtm	    "0123456789+/";
77118121Smbr	const char *pt;
7843809Sjkh	char *ctext, *pc;
7945542Sdes	size_t ptlen, ctlen;
8043809Sjkh	uint32_t t;
8143809Sjkh	unsigned int j;
8287047Sru
8357014Spaul	/*
8461961Sdillon	 * Encoded length is 4 characters per 3-byte block or partial
8561961Sdillon	 * block of plaintext, plus one byte for the terminating NUL
86123029Sbms	 */
87123029Sbms	ptlen = strlen(ptext);
88123029Sbms	if (ptlen > ((SIZE_MAX - 1) / 4) * 3 - 2)
8961961Sdillon		return NULL;	/* Possible integer overflow */
9061961Sdillon	ctlen = 4 * ((ptlen + 2) / 3) + 1;
9144990Sbrian	if ((ctext = malloc(ctlen)) == NULL)
9287047Sru		return NULL;
9390957Scjc	ctext[ctlen - 1] = 0;
9487047Sru
9566745Sdarrenr	/*
9686856Sdarrenr	 * Scan through ptext, reading up to 3 bytes from ptext and
9766745Sdarrenr	 * writing 4 bytes to ctext, until we run out of input.
9866745Sdarrenr	 */
9986856Sdarrenr	for (pt = ptext, pc = ctext; ptlen; ptlen -= 3, pc += 4) {
10086856Sdarrenr		/* Read 3 bytes */
10186856Sdarrenr		for (t = j = 0; j < 3; j++) {
10266745Sdarrenr			t <<= 8;
10366745Sdarrenr			if (j < ptlen)
10486856Sdarrenr				t += *pt++;
10586856Sdarrenr		}
10686856Sdarrenr
10787047Sru		/* Write 4 bytes */
10885219Sdarrenr		for (j = 0; j < 4; j++) {
10986856Sdarrenr			if (j <= ptlen + 1)
11085219Sdarrenr				pc[j] = base64[(t >> 18) & 0x3f];
111127342Smlaier			else
112127342Smlaier				pc[j] = '=';
113127342Smlaier			t <<= 6;
114127342Smlaier		}
115127759Smlaier
116132678Smlaier		/* If we're done, exit the loop */
117127759Smlaier		if (ptlen <= 3)
118127759Smlaier			break;
11977154Sobrien	}
12089808Scjc
12149704Sobrien	return (ctext);
12295547Sdougb}
12395547Sdougb
12451209Sdesstatic void
12560685Swollmanreadenv(void)
12695547Sdougb{
12749603Sdes	char *proxy_auth_userpass, *proxy_auth_userpass64, *p;
12848687Speter	char *proxy_auth_user = NULL;
12983677Sbrooks	char *proxy_auth_pass = NULL;
13083677Sbrooks	long http_timeout;
13143809Sjkh
13243809Sjkh	env_HTTP_PROXY = getenv("HTTP_PROXY");
13364677Ssheldonh	if (env_HTTP_PROXY == NULL)
134137070Spjd		env_HTTP_PROXY = getenv("http_proxy");
13543809Sjkh	if (env_HTTP_PROXY != NULL) {
13643809Sjkh		if (strncmp(env_HTTP_PROXY, "http://", 7) == 0)
13743809Sjkh			env_HTTP_PROXY += 7;
13843809Sjkh		p = strchr(env_HTTP_PROXY, '/');
13943809Sjkh		if (p != NULL)
14043809Sjkh			*p = 0;
14177651Sbrian		p = strchr(env_HTTP_PROXY, ':');
14277651Sbrian		if (p != NULL) {
14377651Sbrian			*p = 0;
14477651Sbrian			proxyport = p + 1;
14577651Sbrian		} else
14643809Sjkh			proxyport = "3128";
14753665Salfred	}
14849110Sbrian
14949110Sbrian	env_HTTP_PROXY_AUTH = getenv("HTTP_PROXY_AUTH");
15049110Sbrian	if ((env_HTTP_PROXY != NULL) &&
15150193Sbrian	    (env_HTTP_PROXY_AUTH != NULL) &&
15249110Sbrian	    (strncasecmp(env_HTTP_PROXY_AUTH, "basic:" , 6) == 0)) {
15364471Sbrian		/* Ignore authentication scheme */
15449110Sbrian		(void) strsep(&env_HTTP_PROXY_AUTH, ":");
15574462Salfred
15643809Sjkh		/* Ignore realm */
15778905Sdd		(void) strsep(&env_HTTP_PROXY_AUTH, ":");
15858400Sbillf
15963980Seivind		/* Obtain username and password */
16078905Sdd		proxy_auth_user = strsep(&env_HTTP_PROXY_AUTH, ":");
161120095Snectar		proxy_auth_pass = env_HTTP_PROXY_AUTH;
16243809Sjkh	}
16343809Sjkh
16443809Sjkh	if ((proxy_auth_user != NULL) && (proxy_auth_pass != NULL)) {
16543809Sjkh		asprintf(&proxy_auth_userpass, "%s:%s",
16643809Sjkh		    proxy_auth_user, proxy_auth_pass);
16795189Scjc		if (proxy_auth_userpass == NULL)
168126978Sdougb			err(1, "asprintf");
169135775Sdougb
170135875Sdougb		proxy_auth_userpass64 = b64enc(proxy_auth_userpass);
17198188Sgordon		if (proxy_auth_userpass64 == NULL)
17298188Sgordon			err(1, "malloc");
173135701Sdougb
17443809Sjkh		asprintf(&proxyauth, "Proxy-Authorization: Basic %s\r\n",
17580515Smarkm		    proxy_auth_userpass64);
17680515Smarkm		if (proxyauth == NULL)
17780515Smarkm			err(1, "asprintf");
17880515Smarkm
17980515Smarkm		free(proxy_auth_userpass);
18080515Smarkm		free(proxy_auth_userpass64);
181114326Smarkm	} else
182114328Smarkm		proxyauth = NULL;
183114328Smarkm
18480515Smarkm	env_HTTP_USER_AGENT = getenv("HTTP_USER_AGENT");
18543809Sjkh	if (env_HTTP_USER_AGENT == NULL)
18643809Sjkh		env_HTTP_USER_AGENT = "phttpget/0.1";
18774462Salfred
18874462Salfred	env_HTTP_TIMEOUT = getenv("HTTP_TIMEOUT");
189102982Sgordon	if (env_HTTP_TIMEOUT != NULL) {
190102982Sgordon		http_timeout = strtol(env_HTTP_TIMEOUT, &p, 10);
19174462Salfred		if ((*env_HTTP_TIMEOUT == '\0') || (*p != '\0') ||
19274462Salfred		    (http_timeout < 0))
19374462Salfred			warnx("HTTP_TIMEOUT (%s) is not a positive integer",
19474462Salfred			    env_HTTP_TIMEOUT);
19587047Sru		else
19674462Salfred			timo.tv_sec = http_timeout;
19787047Sru	}
19874462Salfred}
199101850Sgordon
20043809Sjkhstatic int
20165306Sobrienmakerequest(char ** buf, char * path, char * server, int connclose)
20243809Sjkh{
20343809Sjkh	int buflen;
20443809Sjkh
20543809Sjkh	buflen = asprintf(buf,
20643809Sjkh	    "GET %s%s/%s HTTP/1.1\r\n"
207101850Sgordon	    "Host: %s\r\n"
20843809Sjkh	    "User-Agent: %s\r\n"
20963773Sasmodai	    "%s"
21043809Sjkh	    "%s"
211110570Sgshapiro	    "\r\n",
21285114Salfred	    env_HTTP_PROXY ? "http://" : "",
21385114Salfred	    env_HTTP_PROXY ? server : "",
214101850Sgordon	    path, server, env_HTTP_USER_AGENT,
215101850Sgordon	    proxyauth ? proxyauth : "",
216101850Sgordon	    connclose ? "Connection: Close\r\n" : "Connection: Keep-Alive\r\n");
21743809Sjkh	if (buflen == -1)
21843809Sjkh		err(1, "asprintf");
21943809Sjkh	return(buflen);
22043809Sjkh}
22143809Sjkh
22243809Sjkhstatic int
22343809Sjkhreadln(int sd, char * resbuf, int * resbuflen, int * resbufpos)
224135252Sseanc{
225135252Sseanc	ssize_t len;
226135252Sseanc
227101850Sgordon	while (strnstr(resbuf + *resbufpos, "\r\n",
228101850Sgordon	    *resbuflen - *resbufpos) == NULL) {
229135252Sseanc		/* Move buffered data to the start of the buffer */
230120719Sphk		if (*resbufpos != 0) {
231120719Sphk			memmove(resbuf, resbuf + *resbufpos,
23243809Sjkh			    *resbuflen - *resbufpos);
233101850Sgordon			*resbuflen -= *resbufpos;
23443809Sjkh			*resbufpos = 0;
23543809Sjkh		}
23643809Sjkh
23743809Sjkh		/* If the buffer is full, complain */
23843809Sjkh		if (*resbuflen == BUFSIZ)
23943809Sjkh			return -1;
24043809Sjkh
24143809Sjkh		/* Read more data into the buffer */
24243809Sjkh		len = recv(sd, resbuf + *resbuflen, BUFSIZ - *resbuflen, 0);
24343809Sjkh		if ((len == 0) ||
24443809Sjkh		    ((len == -1) && (errno != EINTR)))
24543809Sjkh			return -1;
24643809Sjkh
24743809Sjkh		if (len != -1)
248118908Sharti			*resbuflen += len;
24943809Sjkh	}
25043809Sjkh
25195189Scjc	return 0;
25243809Sjkh}
25343809Sjkh
25443809Sjkhstatic int
25543809Sjkhcopybytes(int sd, int fd, off_t copylen, char * resbuf, int * resbuflen,
25643809Sjkh    int * resbufpos)
25743809Sjkh{
25887047Sru	ssize_t len;
25943809Sjkh
26043809Sjkh	while (copylen) {
26143809Sjkh		/* Write data from resbuf to fd */
26243809Sjkh		len = *resbuflen - *resbufpos;
26343809Sjkh		if (copylen < len)
26443809Sjkh			len = copylen;
26543809Sjkh		if (len > 0) {
26643809Sjkh			if (fd != -1)
26743809Sjkh				len = write(fd, resbuf + *resbufpos, len);
26843809Sjkh			if (len == -1)
26943809Sjkh				err(1, "write");
27043809Sjkh			*resbufpos += len;
27143809Sjkh			copylen -= len;
27243809Sjkh			continue;
27380209Shm		}
27443809Sjkh
27580209Shm		/* Read more data into buffer */
27643809Sjkh		len = recv(sd, resbuf, BUFSIZ, 0);
27775920Sschweikh		if (len == -1) {
27876592Sschweikh			if (errno == EINTR)
27943809Sjkh				continue;
28043809Sjkh			return -1;
28143809Sjkh		} else if (len == 0) {
28243809Sjkh			return -2;
28343809Sjkh		} else {
28443809Sjkh			*resbuflen = len;
28557398Sshin			*resbufpos = 0;
28657398Sshin		}
28757398Sshin	}
28867906Sume
28987464Snsayer	return 0;
29057944Sshin}
29157944Sshin
29257944Sshinint
29357944Sshinmain(int argc, char *argv[])
29457398Sshin{
29557398Sshin	struct addrinfo hints;	/* Hints to getaddrinfo */
29657398Sshin	struct addrinfo *res;	/* Pointer to server address being used */
29757398Sshin	struct addrinfo *res0;	/* Pointer to server addresses */
29857398Sshin	char * resbuf = NULL;	/* Response buffer */
29957398Sshin	int resbufpos = 0;	/* Response buffer position */
300100279Sume	int resbuflen = 0;	/* Response buffer length */
301100279Sume	char * eolp;		/* Pointer to "\r\n" within resbuf */
30267906Sume	char * hln;		/* Pointer within header line */
30367906Sume	char * servername;	/* Name of server */
30457398Sshin	char * fname = NULL;	/* Name of downloaded file */
30557398Sshin	char * reqbuf = NULL;	/* Request buffer */
30657398Sshin	int reqbufpos = 0;	/* Request buffer position */
30774418Sume	int reqbuflen = 0;	/* Request buffer length */
30874418Sume	ssize_t len;		/* Length sent or received */
30978935Sume	int nreq = 0;		/* Number of next request to send */
31057398Sshin	int nres = 0;		/* Number of next reply to receive */
31157398Sshin	int pipelined = 0;	/* != 0 if connection in pipelined mode. */
312118666Sume	int keepalive;		/* != 0 if HTTP/1.0 keep-alive rcvd. */
31378493Sume	int sd = -1;		/* Socket descriptor */
31457944Sshin	int sdflags = 0;	/* Flags on the socket sd */
31557944Sshin	int fd = -1;		/* Descriptor for download target file */
31657944Sshin	int error;		/* Error code */
31771632Sume	int statuscode;		/* HTTP Status code */
31857398Sshin	off_t contentlength;	/* Value from Content-Length header */
31984421Sume	int chunked;		/* != if transfer-encoding is chunked */
32084421Sume	off_t clen;		/* Chunk length */
32184421Sume	int firstreq = 0;	/* # of first request for this connection */
32284421Sume	int val;		/* Value used for setsockopt call */
32357398Sshin
32457944Sshin	/* Check that the arguments are sensible */
32557944Sshin	if (argc < 2)
32657944Sshin		usage();
32757944Sshin
32857944Sshin	/* Read important environment variables */
32957944Sshin	readenv();
33057944Sshin
33157944Sshin	/* Get server name and adjust arg[cv] to point at file names */
33257944Sshin	servername = argv[1];
33378475Sume	argv += 2;
33478475Sume	argc -= 2;
33578475Sume
336100676Sume	/* Allocate response buffer */
33758752Sshin	resbuf = malloc(BUFSIZ);
33867906Sume	if (resbuf == NULL)
33967906Sume		err(1, "malloc");
34067906Sume
34167906Sume	/* Look up server */
34267906Sume	memset(&hints, 0, sizeof(hints));
34367906Sume	hints.ai_family = PF_UNSPEC;
34467906Sume	hints.ai_socktype = SOCK_STREAM;
345106333Sume	error = getaddrinfo(env_HTTP_PROXY ? env_HTTP_PROXY : servername,
346106333Sume	    env_HTTP_PROXY ? proxyport : "http", &hints, &res0);
347106333Sume	if (error)
348129995Sume		errx(1, "host = %s, port = %s: %s",
349129994Sume		    env_HTTP_PROXY ? env_HTTP_PROXY : servername,
35043809Sjkh		    env_HTTP_PROXY ? proxyport : "http",
35143809Sjkh		    gai_strerror(error));
35243809Sjkh	if (res0 == NULL)
35343809Sjkh		errx(1, "could not look up %s", servername);
35443809Sjkh	res = res0;
355130699Sgreen
35643809Sjkh	/* Do the fetching */
35743809Sjkh	while (nres < argc) {
358112255Sdougb		/* Make sure we have a connected socket */
35943809Sjkh		for (; sd == -1; res = res->ai_next) {
36043809Sjkh			/* No addresses left to try :-( */
36143809Sjkh			if (res == NULL)
36243809Sjkh				errx(1, "Could not connect to %s", servername);
36343809Sjkh
36443809Sjkh			/* Create a socket... */
36543809Sjkh			sd = socket(res->ai_family, res->ai_socktype,
36693977Sasmodai			    res->ai_protocol);
367137112Smtm			if (sd == -1)
368137112Smtm				continue;
36943809Sjkh
37043809Sjkh			/* ... set 15-second timeouts ... */
37187047Sru			setsockopt(sd, SOL_SOCKET, SO_SNDTIMEO,
37243809Sjkh			    (void *)&timo, (socklen_t)sizeof(timo));
373106946Sru			setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO,
37475708Sache			    (void *)&timo, (socklen_t)sizeof(timo));
37575708Sache
37643809Sjkh			/* ... disable SIGPIPE generation ... */
37776110Sdd			val = 1;
37843809Sjkh			setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE,
37943809Sjkh			    (void *)&val, sizeof(int));
38043809Sjkh
381102617Shm			/* ... and connect to the server. */
382102617Shm			if(connect(sd, res->ai_addr, res->ai_addrlen)) {
383102617Shm				close(sd);
384102617Shm				sd = -1;
385102617Shm				continue;
386102617Shm			}
387102617Shm
388102617Shm			firstreq = nres;
389102617Shm		}
390102617Shm
391102617Shm		/*
392102617Shm		 * If in pipelined HTTP mode, put socket into non-blocking
393102617Shm		 * mode, since we're probably going to want to try to send
394102617Shm		 * several HTTP requests.
395102617Shm		 */
396102617Shm		if (pipelined) {
397102617Shm			sdflags = fcntl(sd, F_GETFL);
398102617Shm			if (fcntl(sd, F_SETFL, sdflags | O_NONBLOCK) == -1)
39993853Sgshapiro				err(1, "fcntl");
40043809Sjkh		}
40143809Sjkh
40293853Sgshapiro		/* Construct requests and/or send them without blocking */
40393853Sgshapiro		while ((nreq < argc) && ((reqbuf == NULL) || pipelined)) {
404127895Sfjoe			/* If not in the middle of a request, make one */
405102915Sgshapiro			if (reqbuf == NULL) {
406127895Sfjoe				reqbuflen = makerequest(&reqbuf, argv[nreq],
407127895Sfjoe				    servername, (nreq == argc - 1));
40890808Sgshapiro				reqbufpos = 0;
40993314Sgshapiro			}
41093314Sgshapiro
41193314Sgshapiro			/* If in pipelined mode, try to send the request */
41274198Speter			if (pipelined) {
41390808Sgshapiro				while (reqbufpos < reqbuflen) {
41490808Sgshapiro					len = send(sd, reqbuf + reqbufpos,
41590808Sgshapiro					    reqbuflen - reqbufpos, 0);
41693314Sgshapiro					if (len == -1)
41793853Sgshapiro						break;
41893853Sgshapiro					reqbufpos += len;
41993853Sgshapiro				}
42093853Sgshapiro				if (reqbufpos < reqbuflen) {
42193853Sgshapiro					if (errno != EAGAIN)
42293853Sgshapiro						goto conndied;
42393853Sgshapiro					break;
42493853Sgshapiro				} else {
425123841Sbabkin					free(reqbuf);
42693853Sgshapiro					reqbuf = NULL;
42793853Sgshapiro					nreq++;
42893853Sgshapiro				}
42993853Sgshapiro			}
43093853Sgshapiro		}
43193853Sgshapiro
43251827Sbillf		/* Put connection back into blocking mode */
43384730Sdes		if (pipelined) {
43487047Sru			if (fcntl(sd, F_SETFL, sdflags) == -1)
43587047Sru				err(1, "fcntl");
43651038Scpiazza		}
43743809Sjkh
43843809Sjkh		/* Do we need to blocking-send a request? */
43973242Sjkh		if (nres == nreq) {
44071121Sdes			while (reqbufpos < reqbuflen) {
44151290Sobrien				len = send(sd, reqbuf + reqbufpos,
44243809Sjkh				    reqbuflen - reqbufpos, 0);
44354642Sgallatin				if (len == -1)
44443809Sjkh					goto conndied;
44564520Sjdp				reqbufpos += len;
446136474Sru			}
44743809Sjkh			free(reqbuf);
44843809Sjkh			reqbuf = NULL;
44943809Sjkh			nreq++;
45087047Sru		}
45143809Sjkh
45292192Srwatson		/* Scan through the response processing headers. */
45343809Sjkh		statuscode = 0;
45466634Sbrian		contentlength = -1;
45587047Sru		chunked = 0;
45667180Sjwd		keepalive = 0;
45771014Sdougb		do {
45887047Sru			/* Get a header line */
45987047Sru			error = readln(sd, resbuf, &resbuflen, &resbufpos);
46087047Sru			if (error)
46187047Sru				goto conndied;
46287047Sru			hln = resbuf + resbufpos;
46398188Sgordon			eolp = strnstr(hln, "\r\n", resbuflen - resbufpos);
464116874Ssmkelly			resbufpos = (eolp - resbuf) + 2;
465119166Smtm			*eolp = '\0';
466119166Smtm
467119166Smtm			/* Make sure it doesn't contain a NUL character */
468128096Sgreen			if (strchr(hln, '\0') != eolp)
469128096Sgreen				goto conndied;
470129830Snjl
471123626Snjl			if (statuscode == 0) {
472123626Snjl				/* The first line MUST be HTTP/1.x xxx ... */
473123626Snjl				if ((strncmp(hln, "HTTP/1.", 7) != 0) ||
474126554Smtm				    ! isdigit(hln[7]))
475135927Strhodes					goto conndied;
476135912Strhodes
477135912Strhodes				/*
47843809Sjkh				 * If the minor version number isn't zero,
479119397Smtm				 * then we can assume that pipelining our
480119397Smtm				 * requests is OK -- as long as we don't
481119397Smtm				 * see a "Connection: close" line later
482119397Smtm				 * and we either have a Content-Length or
483119397Smtm				 * Transfer-Encoding: chunked header to
484119397Smtm				 * tell us the length.
485119397Smtm				 */
486119397Smtm				if (hln[7] != '0')
487119166Smtm					pipelined = 1;
488119397Smtm
489119397Smtm				/* Skip over the minor version number */
490119397Smtm				hln = strchr(hln + 7, ' ');
491119397Smtm				if (hln == NULL)
492119397Smtm					goto conndied;
493119397Smtm				else
494119397Smtm					hln++;
495119397Smtm
496119397Smtm				/* Read the status code */
497119397Smtm				while (isdigit(*hln)) {
498119397Smtm					statuscode = statuscode * 10 +
499119397Smtm					    *hln - '0';
500138027Smux					hln++;
501125324Smtm				}
502138027Smux
503119397Smtm				if (statuscode < 100 || statuscode > 599)
50443809Sjkh					goto conndied;
50559674Ssheldonh
50659674Ssheldonh				/* Ignore the rest of the line */
50743809Sjkh				continue;
50843809Sjkh			}
50959674Ssheldonh
51087047Sru			/*
51187047Sru			 * Check for "Connection: close" or
51287047Sru			 * "Connection: Keep-Alive" header
51387047Sru			 */
51487047Sru			if (strncasecmp(hln, "Connection:", 11) == 0) {
51587047Sru				hln += 11;
51687047Sru				if (strcasestr(hln, "close") != NULL)
51787047Sru					pipelined = 0;
51887047Sru				if (strcasestr(hln, "Keep-Alive") != NULL)
51987047Sru					keepalive = 1;
52087047Sru
52187047Sru				/* Next header... */
52287047Sru				continue;
52387047Sru			}
52487047Sru
52587047Sru			/* Check for "Content-Length:" header */
52659674Ssheldonh			if (strncasecmp(hln, "Content-Length:", 15) == 0) {
527				hln += 15;
528				contentlength = 0;
529
530				/* Find the start of the length */
531				while (!isdigit(*hln) && (*hln != '\0'))
532					hln++;
533
534				/* Compute the length */
535				while (isdigit(*hln)) {
536					if (contentlength >= OFF_MAX / 10) {
537						/* Nasty people... */
538						goto conndied;
539					}
540					contentlength = contentlength * 10 +
541					    *hln - '0';
542					hln++;
543				}
544
545				/* Next header... */
546				continue;
547			}
548
549			/* Check for "Transfer-Encoding: chunked" header */
550			if (strncasecmp(hln, "Transfer-Encoding:", 18) == 0) {
551				hln += 18;
552				if (strcasestr(hln, "chunked") != NULL)
553					chunked = 1;
554
555				/* Next header... */
556				continue;
557			}
558
559			/* We blithely ignore any other header lines */
560
561			/* No more header lines */
562			if (strlen(hln) == 0) {
563				/*
564				 * If the status code was 1xx, then there will
565				 * be a real header later.  Servers may emit
566				 * 1xx header blocks at will, but since we
567				 * don't expect one, we should just ignore it.
568				 */
569				if (100 <= statuscode && statuscode <= 199) {
570					statuscode = 0;
571					continue;
572				}
573
574				/* End of header; message body follows */
575				break;
576			}
577		} while (1);
578
579		/* No message body for 204 or 304 */
580		if (statuscode == 204 || statuscode == 304) {
581			nres++;
582			continue;
583		}
584
585		/*
586		 * There should be a message body coming, but we only want
587		 * to send it to a file if the status code is 200
588		 */
589		if (statuscode == 200) {
590			/* Generate a file name for the download */
591			fname = strrchr(argv[nres], '/');
592			if (fname == NULL)
593				fname = argv[nres];
594			else
595				fname++;
596			if (strlen(fname) == 0)
597				errx(1, "Cannot obtain file name from %s\n",
598				    argv[nres]);
599
600			fd = open(fname, O_CREAT | O_TRUNC | O_WRONLY, 0644);
601			if (fd == -1)
602				errx(1, "open(%s)", fname);
603		}
604
605		/* Read the message and send data to fd if appropriate */
606		if (chunked) {
607			/* Handle a chunked-encoded entity */
608
609			/* Read chunks */
610			do {
611				error = readln(sd, resbuf, &resbuflen,
612				    &resbufpos);
613				if (error)
614					goto conndied;
615				hln = resbuf + resbufpos;
616				eolp = strstr(hln, "\r\n");
617				resbufpos = (eolp - resbuf) + 2;
618
619				clen = 0;
620				while (isxdigit(*hln)) {
621					if (clen >= OFF_MAX / 16) {
622						/* Nasty people... */
623						goto conndied;
624					}
625					if (isdigit(*hln))
626						clen = clen * 16 + *hln - '0';
627					else
628						clen = clen * 16 + 10 +
629						    tolower(*hln) - 'a';
630					hln++;
631				}
632
633				error = copybytes(sd, fd, clen, resbuf,
634				    &resbuflen, &resbufpos);
635				if (error) {
636					goto conndied;
637				}
638			} while (clen != 0);
639
640			/* Read trailer and final CRLF */
641			do {
642				error = readln(sd, resbuf, &resbuflen,
643				    &resbufpos);
644				if (error)
645					goto conndied;
646				hln = resbuf + resbufpos;
647				eolp = strstr(hln, "\r\n");
648				resbufpos = (eolp - resbuf) + 2;
649			} while (hln != eolp);
650		} else if (contentlength != -1) {
651			error = copybytes(sd, fd, contentlength, resbuf,
652			    &resbuflen, &resbufpos);
653			if (error)
654				goto conndied;
655		} else {
656			/*
657			 * Not chunked, and no content length header.
658			 * Read everything until the server closes the
659			 * socket.
660			 */
661			error = copybytes(sd, fd, OFF_MAX, resbuf,
662			    &resbuflen, &resbufpos);
663			if (error == -1)
664				goto conndied;
665			pipelined = 0;
666		}
667
668		if (fd != -1) {
669			close(fd);
670			fd = -1;
671		}
672
673		fprintf(stderr, "http://%s/%s: %d ", servername, argv[nres],
674		    statuscode);
675		if (statuscode == 200)
676			fprintf(stderr, "OK\n");
677		else if (statuscode < 300)
678			fprintf(stderr, "Successful (ignored)\n");
679		else if (statuscode < 400)
680			fprintf(stderr, "Redirection (ignored)\n");
681		else
682			fprintf(stderr, "Error (ignored)\n");
683
684		/* We've finished this file! */
685		nres++;
686
687		/*
688		 * If necessary, clean up this connection so that we
689		 * can start a new one.
690		 */
691		if (pipelined == 0 && keepalive == 0)
692			goto cleanupconn;
693		continue;
694
695conndied:
696		/*
697		 * Something went wrong -- our connection died, the server
698		 * sent us garbage, etc.  If this happened on the first
699		 * request we sent over this connection, give up.  Otherwise,
700		 * close this connection, open a new one, and reissue the
701		 * request.
702		 */
703		if (nres == firstreq)
704			errx(1, "Connection failure");
705
706cleanupconn:
707		/*
708		 * Clean up our connection and keep on going
709		 */
710		shutdown(sd, SHUT_RDWR);
711		close(sd);
712		sd = -1;
713		if (fd != -1) {
714			close(fd);
715			fd = -1;
716		}
717		if (reqbuf != NULL) {
718			free(reqbuf);
719			reqbuf = NULL;
720		}
721		nreq = nres;
722		res = res0;
723		pipelined = 0;
724		resbufpos = resbuflen = 0;
725		continue;
726	}
727
728	free(resbuf);
729	freeaddrinfo(res0);
730
731	return 0;
732}
733