mirror of
https://github.com/curl/curl.git
synced 2025-09-08 05:15:08 +03:00
parent
c257753ee9
commit
cbc062a7b8
780
lib/http.c
780
lib/http.c
|
@ -3000,379 +3000,483 @@ checkprotoprefix(struct Curl_easy *data, struct connectdata *conn,
|
||||||
((hdlen) > ((sizeof(n)-1) + (sizeof(v)-1))) && \
|
((hdlen) > ((sizeof(n)-1) + (sizeof(v)-1))) && \
|
||||||
Curl_compareheader(hd, STRCONST(n), STRCONST(v)))
|
Curl_compareheader(hd, STRCONST(n), STRCONST(v)))
|
||||||
|
|
||||||
|
/*
|
||||||
|
* http_header_a() parses a single response header starting with A.
|
||||||
|
*/
|
||||||
|
static CURLcode http_header_a(struct Curl_easy *data,
|
||||||
|
const char *hd, size_t hdlen)
|
||||||
|
{
|
||||||
|
#ifndef CURL_DISABLE_ALTSVC
|
||||||
|
const char *v;
|
||||||
|
struct connectdata *conn = data->conn;
|
||||||
|
v = (data->asi &&
|
||||||
|
(Curl_conn_is_ssl(data->conn, FIRSTSOCKET) ||
|
||||||
|
#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 */
|
||||||
|
struct SingleRequest *k = &data->req;
|
||||||
|
enum alpnid id = (k->httpversion == 30) ? ALPN_h3 :
|
||||||
|
(k->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));
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
(void)data;
|
||||||
|
(void)hd;
|
||||||
|
(void)hdlen;
|
||||||
|
#endif
|
||||||
|
return CURLE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* http_header_c() parses a single response header starting with C.
|
||||||
|
*/
|
||||||
|
static CURLcode http_header_c(struct Curl_easy *data,
|
||||||
|
const char *hd, size_t hdlen)
|
||||||
|
{
|
||||||
|
struct connectdata *conn = data->conn;
|
||||||
|
struct SingleRequest *k = &data->req;
|
||||||
|
const char *v;
|
||||||
|
|
||||||
|
/* 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;
|
||||||
|
int offt = curlx_str_numblanks(&v, &contentlength);
|
||||||
|
|
||||||
|
if(offt == STRE_OK) {
|
||||||
|
k->size = contentlength;
|
||||||
|
k->maxdownload = k->size;
|
||||||
|
}
|
||||||
|
else if(offt == STRE_OVERFLOW) {
|
||||||
|
/* 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 {
|
||||||
|
free(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((k->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_str_number(&ptr, &k->offset, CURL_OFF_T_MAX) &&
|
||||||
|
(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 */
|
||||||
|
}
|
||||||
|
return CURLE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* http_header_l() parses a single response header starting with L.
|
||||||
|
*/
|
||||||
|
static CURLcode http_header_l(struct Curl_easy *data,
|
||||||
|
const char *hd, size_t hdlen)
|
||||||
|
{
|
||||||
|
struct connectdata *conn = data->conn;
|
||||||
|
struct SingleRequest *k = &data->req;
|
||||||
|
const char *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_mode) {
|
||||||
|
CURLcode result;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return CURLE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* http_header_p() parses a single response header starting with P.
|
||||||
|
*/
|
||||||
|
static CURLcode http_header_p(struct Curl_easy *data,
|
||||||
|
const char *hd, size_t hdlen)
|
||||||
|
{
|
||||||
|
struct SingleRequest *k = &data->req;
|
||||||
|
|
||||||
|
#ifndef CURL_DISABLE_PROXY
|
||||||
|
const char *v = HD_VAL(hd, hdlen, "Proxy-Connection:");
|
||||||
|
if(v) {
|
||||||
|
struct connectdata *conn = data->conn;
|
||||||
|
if((k->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((k->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);
|
||||||
|
CURLcode result;
|
||||||
|
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 connectdata *conn = data->conn;
|
||||||
|
struct negotiatedata *negdata = Curl_auth_nego_get(conn, FALSE);
|
||||||
|
struct auth *authp = &data->state.authhost;
|
||||||
|
if(!negdata)
|
||||||
|
return CURLE_OUT_OF_MEMORY;
|
||||||
|
if(authp->picked == CURLAUTH_NEGOTIATE) {
|
||||||
|
char *persistentauth = Curl_copy_header_value(hd);
|
||||||
|
if(!persistentauth)
|
||||||
|
return CURLE_OUT_OF_MEMORY;
|
||||||
|
negdata->noauthpersist = !!checkprefix("false", persistentauth);
|
||||||
|
negdata->havenoauthpersist = TRUE;
|
||||||
|
infof(data, "Negotiate: noauthpersist -> %d, header part: %s",
|
||||||
|
negdata->noauthpersist, persistentauth);
|
||||||
|
free(persistentauth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return CURLE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* http_header_r() parses a single response header starting with R.
|
||||||
|
*/
|
||||||
|
static CURLcode http_header_r(struct Curl_easy *data,
|
||||||
|
const char *hd, size_t hdlen)
|
||||||
|
{
|
||||||
|
const char *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" */
|
||||||
|
time_t date;
|
||||||
|
curlx_str_passblanks(&v);
|
||||||
|
|
||||||
|
/* try it as a date first, because a date can otherwise start with and
|
||||||
|
get treated as a number */
|
||||||
|
date = Curl_getdate_capped(v);
|
||||||
|
|
||||||
|
if((time_t)-1 != date) {
|
||||||
|
time_t current = time(NULL);
|
||||||
|
if(date >= current)
|
||||||
|
/* convert date to number of seconds into the future */
|
||||||
|
retry_after = date - current;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
/* Try it as a decimal number */
|
||||||
|
curlx_str_number(&v, &retry_after, CURL_OFF_T_MAX);
|
||||||
|
/* limit to 6 hours max. this is not documented so that it can be changed
|
||||||
|
in the future if necessary. */
|
||||||
|
if(retry_after > 21600)
|
||||||
|
retry_after = 21600;
|
||||||
|
data->info.retry_after = retry_after;
|
||||||
|
}
|
||||||
|
return CURLE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* http_header_s() parses a single response header starting with S.
|
||||||
|
*/
|
||||||
|
static CURLcode http_header_s(struct Curl_easy *data,
|
||||||
|
const char *hd, size_t hdlen)
|
||||||
|
{
|
||||||
|
struct connectdata *conn = data->conn;
|
||||||
|
const char *v;
|
||||||
|
|
||||||
|
#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");
|
||||||
|
|
||||||
|
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 &&
|
||||||
|
(Curl_conn_is_ssl(conn, FIRSTSOCKET) ||
|
||||||
|
#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)",
|
||||||
|
Curl_llist_count(&data->hsts->list));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return CURLE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* http_header_t() parses a single response header starting with T.
|
||||||
|
*/
|
||||||
|
static CURLcode http_header_t(struct Curl_easy *data,
|
||||||
|
const char *hd, size_t hdlen)
|
||||||
|
{
|
||||||
|
struct connectdata *conn = data->conn;
|
||||||
|
struct SingleRequest *k = &data->req;
|
||||||
|
|
||||||
|
/* 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.
|
||||||
|
*/
|
||||||
|
const char *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. */
|
||||||
|
CURLcode 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;
|
||||||
|
}
|
||||||
|
v = HD_VAL(hd, hdlen, "Trailer:");
|
||||||
|
if(v) {
|
||||||
|
data->req.resp_trailer = TRUE;
|
||||||
|
return CURLE_OK;
|
||||||
|
}
|
||||||
|
return CURLE_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* http_header_w() parses a single response header starting with W.
|
||||||
|
*/
|
||||||
|
static CURLcode http_header_w(struct Curl_easy *data,
|
||||||
|
const char *hd, size_t hdlen)
|
||||||
|
{
|
||||||
|
struct SingleRequest *k = &data->req;
|
||||||
|
CURLcode result = CURLE_OK;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* http_header() parses a single response header.
|
* http_header() parses a single response header.
|
||||||
*/
|
*/
|
||||||
static CURLcode http_header(struct Curl_easy *data,
|
static CURLcode http_header(struct Curl_easy *data,
|
||||||
const char *hd, size_t hdlen)
|
const char *hd, size_t hdlen)
|
||||||
{
|
{
|
||||||
struct connectdata *conn = data->conn;
|
CURLcode result = CURLE_OK;
|
||||||
CURLcode result;
|
|
||||||
struct SingleRequest *k = &data->req;
|
|
||||||
const char *v;
|
|
||||||
|
|
||||||
switch(hd[0]) {
|
switch(hd[0]) {
|
||||||
case 'a':
|
case 'a':
|
||||||
case 'A':
|
case 'A':
|
||||||
#ifndef CURL_DISABLE_ALTSVC
|
result = http_header_a(data, hd, hdlen);
|
||||||
v = (data->asi &&
|
|
||||||
(Curl_conn_is_ssl(data->conn, FIRSTSOCKET) ||
|
|
||||||
#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 = (k->httpversion == 30) ? ALPN_h3 :
|
|
||||||
(k->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;
|
break;
|
||||||
case 'c':
|
case 'c':
|
||||||
case 'C':
|
case 'C':
|
||||||
/* Check for Content-Length: header lines to get size */
|
result = http_header_c(data, hd, hdlen);
|
||||||
v = (!k->http_bodyless && !data->set.ignorecl) ?
|
|
||||||
HD_VAL(hd, hdlen, "Content-Length:") : NULL;
|
|
||||||
if(v) {
|
|
||||||
curl_off_t contentlength;
|
|
||||||
int offt = curlx_str_numblanks(&v, &contentlength);
|
|
||||||
|
|
||||||
if(offt == STRE_OK) {
|
|
||||||
k->size = contentlength;
|
|
||||||
k->maxdownload = k->size;
|
|
||||||
}
|
|
||||||
else if(offt == STRE_OVERFLOW) {
|
|
||||||
/* 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 {
|
|
||||||
free(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((k->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_str_number(&ptr, &k->offset, CURL_OFF_T_MAX) &&
|
|
||||||
(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;
|
break;
|
||||||
case 'l':
|
case 'l':
|
||||||
case 'L':
|
case 'L':
|
||||||
v = (!k->http_bodyless &&
|
result = http_header_l(data, hd, hdlen);
|
||||||
(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_mode) {
|
|
||||||
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;
|
break;
|
||||||
case 'p':
|
case 'p':
|
||||||
case 'P':
|
case 'P':
|
||||||
#ifndef CURL_DISABLE_PROXY
|
result = http_header_p(data, hd, hdlen);
|
||||||
v = HD_VAL(hd, hdlen, "Proxy-Connection:");
|
|
||||||
if(v) {
|
|
||||||
if((k->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((k->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 = Curl_auth_nego_get(conn, FALSE);
|
|
||||||
struct auth *authp = &data->state.authhost;
|
|
||||||
if(!negdata)
|
|
||||||
return CURLE_OUT_OF_MEMORY;
|
|
||||||
if(authp->picked == CURLAUTH_NEGOTIATE) {
|
|
||||||
char *persistentauth = Curl_copy_header_value(hd);
|
|
||||||
if(!persistentauth)
|
|
||||||
return CURLE_OUT_OF_MEMORY;
|
|
||||||
negdata->noauthpersist = !!checkprefix("false", persistentauth);
|
|
||||||
negdata->havenoauthpersist = TRUE;
|
|
||||||
infof(data, "Negotiate: noauthpersist -> %d, header part: %s",
|
|
||||||
negdata->noauthpersist, persistentauth);
|
|
||||||
free(persistentauth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
break;
|
break;
|
||||||
case 'r':
|
case 'r':
|
||||||
case 'R':
|
case 'R':
|
||||||
v = HD_VAL(hd, hdlen, "Retry-After:");
|
result = http_header_r(data, hd, hdlen);
|
||||||
if(v) {
|
|
||||||
/* Retry-After = HTTP-date / delay-seconds */
|
|
||||||
curl_off_t retry_after = 0; /* zero for unknown or "now" */
|
|
||||||
time_t date;
|
|
||||||
curlx_str_passblanks(&v);
|
|
||||||
|
|
||||||
/* try it as a date first, because a date can otherwise start with and
|
|
||||||
get treated as a number */
|
|
||||||
date = Curl_getdate_capped(v);
|
|
||||||
|
|
||||||
if((time_t)-1 != date) {
|
|
||||||
time_t current = time(NULL);
|
|
||||||
if(date >= current)
|
|
||||||
/* convert date to number of seconds into the future */
|
|
||||||
retry_after = date - current;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
/* Try it as a decimal number */
|
|
||||||
curlx_str_number(&v, &retry_after, CURL_OFF_T_MAX);
|
|
||||||
/* limit to 6 hours max. this is not documented so that it can be changed
|
|
||||||
in the future if necessary. */
|
|
||||||
if(retry_after > 21600)
|
|
||||||
retry_after = 21600;
|
|
||||||
data->info.retry_after = retry_after;
|
|
||||||
return CURLE_OK;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 's':
|
case 's':
|
||||||
case 'S':
|
case 'S':
|
||||||
#if !defined(CURL_DISABLE_COOKIES)
|
result = http_header_s(data, hd, hdlen);
|
||||||
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");
|
|
||||||
|
|
||||||
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 &&
|
|
||||||
(Curl_conn_is_ssl(conn, FIRSTSOCKET) ||
|
|
||||||
#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)",
|
|
||||||
Curl_llist_count(&data->hsts->list));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
break;
|
break;
|
||||||
case 't':
|
case 't':
|
||||||
case 'T':
|
case 'T':
|
||||||
/* RFC 9112, ch. 6.1
|
result = http_header_t(data, hd, hdlen);
|
||||||
* "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;
|
|
||||||
}
|
|
||||||
v = HD_VAL(hd, hdlen, "Trailer:");
|
|
||||||
if(v) {
|
|
||||||
data->req.resp_trailer = TRUE;
|
|
||||||
return CURLE_OK;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case 'w':
|
case 'w':
|
||||||
case 'W':
|
case 'W':
|
||||||
if((401 == k->httpcode) && HD_IS(hd, hdlen, "WWW-Authenticate:")) {
|
result = http_header_w(data, hd, hdlen);
|
||||||
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(conn->handler->protocol & CURLPROTO_RTSP) {
|
if(!result) {
|
||||||
result = Curl_rtsp_parseheader(data, hd);
|
struct connectdata *conn = data->conn;
|
||||||
if(result)
|
if(conn->handler->protocol & CURLPROTO_RTSP)
|
||||||
return result;
|
result = Curl_rtsp_parseheader(data, hd);
|
||||||
}
|
}
|
||||||
return CURLE_OK;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
Loading…
Reference in New Issue
Block a user