xref: /freebsd/libexec/phttpget/phttpget.c (revision af23369a6deaaeb612ab266eb88b8bb8d560c322)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
3  *
4  * Copyright 2005 Colin Percival
5  * All rights reserved
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted providing that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  *
16  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
17  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
20  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
24  * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
25  * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
26  * POSSIBILITY OF SUCH DAMAGE.
27  */
28 
29 #include <sys/cdefs.h>
30 __FBSDID("$FreeBSD$");
31 
32 #include <sys/types.h>
33 #include <sys/time.h>
34 #include <sys/socket.h>
35 
36 #include <ctype.h>
37 #include <err.h>
38 #include <errno.h>
39 #include <fcntl.h>
40 #include <limits.h>
41 #include <netdb.h>
42 #include <stdint.h>
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <sysexits.h>
47 #include <unistd.h>
48 
49 static const char *	env_HTTP_PROXY;
50 static char *		env_HTTP_PROXY_AUTH;
51 static const char *	env_HTTP_USER_AGENT;
52 static char *		env_HTTP_TIMEOUT;
53 static const char *	proxyport;
54 static char *		proxyauth;
55 
56 static struct timeval	timo = { 15, 0};
57 
58 static void
59 usage(void)
60 {
61 
62 	fprintf(stderr, "usage: phttpget server [file ...]\n");
63 	exit(EX_USAGE);
64 }
65 
66 /*
67  * Base64 encode a string; the string returned, if non-NULL, is
68  * allocated using malloc() and must be freed by the caller.
69  */
70 static char *
71 b64enc(const char *ptext)
72 {
73 	static const char base64[] =
74 	    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
75 	    "abcdefghijklmnopqrstuvwxyz"
76 	    "0123456789+/";
77 	const char *pt;
78 	char *ctext, *pc;
79 	size_t ptlen, ctlen;
80 	uint32_t t;
81 	unsigned int j;
82 
83 	/*
84 	 * Encoded length is 4 characters per 3-byte block or partial
85 	 * block of plaintext, plus one byte for the terminating NUL
86 	 */
87 	ptlen = strlen(ptext);
88 	if (ptlen > ((SIZE_MAX - 1) / 4) * 3 - 2)
89 		return NULL;	/* Possible integer overflow */
90 	ctlen = 4 * ((ptlen + 2) / 3) + 1;
91 	if ((ctext = malloc(ctlen)) == NULL)
92 		return NULL;
93 	ctext[ctlen - 1] = 0;
94 
95 	/*
96 	 * Scan through ptext, reading up to 3 bytes from ptext and
97 	 * writing 4 bytes to ctext, until we run out of input.
98 	 */
99 	for (pt = ptext, pc = ctext; ptlen; ptlen -= 3, pc += 4) {
100 		/* Read 3 bytes */
101 		for (t = j = 0; j < 3; j++) {
102 			t <<= 8;
103 			if (j < ptlen)
104 				t += *pt++;
105 		}
106 
107 		/* Write 4 bytes */
108 		for (j = 0; j < 4; j++) {
109 			if (j <= ptlen + 1)
110 				pc[j] = base64[(t >> 18) & 0x3f];
111 			else
112 				pc[j] = '=';
113 			t <<= 6;
114 		}
115 
116 		/* If we're done, exit the loop */
117 		if (ptlen <= 3)
118 			break;
119 	}
120 
121 	return (ctext);
122 }
123 
124 static void
125 readenv(void)
126 {
127 	char *proxy_auth_userpass, *proxy_auth_userpass64, *p;
128 	char *proxy_auth_user = NULL;
129 	char *proxy_auth_pass = NULL;
130 	long http_timeout;
131 
132 	env_HTTP_PROXY = getenv("HTTP_PROXY");
133 	if (env_HTTP_PROXY == NULL)
134 		env_HTTP_PROXY = getenv("http_proxy");
135 	if (env_HTTP_PROXY != NULL) {
136 		if (strncmp(env_HTTP_PROXY, "http://", 7) == 0)
137 			env_HTTP_PROXY += 7;
138 		p = strchr(env_HTTP_PROXY, '/');
139 		if (p != NULL)
140 			*p = 0;
141 		p = strchr(env_HTTP_PROXY, ':');
142 		if (p != NULL) {
143 			*p = 0;
144 			proxyport = p + 1;
145 		} else
146 			proxyport = "3128";
147 	}
148 
149 	env_HTTP_PROXY_AUTH = getenv("HTTP_PROXY_AUTH");
150 	if ((env_HTTP_PROXY != NULL) &&
151 	    (env_HTTP_PROXY_AUTH != NULL) &&
152 	    (strncasecmp(env_HTTP_PROXY_AUTH, "basic:" , 6) == 0)) {
153 		/* Ignore authentication scheme */
154 		(void) strsep(&env_HTTP_PROXY_AUTH, ":");
155 
156 		/* Ignore realm */
157 		(void) strsep(&env_HTTP_PROXY_AUTH, ":");
158 
159 		/* Obtain username and password */
160 		proxy_auth_user = strsep(&env_HTTP_PROXY_AUTH, ":");
161 		proxy_auth_pass = env_HTTP_PROXY_AUTH;
162 	}
163 
164 	if ((proxy_auth_user != NULL) && (proxy_auth_pass != NULL)) {
165 		asprintf(&proxy_auth_userpass, "%s:%s",
166 		    proxy_auth_user, proxy_auth_pass);
167 		if (proxy_auth_userpass == NULL)
168 			err(1, "asprintf");
169 
170 		proxy_auth_userpass64 = b64enc(proxy_auth_userpass);
171 		if (proxy_auth_userpass64 == NULL)
172 			err(1, "malloc");
173 
174 		asprintf(&proxyauth, "Proxy-Authorization: Basic %s\r\n",
175 		    proxy_auth_userpass64);
176 		if (proxyauth == NULL)
177 			err(1, "asprintf");
178 
179 		free(proxy_auth_userpass);
180 		free(proxy_auth_userpass64);
181 	} else
182 		proxyauth = NULL;
183 
184 	env_HTTP_USER_AGENT = getenv("HTTP_USER_AGENT");
185 	if (env_HTTP_USER_AGENT == NULL)
186 		env_HTTP_USER_AGENT = "phttpget/0.1";
187 
188 	env_HTTP_TIMEOUT = getenv("HTTP_TIMEOUT");
189 	if (env_HTTP_TIMEOUT != NULL) {
190 		http_timeout = strtol(env_HTTP_TIMEOUT, &p, 10);
191 		if ((*env_HTTP_TIMEOUT == '\0') || (*p != '\0') ||
192 		    (http_timeout < 0))
193 			warnx("HTTP_TIMEOUT (%s) is not a positive integer",
194 			    env_HTTP_TIMEOUT);
195 		else
196 			timo.tv_sec = http_timeout;
197 	}
198 }
199 
200 static int
201 makerequest(char ** buf, char * path, char * server, int connclose)
202 {
203 	int buflen;
204 
205 	buflen = asprintf(buf,
206 	    "GET %s%s/%s HTTP/1.1\r\n"
207 	    "Host: %s\r\n"
208 	    "User-Agent: %s\r\n"
209 	    "%s"
210 	    "%s"
211 	    "\r\n",
212 	    env_HTTP_PROXY ? "http://" : "",
213 	    env_HTTP_PROXY ? server : "",
214 	    path, server, env_HTTP_USER_AGENT,
215 	    proxyauth ? proxyauth : "",
216 	    connclose ? "Connection: Close\r\n" : "Connection: Keep-Alive\r\n");
217 	if (buflen == -1)
218 		err(1, "asprintf");
219 	return(buflen);
220 }
221 
222 static int
223 readln(int sd, char * resbuf, int * resbuflen, int * resbufpos)
224 {
225 	ssize_t len;
226 
227 	while (strnstr(resbuf + *resbufpos, "\r\n",
228 	    *resbuflen - *resbufpos) == NULL) {
229 		/* Move buffered data to the start of the buffer */
230 		if (*resbufpos != 0) {
231 			memmove(resbuf, resbuf + *resbufpos,
232 			    *resbuflen - *resbufpos);
233 			*resbuflen -= *resbufpos;
234 			*resbufpos = 0;
235 		}
236 
237 		/* If the buffer is full, complain */
238 		if (*resbuflen == BUFSIZ)
239 			return -1;
240 
241 		/* Read more data into the buffer */
242 		len = recv(sd, resbuf + *resbuflen, BUFSIZ - *resbuflen, 0);
243 		if ((len == 0) ||
244 		    ((len == -1) && (errno != EINTR)))
245 			return -1;
246 
247 		if (len != -1)
248 			*resbuflen += len;
249 	}
250 
251 	return 0;
252 }
253 
254 static int
255 copybytes(int sd, int fd, off_t copylen, char * resbuf, int * resbuflen,
256     int * resbufpos)
257 {
258 	ssize_t len;
259 
260 	while (copylen) {
261 		/* Write data from resbuf to fd */
262 		len = *resbuflen - *resbufpos;
263 		if (copylen < len)
264 			len = copylen;
265 		if (len > 0) {
266 			if (fd != -1)
267 				len = write(fd, resbuf + *resbufpos, len);
268 			if (len == -1)
269 				err(1, "write");
270 			*resbufpos += len;
271 			copylen -= len;
272 			continue;
273 		}
274 
275 		/* Read more data into buffer */
276 		len = recv(sd, resbuf, BUFSIZ, 0);
277 		if (len == -1) {
278 			if (errno == EINTR)
279 				continue;
280 			return -1;
281 		} else if (len == 0) {
282 			return -2;
283 		} else {
284 			*resbuflen = len;
285 			*resbufpos = 0;
286 		}
287 	}
288 
289 	return 0;
290 }
291 
292 int
293 main(int argc, char *argv[])
294 {
295 	struct addrinfo hints;	/* Hints to getaddrinfo */
296 	struct addrinfo *res;	/* Pointer to server address being used */
297 	struct addrinfo *res0;	/* Pointer to server addresses */
298 	char * resbuf = NULL;	/* Response buffer */
299 	int resbufpos = 0;	/* Response buffer position */
300 	int resbuflen = 0;	/* Response buffer length */
301 	char * eolp;		/* Pointer to "\r\n" within resbuf */
302 	char * hln;		/* Pointer within header line */
303 	char * servername;	/* Name of server */
304 	char * fname = NULL;	/* Name of downloaded file */
305 	char * reqbuf = NULL;	/* Request buffer */
306 	int reqbufpos = 0;	/* Request buffer position */
307 	int reqbuflen = 0;	/* Request buffer length */
308 	ssize_t len;		/* Length sent or received */
309 	int nreq = 0;		/* Number of next request to send */
310 	int nres = 0;		/* Number of next reply to receive */
311 	int pipelined = 0;	/* != 0 if connection in pipelined mode. */
312 	int keepalive;		/* != 0 if HTTP/1.0 keep-alive rcvd. */
313 	int sd = -1;		/* Socket descriptor */
314 	int sdflags = 0;	/* Flags on the socket sd */
315 	int fd = -1;		/* Descriptor for download target file */
316 	int error;		/* Error code */
317 	int statuscode;		/* HTTP Status code */
318 	off_t contentlength;	/* Value from Content-Length header */
319 	int chunked;		/* != if transfer-encoding is chunked */
320 	off_t clen;		/* Chunk length */
321 	int firstreq = 0;	/* # of first request for this connection */
322 	int val;		/* Value used for setsockopt call */
323 
324 	/* Check that the arguments are sensible */
325 	if (argc < 2)
326 		usage();
327 
328 	/* Read important environment variables */
329 	readenv();
330 
331 	/* Get server name and adjust arg[cv] to point at file names */
332 	servername = argv[1];
333 	argv += 2;
334 	argc -= 2;
335 
336 	/* Allocate response buffer */
337 	resbuf = malloc(BUFSIZ);
338 	if (resbuf == NULL)
339 		err(1, "malloc");
340 
341 	/* Look up server */
342 	memset(&hints, 0, sizeof(hints));
343 	hints.ai_family = PF_UNSPEC;
344 	hints.ai_socktype = SOCK_STREAM;
345 	error = getaddrinfo(env_HTTP_PROXY ? env_HTTP_PROXY : servername,
346 	    env_HTTP_PROXY ? proxyport : "http", &hints, &res0);
347 	if (error)
348 		errx(1, "host = %s, port = %s: %s",
349 		    env_HTTP_PROXY ? env_HTTP_PROXY : servername,
350 		    env_HTTP_PROXY ? proxyport : "http",
351 		    gai_strerror(error));
352 	if (res0 == NULL)
353 		errx(1, "could not look up %s", servername);
354 	res = res0;
355 
356 	/* Do the fetching */
357 	while (nres < argc) {
358 		/* Make sure we have a connected socket */
359 		for (; sd == -1; res = res->ai_next) {
360 			/* No addresses left to try :-( */
361 			if (res == NULL)
362 				errx(1, "Could not connect to %s", servername);
363 
364 			/* Create a socket... */
365 			sd = socket(res->ai_family, res->ai_socktype,
366 			    res->ai_protocol);
367 			if (sd == -1)
368 				continue;
369 
370 			/* ... set 15-second timeouts ... */
371 			setsockopt(sd, SOL_SOCKET, SO_SNDTIMEO,
372 			    (void *)&timo, (socklen_t)sizeof(timo));
373 			setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO,
374 			    (void *)&timo, (socklen_t)sizeof(timo));
375 
376 			/* ... disable SIGPIPE generation ... */
377 			val = 1;
378 			setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE,
379 			    (void *)&val, sizeof(int));
380 
381 			/* ... and connect to the server. */
382 			if(connect(sd, res->ai_addr, res->ai_addrlen)) {
383 				close(sd);
384 				sd = -1;
385 				continue;
386 			}
387 
388 			firstreq = nres;
389 		}
390 
391 		/*
392 		 * If in pipelined HTTP mode, put socket into non-blocking
393 		 * mode, since we're probably going to want to try to send
394 		 * several HTTP requests.
395 		 */
396 		if (pipelined) {
397 			sdflags = fcntl(sd, F_GETFL);
398 			if (fcntl(sd, F_SETFL, sdflags | O_NONBLOCK) == -1)
399 				err(1, "fcntl");
400 		}
401 
402 		/* Construct requests and/or send them without blocking */
403 		while ((nreq < argc) && ((reqbuf == NULL) || pipelined)) {
404 			/* If not in the middle of a request, make one */
405 			if (reqbuf == NULL) {
406 				reqbuflen = makerequest(&reqbuf, argv[nreq],
407 				    servername, (nreq == argc - 1));
408 				reqbufpos = 0;
409 			}
410 
411 			/* If in pipelined mode, try to send the request */
412 			if (pipelined) {
413 				while (reqbufpos < reqbuflen) {
414 					len = send(sd, reqbuf + reqbufpos,
415 					    reqbuflen - reqbufpos, 0);
416 					if (len == -1)
417 						break;
418 					reqbufpos += len;
419 				}
420 				if (reqbufpos < reqbuflen) {
421 					if (errno != EAGAIN)
422 						goto conndied;
423 					break;
424 				} else {
425 					free(reqbuf);
426 					reqbuf = NULL;
427 					nreq++;
428 				}
429 			}
430 		}
431 
432 		/* Put connection back into blocking mode */
433 		if (pipelined) {
434 			if (fcntl(sd, F_SETFL, sdflags) == -1)
435 				err(1, "fcntl");
436 		}
437 
438 		/* Do we need to blocking-send a request? */
439 		if (nres == nreq) {
440 			while (reqbufpos < reqbuflen) {
441 				len = send(sd, reqbuf + reqbufpos,
442 				    reqbuflen - reqbufpos, 0);
443 				if (len == -1)
444 					goto conndied;
445 				reqbufpos += len;
446 			}
447 			free(reqbuf);
448 			reqbuf = NULL;
449 			nreq++;
450 		}
451 
452 		/* Scan through the response processing headers. */
453 		statuscode = 0;
454 		contentlength = -1;
455 		chunked = 0;
456 		keepalive = 0;
457 		do {
458 			/* Get a header line */
459 			error = readln(sd, resbuf, &resbuflen, &resbufpos);
460 			if (error)
461 				goto conndied;
462 			hln = resbuf + resbufpos;
463 			eolp = strnstr(hln, "\r\n", resbuflen - resbufpos);
464 			resbufpos = (eolp - resbuf) + 2;
465 			*eolp = '\0';
466 
467 			/* Make sure it doesn't contain a NUL character */
468 			if (strchr(hln, '\0') != eolp)
469 				goto conndied;
470 
471 			if (statuscode == 0) {
472 				/* The first line MUST be HTTP/1.x xxx ... */
473 				if ((strncmp(hln, "HTTP/1.", 7) != 0) ||
474 				    ! isdigit(hln[7]))
475 					goto conndied;
476 
477 				/*
478 				 * If the minor version number isn't zero,
479 				 * then we can assume that pipelining our
480 				 * requests is OK -- as long as we don't
481 				 * see a "Connection: close" line later
482 				 * and we either have a Content-Length or
483 				 * Transfer-Encoding: chunked header to
484 				 * tell us the length.
485 				 */
486 				if (hln[7] != '0')
487 					pipelined = 1;
488 
489 				/* Skip over the minor version number */
490 				hln = strchr(hln + 7, ' ');
491 				if (hln == NULL)
492 					goto conndied;
493 				else
494 					hln++;
495 
496 				/* Read the status code */
497 				while (isdigit(*hln)) {
498 					statuscode = statuscode * 10 +
499 					    *hln - '0';
500 					hln++;
501 				}
502 
503 				if (statuscode < 100 || statuscode > 599)
504 					goto conndied;
505 
506 				/* Ignore the rest of the line */
507 				continue;
508 			}
509 
510 			/*
511 			 * Check for "Connection: close" or
512 			 * "Connection: Keep-Alive" header
513 			 */
514 			if (strncasecmp(hln, "Connection:", 11) == 0) {
515 				hln += 11;
516 				if (strcasestr(hln, "close") != NULL)
517 					pipelined = 0;
518 				if (strcasestr(hln, "Keep-Alive") != NULL)
519 					keepalive = 1;
520 
521 				/* Next header... */
522 				continue;
523 			}
524 
525 			/* Check for "Content-Length:" header */
526 			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 
695 conndied:
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 
706 cleanupconn:
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