in libs/curl/lib/http.c [2824:3177]
CURLcode Curl_http_header(struct Curl_easy *data,
const char *hd, size_t hdlen)
{
struct connectdata *conn = data->conn;
CURLcode result;
struct SingleRequest *k = &data->req;
const char *v;
switch(hd[0]) {
case 'a':
case 'A':
#ifndef CURL_DISABLE_ALTSVC
v = (data->asi &&
((data->conn->handler->flags & PROTOPT_SSL) ||
#ifdef DEBUGBUILD
/* allow debug builds to circumvent the HTTPS restriction */
getenv("CURL_ALTSVC_HTTP")
#else
0
#endif
))? HD_VAL(hd, hdlen, "Alt-Svc:") : NULL;
if(v) {
/* the ALPN of the current request */
enum alpnid id = (conn->httpversion == 30)? ALPN_h3 :
(conn->httpversion == 20) ? ALPN_h2 : ALPN_h1;
return Curl_altsvc_parse(data, data->asi, v, id, conn->host.name,
curlx_uitous((unsigned int)conn->remote_port));
}
#endif
break;
case 'c':
case 'C':
/* Check for Content-Length: header lines to get size */
v = (!k->http_bodyless && !data->set.ignorecl)?
HD_VAL(hd, hdlen, "Content-Length:") : NULL;
if(v) {
curl_off_t contentlength;
CURLofft offt = curlx_strtoofft(v, NULL, 10, &contentlength);
if(offt == CURL_OFFT_OK) {
k->size = contentlength;
k->maxdownload = k->size;
}
else if(offt == CURL_OFFT_FLOW) {
/* out of range */
if(data->set.max_filesize) {
failf(data, "Maximum file size exceeded");
return CURLE_FILESIZE_EXCEEDED;
}
streamclose(conn, "overflow content-length");
infof(data, "Overflow Content-Length: value");
}
else {
/* negative or just rubbish - bad HTTP */
failf(data, "Invalid Content-Length: value");
return CURLE_WEIRD_SERVER_REPLY;
}
return CURLE_OK;
}
v = (!k->http_bodyless && data->set.str[STRING_ENCODING])?
HD_VAL(hd, hdlen, "Content-Encoding:") : NULL;
if(v) {
/*
* Process Content-Encoding. Look for the values: identity,
* gzip, deflate, compress, x-gzip and x-compress. x-gzip and
* x-compress are the same as gzip and compress. (Sec 3.5 RFC
* 2616). zlib cannot handle compress. However, errors are
* handled further down when the response body is processed
*/
return Curl_build_unencoding_stack(data, v, FALSE);
}
/* check for Content-Type: header lines to get the MIME-type */
v = HD_VAL(hd, hdlen, "Content-Type:");
if(v) {
char *contenttype = Curl_copy_header_value(hd);
if(!contenttype)
return CURLE_OUT_OF_MEMORY;
if(!*contenttype)
/* ignore empty data */
free(contenttype);
else {
Curl_safefree(data->info.contenttype);
data->info.contenttype = contenttype;
}
return CURLE_OK;
}
if(HD_IS_AND_SAYS(hd, hdlen, "Connection:", "close")) {
/*
* [RFC 2616, section 8.1.2.1]
* "Connection: close" is HTTP/1.1 language and means that
* the connection will close when this request has been
* served.
*/
streamclose(conn, "Connection: close used");
return CURLE_OK;
}
if((conn->httpversion == 10) &&
HD_IS_AND_SAYS(hd, hdlen, "Connection:", "keep-alive")) {
/*
* An HTTP/1.0 reply with the 'Connection: keep-alive' line
* tells us the connection will be kept alive for our
* pleasure. Default action for 1.0 is to close.
*
* [RFC2068, section 19.7.1] */
connkeep(conn, "Connection keep-alive");
infof(data, "HTTP/1.0 connection set to keep alive");
return CURLE_OK;
}
v = !k->http_bodyless? HD_VAL(hd, hdlen, "Content-Range:") : NULL;
if(v) {
/* Content-Range: bytes [num]-
Content-Range: bytes: [num]-
Content-Range: [num]-
Content-Range: [asterisk]/[total]
The second format was added since Sun's webserver
JavaWebServer/1.1.1 obviously sends the header this way!
The third added since some servers use that!
The fourth means the requested range was unsatisfied.
*/
const char *ptr = v;
/* Move forward until first digit or asterisk */
while(*ptr && !ISDIGIT(*ptr) && *ptr != '*')
ptr++;
/* if it truly stopped on a digit */
if(ISDIGIT(*ptr)) {
if(!curlx_strtoofft(ptr, NULL, 10, &k->offset)) {
if(data->state.resume_from == k->offset)
/* we asked for a resume and we got it */
k->content_range = TRUE;
}
}
else if(k->httpcode < 300)
data->state.resume_from = 0; /* get everything */
}
break;
case 'l':
case 'L':
v = (!k->http_bodyless &&
(data->set.timecondition || data->set.get_filetime))?
HD_VAL(hd, hdlen, "Last-Modified:") : NULL;
if(v) {
k->timeofdoc = Curl_getdate_capped(v);
if(data->set.get_filetime)
data->info.filetime = k->timeofdoc;
return CURLE_OK;
}
if((k->httpcode >= 300 && k->httpcode < 400) &&
HD_IS(hd, hdlen, "Location:") &&
!data->req.location) {
/* this is the URL that the server advises us to use instead */
char *location = Curl_copy_header_value(hd);
if(!location)
return CURLE_OUT_OF_MEMORY;
if(!*location)
/* ignore empty data */
free(location);
else {
data->req.location = location;
if(data->set.http_follow_location) {
DEBUGASSERT(!data->req.newurl);
data->req.newurl = strdup(data->req.location); /* clone */
if(!data->req.newurl)
return CURLE_OUT_OF_MEMORY;
/* some cases of POST and PUT etc needs to rewind the data
stream at this point */
result = http_perhapsrewind(data, conn);
if(result)
return result;
/* mark the next request as a followed location: */
data->state.this_is_a_follow = TRUE;
}
}
}
break;
case 'p':
case 'P':
#ifndef CURL_DISABLE_PROXY
v = HD_VAL(hd, hdlen, "Proxy-Connection:");
if(v) {
if((conn->httpversion == 10) && conn->bits.httpproxy &&
HD_IS_AND_SAYS(hd, hdlen, "Proxy-Connection:", "keep-alive")) {
/*
* When an HTTP/1.0 reply comes when using a proxy, the
* 'Proxy-Connection: keep-alive' line tells us the
* connection will be kept alive for our pleasure.
* Default action for 1.0 is to close.
*/
connkeep(conn, "Proxy-Connection keep-alive"); /* do not close */
infof(data, "HTTP/1.0 proxy connection set to keep alive");
}
else if((conn->httpversion == 11) && conn->bits.httpproxy &&
HD_IS_AND_SAYS(hd, hdlen, "Proxy-Connection:", "close")) {
/*
* We get an HTTP/1.1 response from a proxy and it says it will
* close down after this transfer.
*/
connclose(conn, "Proxy-Connection: asked to close after done");
infof(data, "HTTP/1.1 proxy connection set close");
}
return CURLE_OK;
}
#endif
if((407 == k->httpcode) && HD_IS(hd, hdlen, "Proxy-authenticate:")) {
char *auth = Curl_copy_header_value(hd);
if(!auth)
return CURLE_OUT_OF_MEMORY;
result = Curl_http_input_auth(data, TRUE, auth);
free(auth);
return result;
}
#ifdef USE_SPNEGO
if(HD_IS(hd, hdlen, "Persistent-Auth:")) {
struct negotiatedata *negdata = &conn->negotiate;
struct auth *authp = &data->state.authhost;
if(authp->picked == CURLAUTH_NEGOTIATE) {
char *persistentauth = Curl_copy_header_value(hd);
if(!persistentauth)
return CURLE_OUT_OF_MEMORY;
negdata->noauthpersist = checkprefix("false", persistentauth)?
TRUE:FALSE;
negdata->havenoauthpersist = TRUE;
infof(data, "Negotiate: noauthpersist -> %d, header part: %s",
negdata->noauthpersist, persistentauth);
free(persistentauth);
}
}
#endif
break;
case 'r':
case 'R':
v = HD_VAL(hd, hdlen, "Retry-After:");
if(v) {
/* Retry-After = HTTP-date / delay-seconds */
curl_off_t retry_after = 0; /* zero for unknown or "now" */
/* Try it as a decimal number, if it works it is not a date */
(void)curlx_strtoofft(v, NULL, 10, &retry_after);
if(!retry_after) {
time_t date = Curl_getdate_capped(v);
if(-1 != date)
/* convert date to number of seconds into the future */
retry_after = date - time(NULL);
}
data->info.retry_after = retry_after; /* store it */
return CURLE_OK;
}
break;
case 's':
case 'S':
#if !defined(CURL_DISABLE_COOKIES)
v = (data->cookies && data->state.cookie_engine)?
HD_VAL(hd, hdlen, "Set-Cookie:") : NULL;
if(v) {
/* If there is a custom-set Host: name, use it here, or else use
* real peer hostname. */
const char *host = data->state.aptr.cookiehost?
data->state.aptr.cookiehost:conn->host.name;
const bool secure_context =
conn->handler->protocol&(CURLPROTO_HTTPS|CURLPROTO_WSS) ||
strcasecompare("localhost", host) ||
!strcmp(host, "127.0.0.1") ||
!strcmp(host, "::1") ? TRUE : FALSE;
Curl_share_lock(data, CURL_LOCK_DATA_COOKIE,
CURL_LOCK_ACCESS_SINGLE);
Curl_cookie_add(data, data->cookies, TRUE, FALSE, v, host,
data->state.up.path, secure_context);
Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
return CURLE_OK;
}
#endif
#ifndef CURL_DISABLE_HSTS
/* If enabled, the header is incoming and this is over HTTPS */
v = (data->hsts &&
((conn->handler->flags & PROTOPT_SSL) ||
#ifdef DEBUGBUILD
/* allow debug builds to circumvent the HTTPS restriction */
getenv("CURL_HSTS_HTTP")
#else
0
#endif
)
)? HD_VAL(hd, hdlen, "Strict-Transport-Security:") : NULL;
if(v) {
CURLcode check =
Curl_hsts_parse(data->hsts, conn->host.name, v);
if(check)
infof(data, "Illegal STS header skipped");
#ifdef DEBUGBUILD
else
infof(data, "Parsed STS header fine (%zu entries)",
data->hsts->list.size);
#endif
}
#endif
break;
case 't':
case 'T':
/* RFC 9112, ch. 6.1
* "Transfer-Encoding MAY be sent in a response to a HEAD request or
* in a 304 (Not Modified) response (Section 15.4.5 of [HTTP]) to a
* GET request, neither of which includes a message body, to indicate
* that the origin server would have applied a transfer coding to the
* message body if the request had been an unconditional GET."
*
* Read: in these cases the 'Transfer-Encoding' does not apply
* to any data following the response headers. Do not add any decoders.
*/
v = (!k->http_bodyless &&
(data->state.httpreq != HTTPREQ_HEAD) &&
(k->httpcode != 304))?
HD_VAL(hd, hdlen, "Transfer-Encoding:") : NULL;
if(v) {
/* One or more encodings. We check for chunked and/or a compression
algorithm. */
result = Curl_build_unencoding_stack(data, v, TRUE);
if(result)
return result;
if(!k->chunk && data->set.http_transfer_encoding) {
/* if this is not chunked, only close can signal the end of this
* transfer as Content-Length is said not to be trusted for
* transfer-encoding! */
connclose(conn, "HTTP/1.1 transfer-encoding without chunks");
k->ignore_cl = TRUE;
}
return CURLE_OK;
}
break;
case 'w':
case 'W':
if((401 == k->httpcode) && HD_IS(hd, hdlen, "WWW-Authenticate:")) {
char *auth = Curl_copy_header_value(hd);
if(!auth)
return CURLE_OUT_OF_MEMORY;
result = Curl_http_input_auth(data, FALSE, auth);
free(auth);
return result;
}
break;
}
if(conn->handler->protocol & CURLPROTO_RTSP) {
result = Curl_rtsp_parseheader(data, hd);
if(result)
return result;
}
return CURLE_OK;
}