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