#include "curl_setup.h"
#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
#ifndef UNDER_CE
#include <signal.h>
#endif
#ifdef HAVE_SYS_PARAM_H
#include <sys/param.h>
#endif
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#elif defined(HAVE_UNISTD_H)
#include <unistd.h>
#endif
#ifndef HAVE_SOCKET
#error "We cannot compile without socket() support!"
#endif
#include "urldata.h"
#include <curl/curl.h>
#include "netrc.h"
#include "content_encoding.h"
#include "hostip.h"
#include "cfilters.h"
#include "cw-out.h"
#include "transfer.h"
#include "sendf.h"
#include "speedcheck.h"
#include "progress.h"
#include "http.h"
#include "url.h"
#include "getinfo.h"
#include "vtls/vtls.h"
#include "vquic/vquic.h"
#include "select.h"
#include "multiif.h"
#include "connect.h"
#include "http2.h"
#include "mime.h"
#include "hsts.h"
#include "setopt.h"
#include "headers.h"
#include "curl_memory.h"
#include "memdebug.h"
#if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_SMTP) || \
!defined(CURL_DISABLE_IMAP)
char *Curl_checkheaders(const struct Curl_easy *data,
const char *thisheader,
const size_t thislen)
{
struct curl_slist *head;
DEBUGASSERT(thislen);
DEBUGASSERT(thisheader[thislen-1] != ':');
for(head = data->set.headers; head; head = head->next) {
if(curl_strnequal(head->data, thisheader, thislen) &&
Curl_headersep(head->data[thislen]) )
return head->data;
}
return NULL;
}
#endif
static int data_pending(struct Curl_easy *data, bool rcvd_eagain)
{
struct connectdata *conn = data->conn;
if(conn->handler->protocol&PROTO_FAMILY_FTP)
return Curl_conn_data_pending(data, SECONDARYSOCKET);
return (!rcvd_eagain &&
conn->handler->protocol&(CURLPROTO_SCP|CURLPROTO_SFTP)) ||
Curl_conn_data_pending(data, FIRSTSOCKET);
}
bool Curl_meets_timecondition(struct Curl_easy *data, time_t timeofdoc)
{
if((timeofdoc == 0) || (data->set.timevalue == 0))
return TRUE;
switch(data->set.timecondition) {
case CURL_TIMECOND_IFMODSINCE:
default:
if(timeofdoc <= data->set.timevalue) {
infof(data,
"The requested document is not new enough");
data->info.timecond = TRUE;
return FALSE;
}
break;
case CURL_TIMECOND_IFUNMODSINCE:
if(timeofdoc >= data->set.timevalue) {
infof(data,
"The requested document is not old enough");
data->info.timecond = TRUE;
return FALSE;
}
break;
}
return TRUE;
}
static CURLcode xfer_recv_shutdown(struct Curl_easy *data, bool *done)
{
if(!data || !data->conn)
return CURLE_FAILED_INIT;
return Curl_conn_shutdown(data, data->conn->recv_idx, done);
}
static bool xfer_recv_shutdown_started(struct Curl_easy *data)
{
if(!data || !data->conn)
return FALSE;
return Curl_shutdown_started(data, data->conn->recv_idx);
}
CURLcode Curl_xfer_send_shutdown(struct Curl_easy *data, bool *done)
{
if(!data || !data->conn)
return CURLE_FAILED_INIT;
return Curl_conn_shutdown(data, data->conn->send_idx, done);
}
static ssize_t xfer_recv_resp(struct Curl_easy *data,
char *buf, size_t blen,
bool eos_reliable,
CURLcode *err)
{
size_t nread;
DEBUGASSERT(blen > 0);
if(!eos_reliable && !data->req.header && data->req.size != -1) {
curl_off_t totalleft = data->req.size - data->req.bytecount;
if(totalleft <= 0)
blen = 0;
else if(totalleft < (curl_off_t)blen)
blen = (size_t)totalleft;
}
else if(xfer_recv_shutdown_started(data)) {
blen = 0;
}
if(!blen) {
*err = CURLE_OK;
nread = 0;
}
else {
*err = Curl_xfer_recv(data, buf, blen, &nread);
}
if(*err)
return -1;
if(nread == 0) {
if(data->req.shutdown) {
bool done;
*err = xfer_recv_shutdown(data, &done);
if(*err)
return -1;
if(!done) {
*err = CURLE_AGAIN;
return -1;
}
}
DEBUGF(infof(data, "sendrecv_dl: we are done"));
}
return (ssize_t)nread;
}
static CURLcode sendrecv_dl(struct Curl_easy *data,
struct SingleRequest *k)
{
struct connectdata *conn = data->conn;
CURLcode result = CURLE_OK;
char *buf, *xfer_buf;
size_t blen, xfer_blen;
int maxloops = 10;
curl_off_t total_received = 0;
bool is_multiplex = FALSE;
bool rcvd_eagain = FALSE;
bool is_eos = FALSE;
result = Curl_multi_xfer_buf_borrow(data, &xfer_buf, &xfer_blen);
if(result)
goto out;
do {
size_t bytestoread;
ssize_t nread;
if(!is_multiplex) {
is_multiplex = Curl_conn_is_multiplex(conn, FIRSTSOCKET);
}
buf = xfer_buf;
bytestoread = xfer_blen;
if(bytestoread && data->set.max_recv_speed > 0) {
if(total_received && (total_received >= (data->set.max_recv_speed / 4)))
break;
if(data->set.max_recv_speed < (curl_off_t)bytestoread)
bytestoread = (size_t)data->set.max_recv_speed;
}
rcvd_eagain = FALSE;
nread = xfer_recv_resp(data, buf, bytestoread, is_multiplex, &result);
if(nread < 0) {
if(CURLE_AGAIN != result)
goto out;
rcvd_eagain = TRUE;
result = CURLE_OK;
if(data->req.download_done && data->req.no_body &&
!data->req.resp_trailer) {
DEBUGF(infof(data, "EAGAIN, download done, no trailer announced, "
"not waiting for EOS"));
nread = 0;
}
else
break;
}
blen = (size_t)nread;
is_eos = (blen == 0);
if(!blen && (conn->recv[FIRSTSOCKET] == Curl_cf_recv)) {
if(is_multiplex)
DEBUGF(infof(data, "nread == 0, stream closed, bailing"));
else
DEBUGF(infof(data, "nread <= 0, server closed connection, bailing"));
result = Curl_req_stop_send_recv(data);
if(result)
goto out;
if(k->eos_written)
break;
}
total_received += blen;
result = Curl_xfer_write_resp(data, buf, blen, is_eos);
if(result || data->req.done)
goto out;
if((!is_multiplex && data->req.download_done) || is_eos) {
data->req.keepon &= ~KEEP_RECV;
}
if((k->keepon & KEEP_RECV_PAUSE) || !(k->keepon & KEEP_RECV))
break;
} while(maxloops--);
if(!is_eos && !Curl_xfer_is_blocked(data) &&
(!rcvd_eagain || data_pending(data, rcvd_eagain))) {
Curl_multi_mark_dirty(data);
CURL_TRC_M(data, "sendrecv_dl() no EAGAIN/pending data, mark as dirty");
}
if(((k->keepon & (KEEP_RECV|KEEP_SEND)) == KEEP_SEND) &&
(conn->bits.close || is_multiplex)) {
infof(data, "we are done reading and this is set to close, stop send");
Curl_req_abort_sending(data);
}
out:
Curl_multi_xfer_buf_release(data, xfer_buf);
if(result)
DEBUGF(infof(data, "sendrecv_dl() -> %d", result));
return result;
}
static CURLcode sendrecv_ul(struct Curl_easy *data)
{
DEBUGASSERT(!Curl_req_done_sending(data));
if(!Curl_req_done_sending(data))
return Curl_req_send_more(data);
return CURLE_OK;
}
CURLcode Curl_sendrecv(struct Curl_easy *data, struct curltime *nowp)
{
struct SingleRequest *k = &data->req;
CURLcode result = CURLE_OK;
DEBUGASSERT(nowp);
if(Curl_xfer_is_blocked(data)) {
result = CURLE_OK;
goto out;
}
if(k->keepon & KEEP_RECV) {
result = sendrecv_dl(data, k);
if(result || data->req.done)
goto out;
}
if(Curl_req_want_send(data) || (data->req.keepon & KEEP_SEND_TIMED)) {
result = sendrecv_ul(data);
if(result)
goto out;
}
if(Curl_pgrsUpdate(data))
result = CURLE_ABORTED_BY_CALLBACK;
else
result = Curl_speedcheck(data, *nowp);
if(result)
goto out;
if(k->keepon) {
if(0 > Curl_timeleft(data, nowp, FALSE)) {
if(k->size != -1) {
failf(data, "Operation timed out after %" FMT_TIMEDIFF_T
" milliseconds with %" FMT_OFF_T " out of %"
FMT_OFF_T " bytes received",
curlx_timediff(*nowp, data->progress.t_startsingle),
k->bytecount, k->size);
}
else {
failf(data, "Operation timed out after %" FMT_TIMEDIFF_T
" milliseconds with %" FMT_OFF_T " bytes received",
curlx_timediff(*nowp, data->progress.t_startsingle),
k->bytecount);
}
result = CURLE_OPERATION_TIMEDOUT;
goto out;
}
}
else {
if(!(data->req.no_body) && (k->size != -1) &&
(k->bytecount != k->size) && !k->newurl) {
failf(data, "transfer closed with %" FMT_OFF_T
" bytes remaining to read", k->size - k->bytecount);
result = CURLE_PARTIAL_FILE;
goto out;
}
if(Curl_pgrsUpdate(data)) {
result = CURLE_ABORTED_BY_CALLBACK;
goto out;
}
}
if((k->keepon & (KEEP_RECVBITS|KEEP_SENDBITS)) == 0)
data->req.done = TRUE;
out:
if(result)
DEBUGF(infof(data, "Curl_sendrecv() -> %d", result));
return result;
}
void Curl_init_CONNECT(struct Curl_easy *data)
{
data->state.fread_func = data->set.fread_func_set;
data->state.in = data->set.in_set;
data->state.upload = (data->state.httpreq == HTTPREQ_PUT);
}
CURLcode Curl_pretransfer(struct Curl_easy *data)
{
CURLcode result = CURLE_OK;
data->state.retrycount = 0;
if(!data->set.str[STRING_SET_URL] && !data->set.uh) {
failf(data, "No URL set");
return CURLE_URL_MALFORMAT;
}
if(data->set.uh) {
CURLUcode uc;
free(data->set.str[STRING_SET_URL]);
uc = curl_url_get(data->set.uh,
CURLUPART_URL, &data->set.str[STRING_SET_URL], 0);
if(uc) {
failf(data, "No URL set");
return CURLE_URL_MALFORMAT;
}
}
if(data->state.url_alloc) {
Curl_safefree(data->state.url);
data->state.url_alloc = FALSE;
}
data->state.url = data->set.str[STRING_SET_URL];
if(data->set.postfields && data->set.set_resume_from) {
failf(data, "cannot mix POSTFIELDS with RESUME_FROM");
return CURLE_BAD_FUNCTION_ARGUMENT;
}
data->state.prefer_ascii = data->set.prefer_ascii;
#ifdef CURL_LIST_ONLY_PROTOCOL
data->state.list_only = data->set.list_only;
#endif
data->state.httpreq = data->set.method;
data->state.requests = 0;
data->state.followlocation = 0;
data->state.this_is_a_follow = FALSE;
data->state.errorbuf = FALSE;
#ifndef CURL_DISABLE_HTTP
Curl_http_neg_init(data, &data->state.http_neg);
#endif
data->state.authproblem = FALSE;
data->state.authhost.want = data->set.httpauth;
data->state.authproxy.want = data->set.proxyauth;
Curl_safefree(data->info.wouldredirect);
Curl_data_priority_clear_state(data);
if(data->state.httpreq == HTTPREQ_PUT)
data->state.infilesize = data->set.filesize;
else if((data->state.httpreq != HTTPREQ_GET) &&
(data->state.httpreq != HTTPREQ_HEAD)) {
data->state.infilesize = data->set.postfieldsize;
if(data->set.postfields && (data->state.infilesize == -1))
data->state.infilesize = (curl_off_t)strlen(data->set.postfields);
}
else
data->state.infilesize = 0;
Curl_cookie_loadfiles(data);
if(data->state.resolve)
result = Curl_loadhostpairs(data);
Curl_hsts_loadfiles(data);
if(!result) {
data->state.allow_port = TRUE;
#if defined(HAVE_SIGNAL) && defined(SIGPIPE) && !defined(HAVE_MSG_NOSIGNAL)
if(!data->set.no_signal)
data->state.prev_signal = signal(SIGPIPE, SIG_IGN);
#endif
Curl_initinfo(data);
Curl_pgrsResetTransferSizes(data);
Curl_pgrsStartNow(data);
data->state.authhost.picked &= data->state.authhost.want;
data->state.authproxy.picked &= data->state.authproxy.want;
#ifndef CURL_DISABLE_FTP
data->state.wildcardmatch = data->set.wildcard_enabled;
if(data->state.wildcardmatch) {
struct WildcardData *wc;
if(!data->wildcard) {
data->wildcard = calloc(1, sizeof(struct WildcardData));
if(!data->wildcard)
return CURLE_OUT_OF_MEMORY;
}
wc = data->wildcard;
if(wc->state < CURLWC_INIT) {
if(wc->ftpwc)
wc->dtor(wc->ftpwc);
Curl_safefree(wc->pattern);
Curl_safefree(wc->path);
result = Curl_wildcard_init(wc);
if(result)
return CURLE_OUT_OF_MEMORY;
}
}
#endif
result = Curl_hsts_loadcb(data, data->hsts);
}
if(data->set.str[STRING_USERAGENT]) {
free(data->state.aptr.uagent);
data->state.aptr.uagent =
curl_maprintf("User-Agent: %s\r\n", data->set.str[STRING_USERAGENT]);
if(!data->state.aptr.uagent)
return CURLE_OUT_OF_MEMORY;
}
if(data->set.str[STRING_USERNAME] ||
data->set.str[STRING_PASSWORD])
data->state.creds_from = CREDS_OPTION;
if(!result)
result = Curl_setstropt(&data->state.aptr.user,
data->set.str[STRING_USERNAME]);
if(!result)
result = Curl_setstropt(&data->state.aptr.passwd,
data->set.str[STRING_PASSWORD]);
#ifndef CURL_DISABLE_PROXY
if(!result)
result = Curl_setstropt(&data->state.aptr.proxyuser,
data->set.str[STRING_PROXYUSERNAME]);
if(!result)
result = Curl_setstropt(&data->state.aptr.proxypasswd,
data->set.str[STRING_PROXYPASSWORD]);
#endif
data->req.headerbytecount = 0;
Curl_headers_cleanup(data);
return result;
}
CURLcode Curl_retry_request(struct Curl_easy *data, char **url)
{
struct connectdata *conn = data->conn;
bool retry = FALSE;
*url = NULL;
if(data->state.upload &&
!(conn->handler->protocol&(PROTO_FAMILY_HTTP|CURLPROTO_RTSP)))
return CURLE_OK;
if(conn->bits.reuse &&
(data->req.bytecount + data->req.headerbytecount == 0) &&
((!data->req.no_body && !data->req.done) ||
(conn->handler->protocol & PROTO_FAMILY_HTTP))
#ifndef CURL_DISABLE_RTSP
&& (data->set.rtspreq != RTSPREQ_RECEIVE)
#endif
)
retry = TRUE;
else if(data->state.refused_stream &&
(data->req.bytecount + data->req.headerbytecount == 0) ) {
infof(data, "REFUSED_STREAM, retrying a fresh connect");
data->state.refused_stream = FALSE;
retry = TRUE;
}
if(retry) {
#define CONN_MAX_RETRIES 5
if(data->state.retrycount++ >= CONN_MAX_RETRIES) {
failf(data, "Connection died, tried %d times before giving up",
CONN_MAX_RETRIES);
data->state.retrycount = 0;
return CURLE_SEND_ERROR;
}
infof(data, "Connection died, retrying a fresh connect (retry count: %d)",
data->state.retrycount);
*url = strdup(data->state.url);
if(!*url)
return CURLE_OUT_OF_MEMORY;
connclose(conn, "retry");
conn->bits.retry = TRUE;
Curl_creader_set_rewind(data, TRUE);
}
return CURLE_OK;
}
static void xfer_setup(
struct Curl_easy *data,
int send_idx,
int recv_idx,
curl_off_t recv_size
)
{
struct SingleRequest *k = &data->req;
struct connectdata *conn = data->conn;
DEBUGASSERT(conn != NULL);
DEBUGASSERT((send_idx <= 1) && (send_idx >= -1));
DEBUGASSERT((recv_idx <= 1) && (recv_idx >= -1));
DEBUGASSERT((send_idx >= 0) || !Curl_req_want_send(data));
conn->send_idx = send_idx;
conn->recv_idx = recv_idx;
DEBUGASSERT((conn->recv_idx >= 0) || (recv_size == -1));
k->size = recv_size;
k->header = !!conn->handler->write_resp_hd;
k->shutdown = FALSE;
k->shutdown_err_ignore = FALSE;
if(!k->header && (recv_size > 0))
Curl_pgrsSetDownloadSize(data, recv_size);
if(conn->handler->write_resp_hd || !data->req.no_body) {
if(conn->recv_idx != -1)
k->keepon |= KEEP_RECV;
if(conn->send_idx != -1)
k->keepon |= KEEP_SEND;
}
CURL_TRC_M(data, "xfer_setup: recv_idx=%d, send_idx=%d",
conn->recv_idx, conn->send_idx);
}
void Curl_xfer_setup_nop(struct Curl_easy *data)
{
xfer_setup(data, -1, -1, -1);
}
void Curl_xfer_setup_sendrecv(struct Curl_easy *data,
int sockindex,
curl_off_t recv_size)
{
xfer_setup(data, sockindex, sockindex, recv_size);
}
void Curl_xfer_setup_send(struct Curl_easy *data,
int sockindex)
{
xfer_setup(data, sockindex, -1, -1);
}
void Curl_xfer_setup_recv(struct Curl_easy *data,
int sockindex,
curl_off_t recv_size)
{
xfer_setup(data, -1, sockindex, recv_size);
}
void Curl_xfer_set_shutdown(struct Curl_easy *data,
bool shutdown,
bool ignore_errors)
{
DEBUGASSERT(!shutdown ||
(data->conn->send_idx < 0) || (data->conn->recv_idx < 0));
data->req.shutdown = shutdown;
data->req.shutdown_err_ignore = ignore_errors;
}
CURLcode Curl_xfer_write_resp(struct Curl_easy *data,
const char *buf, size_t blen,
bool is_eos)
{
CURLcode result = CURLE_OK;
if(data->conn->handler->write_resp) {
result = data->conn->handler->write_resp(data, buf, blen, is_eos);
}
else {
if(blen || is_eos) {
int cwtype = CLIENTWRITE_BODY;
if(is_eos)
cwtype |= CLIENTWRITE_EOS;
result = Curl_client_write(data, cwtype, buf, blen);
}
}
if(!result && is_eos) {
data->req.eos_written = TRUE;
data->req.download_done = TRUE;
}
CURL_TRC_WRITE(data, "xfer_write_resp(len=%zu, eos=%d) -> %d",
blen, is_eos, result);
return result;
}
bool Curl_xfer_write_is_paused(struct Curl_easy *data)
{
return Curl_cwriter_is_paused(data);
}
CURLcode Curl_xfer_write_resp_hd(struct Curl_easy *data,
const char *hd0, size_t hdlen, bool is_eos)
{
if(data->conn->handler->write_resp_hd) {
return data->conn->handler->write_resp_hd(data, hd0, hdlen, is_eos);
}
return Curl_xfer_write_resp(data, hd0, hdlen, is_eos);
}
CURLcode Curl_xfer_write_done(struct Curl_easy *data, bool premature)
{
(void)premature;
return Curl_cw_out_done(data);
}
bool Curl_xfer_needs_flush(struct Curl_easy *data)
{
return Curl_conn_needs_flush(data, data->conn->send_idx);
}
CURLcode Curl_xfer_flush(struct Curl_easy *data)
{
return Curl_conn_flush(data, data->conn->send_idx);
}
CURLcode Curl_xfer_send(struct Curl_easy *data,
const void *buf, size_t blen, bool eos,
size_t *pnwritten)
{
CURLcode result;
DEBUGASSERT(data);
DEBUGASSERT(data->conn);
result = Curl_conn_send(data, data->conn->send_idx,
buf, blen, eos, pnwritten);
if(result == CURLE_AGAIN) {
result = CURLE_OK;
*pnwritten = 0;
}
else if(!result && *pnwritten)
data->info.request_size += *pnwritten;
DEBUGF(infof(data, "Curl_xfer_send(len=%zu, eos=%d) -> %d, %zu",
blen, eos, result, *pnwritten));
return result;
}
CURLcode Curl_xfer_recv(struct Curl_easy *data,
char *buf, size_t blen,
size_t *pnrcvd)
{
DEBUGASSERT(data);
DEBUGASSERT(data->conn);
DEBUGASSERT(data->set.buffer_size > 0);
if((size_t)data->set.buffer_size < blen)
blen = (size_t)data->set.buffer_size;
return Curl_conn_recv(data, data->conn->recv_idx, buf, blen, pnrcvd);
}
CURLcode Curl_xfer_send_close(struct Curl_easy *data)
{
Curl_conn_ev_data_done_send(data);
return CURLE_OK;
}
bool Curl_xfer_is_blocked(struct Curl_easy *data)
{
bool want_send = ((data)->req.keepon & KEEP_SEND);
bool want_recv = ((data)->req.keepon & KEEP_RECV);
if(!want_send)
return want_recv && Curl_xfer_recv_is_paused(data);
else if(!want_recv)
return want_send && Curl_xfer_send_is_paused(data);
else
return Curl_xfer_recv_is_paused(data) && Curl_xfer_send_is_paused(data);
}
bool Curl_xfer_send_is_paused(struct Curl_easy *data)
{
return (data->req.keepon & KEEP_SEND_PAUSE);
}
bool Curl_xfer_recv_is_paused(struct Curl_easy *data)
{
return (data->req.keepon & KEEP_RECV_PAUSE);
}
CURLcode Curl_xfer_pause_send(struct Curl_easy *data, bool enable)
{
CURLcode result = CURLE_OK;
if(enable) {
data->req.keepon |= KEEP_SEND_PAUSE;
}
else {
data->req.keepon &= ~KEEP_SEND_PAUSE;
if(Curl_creader_is_paused(data))
result = Curl_creader_unpause(data);
}
return result;
}
CURLcode Curl_xfer_pause_recv(struct Curl_easy *data, bool enable)
{
CURLcode result = CURLE_OK;
if(enable) {
data->req.keepon |= KEEP_RECV_PAUSE;
}
else {
data->req.keepon &= ~KEEP_RECV_PAUSE;
if(Curl_cwriter_is_paused(data))
result = Curl_cwriter_unpause(data);
}
Curl_conn_ev_data_pause(data, enable);
return result;
}
bool Curl_xfer_is_too_fast(struct Curl_easy *data)
{
struct Curl_llist_node *e = Curl_llist_head(&data->state.timeoutlist);
while(e) {
struct time_node *n = Curl_node_elem(e);
e = Curl_node_next(e);
if(n->eid == EXPIRE_TOOFAST)
return TRUE;
}
return FALSE;
}