1 |
/* $MidnightBSD$ */ |
2 |
/*- |
3 |
* Copyright 2005 Colin Percival |
4 |
* All rights reserved |
5 |
* |
6 |
* Redistribution and use in source and binary forms, with or without |
7 |
* modification, are permitted providing that the following conditions |
8 |
* are met: |
9 |
* 1. Redistributions of source code must retain the above copyright |
10 |
* notice, this list of conditions and the following disclaimer. |
11 |
* 2. Redistributions in binary form must reproduce the above copyright |
12 |
* notice, this list of conditions and the following disclaimer in the |
13 |
* documentation and/or other materials provided with the distribution. |
14 |
* |
15 |
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
16 |
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
17 |
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
18 |
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY |
19 |
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
20 |
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS |
21 |
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) |
22 |
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
23 |
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING |
24 |
* IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
25 |
* POSSIBILITY OF SUCH DAMAGE. |
26 |
*/ |
27 |
|
28 |
#include <sys/cdefs.h> |
29 |
__FBSDID("$FreeBSD: stable/10/usr.sbin/portsnap/phttpget/phttpget.c 306428 2016-09-29 01:53:29Z emaste $"); |
30 |
|
31 |
#include <sys/types.h> |
32 |
#include <sys/time.h> |
33 |
#include <sys/socket.h> |
34 |
|
35 |
#include <ctype.h> |
36 |
#include <err.h> |
37 |
#include <errno.h> |
38 |
#include <fcntl.h> |
39 |
#include <limits.h> |
40 |
#include <netdb.h> |
41 |
#include <stdint.h> |
42 |
#include <stdio.h> |
43 |
#include <stdlib.h> |
44 |
#include <string.h> |
45 |
#include <sysexits.h> |
46 |
#include <unistd.h> |
47 |
|
48 |
static const char * env_HTTP_PROXY; |
49 |
static char * env_HTTP_PROXY_AUTH; |
50 |
static const char * env_HTTP_USER_AGENT; |
51 |
static char * env_HTTP_TIMEOUT; |
52 |
static const char * proxyport; |
53 |
static char * proxyauth; |
54 |
|
55 |
static struct timeval timo = { 15, 0}; |
56 |
|
57 |
static void |
58 |
usage(void) |
59 |
{ |
60 |
|
61 |
fprintf(stderr, "usage: phttpget server [file ...]\n"); |
62 |
exit(EX_USAGE); |
63 |
} |
64 |
|
65 |
/* |
66 |
* Base64 encode a string; the string returned, if non-NULL, is |
67 |
* allocated using malloc() and must be freed by the caller. |
68 |
*/ |
69 |
static char * |
70 |
b64enc(const char *ptext) |
71 |
{ |
72 |
static const char base64[] = |
73 |
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" |
74 |
"abcdefghijklmnopqrstuvwxyz" |
75 |
"0123456789+/"; |
76 |
const char *pt; |
77 |
char *ctext, *pc; |
78 |
size_t ptlen, ctlen; |
79 |
uint32_t t; |
80 |
unsigned int j; |
81 |
|
82 |
/* |
83 |
* Encoded length is 4 characters per 3-byte block or partial |
84 |
* block of plaintext, plus one byte for the terminating NUL |
85 |
*/ |
86 |
ptlen = strlen(ptext); |
87 |
if (ptlen > ((SIZE_MAX - 1) / 4) * 3 - 2) |
88 |
return NULL; /* Possible integer overflow */ |
89 |
ctlen = 4 * ((ptlen + 2) / 3) + 1; |
90 |
if ((ctext = malloc(ctlen)) == NULL) |
91 |
return NULL; |
92 |
ctext[ctlen - 1] = 0; |
93 |
|
94 |
/* |
95 |
* Scan through ptext, reading up to 3 bytes from ptext and |
96 |
* writing 4 bytes to ctext, until we run out of input. |
97 |
*/ |
98 |
for (pt = ptext, pc = ctext; ptlen; ptlen -= 3, pc += 4) { |
99 |
/* Read 3 bytes */ |
100 |
for (t = j = 0; j < 3; j++) { |
101 |
t <<= 8; |
102 |
if (j < ptlen) |
103 |
t += *pt++; |
104 |
} |
105 |
|
106 |
/* Write 4 bytes */ |
107 |
for (j = 0; j < 4; j++) { |
108 |
if (j <= ptlen + 1) |
109 |
pc[j] = base64[(t >> 18) & 0x3f]; |
110 |
else |
111 |
pc[j] = '='; |
112 |
t <<= 6; |
113 |
} |
114 |
|
115 |
/* If we're done, exit the loop */ |
116 |
if (ptlen <= 3) |
117 |
break; |
118 |
} |
119 |
|
120 |
return (ctext); |
121 |
} |
122 |
|
123 |
static void |
124 |
readenv(void) |
125 |
{ |
126 |
char *proxy_auth_userpass, *proxy_auth_userpass64, *p; |
127 |
char *proxy_auth_user = NULL; |
128 |
char *proxy_auth_pass = NULL; |
129 |
long http_timeout; |
130 |
|
131 |
env_HTTP_PROXY = getenv("HTTP_PROXY"); |
132 |
if (env_HTTP_PROXY == NULL) |
133 |
env_HTTP_PROXY = getenv("http_proxy"); |
134 |
if (env_HTTP_PROXY != NULL) { |
135 |
if (strncmp(env_HTTP_PROXY, "http://", 7) == 0) |
136 |
env_HTTP_PROXY += 7; |
137 |
p = strchr(env_HTTP_PROXY, '/'); |
138 |
if (p != NULL) |
139 |
*p = 0; |
140 |
p = strchr(env_HTTP_PROXY, ':'); |
141 |
if (p != NULL) { |
142 |
*p = 0; |
143 |
proxyport = p + 1; |
144 |
} else |
145 |
proxyport = "3128"; |
146 |
} |
147 |
|
148 |
env_HTTP_PROXY_AUTH = getenv("HTTP_PROXY_AUTH"); |
149 |
if ((env_HTTP_PROXY != NULL) && |
150 |
(env_HTTP_PROXY_AUTH != NULL) && |
151 |
(strncasecmp(env_HTTP_PROXY_AUTH, "basic:" , 6) == 0)) { |
152 |
/* Ignore authentication scheme */ |
153 |
(void) strsep(&env_HTTP_PROXY_AUTH, ":"); |
154 |
|
155 |
/* Ignore realm */ |
156 |
(void) strsep(&env_HTTP_PROXY_AUTH, ":"); |
157 |
|
158 |
/* Obtain username and password */ |
159 |
proxy_auth_user = strsep(&env_HTTP_PROXY_AUTH, ":"); |
160 |
proxy_auth_pass = env_HTTP_PROXY_AUTH; |
161 |
} |
162 |
|
163 |
if ((proxy_auth_user != NULL) && (proxy_auth_pass != NULL)) { |
164 |
asprintf(&proxy_auth_userpass, "%s:%s", |
165 |
proxy_auth_user, proxy_auth_pass); |
166 |
if (proxy_auth_userpass == NULL) |
167 |
err(1, "asprintf"); |
168 |
|
169 |
proxy_auth_userpass64 = b64enc(proxy_auth_userpass); |
170 |
if (proxy_auth_userpass64 == NULL) |
171 |
err(1, "malloc"); |
172 |
|
173 |
asprintf(&proxyauth, "Proxy-Authorization: Basic %s\r\n", |
174 |
proxy_auth_userpass64); |
175 |
if (proxyauth == NULL) |
176 |
err(1, "asprintf"); |
177 |
|
178 |
free(proxy_auth_userpass); |
179 |
free(proxy_auth_userpass64); |
180 |
} else |
181 |
proxyauth = NULL; |
182 |
|
183 |
env_HTTP_USER_AGENT = getenv("HTTP_USER_AGENT"); |
184 |
if (env_HTTP_USER_AGENT == NULL) |
185 |
env_HTTP_USER_AGENT = "phttpget/0.1"; |
186 |
|
187 |
env_HTTP_TIMEOUT = getenv("HTTP_TIMEOUT"); |
188 |
if (env_HTTP_TIMEOUT != NULL) { |
189 |
http_timeout = strtol(env_HTTP_TIMEOUT, &p, 10); |
190 |
if ((*env_HTTP_TIMEOUT == '\0') || (*p != '\0') || |
191 |
(http_timeout < 0)) |
192 |
warnx("HTTP_TIMEOUT (%s) is not a positive integer", |
193 |
env_HTTP_TIMEOUT); |
194 |
else |
195 |
timo.tv_sec = http_timeout; |
196 |
} |
197 |
} |
198 |
|
199 |
static int |
200 |
makerequest(char ** buf, char * path, char * server, int connclose) |
201 |
{ |
202 |
int buflen; |
203 |
|
204 |
buflen = asprintf(buf, |
205 |
"GET %s%s/%s HTTP/1.1\r\n" |
206 |
"Host: %s\r\n" |
207 |
"User-Agent: %s\r\n" |
208 |
"%s" |
209 |
"%s" |
210 |
"\r\n", |
211 |
env_HTTP_PROXY ? "http://" : "", |
212 |
env_HTTP_PROXY ? server : "", |
213 |
path, server, env_HTTP_USER_AGENT, |
214 |
proxyauth ? proxyauth : "", |
215 |
connclose ? "Connection: Close\r\n" : "Connection: Keep-Alive\r\n"); |
216 |
if (buflen == -1) |
217 |
err(1, "asprintf"); |
218 |
return(buflen); |
219 |
} |
220 |
|
221 |
static int |
222 |
readln(int sd, char * resbuf, int * resbuflen, int * resbufpos) |
223 |
{ |
224 |
ssize_t len; |
225 |
|
226 |
while (strnstr(resbuf + *resbufpos, "\r\n", |
227 |
*resbuflen - *resbufpos) == NULL) { |
228 |
/* Move buffered data to the start of the buffer */ |
229 |
if (*resbufpos != 0) { |
230 |
memmove(resbuf, resbuf + *resbufpos, |
231 |
*resbuflen - *resbufpos); |
232 |
*resbuflen -= *resbufpos; |
233 |
*resbufpos = 0; |
234 |
} |
235 |
|
236 |
/* If the buffer is full, complain */ |
237 |
if (*resbuflen == BUFSIZ) |
238 |
return -1; |
239 |
|
240 |
/* Read more data into the buffer */ |
241 |
len = recv(sd, resbuf + *resbuflen, BUFSIZ - *resbuflen, 0); |
242 |
if ((len == 0) || |
243 |
((len == -1) && (errno != EINTR))) |
244 |
return -1; |
245 |
|
246 |
if (len != -1) |
247 |
*resbuflen += len; |
248 |
} |
249 |
|
250 |
return 0; |
251 |
} |
252 |
|
253 |
static int |
254 |
copybytes(int sd, int fd, off_t copylen, char * resbuf, int * resbuflen, |
255 |
int * resbufpos) |
256 |
{ |
257 |
ssize_t len; |
258 |
|
259 |
while (copylen) { |
260 |
/* Write data from resbuf to fd */ |
261 |
len = *resbuflen - *resbufpos; |
262 |
if (copylen < len) |
263 |
len = copylen; |
264 |
if (len > 0) { |
265 |
if (fd != -1) |
266 |
len = write(fd, resbuf + *resbufpos, len); |
267 |
if (len == -1) |
268 |
err(1, "write"); |
269 |
*resbufpos += len; |
270 |
copylen -= len; |
271 |
continue; |
272 |
} |
273 |
|
274 |
/* Read more data into buffer */ |
275 |
len = recv(sd, resbuf, BUFSIZ, 0); |
276 |
if (len == -1) { |
277 |
if (errno == EINTR) |
278 |
continue; |
279 |
return -1; |
280 |
} else if (len == 0) { |
281 |
return -2; |
282 |
} else { |
283 |
*resbuflen = len; |
284 |
*resbufpos = 0; |
285 |
} |
286 |
} |
287 |
|
288 |
return 0; |
289 |
} |
290 |
|
291 |
int |
292 |
main(int argc, char *argv[]) |
293 |
{ |
294 |
struct addrinfo hints; /* Hints to getaddrinfo */ |
295 |
struct addrinfo *res; /* Pointer to server address being used */ |
296 |
struct addrinfo *res0; /* Pointer to server addresses */ |
297 |
char * resbuf = NULL; /* Response buffer */ |
298 |
int resbufpos = 0; /* Response buffer position */ |
299 |
int resbuflen = 0; /* Response buffer length */ |
300 |
char * eolp; /* Pointer to "\r\n" within resbuf */ |
301 |
char * hln; /* Pointer within header line */ |
302 |
char * servername; /* Name of server */ |
303 |
char * fname = NULL; /* Name of downloaded file */ |
304 |
char * reqbuf = NULL; /* Request buffer */ |
305 |
int reqbufpos = 0; /* Request buffer position */ |
306 |
int reqbuflen = 0; /* Request buffer length */ |
307 |
ssize_t len; /* Length sent or received */ |
308 |
int nreq = 0; /* Number of next request to send */ |
309 |
int nres = 0; /* Number of next reply to receive */ |
310 |
int pipelined = 0; /* != 0 if connection in pipelined mode. */ |
311 |
int keepalive; /* != 0 if HTTP/1.0 keep-alive rcvd. */ |
312 |
int sd = -1; /* Socket descriptor */ |
313 |
int sdflags = 0; /* Flags on the socket sd */ |
314 |
int fd = -1; /* Descriptor for download target file */ |
315 |
int error; /* Error code */ |
316 |
int statuscode; /* HTTP Status code */ |
317 |
off_t contentlength; /* Value from Content-Length header */ |
318 |
int chunked; /* != if transfer-encoding is chunked */ |
319 |
off_t clen; /* Chunk length */ |
320 |
int firstreq = 0; /* # of first request for this connection */ |
321 |
int val; /* Value used for setsockopt call */ |
322 |
|
323 |
/* Check that the arguments are sensible */ |
324 |
if (argc < 2) |
325 |
usage(); |
326 |
|
327 |
/* Read important environment variables */ |
328 |
readenv(); |
329 |
|
330 |
/* Get server name and adjust arg[cv] to point at file names */ |
331 |
servername = argv[1]; |
332 |
argv += 2; |
333 |
argc -= 2; |
334 |
|
335 |
/* Allocate response buffer */ |
336 |
resbuf = malloc(BUFSIZ); |
337 |
if (resbuf == NULL) |
338 |
err(1, "malloc"); |
339 |
|
340 |
/* Look up server */ |
341 |
memset(&hints, 0, sizeof(hints)); |
342 |
hints.ai_family = PF_UNSPEC; |
343 |
hints.ai_socktype = SOCK_STREAM; |
344 |
error = getaddrinfo(env_HTTP_PROXY ? env_HTTP_PROXY : servername, |
345 |
env_HTTP_PROXY ? proxyport : "http", &hints, &res0); |
346 |
if (error) |
347 |
errx(1, "host = %s, port = %s: %s", |
348 |
env_HTTP_PROXY ? env_HTTP_PROXY : servername, |
349 |
env_HTTP_PROXY ? proxyport : "http", |
350 |
gai_strerror(error)); |
351 |
if (res0 == NULL) |
352 |
errx(1, "could not look up %s", servername); |
353 |
res = res0; |
354 |
|
355 |
/* Do the fetching */ |
356 |
while (nres < argc) { |
357 |
/* Make sure we have a connected socket */ |
358 |
for (; sd == -1; res = res->ai_next) { |
359 |
/* No addresses left to try :-( */ |
360 |
if (res == NULL) |
361 |
errx(1, "Could not connect to %s", servername); |
362 |
|
363 |
/* Create a socket... */ |
364 |
sd = socket(res->ai_family, res->ai_socktype, |
365 |
res->ai_protocol); |
366 |
if (sd == -1) |
367 |
continue; |
368 |
|
369 |
/* ... set 15-second timeouts ... */ |
370 |
setsockopt(sd, SOL_SOCKET, SO_SNDTIMEO, |
371 |
(void *)&timo, (socklen_t)sizeof(timo)); |
372 |
setsockopt(sd, SOL_SOCKET, SO_RCVTIMEO, |
373 |
(void *)&timo, (socklen_t)sizeof(timo)); |
374 |
|
375 |
/* ... disable SIGPIPE generation ... */ |
376 |
val = 1; |
377 |
setsockopt(sd, SOL_SOCKET, SO_NOSIGPIPE, |
378 |
(void *)&val, sizeof(int)); |
379 |
|
380 |
/* ... and connect to the server. */ |
381 |
if(connect(sd, res->ai_addr, res->ai_addrlen)) { |
382 |
close(sd); |
383 |
sd = -1; |
384 |
continue; |
385 |
} |
386 |
|
387 |
firstreq = nres; |
388 |
} |
389 |
|
390 |
/* |
391 |
* If in pipelined HTTP mode, put socket into non-blocking |
392 |
* mode, since we're probably going to want to try to send |
393 |
* several HTTP requests. |
394 |
*/ |
395 |
if (pipelined) { |
396 |
sdflags = fcntl(sd, F_GETFL); |
397 |
if (fcntl(sd, F_SETFL, sdflags | O_NONBLOCK) == -1) |
398 |
err(1, "fcntl"); |
399 |
} |
400 |
|
401 |
/* Construct requests and/or send them without blocking */ |
402 |
while ((nreq < argc) && ((reqbuf == NULL) || pipelined)) { |
403 |
/* If not in the middle of a request, make one */ |
404 |
if (reqbuf == NULL) { |
405 |
reqbuflen = makerequest(&reqbuf, argv[nreq], |
406 |
servername, (nreq == argc - 1)); |
407 |
reqbufpos = 0; |
408 |
} |
409 |
|
410 |
/* If in pipelined mode, try to send the request */ |
411 |
if (pipelined) { |
412 |
while (reqbufpos < reqbuflen) { |
413 |
len = send(sd, reqbuf + reqbufpos, |
414 |
reqbuflen - reqbufpos, 0); |
415 |
if (len == -1) |
416 |
break; |
417 |
reqbufpos += len; |
418 |
} |
419 |
if (reqbufpos < reqbuflen) { |
420 |
if (errno != EAGAIN) |
421 |
goto conndied; |
422 |
break; |
423 |
} else { |
424 |
free(reqbuf); |
425 |
reqbuf = NULL; |
426 |
nreq++; |
427 |
} |
428 |
} |
429 |
} |
430 |
|
431 |
/* Put connection back into blocking mode */ |
432 |
if (pipelined) { |
433 |
if (fcntl(sd, F_SETFL, sdflags) == -1) |
434 |
err(1, "fcntl"); |
435 |
} |
436 |
|
437 |
/* Do we need to blocking-send a request? */ |
438 |
if (nres == nreq) { |
439 |
while (reqbufpos < reqbuflen) { |
440 |
len = send(sd, reqbuf + reqbufpos, |
441 |
reqbuflen - reqbufpos, 0); |
442 |
if (len == -1) |
443 |
goto conndied; |
444 |
reqbufpos += len; |
445 |
} |
446 |
free(reqbuf); |
447 |
reqbuf = NULL; |
448 |
nreq++; |
449 |
} |
450 |
|
451 |
/* Scan through the response processing headers. */ |
452 |
statuscode = 0; |
453 |
contentlength = -1; |
454 |
chunked = 0; |
455 |
keepalive = 0; |
456 |
do { |
457 |
/* Get a header line */ |
458 |
error = readln(sd, resbuf, &resbuflen, &resbufpos); |
459 |
if (error) |
460 |
goto conndied; |
461 |
hln = resbuf + resbufpos; |
462 |
eolp = strnstr(hln, "\r\n", resbuflen - resbufpos); |
463 |
resbufpos = (eolp - resbuf) + 2; |
464 |
*eolp = '\0'; |
465 |
|
466 |
/* Make sure it doesn't contain a NUL character */ |
467 |
if (strchr(hln, '\0') != eolp) |
468 |
goto conndied; |
469 |
|
470 |
if (statuscode == 0) { |
471 |
/* The first line MUST be HTTP/1.x xxx ... */ |
472 |
if ((strncmp(hln, "HTTP/1.", 7) != 0) || |
473 |
! isdigit(hln[7])) |
474 |
goto conndied; |
475 |
|
476 |
/* |
477 |
* If the minor version number isn't zero, |
478 |
* then we can assume that pipelining our |
479 |
* requests is OK -- as long as we don't |
480 |
* see a "Connection: close" line later |
481 |
* and we either have a Content-Length or |
482 |
* Transfer-Encoding: chunked header to |
483 |
* tell us the length. |
484 |
*/ |
485 |
if (hln[7] != '0') |
486 |
pipelined = 1; |
487 |
|
488 |
/* Skip over the minor version number */ |
489 |
hln = strchr(hln + 7, ' '); |
490 |
if (hln == NULL) |
491 |
goto conndied; |
492 |
else |
493 |
hln++; |
494 |
|
495 |
/* Read the status code */ |
496 |
while (isdigit(*hln)) { |
497 |
statuscode = statuscode * 10 + |
498 |
*hln - '0'; |
499 |
hln++; |
500 |
} |
501 |
|
502 |
if (statuscode < 100 || statuscode > 599) |
503 |
goto conndied; |
504 |
|
505 |
/* Ignore the rest of the line */ |
506 |
continue; |
507 |
} |
508 |
|
509 |
/* |
510 |
* Check for "Connection: close" or |
511 |
* "Connection: Keep-Alive" header |
512 |
*/ |
513 |
if (strncasecmp(hln, "Connection:", 11) == 0) { |
514 |
hln += 11; |
515 |
if (strcasestr(hln, "close") != NULL) |
516 |
pipelined = 0; |
517 |
if (strcasestr(hln, "Keep-Alive") != NULL) |
518 |
keepalive = 1; |
519 |
|
520 |
/* Next header... */ |
521 |
continue; |
522 |
} |
523 |
|
524 |
/* Check for "Content-Length:" header */ |
525 |
if (strncasecmp(hln, "Content-Length:", 15) == 0) { |
526 |
hln += 15; |
527 |
contentlength = 0; |
528 |
|
529 |
/* Find the start of the length */ |
530 |
while (!isdigit(*hln) && (*hln != '\0')) |
531 |
hln++; |
532 |
|
533 |
/* Compute the length */ |
534 |
while (isdigit(*hln)) { |
535 |
if (contentlength >= OFF_MAX / 10) { |
536 |
/* Nasty people... */ |
537 |
goto conndied; |
538 |
} |
539 |
contentlength = contentlength * 10 + |
540 |
*hln - '0'; |
541 |
hln++; |
542 |
} |
543 |
|
544 |
/* Next header... */ |
545 |
continue; |
546 |
} |
547 |
|
548 |
/* Check for "Transfer-Encoding: chunked" header */ |
549 |
if (strncasecmp(hln, "Transfer-Encoding:", 18) == 0) { |
550 |
hln += 18; |
551 |
if (strcasestr(hln, "chunked") != NULL) |
552 |
chunked = 1; |
553 |
|
554 |
/* Next header... */ |
555 |
continue; |
556 |
} |
557 |
|
558 |
/* We blithely ignore any other header lines */ |
559 |
|
560 |
/* No more header lines */ |
561 |
if (strlen(hln) == 0) { |
562 |
/* |
563 |
* If the status code was 1xx, then there will |
564 |
* be a real header later. Servers may emit |
565 |
* 1xx header blocks at will, but since we |
566 |
* don't expect one, we should just ignore it. |
567 |
*/ |
568 |
if (100 <= statuscode && statuscode <= 199) { |
569 |
statuscode = 0; |
570 |
continue; |
571 |
} |
572 |
|
573 |
/* End of header; message body follows */ |
574 |
break; |
575 |
} |
576 |
} while (1); |
577 |
|
578 |
/* No message body for 204 or 304 */ |
579 |
if (statuscode == 204 || statuscode == 304) { |
580 |
nres++; |
581 |
continue; |
582 |
} |
583 |
|
584 |
/* |
585 |
* There should be a message body coming, but we only want |
586 |
* to send it to a file if the status code is 200 |
587 |
*/ |
588 |
if (statuscode == 200) { |
589 |
/* Generate a file name for the download */ |
590 |
fname = strrchr(argv[nres], '/'); |
591 |
if (fname == NULL) |
592 |
fname = argv[nres]; |
593 |
else |
594 |
fname++; |
595 |
if (strlen(fname) == 0) |
596 |
errx(1, "Cannot obtain file name from %s\n", |
597 |
argv[nres]); |
598 |
|
599 |
fd = open(fname, O_CREAT | O_TRUNC | O_WRONLY, 0644); |
600 |
if (fd == -1) |
601 |
errx(1, "open(%s)", fname); |
602 |
} |
603 |
|
604 |
/* Read the message and send data to fd if appropriate */ |
605 |
if (chunked) { |
606 |
/* Handle a chunked-encoded entity */ |
607 |
|
608 |
/* Read chunks */ |
609 |
do { |
610 |
error = readln(sd, resbuf, &resbuflen, |
611 |
&resbufpos); |
612 |
if (error) |
613 |
goto conndied; |
614 |
hln = resbuf + resbufpos; |
615 |
eolp = strstr(hln, "\r\n"); |
616 |
resbufpos = (eolp - resbuf) + 2; |
617 |
|
618 |
clen = 0; |
619 |
while (isxdigit(*hln)) { |
620 |
if (clen >= OFF_MAX / 16) { |
621 |
/* Nasty people... */ |
622 |
goto conndied; |
623 |
} |
624 |
if (isdigit(*hln)) |
625 |
clen = clen * 16 + *hln - '0'; |
626 |
else |
627 |
clen = clen * 16 + 10 + |
628 |
tolower(*hln) - 'a'; |
629 |
hln++; |
630 |
} |
631 |
|
632 |
error = copybytes(sd, fd, clen, resbuf, |
633 |
&resbuflen, &resbufpos); |
634 |
if (error) { |
635 |
goto conndied; |
636 |
} |
637 |
} while (clen != 0); |
638 |
|
639 |
/* Read trailer and final CRLF */ |
640 |
do { |
641 |
error = readln(sd, resbuf, &resbuflen, |
642 |
&resbufpos); |
643 |
if (error) |
644 |
goto conndied; |
645 |
hln = resbuf + resbufpos; |
646 |
eolp = strstr(hln, "\r\n"); |
647 |
resbufpos = (eolp - resbuf) + 2; |
648 |
} while (hln != eolp); |
649 |
} else if (contentlength != -1) { |
650 |
error = copybytes(sd, fd, contentlength, resbuf, |
651 |
&resbuflen, &resbufpos); |
652 |
if (error) |
653 |
goto conndied; |
654 |
} else { |
655 |
/* |
656 |
* Not chunked, and no content length header. |
657 |
* Read everything until the server closes the |
658 |
* socket. |
659 |
*/ |
660 |
error = copybytes(sd, fd, OFF_MAX, resbuf, |
661 |
&resbuflen, &resbufpos); |
662 |
if (error == -1) |
663 |
goto conndied; |
664 |
pipelined = 0; |
665 |
} |
666 |
|
667 |
if (fd != -1) { |
668 |
close(fd); |
669 |
fd = -1; |
670 |
} |
671 |
|
672 |
fprintf(stderr, "http://%s/%s: %d ", servername, argv[nres], |
673 |
statuscode); |
674 |
if (statuscode == 200) |
675 |
fprintf(stderr, "OK\n"); |
676 |
else if (statuscode < 300) |
677 |
fprintf(stderr, "Successful (ignored)\n"); |
678 |
else if (statuscode < 400) |
679 |
fprintf(stderr, "Redirection (ignored)\n"); |
680 |
else |
681 |
fprintf(stderr, "Error (ignored)\n"); |
682 |
|
683 |
/* We've finished this file! */ |
684 |
nres++; |
685 |
|
686 |
/* |
687 |
* If necessary, clean up this connection so that we |
688 |
* can start a new one. |
689 |
*/ |
690 |
if (pipelined == 0 && keepalive == 0) |
691 |
goto cleanupconn; |
692 |
continue; |
693 |
|
694 |
conndied: |
695 |
/* |
696 |
* Something went wrong -- our connection died, the server |
697 |
* sent us garbage, etc. If this happened on the first |
698 |
* request we sent over this connection, give up. Otherwise, |
699 |
* close this connection, open a new one, and reissue the |
700 |
* request. |
701 |
*/ |
702 |
if (nres == firstreq) |
703 |
errx(1, "Connection failure"); |
704 |
|
705 |
cleanupconn: |
706 |
/* |
707 |
* Clean up our connection and keep on going |
708 |
*/ |
709 |
shutdown(sd, SHUT_RDWR); |
710 |
close(sd); |
711 |
sd = -1; |
712 |
if (fd != -1) { |
713 |
close(fd); |
714 |
fd = -1; |
715 |
} |
716 |
if (reqbuf != NULL) { |
717 |
free(reqbuf); |
718 |
reqbuf = NULL; |
719 |
} |
720 |
nreq = nres; |
721 |
res = res0; |
722 |
pipelined = 0; |
723 |
resbufpos = resbuflen = 0; |
724 |
continue; |
725 |
} |
726 |
|
727 |
free(resbuf); |
728 |
freeaddrinfo(res0); |
729 |
|
730 |
return 0; |
731 |
} |