#include "curl_setup.h"
#ifndef CURL_DISABLE_HTTP
#ifdef HAVE_NETINET_IN_H
#include <netinet/in.h>
#endif
#ifdef HAVE_NETDB_H
#include <netdb.h>
#endif
#ifdef HAVE_ARPA_INET_H
#include <arpa/inet.h>
#endif
#ifdef HAVE_NET_IF_H
#include <net/if.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#include "urldata.h"
#include <curl/curl.h>
#include "transfer.h"
#include "sendf.h"
#include "formdata.h"
#include "mime.h"
#include "progress.h"
#include "curlx/base64.h"
#include "cookie.h"
#include "vauth/vauth.h"
#include "vtls/vtls.h"
#include "vquic/vquic.h"
#include "http_digest.h"
#include "http_ntlm.h"
#include "http_negotiate.h"
#include "http_aws_sigv4.h"
#include "url.h"
#include "urlapi-int.h"
#include "share.h"
#include "hostip.h"
#include "dynhds.h"
#include "http.h"
#include "headers.h"
#include "select.h"
#include "parsedate.h"
#include "multiif.h"
#include "strcase.h"
#include "content_encoding.h"
#include "http_proxy.h"
#include "curlx/warnless.h"
#include "http2.h"
#include "cfilters.h"
#include "connect.h"
#include "strdup.h"
#include "altsvc.h"
#include "hsts.h"
#include "ws.h"
#include "curl_ctype.h"
#include "curlx/strparse.h"
#include "curl_memory.h"
#include "memdebug.h"
static bool http_should_fail(struct Curl_easy *data, int httpcode);
static bool http_exp100_is_waiting(struct Curl_easy *data);
static CURLcode http_exp100_add_reader(struct Curl_easy *data);
static void http_exp100_send_anyway(struct Curl_easy *data);
static bool http_exp100_is_selected(struct Curl_easy *data);
static void http_exp100_got100(struct Curl_easy *data);
static CURLcode http_firstwrite(struct Curl_easy *data);
static CURLcode http_header(struct Curl_easy *data,
const char *hd, size_t hdlen);
static CURLcode http_range(struct Curl_easy *data,
Curl_HttpReq httpreq);
static CURLcode http_req_set_TE(struct Curl_easy *data,
struct dynbuf *req,
int httpversion);
static CURLcode http_size(struct Curl_easy *data);
static CURLcode http_statusline(struct Curl_easy *data,
struct connectdata *conn);
static CURLcode http_target(struct Curl_easy *data, struct dynbuf *req);
static CURLcode http_useragent(struct Curl_easy *data);
const struct Curl_handler Curl_handler_http = {
"http",
Curl_http_setup_conn,
Curl_http,
Curl_http_done,
ZERO_NULL,
Curl_http_connect,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
Curl_http_do_pollset,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
Curl_http_write_resp,
Curl_http_write_resp_hd,
ZERO_NULL,
ZERO_NULL,
Curl_http_follow,
PORT_HTTP,
CURLPROTO_HTTP,
CURLPROTO_HTTP,
PROTOPT_CREDSPERREQUEST |
PROTOPT_USERPWDCTRL
};
#ifdef USE_SSL
const struct Curl_handler Curl_handler_https = {
"https",
Curl_http_setup_conn,
Curl_http,
Curl_http_done,
ZERO_NULL,
Curl_http_connect,
NULL,
ZERO_NULL,
NULL,
Curl_http_do_pollset,
ZERO_NULL,
ZERO_NULL,
ZERO_NULL,
Curl_http_write_resp,
Curl_http_write_resp_hd,
ZERO_NULL,
ZERO_NULL,
Curl_http_follow,
PORT_HTTPS,
CURLPROTO_HTTPS,
CURLPROTO_HTTP,
PROTOPT_SSL | PROTOPT_CREDSPERREQUEST | PROTOPT_ALPN |
PROTOPT_USERPWDCTRL
};
#endif
void Curl_http_neg_init(struct Curl_easy *data, struct http_negotiation *neg)
{
memset(neg, 0, sizeof(*neg));
neg->accept_09 = data->set.http09_allowed;
switch(data->set.httpwant) {
case CURL_HTTP_VERSION_1_0:
neg->wanted = neg->allowed = (CURL_HTTP_V1x);
neg->only_10 = TRUE;
break;
case CURL_HTTP_VERSION_1_1:
neg->wanted = neg->allowed = (CURL_HTTP_V1x);
break;
case CURL_HTTP_VERSION_2_0:
neg->wanted = neg->allowed = (CURL_HTTP_V1x | CURL_HTTP_V2x);
neg->h2_upgrade = TRUE;
break;
case CURL_HTTP_VERSION_2TLS:
neg->wanted = neg->allowed = (CURL_HTTP_V1x | CURL_HTTP_V2x);
break;
case CURL_HTTP_VERSION_2_PRIOR_KNOWLEDGE:
neg->wanted = neg->allowed = (CURL_HTTP_V2x);
data->state.http_neg.h2_prior_knowledge = TRUE;
break;
case CURL_HTTP_VERSION_3:
neg->wanted = (CURL_HTTP_V1x | CURL_HTTP_V2x | CURL_HTTP_V3x);
neg->allowed = neg->wanted;
break;
case CURL_HTTP_VERSION_3ONLY:
neg->wanted = neg->allowed = (CURL_HTTP_V3x);
break;
case CURL_HTTP_VERSION_NONE:
default:
neg->wanted = (CURL_HTTP_V1x | CURL_HTTP_V2x);
neg->allowed = (CURL_HTTP_V1x | CURL_HTTP_V2x | CURL_HTTP_V3x);
break;
}
}
CURLcode Curl_http_setup_conn(struct Curl_easy *data,
struct connectdata *conn)
{
connkeep(conn, "HTTP default");
if(data->state.http_neg.wanted == CURL_HTTP_V3x) {
CURLcode result = Curl_conn_may_http3(data, conn, conn->transport_wanted);
if(result)
return result;
}
return CURLE_OK;
}
#ifndef CURL_DISABLE_PROXY
char *Curl_checkProxyheaders(struct Curl_easy *data,
const struct connectdata *conn,
const char *thisheader,
const size_t thislen)
{
struct curl_slist *head;
for(head = (conn->bits.proxy && data->set.sep_headers) ?
data->set.proxyheaders : data->set.headers;
head; head = head->next) {
if(curl_strnequal(head->data, thisheader, thislen) &&
Curl_headersep(head->data[thislen]))
return head->data;
}
return NULL;
}
#else
#define Curl_checkProxyheaders(x,y,z,a) NULL
#endif
static bool http_header_is_empty(const char *header)
{
struct Curl_str out;
if(!curlx_str_cspn(&header, &out, ";:") &&
(!curlx_str_single(&header, ':') || !curlx_str_single(&header, ';'))) {
curlx_str_untilnl(&header, &out, MAX_HTTP_RESP_HEADER_SIZE);
curlx_str_trimblanks(&out);
return curlx_strlen(&out) == 0;
}
return TRUE;
}
static CURLcode copy_custom_value(const char *header, char **valp)
{
struct Curl_str out;
if(!curlx_str_cspn(&header, &out, ";:") &&
(!curlx_str_single(&header, ':') || !curlx_str_single(&header, ';'))) {
curlx_str_untilnl(&header, &out, MAX_HTTP_RESP_HEADER_SIZE);
curlx_str_trimblanks(&out);
*valp = Curl_memdup0(curlx_str(&out), curlx_strlen(&out));
if(*valp)
return CURLE_OK;
return CURLE_OUT_OF_MEMORY;
}
*valp = NULL;
return CURLE_BAD_FUNCTION_ARGUMENT;
}
char *Curl_copy_header_value(const char *header)
{
struct Curl_str out;
if(!curlx_str_until(&header, &out, MAX_HTTP_RESP_HEADER_SIZE, ':') &&
!curlx_str_single(&header, ':')) {
curlx_str_untilnl(&header, &out, MAX_HTTP_RESP_HEADER_SIZE);
curlx_str_trimblanks(&out);
return Curl_memdup0(curlx_str(&out), curlx_strlen(&out));
}
DEBUGASSERT(0);
return NULL;
}
#ifndef CURL_DISABLE_HTTP_AUTH
#ifndef CURL_DISABLE_BASIC_AUTH
static CURLcode http_output_basic(struct Curl_easy *data, bool proxy)
{
size_t size = 0;
char *authorization = NULL;
char **userp;
const char *user;
const char *pwd;
CURLcode result;
char *out;
if(proxy) {
#ifndef CURL_DISABLE_PROXY
userp = &data->state.aptr.proxyuserpwd;
user = data->state.aptr.proxyuser;
pwd = data->state.aptr.proxypasswd;
#else
return CURLE_NOT_BUILT_IN;
#endif
}
else {
userp = &data->state.aptr.userpwd;
user = data->state.aptr.user;
pwd = data->state.aptr.passwd;
}
out = curl_maprintf("%s:%s", user ? user : "", pwd ? pwd : "");
if(!out)
return CURLE_OUT_OF_MEMORY;
result = curlx_base64_encode(out, strlen(out), &authorization, &size);
if(result)
goto fail;
if(!authorization) {
result = CURLE_REMOTE_ACCESS_DENIED;
goto fail;
}
free(*userp);
*userp = curl_maprintf("%sAuthorization: Basic %s\r\n",
proxy ? "Proxy-" : "",
authorization);
free(authorization);
if(!*userp) {
result = CURLE_OUT_OF_MEMORY;
goto fail;
}
fail:
free(out);
return result;
}
#endif
#ifndef CURL_DISABLE_BEARER_AUTH
static CURLcode http_output_bearer(struct Curl_easy *data)
{
char **userp;
CURLcode result = CURLE_OK;
userp = &data->state.aptr.userpwd;
free(*userp);
*userp = curl_maprintf("Authorization: Bearer %s\r\n",
data->set.str[STRING_BEARER]);
if(!*userp) {
result = CURLE_OUT_OF_MEMORY;
goto fail;
}
fail:
return result;
}
#endif
#endif
static bool pickoneauth(struct auth *pick, unsigned long mask)
{
bool picked;
unsigned long avail = pick->avail & pick->want & mask;
picked = TRUE;
if(avail & CURLAUTH_NEGOTIATE)
pick->picked = CURLAUTH_NEGOTIATE;
#ifndef CURL_DISABLE_BEARER_AUTH
else if(avail & CURLAUTH_BEARER)
pick->picked = CURLAUTH_BEARER;
#endif
#ifndef CURL_DISABLE_DIGEST_AUTH
else if(avail & CURLAUTH_DIGEST)
pick->picked = CURLAUTH_DIGEST;
#endif
else if(avail & CURLAUTH_NTLM)
pick->picked = CURLAUTH_NTLM;
#ifndef CURL_DISABLE_BASIC_AUTH
else if(avail & CURLAUTH_BASIC)
pick->picked = CURLAUTH_BASIC;
#endif
#ifndef CURL_DISABLE_AWS
else if(avail & CURLAUTH_AWS_SIGV4)
pick->picked = CURLAUTH_AWS_SIGV4;
#endif
else {
pick->picked = CURLAUTH_PICKNONE;
picked = FALSE;
}
pick->avail = CURLAUTH_NONE;
return picked;
}
static CURLcode http_perhapsrewind(struct Curl_easy *data,
struct connectdata *conn)
{
curl_off_t bytessent = data->req.writebytecount;
curl_off_t expectsend = Curl_creader_total_length(data);
curl_off_t upload_remain = (expectsend >= 0) ? (expectsend - bytessent) : -1;
bool little_upload_remains = (upload_remain >= 0 && upload_remain < 2000);
bool needs_rewind = Curl_creader_needs_rewind(data);
bool abort_upload = (!data->req.upload_done && !little_upload_remains);
const char *ongoing_auth = NULL;
if(needs_rewind) {
infof(data, "Need to rewind upload for next request");
Curl_creader_set_rewind(data, TRUE);
}
if(conn->bits.close)
return CURLE_OK;
if(abort_upload) {
#ifdef USE_NTLM
if((data->state.authproxy.picked == CURLAUTH_NTLM) ||
(data->state.authhost.picked == CURLAUTH_NTLM)) {
ongoing_auth = "NTLM";
if((conn->http_ntlm_state != NTLMSTATE_NONE) ||
(conn->proxy_ntlm_state != NTLMSTATE_NONE)) {
abort_upload = FALSE;
}
}
#endif
#ifdef USE_SPNEGO
if((data->state.authproxy.picked == CURLAUTH_NEGOTIATE) ||
(data->state.authhost.picked == CURLAUTH_NEGOTIATE)) {
ongoing_auth = "NEGOTIATE";
if((conn->http_negotiate_state != GSS_AUTHNONE) ||
(conn->proxy_negotiate_state != GSS_AUTHNONE)) {
abort_upload = FALSE;
}
}
#endif
}
if(abort_upload) {
if(upload_remain >= 0)
infof(data, "%s%sclose instead of sending %" FMT_OFF_T " more bytes",
ongoing_auth ? ongoing_auth : "",
ongoing_auth ? " send, " : "",
upload_remain);
else
infof(data, "%s%sclose instead of sending unknown amount "
"of more bytes",
ongoing_auth ? ongoing_auth : "",
ongoing_auth ? " send, " : "");
streamclose(conn, "Mid-auth HTTP and much data left to send");
data->req.size = 0;
data->req.http_bodyless = TRUE;
}
return CURLE_OK;
}
CURLcode Curl_http_auth_act(struct Curl_easy *data)
{
struct connectdata *conn = data->conn;
bool pickhost = FALSE;
bool pickproxy = FALSE;
CURLcode result = CURLE_OK;
unsigned long authmask = ~0ul;
if(!data->set.str[STRING_BEARER])
authmask &= (unsigned long)~CURLAUTH_BEARER;
if(100 <= data->req.httpcode && data->req.httpcode <= 199)
return CURLE_OK;
if(data->state.authproblem)
return data->set.http_fail_on_error ? CURLE_HTTP_RETURNED_ERROR : CURLE_OK;
if((data->state.aptr.user || data->set.str[STRING_BEARER]) &&
((data->req.httpcode == 401) ||
(data->req.authneg && data->req.httpcode < 300))) {
pickhost = pickoneauth(&data->state.authhost, authmask);
if(!pickhost)
data->state.authproblem = TRUE;
else
data->info.httpauthpicked = data->state.authhost.picked;
if(data->state.authhost.picked == CURLAUTH_NTLM &&
(data->req.httpversion_sent > 11)) {
infof(data, "Forcing HTTP/1.1 for NTLM");
connclose(conn, "Force HTTP/1.1 connection");
data->state.http_neg.wanted = CURL_HTTP_V1x;
data->state.http_neg.allowed = CURL_HTTP_V1x;
}
}
#ifndef CURL_DISABLE_PROXY
if(conn->bits.proxy_user_passwd &&
((data->req.httpcode == 407) ||
(data->req.authneg && data->req.httpcode < 300))) {
pickproxy = pickoneauth(&data->state.authproxy,
authmask & ~CURLAUTH_BEARER);
if(!pickproxy)
data->state.authproblem = TRUE;
else
data->info.proxyauthpicked = data->state.authproxy.picked;
}
#endif
if(pickhost || pickproxy) {
result = http_perhapsrewind(data, conn);
if(result)
return result;
free(data->req.newurl);
data->req.newurl = strdup(data->state.url);
if(!data->req.newurl)
return CURLE_OUT_OF_MEMORY;
}
else if((data->req.httpcode < 300) &&
(!data->state.authhost.done) &&
data->req.authneg) {
if((data->state.httpreq != HTTPREQ_GET) &&
(data->state.httpreq != HTTPREQ_HEAD)) {
data->req.newurl = strdup(data->state.url);
if(!data->req.newurl)
return CURLE_OUT_OF_MEMORY;
data->state.authhost.done = TRUE;
}
}
if(http_should_fail(data, data->req.httpcode)) {
failf(data, "The requested URL returned error: %d",
data->req.httpcode);
result = CURLE_HTTP_RETURNED_ERROR;
}
return result;
}
#ifndef CURL_DISABLE_HTTP_AUTH
static CURLcode
output_auth_headers(struct Curl_easy *data,
struct connectdata *conn,
struct auth *authstatus,
const char *request,
const char *path,
bool proxy)
{
const char *auth = NULL;
CURLcode result = CURLE_OK;
(void)conn;
#ifdef CURL_DISABLE_DIGEST_AUTH
(void)request;
(void)path;
#endif
#ifndef CURL_DISABLE_AWS
if((authstatus->picked == CURLAUTH_AWS_SIGV4) && !proxy) {
auth = "AWS_SIGV4";
result = Curl_output_aws_sigv4(data);
if(result)
return result;
}
else
#endif
#ifdef USE_SPNEGO
if(authstatus->picked == CURLAUTH_NEGOTIATE) {
auth = "Negotiate";
result = Curl_output_negotiate(data, conn, proxy);
if(result)
return result;
}
else
#endif
#ifdef USE_NTLM
if(authstatus->picked == CURLAUTH_NTLM) {
auth = "NTLM";
result = Curl_output_ntlm(data, proxy);
if(result)
return result;
}
else
#endif
#ifndef CURL_DISABLE_DIGEST_AUTH
if(authstatus->picked == CURLAUTH_DIGEST) {
auth = "Digest";
result = Curl_output_digest(data,
proxy,
(const unsigned char *)request,
(const unsigned char *)path);
if(result)
return result;
}
else
#endif
#ifndef CURL_DISABLE_BASIC_AUTH
if(authstatus->picked == CURLAUTH_BASIC) {
if(
#ifndef CURL_DISABLE_PROXY
(proxy && conn->bits.proxy_user_passwd &&
!Curl_checkProxyheaders(data, conn, STRCONST("Proxy-authorization"))) ||
#endif
(!proxy && data->state.aptr.user &&
!Curl_checkheaders(data, STRCONST("Authorization")))) {
auth = "Basic";
result = http_output_basic(data, proxy);
if(result)
return result;
}
authstatus->done = TRUE;
}
#endif
#ifndef CURL_DISABLE_BEARER_AUTH
if(authstatus->picked == CURLAUTH_BEARER) {
if((!proxy && data->set.str[STRING_BEARER] &&
!Curl_checkheaders(data, STRCONST("Authorization")))) {
auth = "Bearer";
result = http_output_bearer(data);
if(result)
return result;
}
authstatus->done = TRUE;
}
#endif
if(auth) {
#ifndef CURL_DISABLE_PROXY
infof(data, "%s auth using %s with user '%s'",
proxy ? "Proxy" : "Server", auth,
proxy ? (data->state.aptr.proxyuser ?
data->state.aptr.proxyuser : "") :
(data->state.aptr.user ?
data->state.aptr.user : ""));
#else
(void)proxy;
infof(data, "Server auth using %s with user '%s'",
auth, data->state.aptr.user ?
data->state.aptr.user : "");
#endif
authstatus->multipass = !authstatus->done;
}
else
authstatus->multipass = FALSE;
return result;
}
CURLcode
Curl_http_output_auth(struct Curl_easy *data,
struct connectdata *conn,
const char *request,
Curl_HttpReq httpreq,
const char *path,
bool proxytunnel)
{
CURLcode result = CURLE_OK;
struct auth *authhost;
struct auth *authproxy;
DEBUGASSERT(data);
authhost = &data->state.authhost;
authproxy = &data->state.authproxy;
if(
#ifndef CURL_DISABLE_PROXY
(conn->bits.httpproxy && conn->bits.proxy_user_passwd) ||
#endif
data->state.aptr.user ||
#ifdef USE_SPNEGO
authhost->want & CURLAUTH_NEGOTIATE ||
authproxy->want & CURLAUTH_NEGOTIATE ||
#endif
data->set.str[STRING_BEARER])
;
else {
authhost->done = TRUE;
authproxy->done = TRUE;
return CURLE_OK;
}
if(authhost->want && !authhost->picked)
authhost->picked = authhost->want;
if(authproxy->want && !authproxy->picked)
authproxy->picked = authproxy->want;
#ifndef CURL_DISABLE_PROXY
if(conn->bits.httpproxy &&
(conn->bits.tunnel_proxy == (bit)proxytunnel)) {
result = output_auth_headers(data, conn, authproxy, request, path, TRUE);
if(result)
return result;
}
else
#else
(void)proxytunnel;
#endif
authproxy->done = TRUE;
if(Curl_auth_allowed_to_host(data)
#ifndef CURL_DISABLE_NETRC
|| conn->bits.netrc
#endif
)
result = output_auth_headers(data, conn, authhost, request, path, FALSE);
else
authhost->done = TRUE;
if(((authhost->multipass && !authhost->done) ||
(authproxy->multipass && !authproxy->done)) &&
(httpreq != HTTPREQ_GET) &&
(httpreq != HTTPREQ_HEAD)) {
data->req.authneg = TRUE;
}
else
data->req.authneg = FALSE;
return result;
}
#else
CURLcode
Curl_http_output_auth(struct Curl_easy *data,
struct connectdata *conn,
const char *request,
Curl_HttpReq httpreq,
const char *path,
bool proxytunnel)
{
(void)data;
(void)conn;
(void)request;
(void)httpreq;
(void)path;
(void)proxytunnel;
return CURLE_OK;
}
#endif
#if defined(USE_SPNEGO) || defined(USE_NTLM) || \
!defined(CURL_DISABLE_DIGEST_AUTH) || \
!defined(CURL_DISABLE_BASIC_AUTH) || \
!defined(CURL_DISABLE_BEARER_AUTH)
static bool authcmp(const char *auth, const char *line)
{
size_t n = strlen(auth);
return curl_strnequal(auth, line, n) && !ISALNUM(line[n]);
}
#endif
#ifdef USE_SPNEGO
static CURLcode auth_spnego(struct Curl_easy *data,
bool proxy,
const char *auth,
struct auth *authp,
unsigned long *availp)
{
if((authp->avail & CURLAUTH_NEGOTIATE) || Curl_auth_is_spnego_supported()) {
*availp |= CURLAUTH_NEGOTIATE;
authp->avail |= CURLAUTH_NEGOTIATE;
if(authp->picked == CURLAUTH_NEGOTIATE) {
struct connectdata *conn = data->conn;
CURLcode result = Curl_input_negotiate(data, conn, proxy, auth);
curlnegotiate *negstate = proxy ? &conn->proxy_negotiate_state :
&conn->http_negotiate_state;
if(!result) {
free(data->req.newurl);
data->req.newurl = strdup(data->state.url);
if(!data->req.newurl)
return CURLE_OUT_OF_MEMORY;
data->state.authproblem = FALSE;
*negstate = GSS_AUTHRECV;
}
else
data->state.authproblem = TRUE;
}
}
return CURLE_OK;
}
#endif
#ifdef USE_NTLM
static CURLcode auth_ntlm(struct Curl_easy *data,
bool proxy,
const char *auth,
struct auth *authp,
unsigned long *availp)
{
if((authp->avail & CURLAUTH_NTLM) || Curl_auth_is_ntlm_supported()) {
*availp |= CURLAUTH_NTLM;
authp->avail |= CURLAUTH_NTLM;
if(authp->picked == CURLAUTH_NTLM) {
CURLcode result = Curl_input_ntlm(data, proxy, auth);
if(!result)
data->state.authproblem = FALSE;
else {
infof(data, "NTLM authentication problem, ignoring.");
data->state.authproblem = TRUE;
}
}
}
return CURLE_OK;
}
#endif
#ifndef CURL_DISABLE_DIGEST_AUTH
static CURLcode auth_digest(struct Curl_easy *data,
bool proxy,
const char *auth,
struct auth *authp,
unsigned long *availp)
{
if(authp->avail & CURLAUTH_DIGEST)
infof(data, "Ignoring duplicate digest auth header.");
else if(Curl_auth_is_digest_supported()) {
CURLcode result;
*availp |= CURLAUTH_DIGEST;
authp->avail |= CURLAUTH_DIGEST;
result = Curl_input_digest(data, proxy, auth);
if(result) {
infof(data, "Digest authentication problem, ignoring.");
data->state.authproblem = TRUE;
}
}
return CURLE_OK;
}
#endif
#ifndef CURL_DISABLE_BASIC_AUTH
static CURLcode auth_basic(struct Curl_easy *data,
struct auth *authp,
unsigned long *availp)
{
*availp |= CURLAUTH_BASIC;
authp->avail |= CURLAUTH_BASIC;
if(authp->picked == CURLAUTH_BASIC) {
authp->avail = CURLAUTH_NONE;
infof(data, "Basic authentication problem, ignoring.");
data->state.authproblem = TRUE;
}
return CURLE_OK;
}
#endif
#ifndef CURL_DISABLE_BEARER_AUTH
static CURLcode auth_bearer(struct Curl_easy *data,
struct auth *authp,
unsigned long *availp)
{
*availp |= CURLAUTH_BEARER;
authp->avail |= CURLAUTH_BEARER;
if(authp->picked == CURLAUTH_BEARER) {
authp->avail = CURLAUTH_NONE;
infof(data, "Bearer authentication problem, ignoring.");
data->state.authproblem = TRUE;
}
return CURLE_OK;
}
#endif
CURLcode Curl_http_input_auth(struct Curl_easy *data, bool proxy,
const char *auth)
{
#if defined(USE_SPNEGO) || \
defined(USE_NTLM) || \
!defined(CURL_DISABLE_DIGEST_AUTH) || \
!defined(CURL_DISABLE_BASIC_AUTH) || \
!defined(CURL_DISABLE_BEARER_AUTH)
unsigned long *availp;
struct auth *authp;
CURLcode result = CURLE_OK;
DEBUGASSERT(auth);
DEBUGASSERT(data);
if(proxy) {
availp = &data->info.proxyauthavail;
authp = &data->state.authproxy;
}
else {
availp = &data->info.httpauthavail;
authp = &data->state.authhost;
}
while(*auth) {
#ifdef USE_SPNEGO
if(authcmp("Negotiate", auth))
result = auth_spnego(data, proxy, auth, authp, availp);
#endif
#ifdef USE_NTLM
if(!result && authcmp("NTLM", auth))
result = auth_ntlm(data, proxy, auth, authp, availp);
#endif
#ifndef CURL_DISABLE_DIGEST_AUTH
if(!result && authcmp("Digest", auth))
result = auth_digest(data, proxy, auth, authp, availp);
#endif
#ifndef CURL_DISABLE_BASIC_AUTH
if(!result && authcmp("Basic", auth))
result = auth_basic(data, authp, availp);
#endif
#ifndef CURL_DISABLE_BEARER_AUTH
if(authcmp("Bearer", auth))
result = auth_bearer(data, authp, availp);
#endif
if(result)
break;
auth = strchr(auth, ',');
if(auth)
auth++;
else
break;
curlx_str_passblanks(&auth);
}
return result;
#else
(void)data;
(void)proxy;
(void)auth;
return CURLE_OK;
#endif
}
static bool http_should_fail(struct Curl_easy *data, int httpcode)
{
DEBUGASSERT(data);
DEBUGASSERT(data->conn);
if(!data->set.http_fail_on_error)
return FALSE;
if(httpcode < 400)
return FALSE;
if(data->state.resume_from && data->state.httpreq == HTTPREQ_GET &&
httpcode == 416)
return FALSE;
if((httpcode != 401) && (httpcode != 407))
return TRUE;
DEBUGASSERT((httpcode == 401) || (httpcode == 407));
if((httpcode == 401) && !data->state.aptr.user)
return TRUE;
#ifndef CURL_DISABLE_PROXY
if((httpcode == 407) && !data->conn->bits.proxy_user_passwd)
return TRUE;
#endif
return data->state.authproblem;
}
static void http_switch_to_get(struct Curl_easy *data, int code)
{
const char *req = data->set.str[STRING_CUSTOMREQUEST];
if((req || data->state.httpreq != HTTPREQ_GET) &&
(data->set.http_follow_mode == CURLFOLLOW_OBEYCODE)) {
infof(data, "Switch to GET because of %d response", code);
data->state.http_ignorecustom = TRUE;
}
else if(req && (data->set.http_follow_mode != CURLFOLLOW_FIRSTONLY))
infof(data, "Stick to %s instead of GET", req);
data->state.httpreq = HTTPREQ_GET;
Curl_creader_set_rewind(data, FALSE);
}
CURLcode Curl_http_follow(struct Curl_easy *data, const char *newurl,
followtype type)
{
bool disallowport = FALSE;
bool reachedmax = FALSE;
char *follow_url = NULL;
CURLUcode uc;
CURLcode rewind_result;
bool switch_to_get = FALSE;
DEBUGASSERT(type != FOLLOW_NONE);
if(type != FOLLOW_FAKE)
data->state.requests++;
if(type == FOLLOW_REDIR) {
if((data->set.maxredirs != -1) &&
(data->state.followlocation >= data->set.maxredirs)) {
reachedmax = TRUE;
type = FOLLOW_FAKE;
}
else {
data->state.followlocation++;
if(data->set.http_auto_referer) {
CURLU *u;
char *referer = NULL;
if(data->state.referer_alloc) {
Curl_safefree(data->state.referer);
data->state.referer_alloc = FALSE;
}
u = curl_url();
if(!u)
return CURLE_OUT_OF_MEMORY;
uc = curl_url_set(u, CURLUPART_URL, data->state.url, 0);
if(!uc)
uc = curl_url_set(u, CURLUPART_FRAGMENT, NULL, 0);
if(!uc)
uc = curl_url_set(u, CURLUPART_USER, NULL, 0);
if(!uc)
uc = curl_url_set(u, CURLUPART_PASSWORD, NULL, 0);
if(!uc)
uc = curl_url_get(u, CURLUPART_URL, &referer, 0);
curl_url_cleanup(u);
if(uc || !referer)
return CURLE_OUT_OF_MEMORY;
data->state.referer = referer;
data->state.referer_alloc = TRUE;
}
}
}
if((type != FOLLOW_RETRY) &&
(data->req.httpcode != 401) && (data->req.httpcode != 407) &&
Curl_is_absolute_url(newurl, NULL, 0, FALSE)) {
disallowport = TRUE;
}
DEBUGASSERT(data->state.uh);
uc = curl_url_set(data->state.uh, CURLUPART_URL, newurl, (unsigned int)
((type == FOLLOW_FAKE) ? CURLU_NON_SUPPORT_SCHEME :
((type == FOLLOW_REDIR) ? CURLU_URLENCODE : 0) |
CURLU_ALLOW_SPACE |
(data->set.path_as_is ? CURLU_PATH_AS_IS : 0)));
if(uc) {
if(type != FOLLOW_FAKE) {
failf(data, "The redirect target URL could not be parsed: %s",
curl_url_strerror(uc));
return Curl_uc_to_curlcode(uc);
}
follow_url = strdup(newurl);
if(!follow_url)
return CURLE_OUT_OF_MEMORY;
}
else {
uc = curl_url_get(data->state.uh, CURLUPART_URL, &follow_url, 0);
if(uc)
return Curl_uc_to_curlcode(uc);
if(!data->set.allow_auth_to_other_hosts && (type != FOLLOW_FAKE)) {
char *portnum;
int port;
bool clear = FALSE;
if(data->set.use_port && data->state.allow_port)
port = (int)data->set.use_port;
else {
uc = curl_url_get(data->state.uh, CURLUPART_PORT, &portnum,
CURLU_DEFAULT_PORT);
if(uc) {
free(follow_url);
return Curl_uc_to_curlcode(uc);
}
port = atoi(portnum);
free(portnum);
}
if(port != data->info.conn_remote_port) {
infof(data, "Clear auth, redirects to port from %u to %u",
data->info.conn_remote_port, port);
clear = TRUE;
}
else {
char *scheme;
const struct Curl_handler *p;
uc = curl_url_get(data->state.uh, CURLUPART_SCHEME, &scheme, 0);
if(uc) {
free(follow_url);
return Curl_uc_to_curlcode(uc);
}
p = Curl_get_scheme_handler(scheme);
if(p && (p->protocol != data->info.conn_protocol)) {
infof(data, "Clear auth, redirects scheme from %s to %s",
data->info.conn_scheme, scheme);
clear = TRUE;
}
free(scheme);
}
if(clear) {
Curl_safefree(data->state.aptr.user);
Curl_safefree(data->state.aptr.passwd);
}
}
}
DEBUGASSERT(follow_url);
if(type == FOLLOW_FAKE) {
data->info.wouldredirect = follow_url;
if(reachedmax) {
failf(data, "Maximum (%d) redirects followed", data->set.maxredirs);
return CURLE_TOO_MANY_REDIRECTS;
}
return CURLE_OK;
}
if(disallowport)
data->state.allow_port = FALSE;
if(data->state.url_alloc)
Curl_safefree(data->state.url);
data->state.url = follow_url;
data->state.url_alloc = TRUE;
rewind_result = Curl_req_soft_reset(&data->req, data);
infof(data, "Issue another request to this URL: '%s'", data->state.url);
if((data->set.http_follow_mode == CURLFOLLOW_FIRSTONLY) &&
data->set.str[STRING_CUSTOMREQUEST] &&
!data->state.http_ignorecustom) {
data->state.http_ignorecustom = TRUE;
infof(data, "Drop custom request method for next request");
}
switch(data->info.httpcode) {
default:
break;
case 301:
if((data->state.httpreq == HTTPREQ_POST
|| data->state.httpreq == HTTPREQ_POST_FORM
|| data->state.httpreq == HTTPREQ_POST_MIME)
&& !(data->set.keep_post & CURL_REDIR_POST_301)) {
http_switch_to_get(data, 301);
switch_to_get = TRUE;
}
break;
case 302:
if((data->state.httpreq == HTTPREQ_POST
|| data->state.httpreq == HTTPREQ_POST_FORM
|| data->state.httpreq == HTTPREQ_POST_MIME)
&& !(data->set.keep_post & CURL_REDIR_POST_302)) {
http_switch_to_get(data, 302);
switch_to_get = TRUE;
}
break;
case 303:
if(data->state.httpreq != HTTPREQ_GET &&
((data->state.httpreq != HTTPREQ_POST &&
data->state.httpreq != HTTPREQ_POST_FORM &&
data->state.httpreq != HTTPREQ_POST_MIME) ||
!(data->set.keep_post & CURL_REDIR_POST_303))) {
http_switch_to_get(data, 303);
switch_to_get = TRUE;
}
break;
case 304:
break;
case 305:
break;
}
if(rewind_result && !switch_to_get)
return rewind_result;
Curl_pgrsTime(data, TIMER_REDIRECT);
Curl_pgrsResetTransferSizes(data);
return CURLE_OK;
}
bool
Curl_compareheader(const char *headerline,
const char *header,
const size_t hlen,
const char *content,
const size_t clen)
{
const char *p;
struct Curl_str val;
DEBUGASSERT(hlen);
DEBUGASSERT(clen);
DEBUGASSERT(header);
DEBUGASSERT(content);
if(!curl_strnequal(headerline, header, hlen))
return FALSE;
p = &headerline[hlen];
if(curlx_str_untilnl(&p, &val, MAX_HTTP_RESP_HEADER_SIZE))
return FALSE;
curlx_str_trimblanks(&val);
if(curlx_strlen(&val) >= clen) {
size_t len;
p = curlx_str(&val);
for(len = curlx_strlen(&val); len >= curlx_strlen(&val); len--, p++) {
if(curl_strnequal(p, content, clen))
return TRUE;
}
}
return FALSE;
}
CURLcode Curl_http_connect(struct Curl_easy *data, bool *done)
{
struct connectdata *conn = data->conn;
connkeep(conn, "HTTP default");
return Curl_conn_connect(data, FIRSTSOCKET, FALSE, done);
}
CURLcode Curl_http_do_pollset(struct Curl_easy *data,
struct easy_pollset *ps)
{
return Curl_pollset_add_out(data, ps, data->conn->sock[FIRSTSOCKET]);
}
CURLcode Curl_http_done(struct Curl_easy *data,
CURLcode status, bool premature)
{
struct connectdata *conn = data->conn;
data->state.authhost.multipass = FALSE;
data->state.authproxy.multipass = FALSE;
curlx_dyn_reset(&data->state.headerb);
if(status)
return status;
if(!premature &&
!conn->bits.retry &&
!data->set.connect_only &&
(data->req.bytecount +
data->req.headerbytecount -
data->req.deductheadercount) <= 0) {
failf(data, "Empty reply from server");
streamclose(conn, "Empty reply from server");
return CURLE_GOT_NOTHING;
}
return CURLE_OK;
}
static bool http_may_use_1_1(const struct Curl_easy *data)
{
const struct connectdata *conn = data->conn;
if(data->state.http_neg.rcvd_min == 10)
return FALSE;
if(conn && conn->httpversion_seen == 10)
return FALSE;
if((data->state.http_neg.only_10) &&
(!conn || conn->httpversion_seen <= 10))
return FALSE;
return !data->state.http_neg.only_10;
}
static unsigned char http_request_version(struct Curl_easy *data)
{
unsigned char v = Curl_conn_http_version(data, data->conn);
if(!v) {
v = http_may_use_1_1(data) ? 11 : 10;
}
return v;
}
static const char *get_http_string(int httpversion)
{
switch(httpversion) {
case 30:
return "3";
case 20:
return "2";
case 11:
return "1.1";
default:
return "1.0";
}
}
CURLcode Curl_add_custom_headers(struct Curl_easy *data,
bool is_connect, int httpversion,
struct dynbuf *req)
{
struct curl_slist *h[2];
struct curl_slist *headers;
int numlists = 1;
int i;
#ifndef CURL_DISABLE_PROXY
enum Curl_proxy_use proxy;
if(is_connect)
proxy = HEADER_CONNECT;
else
proxy = data->conn->bits.httpproxy && !data->conn->bits.tunnel_proxy ?
HEADER_PROXY : HEADER_SERVER;
switch(proxy) {
case HEADER_SERVER:
h[0] = data->set.headers;
break;
case HEADER_PROXY:
h[0] = data->set.headers;
if(data->set.sep_headers) {
h[1] = data->set.proxyheaders;
numlists++;
}
break;
case HEADER_CONNECT:
if(data->set.sep_headers)
h[0] = data->set.proxyheaders;
else
h[0] = data->set.headers;
break;
}
#else
(void)is_connect;
h[0] = data->set.headers;
#endif
for(i = 0; i < numlists; i++) {
for(headers = h[i]; headers; headers = headers->next) {
CURLcode result = CURLE_OK;
bool blankheader = FALSE;
struct Curl_str name;
const char *p = headers->data;
const char *origp = p;
if(!curlx_str_until(&p, &name, MAX_HTTP_RESP_HEADER_SIZE, ';') &&
!curlx_str_single(&p, ';') &&
!curlx_str_single(&p, '\0') &&
!memchr(curlx_str(&name), ':', curlx_strlen(&name)))
blankheader = TRUE;
else {
p = origp;
if(!curlx_str_until(&p, &name, MAX_HTTP_RESP_HEADER_SIZE, ':') &&
!curlx_str_single(&p, ':')) {
struct Curl_str val;
curlx_str_untilnl(&p, &val, MAX_HTTP_RESP_HEADER_SIZE);
curlx_str_trimblanks(&val);
if(!curlx_strlen(&val))
continue;
}
else
continue;
}
if(data->state.aptr.host &&
curlx_str_casecompare(&name, "Host"))
;
else if(data->state.httpreq == HTTPREQ_POST_FORM &&
curlx_str_casecompare(&name, "Content-Type"))
;
else if(data->state.httpreq == HTTPREQ_POST_MIME &&
curlx_str_casecompare(&name, "Content-Type"))
;
else if(data->req.authneg &&
curlx_str_casecompare(&name, "Content-Length"))
;
else if(curlx_str_casecompare(&name, "Connection"))
;
else if((httpversion >= 20) &&
curlx_str_casecompare(&name, "Transfer-Encoding"))
;
else if((curlx_str_casecompare(&name, "Authorization") ||
curlx_str_casecompare(&name, "Cookie")) &&
!Curl_auth_allowed_to_host(data))
;
else if(blankheader)
result = curlx_dyn_addf(req, "%.*s:\r\n", (int)curlx_strlen(&name),
curlx_str(&name));
else
result = curlx_dyn_addf(req, "%s\r\n", origp);
if(result)
return result;
}
}
return CURLE_OK;
}
#ifndef CURL_DISABLE_PARSEDATE
CURLcode Curl_add_timecondition(struct Curl_easy *data,
struct dynbuf *req)
{
const struct tm *tm;
struct tm keeptime;
CURLcode result;
char datestr[80];
const char *condp;
size_t len;
if(data->set.timecondition == CURL_TIMECOND_NONE)
return CURLE_OK;
result = Curl_gmtime(data->set.timevalue, &keeptime);
if(result) {
failf(data, "Invalid TIMEVALUE");
return result;
}
tm = &keeptime;
switch(data->set.timecondition) {
default:
DEBUGF(infof(data, "invalid time condition"));
return CURLE_BAD_FUNCTION_ARGUMENT;
case CURL_TIMECOND_IFMODSINCE:
condp = "If-Modified-Since";
len = 17;
break;
case CURL_TIMECOND_IFUNMODSINCE:
condp = "If-Unmodified-Since";
len = 19;
break;
case CURL_TIMECOND_LASTMOD:
condp = "Last-Modified";
len = 13;
break;
}
if(Curl_checkheaders(data, condp, len)) {
return CURLE_OK;
}
curl_msnprintf(datestr, sizeof(datestr),
"%s: %s, %02d %s %4d %02d:%02d:%02d GMT\r\n",
condp,
Curl_wkday[tm->tm_wday ? tm->tm_wday-1 : 6],
tm->tm_mday,
Curl_month[tm->tm_mon],
tm->tm_year + 1900,
tm->tm_hour,
tm->tm_min,
tm->tm_sec);
result = curlx_dyn_add(req, datestr);
return result;
}
#else
CURLcode Curl_add_timecondition(struct Curl_easy *data,
struct dynbuf *req)
{
(void)data;
(void)req;
return CURLE_OK;
}
#endif
void Curl_http_method(struct Curl_easy *data,
const char **method, Curl_HttpReq *reqp)
{
Curl_HttpReq httpreq = (Curl_HttpReq)data->state.httpreq;
const char *request;
#ifndef CURL_DISABLE_WEBSOCKETS
if(data->conn->handler->protocol&(CURLPROTO_WS|CURLPROTO_WSS))
httpreq = HTTPREQ_GET;
else
#endif
if((data->conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_FTP)) &&
data->state.upload)
httpreq = HTTPREQ_PUT;
if(data->set.str[STRING_CUSTOMREQUEST] &&
!data->state.http_ignorecustom) {
request = data->set.str[STRING_CUSTOMREQUEST];
}
else {
if(data->req.no_body)
request = "HEAD";
else {
DEBUGASSERT((httpreq >= HTTPREQ_GET) && (httpreq <= HTTPREQ_HEAD));
switch(httpreq) {
case HTTPREQ_POST:
case HTTPREQ_POST_FORM:
case HTTPREQ_POST_MIME:
request = "POST";
break;
case HTTPREQ_PUT:
request = "PUT";
break;
default:
case HTTPREQ_GET:
request = "GET";
break;
case HTTPREQ_HEAD:
request = "HEAD";
break;
}
}
}
*method = request;
*reqp = httpreq;
}
static CURLcode http_useragent(struct Curl_easy *data)
{
if(Curl_checkheaders(data, STRCONST("User-Agent"))) {
free(data->state.aptr.uagent);
data->state.aptr.uagent = NULL;
}
return CURLE_OK;
}
static CURLcode http_set_aptr_host(struct Curl_easy *data)
{
struct connectdata *conn = data->conn;
struct dynamically_allocated_data *aptr = &data->state.aptr;
const char *ptr;
if(!data->state.this_is_a_follow) {
free(data->state.first_host);
data->state.first_host = strdup(conn->host.name);
if(!data->state.first_host)
return CURLE_OUT_OF_MEMORY;
data->state.first_remote_port = conn->remote_port;
data->state.first_remote_protocol = conn->handler->protocol;
}
Curl_safefree(aptr->host);
ptr = Curl_checkheaders(data, STRCONST("Host"));
if(ptr && (!data->state.this_is_a_follow ||
curl_strequal(data->state.first_host, conn->host.name))) {
#ifndef CURL_DISABLE_COOKIES
char *cookiehost;
CURLcode result = copy_custom_value(ptr, &cookiehost);
if(result)
return result;
if(!*cookiehost)
free(cookiehost);
else {
if(*cookiehost == '[') {
char *closingbracket;
memmove(cookiehost, cookiehost + 1, strlen(cookiehost) - 1);
closingbracket = strchr(cookiehost, ']');
if(closingbracket)
*closingbracket = 0;
}
else {
int startsearch = 0;
char *colon = strchr(cookiehost + startsearch, ':');
if(colon)
*colon = 0;
}
free(aptr->cookiehost);
aptr->cookiehost = cookiehost;
}
#endif
if(!curl_strequal("Host:", ptr)) {
aptr->host = curl_maprintf("Host:%s\r\n", &ptr[5]);
if(!aptr->host)
return CURLE_OUT_OF_MEMORY;
}
}
else {
const char *host = conn->host.name;
if(((conn->given->protocol&(CURLPROTO_HTTPS|CURLPROTO_WSS)) &&
(conn->remote_port == PORT_HTTPS)) ||
((conn->given->protocol&(CURLPROTO_HTTP|CURLPROTO_WS)) &&
(conn->remote_port == PORT_HTTP)) )
aptr->host = curl_maprintf("Host: %s%s%s\r\n",
conn->bits.ipv6_ip ? "[" : "",
host, conn->bits.ipv6_ip ? "]" : "");
else
aptr->host = curl_maprintf("Host: %s%s%s:%d\r\n",
conn->bits.ipv6_ip ? "[" : "",
host, conn->bits.ipv6_ip ? "]" : "",
conn->remote_port);
if(!aptr->host)
return CURLE_OUT_OF_MEMORY;
}
return CURLE_OK;
}
static CURLcode http_target(struct Curl_easy *data,
struct dynbuf *r)
{
CURLcode result = CURLE_OK;
const char *path = data->state.up.path;
const char *query = data->state.up.query;
#ifndef CURL_DISABLE_PROXY
struct connectdata *conn = data->conn;
#endif
if(data->set.str[STRING_TARGET]) {
path = data->set.str[STRING_TARGET];
query = NULL;
}
#ifndef CURL_DISABLE_PROXY
if(conn->bits.httpproxy && !conn->bits.tunnel_proxy) {
CURLUcode uc;
char *url;
CURLU *h = curl_url_dup(data->state.uh);
if(!h)
return CURLE_OUT_OF_MEMORY;
if(conn->host.dispname != conn->host.name) {
uc = curl_url_set(h, CURLUPART_HOST, conn->host.name, 0);
if(uc) {
curl_url_cleanup(h);
return CURLE_OUT_OF_MEMORY;
}
}
uc = curl_url_set(h, CURLUPART_FRAGMENT, NULL, 0);
if(uc) {
curl_url_cleanup(h);
return CURLE_OUT_OF_MEMORY;
}
if(curl_strequal("http", data->state.up.scheme)) {
uc = curl_url_set(h, CURLUPART_USER, NULL, 0);
if(uc) {
curl_url_cleanup(h);
return CURLE_OUT_OF_MEMORY;
}
uc = curl_url_set(h, CURLUPART_PASSWORD, NULL, 0);
if(uc) {
curl_url_cleanup(h);
return CURLE_OUT_OF_MEMORY;
}
}
uc = curl_url_get(h, CURLUPART_URL, &url, CURLU_NO_DEFAULT_PORT);
if(uc) {
curl_url_cleanup(h);
return CURLE_OUT_OF_MEMORY;
}
curl_url_cleanup(h);
result = curlx_dyn_add(r, data->set.str[STRING_TARGET] ?
data->set.str[STRING_TARGET] : url);
free(url);
if(result)
return result;
if(curl_strequal("ftp", data->state.up.scheme) &&
data->set.proxy_transfer_mode) {
size_t len = strlen(path);
bool type_present = FALSE;
if((len >= 7) && !memcmp(&path[len - 7], ";type=", 6)) {
switch(Curl_raw_toupper(path[len - 1])) {
case 'A':
case 'D':
case 'I':
type_present = TRUE;
break;
}
}
if(!type_present) {
result = curlx_dyn_addf(r, ";type=%c",
data->state.prefer_ascii ? 'a' : 'i');
if(result)
return result;
}
}
}
else
#endif
{
result = curlx_dyn_add(r, path);
if(result)
return result;
if(query)
result = curlx_dyn_addf(r, "?%s", query);
}
return result;
}
#if !defined(CURL_DISABLE_MIME) || !defined(CURL_DISABLE_FORM_API)
static CURLcode set_post_reader(struct Curl_easy *data, Curl_HttpReq httpreq)
{
CURLcode result;
switch(httpreq) {
#ifndef CURL_DISABLE_MIME
case HTTPREQ_POST_MIME:
data->state.mimepost = &data->set.mimepost;
break;
#endif
#ifndef CURL_DISABLE_FORM_API
case HTTPREQ_POST_FORM:
if(!data->state.formp) {
data->state.formp = calloc(1, sizeof(curl_mimepart));
if(!data->state.formp)
return CURLE_OUT_OF_MEMORY;
Curl_mime_cleanpart(data->state.formp);
result = Curl_getformdata(data, data->state.formp, data->set.httppost,
data->state.fread_func);
if(result) {
Curl_safefree(data->state.formp);
return result;
}
data->state.mimepost = data->state.formp;
}
break;
#endif
default:
data->state.mimepost = NULL;
break;
}
switch(httpreq) {
case HTTPREQ_POST_FORM:
case HTTPREQ_POST_MIME:
#ifndef CURL_DISABLE_MIME
if(data->state.mimepost) {
const char *cthdr = Curl_checkheaders(data, STRCONST("Content-Type"));
data->state.mimepost->flags |= MIME_BODY_ONLY;
if(cthdr)
for(cthdr += 13; *cthdr == ' '; cthdr++)
;
else if(data->state.mimepost->kind == MIMEKIND_MULTIPART)
cthdr = "multipart/form-data";
curl_mime_headers(data->state.mimepost, data->set.headers, 0);
result = Curl_mime_prepare_headers(data, data->state.mimepost, cthdr,
NULL, MIMESTRATEGY_FORM);
if(result)
return result;
curl_mime_headers(data->state.mimepost, NULL, 0);
result = Curl_creader_set_mime(data, data->state.mimepost);
if(result)
return result;
}
else
#endif
{
result = Curl_creader_set_null(data);
}
data->state.infilesize = Curl_creader_total_length(data);
return result;
default:
return Curl_creader_set_null(data);
}
}
#endif
static CURLcode set_reader(struct Curl_easy *data, Curl_HttpReq httpreq)
{
CURLcode result = CURLE_OK;
curl_off_t postsize = data->state.infilesize;
DEBUGASSERT(data->conn);
if(data->req.authneg) {
return Curl_creader_set_null(data);
}
switch(httpreq) {
case HTTPREQ_PUT:
return postsize ? Curl_creader_set_fread(data, postsize) :
Curl_creader_set_null(data);
#if !defined(CURL_DISABLE_MIME) || !defined(CURL_DISABLE_FORM_API)
case HTTPREQ_POST_FORM:
case HTTPREQ_POST_MIME:
return set_post_reader(data, httpreq);
#endif
case HTTPREQ_POST:
if(!postsize) {
result = Curl_creader_set_null(data);
}
else if(data->set.postfields) {
if(postsize > 0)
result = Curl_creader_set_buf(data, data->set.postfields,
(size_t)postsize);
else
result = Curl_creader_set_null(data);
}
else {
bool chunked = FALSE;
char *ptr = Curl_checkheaders(data, STRCONST("Transfer-Encoding"));
if(ptr) {
chunked = Curl_compareheader(ptr, STRCONST("Transfer-Encoding:"),
STRCONST("chunked"));
}
result = Curl_creader_set_fread(data, chunked ? -1 : postsize);
}
return result;
default:
data->state.infilesize = 0;
return Curl_creader_set_null(data);
}
}
static CURLcode http_resume(struct Curl_easy *data, Curl_HttpReq httpreq)
{
if((HTTPREQ_POST == httpreq || HTTPREQ_PUT == httpreq) &&
data->state.resume_from) {
if(data->state.resume_from < 0) {
data->state.resume_from = 0;
}
if(data->state.resume_from && !data->req.authneg) {
CURLcode result;
result = Curl_creader_resume_from(data, data->state.resume_from);
if(result) {
failf(data, "Unable to resume from offset %" FMT_OFF_T,
data->state.resume_from);
return result;
}
}
}
return CURLE_OK;
}
static CURLcode http_req_set_TE(struct Curl_easy *data,
struct dynbuf *req,
int httpversion)
{
CURLcode result = CURLE_OK;
const char *ptr;
ptr = Curl_checkheaders(data, STRCONST("Transfer-Encoding"));
if(ptr) {
data->req.upload_chunky =
Curl_compareheader(ptr,
STRCONST("Transfer-Encoding:"), STRCONST("chunked"));
if(data->req.upload_chunky && (httpversion >= 20)) {
infof(data, "suppressing chunked transfer encoding on connection "
"using HTTP version 2 or higher");
data->req.upload_chunky = FALSE;
}
}
else {
curl_off_t req_clen = Curl_creader_total_length(data);
if(req_clen < 0) {
if(httpversion > 10) {
data->req.upload_chunky = (httpversion < 20);
}
else {
failf(data, "Chunky upload is not supported by HTTP 1.0");
return CURLE_UPLOAD_FAILED;
}
}
else {
data->req.upload_chunky = FALSE;
}
if(data->req.upload_chunky)
result = curlx_dyn_add(req, "Transfer-Encoding: chunked\r\n");
}
return result;
}
static CURLcode addexpect(struct Curl_easy *data, struct dynbuf *r,
int httpversion, bool *announced_exp100)
{
CURLcode result;
char *ptr;
*announced_exp100 = FALSE;
if(data->req.upgr101 != UPGR101_NONE)
return CURLE_OK;
ptr = Curl_checkheaders(data, STRCONST("Expect"));
if(ptr) {
*announced_exp100 =
Curl_compareheader(ptr, STRCONST("Expect:"), STRCONST("100-continue"));
}
else if(!data->state.disableexpect && (httpversion == 11)) {
curl_off_t client_len = Curl_creader_client_length(data);
if(client_len > EXPECT_100_THRESHOLD || client_len < 0) {
result = curlx_dyn_addn(r, STRCONST("Expect: 100-continue\r\n"));
if(result)
return result;
*announced_exp100 = TRUE;
}
}
return CURLE_OK;
}
static CURLcode http_add_content_hds(struct Curl_easy *data,
struct dynbuf *r,
int httpversion,
Curl_HttpReq httpreq)
{
CURLcode result = CURLE_OK;
curl_off_t req_clen;
bool announced_exp100 = FALSE;
DEBUGASSERT(data->conn);
if(data->req.upload_chunky) {
result = Curl_httpchunk_add_reader(data);
if(result)
return result;
}
req_clen = Curl_creader_total_length(data);
switch(httpreq) {
case HTTPREQ_PUT:
case HTTPREQ_POST:
#if !defined(CURL_DISABLE_MIME) || !defined(CURL_DISABLE_FORM_API)
case HTTPREQ_POST_FORM:
case HTTPREQ_POST_MIME:
#endif
if(req_clen >= 0 && !data->req.upload_chunky &&
(data->req.authneg ||
!Curl_checkheaders(data, STRCONST("Content-Length")))) {
result = curlx_dyn_addf(r, "Content-Length: %" FMT_OFF_T "\r\n",
req_clen);
}
if(result)
goto out;
#ifndef CURL_DISABLE_MIME
if(data->state.mimepost &&
((httpreq == HTTPREQ_POST_FORM) || (httpreq == HTTPREQ_POST_MIME))) {
struct curl_slist *hdr;
for(hdr = data->state.mimepost->curlheaders; hdr; hdr = hdr->next) {
result = curlx_dyn_addf(r, "%s\r\n", hdr->data);
if(result)
goto out;
}
}
#endif
if(httpreq == HTTPREQ_POST) {
if(!Curl_checkheaders(data, STRCONST("Content-Type"))) {
result = curlx_dyn_addn(r, STRCONST("Content-Type: application/"
"x-www-form-urlencoded\r\n"));
if(result)
goto out;
}
}
result = addexpect(data, r, httpversion, &announced_exp100);
if(result)
goto out;
break;
default:
break;
}
Curl_pgrsSetUploadSize(data, req_clen);
if(announced_exp100)
result = http_exp100_add_reader(data);
out:
return result;
}
#ifndef CURL_DISABLE_COOKIES
static CURLcode http_cookies(struct Curl_easy *data,
struct dynbuf *r)
{
CURLcode result = CURLE_OK;
char *addcookies = NULL;
bool linecap = FALSE;
if(data->set.str[STRING_COOKIE] &&
!Curl_checkheaders(data, STRCONST("Cookie")))
addcookies = data->set.str[STRING_COOKIE];
if(data->cookies || addcookies) {
struct Curl_llist list;
int count = 0;
if(data->cookies && data->state.cookie_engine) {
const char *host = data->state.aptr.cookiehost ?
data->state.aptr.cookiehost : data->conn->host.name;
Curl_share_lock(data, CURL_LOCK_DATA_COOKIE, CURL_LOCK_ACCESS_SINGLE);
if(!Curl_cookie_getlist(data, data->conn, host, &list)) {
struct Curl_llist_node *n;
size_t clen = 8;
for(n = Curl_llist_head(&list); n; n = Curl_node_next(n)) {
struct Cookie *co = Curl_node_elem(n);
if(co->value) {
size_t add;
if(!count) {
result = curlx_dyn_addn(r, STRCONST("Cookie: "));
if(result)
break;
}
add = strlen(co->name) + strlen(co->value) + 1;
if(clen + add >= MAX_COOKIE_HEADER_LEN) {
infof(data, "Restricted outgoing cookies due to header size, "
"'%s' not sent", co->name);
linecap = TRUE;
break;
}
result = curlx_dyn_addf(r, "%s%s=%s", count ? "; " : "",
co->name, co->value);
if(result)
break;
clen += add + (count ? 2 : 0);
count++;
}
}
Curl_llist_destroy(&list, NULL);
}
Curl_share_unlock(data, CURL_LOCK_DATA_COOKIE);
}
if(addcookies && !result && !linecap) {
if(!count)
result = curlx_dyn_addn(r, STRCONST("Cookie: "));
if(!result) {
result = curlx_dyn_addf(r, "%s%s", count ? "; " : "", addcookies);
count++;
}
}
if(count && !result)
result = curlx_dyn_addn(r, STRCONST("\r\n"));
if(result)
return result;
}
return result;
}
#else
#define http_cookies(a,b) CURLE_OK
#endif
static CURLcode http_range(struct Curl_easy *data,
Curl_HttpReq httpreq)
{
if(data->state.use_range) {
if(((httpreq == HTTPREQ_GET) || (httpreq == HTTPREQ_HEAD)) &&
!Curl_checkheaders(data, STRCONST("Range"))) {
free(data->state.aptr.rangeline);
data->state.aptr.rangeline = curl_maprintf("Range: bytes=%s\r\n",
data->state.range);
}
else if((httpreq == HTTPREQ_POST || httpreq == HTTPREQ_PUT) &&
!Curl_checkheaders(data, STRCONST("Content-Range"))) {
curl_off_t req_clen = Curl_creader_total_length(data);
free(data->state.aptr.rangeline);
if(data->set.set_resume_from < 0) {
data->state.aptr.rangeline =
curl_maprintf("Content-Range: bytes 0-%" FMT_OFF_T "/"
"%" FMT_OFF_T "\r\n", req_clen - 1, req_clen);
}
else if(data->state.resume_from) {
curl_off_t total_len = data->req.authneg ?
data->state.infilesize :
(data->state.resume_from + req_clen);
data->state.aptr.rangeline =
curl_maprintf("Content-Range: bytes %s%" FMT_OFF_T "/"
"%" FMT_OFF_T "\r\n",
data->state.range, total_len-1, total_len);
}
else {
data->state.aptr.rangeline =
curl_maprintf("Content-Range: bytes %s/%" FMT_OFF_T "\r\n",
data->state.range, req_clen);
}
if(!data->state.aptr.rangeline)
return CURLE_OUT_OF_MEMORY;
}
}
return CURLE_OK;
}
static CURLcode http_firstwrite(struct Curl_easy *data)
{
struct connectdata *conn = data->conn;
struct SingleRequest *k = &data->req;
if(data->req.newurl) {
if(conn->bits.close) {
k->keepon &= ~KEEP_RECV;
k->done = TRUE;
return CURLE_OK;
}
k->ignorebody = TRUE;
infof(data, "Ignoring the response-body");
}
if(data->state.resume_from && !k->content_range &&
(data->state.httpreq == HTTPREQ_GET) &&
!k->ignorebody) {
if(k->size == data->state.resume_from) {
infof(data, "The entire document is already downloaded");
streamclose(conn, "already downloaded");
k->keepon &= ~KEEP_RECV;
k->done = TRUE;
return CURLE_OK;
}
failf(data, "HTTP server does not seem to support "
"byte ranges. Cannot resume.");
return CURLE_RANGE_ERROR;
}
if(data->set.timecondition && !data->state.range) {
if(!Curl_meets_timecondition(data, k->timeofdoc)) {
k->done = TRUE;
data->info.httpcode = 304;
infof(data, "Simulate an HTTP 304 response");
streamclose(conn, "Simulated 304 handling");
return CURLE_OK;
}
}
return CURLE_OK;
}
static CURLcode http_check_new_conn(struct Curl_easy *data)
{
struct connectdata *conn = data->conn;
const char *info_version = NULL;
const char *alpn;
CURLcode result;
alpn = Curl_conn_get_alpn_negotiated(data, conn);
if(alpn && !strcmp("h3", alpn)) {
DEBUGASSERT(Curl_conn_http_version(data, conn) == 30);
info_version = "HTTP/3";
}
else if(alpn && !strcmp("h2", alpn)) {
#ifndef CURL_DISABLE_PROXY
if((Curl_conn_http_version(data, conn) != 20) &&
conn->bits.proxy && !conn->bits.tunnel_proxy) {
result = Curl_http2_switch(data);
if(result)
return result;
}
else
#endif
DEBUGASSERT(Curl_conn_http_version(data, conn) == 20);
info_version = "HTTP/2";
}
else {
if(Curl_http2_may_switch(data)) {
DEBUGF(infof(data, "HTTP/2 over clean TCP"));
result = Curl_http2_switch(data);
if(result)
return result;
info_version = "HTTP/2";
conn->httpversion_seen = 20;
Curl_conn_set_multiplex(conn);
}
else
info_version = "HTTP/1.x";
}
if(info_version)
infof(data, "using %s", info_version);
return CURLE_OK;
}
static CURLcode http_add_connection_hd(struct Curl_easy *data,
struct dynbuf *req)
{
struct curl_slist *head;
const char *sep = "Connection: ";
CURLcode result = CURLE_OK;
size_t rlen = curlx_dyn_len(req);
bool skip;
for(head = data->set.headers; head; head = head->next) {
if(curl_strnequal(head->data, "Connection", 10) &&
Curl_headersep(head->data[10]) &&
!http_header_is_empty(head->data)) {
char *value;
result = copy_custom_value(head->data, &value);
if(result)
return result;
result = curlx_dyn_addf(req, "%s%s", sep, value);
sep = ", ";
free(value);
break;
}
}
if(!result && data->state.http_hd_te) {
result = curlx_dyn_addf(req, "%s%s", sep, "TE");
sep = ", ";
}
if(!result && data->state.http_hd_upgrade) {
result = curlx_dyn_addf(req, "%s%s", sep, "Upgrade");
sep = ", ";
}
if(!result && data->state.http_hd_h2_settings) {
result = curlx_dyn_addf(req, "%s%s", sep, "HTTP2-Settings");
}
if(!result && (rlen < curlx_dyn_len(req)))
result = curlx_dyn_addn(req, STRCONST("\r\n"));
if(result)
return result;
skip = TRUE;
for(head = data->set.headers; head; head = head->next) {
if(curl_strnequal(head->data, "Connection", 10) &&
Curl_headersep(head->data[10]) &&
!http_header_is_empty(head->data)) {
if(skip) {
skip = FALSE;
continue;
}
result = curlx_dyn_addf(req, "%s\r\n", head->data);
if(result)
return result;
}
}
return CURLE_OK;
}
typedef enum {
H1_HD_REQUEST,
H1_HD_HOST,
#ifndef CURL_DISABLE_PROXY
H1_HD_PROXY_AUTH,
#endif
H1_HD_USER_AUTH,
H1_HD_RANGE,
H1_HD_USER_AGENT,
H1_HD_ACCEPT,
H1_HD_TE,
H1_HD_ACCEPT_ENCODING,
H1_HD_REFERER,
#ifndef CURL_DISABLE_PROXY
H1_HD_PROXY_CONNECTION,
#endif
H1_HD_TRANSFER_ENCODING,
#ifndef CURL_DISABLE_ALTSVC
H1_HD_ALT_USED,
#endif
H1_HD_UPGRADE,
H1_HD_COOKIES,
H1_HD_CONDITIONALS,
H1_HD_CUSTOM,
H1_HD_CONTENT,
H1_HD_CONNECTION,
H1_HD_LAST
} http_hd_t;
static CURLcode http_add_hd(struct Curl_easy *data,
struct dynbuf *req,
http_hd_t id,
unsigned char httpversion,
const char *method,
Curl_HttpReq httpreq)
{
CURLcode result = CURLE_OK;
#if !defined(CURL_DISABLE_ALTSVC) || \
!defined(CURL_DISABLE_PROXY) || \
!defined(CURL_DISABLE_WEBSOCKETS)
struct connectdata *conn = data->conn;
#endif
switch(id) {
case H1_HD_REQUEST:
result = curlx_dyn_addf(req, "%s ", method);
if(!result)
result = http_target(data, req);
if(!result)
result = curlx_dyn_addf(req, " HTTP/%s\r\n",
get_http_string(httpversion));
break;
case H1_HD_HOST:
if(data->state.aptr.host)
result = curlx_dyn_add(req, data->state.aptr.host);
break;
#ifndef CURL_DISABLE_PROXY
case H1_HD_PROXY_AUTH:
if(data->state.aptr.proxyuserpwd)
result = curlx_dyn_add(req, data->state.aptr.proxyuserpwd);
break;
#endif
case H1_HD_USER_AUTH:
if(data->state.aptr.userpwd)
result = curlx_dyn_add(req, data->state.aptr.userpwd);
break;
case H1_HD_RANGE:
if(data->state.use_range && data->state.aptr.rangeline)
result = curlx_dyn_add(req, data->state.aptr.rangeline);
break;
case H1_HD_USER_AGENT:
if(data->set.str[STRING_USERAGENT] &&
*data->set.str[STRING_USERAGENT] &&
data->state.aptr.uagent)
result = curlx_dyn_add(req, data->state.aptr.uagent);
break;
case H1_HD_ACCEPT:
if(!Curl_checkheaders(data, STRCONST("Accept")))
result = curlx_dyn_add(req, "Accept: */*\r\n");
break;
case H1_HD_TE:
#ifdef HAVE_LIBZ
if(!Curl_checkheaders(data, STRCONST("TE")) &&
data->set.http_transfer_encoding) {
data->state.http_hd_te = TRUE;
result = curlx_dyn_add(req, "TE: gzip\r\n");
}
#endif
break;
case H1_HD_ACCEPT_ENCODING:
Curl_safefree(data->state.aptr.accept_encoding);
if(!Curl_checkheaders(data, STRCONST("Accept-Encoding")) &&
data->set.str[STRING_ENCODING])
result = curlx_dyn_addf(req, "Accept-Encoding: %s\r\n",
data->set.str[STRING_ENCODING]);
break;
case H1_HD_REFERER:
Curl_safefree(data->state.aptr.ref);
if(data->state.referer && !Curl_checkheaders(data, STRCONST("Referer")))
result = curlx_dyn_addf(req, "Referer: %s\r\n", data->state.referer);
break;
#ifndef CURL_DISABLE_PROXY
case H1_HD_PROXY_CONNECTION:
if(conn->bits.httpproxy &&
!conn->bits.tunnel_proxy &&
!Curl_checkheaders(data, STRCONST("Proxy-Connection")) &&
!Curl_checkProxyheaders(data, data->conn, STRCONST("Proxy-Connection")))
result = curlx_dyn_add(req, "Proxy-Connection: Keep-Alive\r\n");
break;
#endif
case H1_HD_TRANSFER_ENCODING:
result = http_req_set_TE(data, req, httpversion);
break;
#ifndef CURL_DISABLE_ALTSVC
case H1_HD_ALT_USED:
if(conn->bits.altused && !Curl_checkheaders(data, STRCONST("Alt-Used")))
result = curlx_dyn_addf(req, "Alt-Used: %s:%d\r\n",
conn->conn_to_host.name,
conn->conn_to_port);
break;
#endif
case H1_HD_UPGRADE:
if(!Curl_conn_is_ssl(data->conn, FIRSTSOCKET) && (httpversion < 20) &&
(data->state.http_neg.wanted & CURL_HTTP_V2x) &&
data->state.http_neg.h2_upgrade) {
result = Curl_http2_request_upgrade(req, data);
}
#ifndef CURL_DISABLE_WEBSOCKETS
if(!result && conn->handler->protocol&(CURLPROTO_WS|CURLPROTO_WSS))
result = Curl_ws_request(data, req);
#endif
break;
case H1_HD_COOKIES:
result = http_cookies(data, req);
break;
case H1_HD_CONDITIONALS:
result = Curl_add_timecondition(data, req);
break;
case H1_HD_CUSTOM:
result = Curl_add_custom_headers(data, FALSE, httpversion, req);
break;
case H1_HD_CONTENT:
result = http_add_content_hds(data, req, httpversion, httpreq);
break;
case H1_HD_CONNECTION: {
result = http_add_connection_hd(data, req);
break;
}
case H1_HD_LAST:
result = curlx_dyn_addn(req, STRCONST("\r\n"));
break;
}
return result;
}
CURLcode Curl_http(struct Curl_easy *data, bool *done)
{
CURLcode result = CURLE_OK;
Curl_HttpReq httpreq;
const char *method;
struct dynbuf req;
unsigned char httpversion;
size_t hd_id;
*done = TRUE;
curlx_dyn_init(&req, DYN_HTTP_REQUEST);
curlx_dyn_reset(&data->state.headerb);
if(!data->conn->bits.reuse) {
result = http_check_new_conn(data);
if(result)
goto out;
}
result = Curl_headers_init(data);
if(result)
goto out;
data->state.http_hd_te = FALSE;
data->state.http_hd_upgrade = FALSE;
data->state.http_hd_h2_settings = FALSE;
Curl_http_method(data, &method, &httpreq);
result = http_set_aptr_host(data);
if(!result) {
char *pq = NULL;
if(data->state.up.query) {
pq = curl_maprintf("%s?%s", data->state.up.path, data->state.up.query);
if(!pq) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
}
result = Curl_http_output_auth(data, data->conn, method, httpreq,
(pq ? pq : data->state.up.path), FALSE);
free(pq);
}
if(result)
goto out;
result = http_useragent(data);
if(result)
goto out;
result = set_reader(data, httpreq);
if(!result)
result = http_resume(data, httpreq);
if(!result)
result = http_range(data, httpreq);
if(result)
goto out;
httpversion = http_request_version(data);
for(hd_id = 0; hd_id <= H1_HD_LAST; ++hd_id) {
result = http_add_hd(data, &req, (http_hd_t)hd_id,
httpversion, method, httpreq);
if(result)
goto out;
}
Curl_xfer_setup_sendrecv(data, FIRSTSOCKET, -1);
result = Curl_req_send(data, &req, httpversion);
if((httpversion >= 20) && data->req.upload_chunky)
data->req.upload_chunky = FALSE;
out:
if(CURLE_TOO_LARGE == result)
failf(data, "HTTP request too large");
Curl_safefree(data->state.aptr.userpwd);
#ifndef CURL_DISABLE_PROXY
Curl_safefree(data->state.aptr.proxyuserpwd);
#endif
curlx_dyn_free(&req);
return result;
}
typedef enum {
STATUS_UNKNOWN,
STATUS_DONE,
STATUS_BAD
} statusline;
static bool checkprefixmax(const char *prefix, const char *buffer, size_t len)
{
size_t ch = CURLMIN(strlen(prefix), len);
return curl_strnequal(prefix, buffer, ch);
}
static statusline
checkhttpprefix(struct Curl_easy *data,
const char *s, size_t len)
{
struct curl_slist *head = data->set.http200aliases;
statusline rc = STATUS_BAD;
statusline onmatch = len >= 5 ? STATUS_DONE : STATUS_UNKNOWN;
while(head) {
if(checkprefixmax(head->data, s, len)) {
rc = onmatch;
break;
}
head = head->next;
}
if((rc != STATUS_DONE) && (checkprefixmax("HTTP/", s, len)))
rc = onmatch;
return rc;
}
#ifndef CURL_DISABLE_RTSP
static statusline
checkrtspprefix(struct Curl_easy *data,
const char *s, size_t len)
{
statusline result = STATUS_BAD;
statusline onmatch = len >= 5 ? STATUS_DONE : STATUS_UNKNOWN;
(void)data;
if(checkprefixmax("RTSP/", s, len))
result = onmatch;
return result;
}
#endif
static statusline
checkprotoprefix(struct Curl_easy *data, struct connectdata *conn,
const char *s, size_t len)
{
#ifndef CURL_DISABLE_RTSP
if(conn->handler->protocol & CURLPROTO_RTSP)
return checkrtspprefix(data, s, len);
#else
(void)conn;
#endif
return checkhttpprefix(data, s, len);
}
#define HD_IS(hd, hdlen, n) \
(((hdlen) >= (sizeof(n)-1)) && curl_strnequal((n), (hd), (sizeof(n)-1)))
#define HD_VAL(hd, hdlen, n) \
((((hdlen) >= (sizeof(n)-1)) && \
curl_strnequal((n), (hd), (sizeof(n)-1)))? (hd + (sizeof(n)-1)) : NULL)
#define HD_IS_AND_SAYS(hd, hdlen, n, v) \
(HD_IS(hd, hdlen, n) && \
((hdlen) > ((sizeof(n)-1) + (sizeof(v)-1))) && \
Curl_compareheader(hd, STRCONST(n), STRCONST(v)))
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
getenv("CURL_ALTSVC_HTTP")
#else
0
#endif
)) ? HD_VAL(hd, hdlen, "Alt-Svc:") : NULL;
if(v) {
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;
}
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;
v = (!k->http_bodyless && !data->set.ignorecl) ?
HD_VAL(hd, hdlen, "Content-Length:") : NULL;
if(v) {
do {
curl_off_t contentlength;
int offt = curlx_str_numblanks(&v, &contentlength);
if(offt == STRE_OVERFLOW) {
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");
return CURLE_OK;
}
else {
if((offt == STRE_OK) &&
((k->size == -1) ||
(k->size == contentlength))) {
k->size = contentlength;
curlx_str_passblanks(&v);
if(!curlx_str_single(&v, ','))
continue;
if(!curlx_str_newline(&v)) {
k->maxdownload = k->size;
return CURLE_OK;
}
}
failf(data, "Invalid Content-Length: value");
return CURLE_WEIRD_SERVER_REPLY;
}
} while(1);
}
v = (!k->http_bodyless && data->set.str[STRING_ENCODING]) ?
HD_VAL(hd, hdlen, "Content-Encoding:") : NULL;
if(v) {
return Curl_build_unencoding_stack(data, v, FALSE);
}
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)
free(contenttype);
else {
free(data->info.contenttype);
data->info.contenttype = contenttype;
}
return CURLE_OK;
}
if(HD_IS_AND_SAYS(hd, hdlen, "Connection:", "close")) {
streamclose(conn, "Connection: close used");
return CURLE_OK;
}
if((k->httpversion == 10) &&
HD_IS_AND_SAYS(hd, hdlen, "Connection:", "keep-alive")) {
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) {
const char *ptr = v;
while(*ptr && !ISDIGIT(*ptr) && *ptr != '*')
ptr++;
if(ISDIGIT(*ptr)) {
if(!curlx_str_number(&ptr, &k->offset, CURL_OFF_T_MAX) &&
(data->state.resume_from == k->offset))
k->content_range = TRUE;
}
else if(k->httpcode < 300)
data->state.resume_from = 0;
}
return CURLE_OK;
}
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) {
if(Curl_getdate_capped(v, &k->timeofdoc))
k->timeofdoc = 0;
if(data->set.get_filetime)
data->info.filetime = k->timeofdoc;
return CURLE_OK;
}
if(HD_IS(hd, hdlen, "Location:")) {
char *location = Curl_copy_header_value(hd);
if(!location)
return CURLE_OUT_OF_MEMORY;
if(!*location ||
(data->req.location && !strcmp(data->req.location, location))) {
free(location);
return CURLE_OK;
}
else {
if(data->req.location) {
failf(data, "Multiple Location headers");
free(location);
return CURLE_WEIRD_SERVER_REPLY;
}
data->req.location = location;
if((k->httpcode >= 300 && k->httpcode < 400) &&
data->set.http_follow_mode) {
CURLcode result;
DEBUGASSERT(!data->req.newurl);
data->req.newurl = strdup(data->req.location);
if(!data->req.newurl)
return CURLE_OUT_OF_MEMORY;
result = http_perhapsrewind(data, conn);
if(result)
return result;
data->state.this_is_a_follow = TRUE;
}
}
}
return CURLE_OK;
}
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")) {
connkeep(conn, "Proxy-Connection keep-alive");
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")) {
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 = auth ? CURLE_OK : CURLE_OUT_OF_MEMORY;
if(!result) {
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;
}
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) {
curl_off_t retry_after = 0;
time_t date = 0;
curlx_str_passblanks(&v);
if(!Curl_getdate_capped(v, &date)) {
time_t current = time(NULL);
if(date >= current)
retry_after = date - current;
}
else
(void)curlx_str_number(&v, &retry_after, CURL_OFF_T_MAX);
if(retry_after > 21600)
retry_after = 21600;
data->info.retry_after = retry_after;
}
return CURLE_OK;
}
static CURLcode http_header_s(struct Curl_easy *data,
const char *hd, size_t hdlen)
{
#if !defined(CURL_DISABLE_COOKIES) || !defined(CURL_DISABLE_HSTS)
struct connectdata *conn = data->conn;
const char *v;
#else
(void)data;
(void)hd;
(void)hdlen;
#endif
#ifndef CURL_DISABLE_COOKIES
v = (data->cookies && data->state.cookie_engine) ?
HD_VAL(hd, hdlen, "Set-Cookie:") : NULL;
if(v) {
const char *host = data->state.aptr.cookiehost ?
data->state.aptr.cookiehost : conn->host.name;
const bool secure_context = Curl_secure_context(conn, host);
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
v = (data->hsts &&
(Curl_conn_is_ssl(conn, FIRSTSOCKET) ||
#ifdef DEBUGBUILD
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;
}
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;
const char *v = (!k->http_bodyless &&
(data->state.httpreq != HTTPREQ_HEAD) &&
(k->httpcode != 304)) ?
HD_VAL(hd, hdlen, "Transfer-Encoding:") : NULL;
if(v) {
CURLcode result = Curl_build_unencoding_stack(data, v, TRUE);
if(result)
return result;
if(!k->chunk && data->set.http_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;
}
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)
result = CURLE_OUT_OF_MEMORY;
else {
result = Curl_http_input_auth(data, FALSE, auth);
free(auth);
}
}
return result;
}
static CURLcode http_header(struct Curl_easy *data,
const char *hd, size_t hdlen)
{
CURLcode result = CURLE_OK;
switch(hd[0]) {
case 'a':
case 'A':
result = http_header_a(data, hd, hdlen);
break;
case 'c':
case 'C':
result = http_header_c(data, hd, hdlen);
break;
case 'l':
case 'L':
result = http_header_l(data, hd, hdlen);
break;
case 'p':
case 'P':
result = http_header_p(data, hd, hdlen);
break;
case 'r':
case 'R':
result = http_header_r(data, hd, hdlen);
break;
case 's':
case 'S':
result = http_header_s(data, hd, hdlen);
break;
case 't':
case 'T':
result = http_header_t(data, hd, hdlen);
break;
case 'w':
case 'W':
result = http_header_w(data, hd, hdlen);
break;
}
if(!result) {
struct connectdata *conn = data->conn;
if(conn->handler->protocol & CURLPROTO_RTSP)
result = Curl_rtsp_parseheader(data, hd);
}
return result;
}
static CURLcode http_statusline(struct Curl_easy *data,
struct connectdata *conn)
{
struct SingleRequest *k = &data->req;
switch(k->httpversion) {
case 10:
case 11:
#ifdef USE_HTTP2
case 20:
#endif
#ifdef USE_HTTP3
case 30:
#endif
if(k->httpversion_sent &&
(k->httpversion/10 != k->httpversion_sent/10)) {
failf(data, "Version mismatch (from HTTP/%u to HTTP/%u)",
k->httpversion_sent/10, k->httpversion/10);
return CURLE_WEIRD_SERVER_REPLY;
}
break;
default:
failf(data, "Unsupported HTTP version (%u.%d) in response",
k->httpversion/10, k->httpversion%10);
return CURLE_UNSUPPORTED_PROTOCOL;
}
data->info.httpcode = k->httpcode;
data->info.httpversion = k->httpversion;
conn->httpversion_seen = (unsigned char)k->httpversion;
if(!data->state.http_neg.rcvd_min ||
data->state.http_neg.rcvd_min > k->httpversion)
data->state.http_neg.rcvd_min = (unsigned char)k->httpversion;
if(data->state.resume_from && data->state.httpreq == HTTPREQ_GET &&
k->httpcode == 416) {
k->ignorebody = TRUE;
}
if(k->httpversion == 10) {
infof(data, "HTTP 1.0, assume close after body");
connclose(conn, "HTTP/1.0 close after body");
}
k->http_bodyless = k->httpcode >= 100 && k->httpcode < 200;
switch(k->httpcode) {
case 304:
if(data->set.timecondition)
data->info.timecond = TRUE;
FALLTHROUGH();
case 204:
k->size = 0;
k->maxdownload = 0;
k->http_bodyless = TRUE;
break;
default:
break;
}
return CURLE_OK;
}
static CURLcode http_size(struct Curl_easy *data)
{
struct SingleRequest *k = &data->req;
if(data->req.ignore_cl || k->chunk) {
k->size = k->maxdownload = -1;
}
else if(k->size != -1) {
if(data->set.max_filesize &&
!k->ignorebody &&
(k->size > data->set.max_filesize)) {
failf(data, "Maximum file size exceeded");
return CURLE_FILESIZE_EXCEEDED;
}
if(k->ignorebody)
infof(data, "setting size while ignoring");
Curl_pgrsSetDownloadSize(data, k->size);
k->maxdownload = k->size;
}
return CURLE_OK;
}
static CURLcode verify_header(struct Curl_easy *data,
const char *hd, size_t hdlen)
{
struct SingleRequest *k = &data->req;
char *ptr = memchr(hd, 0x00, hdlen);
if(ptr) {
failf(data, "Nul byte in header");
return CURLE_WEIRD_SERVER_REPLY;
}
if(k->headerline < 2)
return CURLE_OK;
if(((hd[0] == ' ') || (hd[0] == '\t')) && k->headerline > 2)
;
else {
ptr = memchr(hd, ':', hdlen);
if(!ptr) {
failf(data, "Header without colon");
return CURLE_WEIRD_SERVER_REPLY;
}
}
return CURLE_OK;
}
CURLcode Curl_bump_headersize(struct Curl_easy *data,
size_t delta,
bool connect_only)
{
size_t bad = 0;
unsigned int max = MAX_HTTP_RESP_HEADER_SIZE;
if(delta < MAX_HTTP_RESP_HEADER_SIZE) {
data->info.header_size += (unsigned int)delta;
data->req.allheadercount += (unsigned int)delta;
if(!connect_only)
data->req.headerbytecount += (unsigned int)delta;
if(data->req.allheadercount > max)
bad = data->req.allheadercount;
else if(data->info.header_size > (max * 20)) {
bad = data->info.header_size;
max *= 20;
}
}
else
bad = data->req.allheadercount + delta;
if(bad) {
failf(data, "Too large response headers: %zu > %u", bad, max);
return CURLE_RECV_ERROR;
}
return CURLE_OK;
}
static CURLcode http_write_header(struct Curl_easy *data,
const char *hd, size_t hdlen)
{
CURLcode result;
int writetype;
Curl_debug(data, CURLINFO_HEADER_IN, hd, hdlen);
writetype = CLIENTWRITE_HEADER |
((data->req.httpcode/100 == 1) ? CLIENTWRITE_1XX : 0);
result = Curl_client_write(data, writetype, hd, hdlen);
if(result)
return result;
result = Curl_bump_headersize(data, hdlen, FALSE);
if(result)
return result;
data->req.deductheadercount = (100 <= data->req.httpcode &&
199 >= data->req.httpcode) ?
data->req.headerbytecount : 0;
return result;
}
static CURLcode http_on_response(struct Curl_easy *data,
const char *last_hd, size_t last_hd_len,
const char *buf, size_t blen,
size_t *pconsumed)
{
struct connectdata *conn = data->conn;
CURLcode result = CURLE_OK;
struct SingleRequest *k = &data->req;
bool conn_changed = FALSE;
(void)buf;
*pconsumed = 0;
if(k->upgr101 == UPGR101_RECEIVED) {
if(data->req.httpversion != 20)
infof(data, "Lying server, not serving HTTP/2");
}
if(k->httpcode < 200 && last_hd) {
result = http_write_header(data, last_hd, last_hd_len);
last_hd = NULL;
if(result)
goto out;
}
if(k->httpcode < 100) {
failf(data, "Unsupported response code in HTTP response");
result = CURLE_UNSUPPORTED_PROTOCOL;
goto out;
}
else if(k->httpcode < 200) {
k->header = TRUE;
k->headerline = 0;
switch(k->httpcode) {
case 100:
http_exp100_got100(data);
break;
case 101: {
int upgr101_requested = k->upgr101;
if(k->httpversion_sent != 11) {
failf(data, "server sent 101 response while not talking HTTP/1.1");
result = CURLE_WEIRD_SERVER_REPLY;
goto out;
}
k->upgr101 = UPGR101_RECEIVED;
data->conn->bits.upgrade_in_progress = FALSE;
conn_changed = TRUE;
switch(upgr101_requested) {
case UPGR101_H2:
infof(data, "Received 101, Switching to HTTP/2");
result = Curl_http2_upgrade(data, conn, FIRSTSOCKET, buf, blen);
if(result)
goto out;
*pconsumed += blen;
break;
#ifndef CURL_DISABLE_WEBSOCKETS
case UPGR101_WS:
infof(data, "Received 101, Switching to WebSocket");
result = Curl_ws_accept(data, buf, blen);
if(result)
goto out;
*pconsumed += blen;
break;
#endif
default:
k->header = FALSE;
break;
}
break;
}
default:
break;
}
goto out;
}
k->header = FALSE;
if(data->conn->bits.upgrade_in_progress) {
data->conn->bits.upgrade_in_progress = FALSE;
conn_changed = TRUE;
}
if((k->size == -1) && !k->chunk && !conn->bits.close &&
(k->httpversion == 11) &&
!(conn->handler->protocol & CURLPROTO_RTSP) &&
data->state.httpreq != HTTPREQ_HEAD) {
infof(data, "no chunk, no close, no size. Assume close to "
"signal end");
streamclose(conn, "HTTP: No end-of-message indicator");
}
#ifdef USE_NTLM
if(conn->bits.close &&
(((data->req.httpcode == 401) &&
(conn->http_ntlm_state == NTLMSTATE_TYPE2)) ||
((data->req.httpcode == 407) &&
(conn->proxy_ntlm_state == NTLMSTATE_TYPE2)))) {
infof(data, "Connection closure while negotiating auth (HTTP 1.0?)");
data->state.authproblem = TRUE;
}
#endif
#ifdef USE_SPNEGO
if(conn->bits.close &&
(((data->req.httpcode == 401) &&
(conn->http_negotiate_state == GSS_AUTHRECV)) ||
((data->req.httpcode == 407) &&
(conn->proxy_negotiate_state == GSS_AUTHRECV)))) {
infof(data, "Connection closure while negotiating auth (HTTP 1.0?)");
data->state.authproblem = TRUE;
}
if((conn->http_negotiate_state == GSS_AUTHDONE) &&
(data->req.httpcode != 401)) {
conn->http_negotiate_state = GSS_AUTHSUCC;
}
if((conn->proxy_negotiate_state == GSS_AUTHDONE) &&
(data->req.httpcode != 407)) {
conn->proxy_negotiate_state = GSS_AUTHSUCC;
}
#endif
#ifndef CURL_DISABLE_WEBSOCKETS
if(data->req.upgr101 == UPGR101_WS) {
failf(data, "Refused WebSocket upgrade: %d", k->httpcode);
result = CURLE_HTTP_RETURNED_ERROR;
goto out;
}
#endif
if(http_should_fail(data, data->req.httpcode)) {
failf(data, "The requested URL returned error: %d",
k->httpcode);
result = CURLE_HTTP_RETURNED_ERROR;
goto out;
}
result = Curl_http_auth_act(data);
if(result)
goto out;
if(k->httpcode >= 300) {
if((!data->req.authneg) && !conn->bits.close &&
!Curl_creader_will_rewind(data)) {
switch(data->state.httpreq) {
case HTTPREQ_PUT:
case HTTPREQ_POST:
case HTTPREQ_POST_FORM:
case HTTPREQ_POST_MIME:
if(!Curl_req_done_sending(data)) {
if((k->httpcode == 417) && http_exp100_is_selected(data)) {
if(!k->writebytecount && http_exp100_is_waiting(data)) {
infof(data, "Got HTTP failure 417 while waiting for a 100");
}
else {
infof(data, "Got HTTP failure 417 while sending data");
streamclose(conn,
"Stop sending data before everything sent");
result = http_perhapsrewind(data, conn);
if(result)
goto out;
}
data->state.disableexpect = TRUE;
Curl_req_abort_sending(data);
DEBUGASSERT(!data->req.newurl);
data->req.newurl = strdup(data->state.url);
if(!data->req.newurl) {
result = CURLE_OUT_OF_MEMORY;
goto out;
}
}
else if(data->set.http_keep_sending_on_error) {
infof(data, "HTTP error before end of send, keep sending");
http_exp100_send_anyway(data);
}
else {
infof(data, "HTTP error before end of send, stop sending");
streamclose(conn, "Stop sending data before everything sent");
result = Curl_req_abort_sending(data);
if(result)
goto out;
}
}
break;
default:
break;
}
}
if(Curl_creader_will_rewind(data) && !Curl_req_done_sending(data)) {
infof(data, "Keep sending data to get tossed away");
k->keepon |= KEEP_SEND;
}
}
if(data->req.no_body)
k->download_done = TRUE;
if((k->maxdownload == 0) && (k->httpversion_sent < 20))
k->download_done = TRUE;
result = http_firstwrite(data);
if(!result)
result = http_size(data);
out:
if(last_hd) {
result = Curl_1st_err(
result, http_write_header(data, last_hd, last_hd_len));
}
if(conn_changed) {
Curl_multi_connchanged(data->multi);
}
return result;
}
static CURLcode http_rw_hd(struct Curl_easy *data,
const char *hd, size_t hdlen,
const char *buf_remain, size_t blen,
size_t *pconsumed)
{
CURLcode result = CURLE_OK;
struct SingleRequest *k = &data->req;
int writetype;
*pconsumed = 0;
if((0x0a == *hd) || (0x0d == *hd)) {
struct dynbuf last_header;
size_t consumed;
curlx_dyn_init(&last_header, hdlen + 1);
result = curlx_dyn_addn(&last_header, hd, hdlen);
if(result)
return result;
curlx_dyn_reset(&data->state.headerb);
result = http_on_response(data, curlx_dyn_ptr(&last_header),
curlx_dyn_len(&last_header),
buf_remain, blen, &consumed);
*pconsumed += consumed;
curlx_dyn_free(&last_header);
return result;
}
writetype = CLIENTWRITE_HEADER;
if(!k->headerline++) {
bool fine_statusline = FALSE;
k->httpversion = 0;
if(data->conn->handler->protocol & PROTO_FAMILY_HTTP) {
const char *p = hd;
curlx_str_passblanks(&p);
if(!strncmp(p, "HTTP/", 5)) {
p += 5;
switch(*p) {
case '1':
p++;
if((p[0] == '.') && (p[1] == '0' || p[1] == '1')) {
if(ISBLANK(p[2])) {
k->httpversion = (unsigned char)(10 + (p[1] - '0'));
p += 3;
if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) {
k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 +
(p[2] - '0');
fine_statusline = TRUE;
}
}
}
if(!fine_statusline) {
failf(data, "Unsupported HTTP/1 subversion in response");
return CURLE_UNSUPPORTED_PROTOCOL;
}
break;
case '2':
case '3':
if(!ISBLANK(p[1]))
break;
k->httpversion = (unsigned char)((*p - '0') * 10);
p += 2;
if(ISDIGIT(p[0]) && ISDIGIT(p[1]) && ISDIGIT(p[2])) {
k->httpcode = (p[0] - '0') * 100 + (p[1] - '0') * 10 +
(p[2] - '0');
p += 3;
if(!ISBLANK(*p))
break;
fine_statusline = TRUE;
}
break;
default:
failf(data, "Unsupported HTTP version in response");
return CURLE_UNSUPPORTED_PROTOCOL;
}
}
if(!fine_statusline) {
statusline check = checkhttpprefix(data, hd, hdlen);
if(check == STATUS_DONE) {
fine_statusline = TRUE;
k->httpcode = 200;
k->httpversion = 10;
}
}
}
else if(data->conn->handler->protocol & CURLPROTO_RTSP) {
const char *p = hd;
struct Curl_str ver;
curl_off_t status;
if(!curlx_str_until(&p, &ver, 32, ' ') &&
!curlx_str_single(&p, ' ') &&
!curlx_str_number(&p, &status, 999)) {
curlx_str_trimblanks(&ver);
if(curlx_str_cmp(&ver, "RTSP/1.0")) {
k->httpcode = (int)status;
fine_statusline = TRUE;
k->httpversion = 11;
}
}
if(!fine_statusline)
return CURLE_WEIRD_SERVER_REPLY;
}
if(fine_statusline) {
result = http_statusline(data, data->conn);
if(result)
return result;
writetype |= CLIENTWRITE_STATUS;
}
else {
k->header = FALSE;
return CURLE_WEIRD_SERVER_REPLY;
}
}
result = verify_header(data, hd, hdlen);
if(result)
return result;
result = http_header(data, hd, hdlen);
if(result)
return result;
Curl_debug(data, CURLINFO_HEADER_IN, hd, hdlen);
if(k->httpcode/100 == 1)
writetype |= CLIENTWRITE_1XX;
result = Curl_client_write(data, writetype, hd, hdlen);
if(result)
return result;
result = Curl_bump_headersize(data, hdlen, FALSE);
if(result)
return result;
return CURLE_OK;
}
static CURLcode http_parse_headers(struct Curl_easy *data,
const char *buf, size_t blen,
size_t *pconsumed)
{
struct connectdata *conn = data->conn;
CURLcode result = CURLE_OK;
struct SingleRequest *k = &data->req;
char *end_ptr;
bool leftover_body = FALSE;
*pconsumed = 0;
while(blen && k->header) {
size_t consumed;
end_ptr = memchr(buf, '\n', blen);
if(!end_ptr) {
result = curlx_dyn_addn(&data->state.headerb, buf, blen);
if(result)
return result;
*pconsumed += blen;
if(!k->headerline) {
statusline st =
checkprotoprefix(data, conn,
curlx_dyn_ptr(&data->state.headerb),
curlx_dyn_len(&data->state.headerb));
if(st == STATUS_BAD) {
k->header = FALSE;
streamclose(conn, "bad HTTP: No end-of-message indicator");
if((k->httpversion >= 10) || conn->bits.reuse) {
failf(data, "Invalid status line");
return CURLE_WEIRD_SERVER_REPLY;
}
if(!data->state.http_neg.accept_09) {
failf(data, "Received HTTP/0.9 when not allowed");
return CURLE_UNSUPPORTED_PROTOCOL;
}
leftover_body = TRUE;
goto out;
}
}
goto out;
}
consumed = (end_ptr - buf) + 1;
result = curlx_dyn_addn(&data->state.headerb, buf, consumed);
if(result)
return result;
blen -= consumed;
buf += consumed;
*pconsumed += consumed;
if(!k->headerline) {
statusline st = checkprotoprefix(data, conn,
curlx_dyn_ptr(&data->state.headerb),
curlx_dyn_len(&data->state.headerb));
if(st == STATUS_BAD) {
streamclose(conn, "bad HTTP: No end-of-message indicator");
if((k->httpversion >= 10) || conn->bits.reuse) {
failf(data, "Invalid status line");
return CURLE_WEIRD_SERVER_REPLY;
}
if(!data->state.http_neg.accept_09) {
failf(data, "Received HTTP/0.9 when not allowed");
return CURLE_UNSUPPORTED_PROTOCOL;
}
k->header = FALSE;
leftover_body = TRUE;
goto out;
}
}
result = http_rw_hd(data, curlx_dyn_ptr(&data->state.headerb),
curlx_dyn_len(&data->state.headerb),
buf, blen, &consumed);
curlx_dyn_reset(&data->state.headerb);
if(consumed) {
blen -= consumed;
buf += consumed;
*pconsumed += consumed;
}
if(result)
return result;
}
out:
if(!k->header && !leftover_body) {
curlx_dyn_free(&data->state.headerb);
}
return CURLE_OK;
}
CURLcode Curl_http_write_resp_hd(struct Curl_easy *data,
const char *hd, size_t hdlen,
bool is_eos)
{
CURLcode result;
size_t consumed;
char tmp = 0;
result = http_rw_hd(data, hd, hdlen, &tmp, 0, &consumed);
if(!result && is_eos) {
result = Curl_client_write(data, (CLIENTWRITE_BODY|CLIENTWRITE_EOS),
&tmp, 0);
}
return result;
}
CURLcode Curl_http_write_resp_hds(struct Curl_easy *data,
const char *buf, size_t blen,
size_t *pconsumed)
{
if(!data->req.header) {
*pconsumed = 0;
return CURLE_OK;
}
else {
CURLcode result;
result = http_parse_headers(data, buf, blen, pconsumed);
if(!result && !data->req.header) {
if(!data->req.no_body && curlx_dyn_len(&data->state.headerb)) {
result = Curl_client_write(data, CLIENTWRITE_BODY,
curlx_dyn_ptr(&data->state.headerb),
curlx_dyn_len(&data->state.headerb));
}
curlx_dyn_free(&data->state.headerb);
}
return result;
}
}
CURLcode Curl_http_write_resp(struct Curl_easy *data,
const char *buf, size_t blen,
bool is_eos)
{
CURLcode result;
size_t consumed;
int flags;
result = Curl_http_write_resp_hds(data, buf, blen, &consumed);
if(result || data->req.done)
goto out;
DEBUGASSERT(consumed <= blen);
blen -= consumed;
buf += consumed;
DEBUGASSERT(!blen || !data->req.header);
if(!data->req.header && (blen || is_eos)) {
flags = CLIENTWRITE_BODY;
if(is_eos)
flags |= CLIENTWRITE_EOS;
result = Curl_client_write(data, flags, buf, blen);
}
out:
return result;
}
CURLcode Curl_http_decode_status(int *pstatus, const char *s, size_t len)
{
CURLcode result = CURLE_BAD_FUNCTION_ARGUMENT;
int status = 0;
int i;
if(len != 3)
goto out;
for(i = 0; i < 3; ++i) {
char c = s[i];
if(c < '0' || c > '9')
goto out;
status *= 10;
status += c - '0';
}
result = CURLE_OK;
out:
*pstatus = result ? -1 : status;
return result;
}
CURLcode Curl_http_req_make(struct httpreq **preq,
const char *method, size_t m_len,
const char *scheme, size_t s_len,
const char *authority, size_t a_len,
const char *path, size_t p_len)
{
struct httpreq *req;
CURLcode result = CURLE_OUT_OF_MEMORY;
DEBUGASSERT(method && m_len);
req = calloc(1, sizeof(*req) + m_len);
if(!req)
goto out;
#if defined(__GNUC__) && __GNUC__ >= 13
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Warray-bounds"
#endif
memcpy(req->method, method, m_len);
#if defined(__GNUC__) && __GNUC__ >= 13
#pragma GCC diagnostic pop
#endif
if(scheme) {
req->scheme = Curl_memdup0(scheme, s_len);
if(!req->scheme)
goto out;
}
if(authority) {
req->authority = Curl_memdup0(authority, a_len);
if(!req->authority)
goto out;
}
if(path) {
req->path = Curl_memdup0(path, p_len);
if(!req->path)
goto out;
}
Curl_dynhds_init(&req->headers, 0, DYN_HTTP_REQUEST);
Curl_dynhds_init(&req->trailers, 0, DYN_HTTP_REQUEST);
result = CURLE_OK;
out:
if(result && req)
Curl_http_req_free(req);
*preq = result ? NULL : req;
return result;
}
static CURLcode req_assign_url_authority(struct httpreq *req, CURLU *url)
{
char *user, *pass, *host, *port;
struct dynbuf buf;
CURLUcode uc;
CURLcode result = CURLE_URL_MALFORMAT;
user = pass = host = port = NULL;
curlx_dyn_init(&buf, DYN_HTTP_REQUEST);
uc = curl_url_get(url, CURLUPART_HOST, &host, 0);
if(uc && uc != CURLUE_NO_HOST)
goto out;
if(!host) {
req->authority = NULL;
result = CURLE_OK;
goto out;
}
uc = curl_url_get(url, CURLUPART_PORT, &port, CURLU_NO_DEFAULT_PORT);
if(uc && uc != CURLUE_NO_PORT)
goto out;
uc = curl_url_get(url, CURLUPART_USER, &user, 0);
if(uc && uc != CURLUE_NO_USER)
goto out;
if(user) {
uc = curl_url_get(url, CURLUPART_PASSWORD, &pass, 0);
if(uc && uc != CURLUE_NO_PASSWORD)
goto out;
}
if(user) {
result = curlx_dyn_add(&buf, user);
if(result)
goto out;
if(pass) {
result = curlx_dyn_addf(&buf, ":%s", pass);
if(result)
goto out;
}
result = curlx_dyn_add(&buf, "@");
if(result)
goto out;
}
result = curlx_dyn_add(&buf, host);
if(result)
goto out;
if(port) {
result = curlx_dyn_addf(&buf, ":%s", port);
if(result)
goto out;
}
req->authority = strdup(curlx_dyn_ptr(&buf));
if(!req->authority)
goto out;
result = CURLE_OK;
out:
free(user);
free(pass);
free(host);
free(port);
curlx_dyn_free(&buf);
return result;
}
static CURLcode req_assign_url_path(struct httpreq *req, CURLU *url)
{
char *path, *query;
struct dynbuf buf;
CURLUcode uc;
CURLcode result = CURLE_URL_MALFORMAT;
path = query = NULL;
curlx_dyn_init(&buf, DYN_HTTP_REQUEST);
uc = curl_url_get(url, CURLUPART_PATH, &path, CURLU_PATH_AS_IS);
if(uc)
goto out;
uc = curl_url_get(url, CURLUPART_QUERY, &query, 0);
if(uc && uc != CURLUE_NO_QUERY)
goto out;
if(!path && !query) {
req->path = NULL;
}
else if(path && !query) {
req->path = path;
path = NULL;
}
else {
if(path) {
result = curlx_dyn_add(&buf, path);
if(result)
goto out;
}
if(query) {
result = curlx_dyn_addf(&buf, "?%s", query);
if(result)
goto out;
}
req->path = strdup(curlx_dyn_ptr(&buf));
if(!req->path)
goto out;
}
result = CURLE_OK;
out:
free(path);
free(query);
curlx_dyn_free(&buf);
return result;
}
CURLcode Curl_http_req_make2(struct httpreq **preq,
const char *method, size_t m_len,
CURLU *url, const char *scheme_default)
{
struct httpreq *req;
CURLcode result = CURLE_OUT_OF_MEMORY;
CURLUcode uc;
DEBUGASSERT(method && m_len);
req = calloc(1, sizeof(*req) + m_len);
if(!req)
goto out;
memcpy(req->method, method, m_len);
uc = curl_url_get(url, CURLUPART_SCHEME, &req->scheme, 0);
if(uc && uc != CURLUE_NO_SCHEME)
goto out;
if(!req->scheme && scheme_default) {
req->scheme = strdup(scheme_default);
if(!req->scheme)
goto out;
}
result = req_assign_url_authority(req, url);
if(result)
goto out;
result = req_assign_url_path(req, url);
if(result)
goto out;
Curl_dynhds_init(&req->headers, 0, DYN_HTTP_REQUEST);
Curl_dynhds_init(&req->trailers, 0, DYN_HTTP_REQUEST);
result = CURLE_OK;
out:
if(result && req)
Curl_http_req_free(req);
*preq = result ? NULL : req;
return result;
}
void Curl_http_req_free(struct httpreq *req)
{
if(req) {
free(req->scheme);
free(req->authority);
free(req->path);
Curl_dynhds_free(&req->headers);
Curl_dynhds_free(&req->trailers);
free(req);
}
}
struct name_const {
const char *name;
size_t namelen;
};
static const struct name_const H2_NON_FIELD[] = {
{ STRCONST("Host") },
{ STRCONST("Upgrade") },
{ STRCONST("Connection") },
{ STRCONST("Keep-Alive") },
{ STRCONST("Proxy-Connection") },
{ STRCONST("Transfer-Encoding") },
};
static bool h2_permissible_field(struct dynhds_entry *e)
{
size_t i;
for(i = 0; i < CURL_ARRAYSIZE(H2_NON_FIELD); ++i) {
if(e->namelen < H2_NON_FIELD[i].namelen)
return TRUE;
if(e->namelen == H2_NON_FIELD[i].namelen &&
curl_strequal(H2_NON_FIELD[i].name, e->name))
return FALSE;
}
return TRUE;
}
static bool http_TE_has_token(const char *fvalue, const char *token)
{
while(*fvalue) {
struct Curl_str name;
while(ISBLANK(*fvalue) || *fvalue == ',')
fvalue++;
if(curlx_str_cspn(&fvalue, &name, " \t\r;,"))
return FALSE;
if(curlx_str_casecompare(&name, token))
return TRUE;
while(*fvalue && *fvalue != ',') {
if(*fvalue == '"') {
struct Curl_str qw;
if(curlx_str_quotedword(&fvalue, &qw, CURL_MAX_HTTP_HEADER))
return FALSE;
}
else
fvalue++;
}
}
return FALSE;
}
CURLcode Curl_http_req_to_h2(struct dynhds *h2_headers,
struct httpreq *req, struct Curl_easy *data)
{
const char *scheme = NULL, *authority = NULL;
struct dynhds_entry *e;
size_t i;
CURLcode result;
DEBUGASSERT(req);
DEBUGASSERT(h2_headers);
if(req->scheme) {
scheme = req->scheme;
}
else if(strcmp("CONNECT", req->method)) {
scheme = Curl_checkheaders(data, STRCONST(HTTP_PSEUDO_SCHEME));
if(scheme) {
scheme += sizeof(HTTP_PSEUDO_SCHEME);
curlx_str_passblanks(&scheme);
infof(data, "set pseudo header %s to %s", HTTP_PSEUDO_SCHEME, scheme);
}
else {
scheme = Curl_conn_is_ssl(data->conn, FIRSTSOCKET) ?
"https" : "http";
}
}
if(req->authority) {
authority = req->authority;
}
else {
e = Curl_dynhds_get(&req->headers, STRCONST("Host"));
if(e)
authority = e->value;
}
Curl_dynhds_reset(h2_headers);
Curl_dynhds_set_opts(h2_headers, DYNHDS_OPT_LOWERCASE);
result = Curl_dynhds_add(h2_headers, STRCONST(HTTP_PSEUDO_METHOD),
req->method, strlen(req->method));
if(!result && scheme) {
result = Curl_dynhds_add(h2_headers, STRCONST(HTTP_PSEUDO_SCHEME),
scheme, strlen(scheme));
}
if(!result && authority) {
result = Curl_dynhds_add(h2_headers, STRCONST(HTTP_PSEUDO_AUTHORITY),
authority, strlen(authority));
}
if(!result && req->path) {
result = Curl_dynhds_add(h2_headers, STRCONST(HTTP_PSEUDO_PATH),
req->path, strlen(req->path));
}
for(i = 0; !result && i < Curl_dynhds_count(&req->headers); ++i) {
e = Curl_dynhds_getn(&req->headers, i);
if(e->namelen == 2 && curl_strequal("TE", e->name)) {
if(http_TE_has_token(e->value, "trailers"))
result = Curl_dynhds_add(h2_headers, e->name, e->namelen,
"trailers", sizeof("trailers") - 1);
}
else if(h2_permissible_field(e)) {
result = Curl_dynhds_add(h2_headers, e->name, e->namelen,
e->value, e->valuelen);
}
}
return result;
}
CURLcode Curl_http_resp_make(struct http_resp **presp,
int status,
const char *description)
{
struct http_resp *resp;
CURLcode result = CURLE_OUT_OF_MEMORY;
resp = calloc(1, sizeof(*resp));
if(!resp)
goto out;
resp->status = status;
if(description) {
resp->description = strdup(description);
if(!resp->description)
goto out;
}
Curl_dynhds_init(&resp->headers, 0, DYN_HTTP_REQUEST);
Curl_dynhds_init(&resp->trailers, 0, DYN_HTTP_REQUEST);
result = CURLE_OK;
out:
if(result && resp)
Curl_http_resp_free(resp);
*presp = result ? NULL : resp;
return result;
}
void Curl_http_resp_free(struct http_resp *resp)
{
if(resp) {
free(resp->description);
Curl_dynhds_free(&resp->headers);
Curl_dynhds_free(&resp->trailers);
if(resp->prev)
Curl_http_resp_free(resp->prev);
free(resp);
}
}
struct cr_exp100_ctx {
struct Curl_creader super;
struct curltime start;
enum expect100 state;
};
static void http_exp100_continue(struct Curl_easy *data,
struct Curl_creader *reader)
{
struct cr_exp100_ctx *ctx = reader->ctx;
if(ctx->state > EXP100_SEND_DATA) {
ctx->state = EXP100_SEND_DATA;
data->req.keepon |= KEEP_SEND;
data->req.keepon &= ~KEEP_SEND_TIMED;
Curl_expire_done(data, EXPIRE_100_TIMEOUT);
}
}
static CURLcode cr_exp100_read(struct Curl_easy *data,
struct Curl_creader *reader,
char *buf, size_t blen,
size_t *nread, bool *eos)
{
struct cr_exp100_ctx *ctx = reader->ctx;
timediff_t ms;
switch(ctx->state) {
case EXP100_SENDING_REQUEST:
if(!Curl_req_sendbuf_empty(data)) {
DEBUGF(infof(data, "cr_exp100_read, request not full sent yet"));
*nread = 0;
*eos = FALSE;
return CURLE_OK;
}
DEBUGF(infof(data, "cr_exp100_read, start AWAITING_CONTINUE, "
"timeout %dms", data->set.expect_100_timeout));
ctx->state = EXP100_AWAITING_CONTINUE;
ctx->start = curlx_now();
Curl_expire(data, data->set.expect_100_timeout, EXPIRE_100_TIMEOUT);
data->req.keepon &= ~KEEP_SEND;
data->req.keepon |= KEEP_SEND_TIMED;
*nread = 0;
*eos = FALSE;
return CURLE_OK;
case EXP100_FAILED:
DEBUGF(infof(data, "cr_exp100_read, expectation failed, error"));
*nread = 0;
*eos = FALSE;
return CURLE_READ_ERROR;
case EXP100_AWAITING_CONTINUE:
ms = curlx_timediff(curlx_now(), ctx->start);
if(ms < data->set.expect_100_timeout) {
DEBUGF(infof(data, "cr_exp100_read, AWAITING_CONTINUE, not expired"));
data->req.keepon &= ~KEEP_SEND;
data->req.keepon |= KEEP_SEND_TIMED;
*nread = 0;
*eos = FALSE;
return CURLE_OK;
}
http_exp100_continue(data, reader);
infof(data, "Done waiting for 100-continue");
FALLTHROUGH();
default:
DEBUGF(infof(data, "cr_exp100_read, pass through"));
return Curl_creader_read(data, reader->next, buf, blen, nread, eos);
}
}
static void cr_exp100_done(struct Curl_easy *data,
struct Curl_creader *reader, int premature)
{
struct cr_exp100_ctx *ctx = reader->ctx;
ctx->state = premature ? EXP100_FAILED : EXP100_SEND_DATA;
data->req.keepon &= ~KEEP_SEND_TIMED;
Curl_expire_done(data, EXPIRE_100_TIMEOUT);
}
static const struct Curl_crtype cr_exp100 = {
"cr-exp100",
Curl_creader_def_init,
cr_exp100_read,
Curl_creader_def_close,
Curl_creader_def_needs_rewind,
Curl_creader_def_total_length,
Curl_creader_def_resume_from,
Curl_creader_def_cntrl,
Curl_creader_def_is_paused,
cr_exp100_done,
sizeof(struct cr_exp100_ctx)
};
static CURLcode http_exp100_add_reader(struct Curl_easy *data)
{
struct Curl_creader *reader = NULL;
CURLcode result;
result = Curl_creader_create(&reader, data, &cr_exp100,
CURL_CR_PROTOCOL);
if(!result)
result = Curl_creader_add(data, reader);
if(!result) {
struct cr_exp100_ctx *ctx = reader->ctx;
ctx->state = EXP100_SENDING_REQUEST;
}
if(result && reader)
Curl_creader_free(data, reader);
return result;
}
static void http_exp100_got100(struct Curl_easy *data)
{
struct Curl_creader *r = Curl_creader_get_by_type(data, &cr_exp100);
if(r)
http_exp100_continue(data, r);
}
static bool http_exp100_is_waiting(struct Curl_easy *data)
{
struct Curl_creader *r = Curl_creader_get_by_type(data, &cr_exp100);
if(r) {
struct cr_exp100_ctx *ctx = r->ctx;
return ctx->state == EXP100_AWAITING_CONTINUE;
}
return FALSE;
}
static void http_exp100_send_anyway(struct Curl_easy *data)
{
struct Curl_creader *r = Curl_creader_get_by_type(data, &cr_exp100);
if(r)
http_exp100_continue(data, r);
}
static bool http_exp100_is_selected(struct Curl_easy *data)
{
struct Curl_creader *r = Curl_creader_get_by_type(data, &cr_exp100);
return !!r;
}
#endif