Update networking layer w/ CURL and emscripten impl
This commit is contained in:
Vendored
+974
@@ -0,0 +1,974 @@
|
||||
/***************************************************************************
|
||||
* _ _ ____ _
|
||||
* Project ___| | | | _ \| |
|
||||
* / __| | | | |_) | |
|
||||
* | (__| |_| | _ <| |___
|
||||
* \___|\___/|_| \_\_____|
|
||||
*
|
||||
* Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
|
||||
*
|
||||
* This software is licensed as described in the file COPYING, which
|
||||
* you should have received as part of this distribution. The terms
|
||||
* are also available at https://curl.se/docs/copyright.html.
|
||||
*
|
||||
* You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
||||
* copies of the Software, and permit persons to whom the Software is
|
||||
* furnished to do so, under the terms of the COPYING file.
|
||||
*
|
||||
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
* KIND, either express or implied.
|
||||
*
|
||||
* SPDX-License-Identifier: curl
|
||||
*
|
||||
***************************************************************************/
|
||||
|
||||
#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"
|
||||
|
||||
/* The last 2 #include files should be in this order */
|
||||
#include "curl_memory.h"
|
||||
#include "memdebug.h"
|
||||
|
||||
#if !defined(CURL_DISABLE_HTTP) || !defined(CURL_DISABLE_SMTP) || \
|
||||
!defined(CURL_DISABLE_IMAP)
|
||||
/*
|
||||
* checkheaders() checks the linked list of custom headers for a
|
||||
* particular header (prefix). Provide the prefix without colon!
|
||||
*
|
||||
* Returns a pointer to the first matching header or NULL if none matched.
|
||||
*/
|
||||
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);
|
||||
|
||||
/* in the case of libssh2, we can never be really sure that we have emptied
|
||||
its internal buffers so we MUST always try until we get EAGAIN back */
|
||||
return (!rcvd_eagain &&
|
||||
conn->handler->protocol&(CURLPROTO_SCP|CURLPROTO_SFTP)) ||
|
||||
Curl_conn_data_pending(data, FIRSTSOCKET);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check to see if CURLOPT_TIMECONDITION was met by comparing the time of the
|
||||
* remote document with the time provided by CURLOPT_TIMEVAL
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Receive raw response data for the transfer.
|
||||
* @param data the transfer
|
||||
* @param buf buffer to keep response data received
|
||||
* @param blen length of `buf`
|
||||
* @param eos_reliable if EOS detection in underlying connection is reliable
|
||||
* @param err error code in case of -1 return
|
||||
* @return number of bytes read or -1 for error
|
||||
*/
|
||||
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 we are reading BODY data and the connection does NOT handle EOF
|
||||
* and we know the size of the BODY data, limit the read amount */
|
||||
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)) {
|
||||
/* we already received everything. Do not try more. */
|
||||
blen = 0;
|
||||
}
|
||||
|
||||
if(!blen) {
|
||||
/* want nothing more */
|
||||
*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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Go ahead and do a read if we have a readable socket or if
|
||||
* the stream was rewound (in which case we have data in a
|
||||
* buffer)
|
||||
*/
|
||||
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;
|
||||
|
||||
/* This is where we loop until we have read everything there is to
|
||||
read or we get a CURLE_AGAIN */
|
||||
do {
|
||||
size_t bytestoread;
|
||||
ssize_t nread;
|
||||
|
||||
if(!is_multiplex) {
|
||||
/* Multiplexed connection have inherent handling of EOF and we do not
|
||||
* have to carefully restrict the amount we try to read.
|
||||
* Multiplexed changes only in one direction. */
|
||||
is_multiplex = Curl_conn_is_multiplex(conn, FIRSTSOCKET);
|
||||
}
|
||||
|
||||
buf = xfer_buf;
|
||||
bytestoread = xfer_blen;
|
||||
|
||||
if(bytestoread && data->set.max_recv_speed > 0) {
|
||||
/* In case of speed limit on receiving: if this loop already got
|
||||
* a quarter of the quota, break out. We want to stutter a bit
|
||||
* to keep in the limit, but too small receives will just cost
|
||||
* cpu unnecessarily. */
|
||||
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; /* real error */
|
||||
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;
|
||||
/* continue as if we received the EOS */
|
||||
}
|
||||
else
|
||||
break; /* get out of loop */
|
||||
}
|
||||
|
||||
/* We only get a 0-length receive at the end of the response */
|
||||
blen = (size_t)nread;
|
||||
is_eos = (blen == 0);
|
||||
|
||||
if(!blen && (conn->recv[FIRSTSOCKET] == Curl_cf_recv)) {
|
||||
/* if we receive 0 or less here and the protocol handler did not
|
||||
replace the connection's `recv` callback, either the data transfer
|
||||
is done or the server closed the connection and
|
||||
we bail out from this!
|
||||
With a `recv` replacement, we assume the protocol handler knows
|
||||
what it is doing and a 0-length receive is fine. For example,
|
||||
SFTP downloads of an empty file would show this. See #19165. */
|
||||
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) /* already did write this to client, leave */
|
||||
break;
|
||||
}
|
||||
total_received += blen;
|
||||
|
||||
result = Curl_xfer_write_resp(data, buf, blen, is_eos);
|
||||
if(result || data->req.done)
|
||||
goto out;
|
||||
|
||||
/* if we are done, we stop receiving. On multiplexed connections,
|
||||
* we should read the EOS. Which may arrive as meta data after
|
||||
* the bytes. Not taking it in might lead to RST of streams. */
|
||||
if((!is_multiplex && data->req.download_done) || is_eos) {
|
||||
data->req.keepon &= ~KEEP_RECV;
|
||||
}
|
||||
/* if we are PAUSEd or stopped receiving, leave the loop */
|
||||
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))) {
|
||||
/* Did not read until EAGAIN/EOS or there is still data pending
|
||||
* in buffers. Mark as read-again via simulated SELECT results. */
|
||||
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)) {
|
||||
/* When we have read the entire thing and the close bit is set, the server
|
||||
may now close the connection. If there is now any kind of sending going
|
||||
on from our side, we need to stop that immediately. */
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* Send data to upload to the server, when the socket is writable.
|
||||
*/
|
||||
static CURLcode sendrecv_ul(struct Curl_easy *data)
|
||||
{
|
||||
/* We should not get here when the sending is already done. It
|
||||
* probably means that someone set `data-req.keepon |= KEEP_SEND`
|
||||
* when it should not. */
|
||||
DEBUGASSERT(!Curl_req_done_sending(data));
|
||||
|
||||
if(!Curl_req_done_sending(data))
|
||||
return Curl_req_send_more(data);
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
/*
|
||||
* Curl_sendrecv() is the low-level function to be called when data is to
|
||||
* be read and written to/from the connection.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/* We go ahead and do a read if we have a readable socket or if the stream
|
||||
was rewound (in which case we have data in a buffer) */
|
||||
if(k->keepon & KEEP_RECV) {
|
||||
result = sendrecv_dl(data, k);
|
||||
if(result || data->req.done)
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* If we still have writing to do, we check if we have a writable socket. */
|
||||
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 {
|
||||
/*
|
||||
* The transfer has been performed. Just make some general checks before
|
||||
* returning.
|
||||
*/
|
||||
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 there is nothing more to send/recv, the request is done */
|
||||
if((k->keepon & (KEEP_RECVBITS|KEEP_SENDBITS)) == 0)
|
||||
data->req.done = TRUE;
|
||||
|
||||
out:
|
||||
if(result)
|
||||
DEBUGF(infof(data, "Curl_sendrecv() -> %d", result));
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Curl_init_CONNECT() gets called each time the handle switches to CONNECT
|
||||
which means this gets called once for each subsequent redirect etc */
|
||||
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);
|
||||
}
|
||||
|
||||
/*
|
||||
* Curl_pretransfer() is called immediately before a transfer starts, and only
|
||||
* once for one transfer no matter if it has redirects or do multi-pass
|
||||
* authentication etc.
|
||||
*/
|
||||
CURLcode Curl_pretransfer(struct Curl_easy *data)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
|
||||
/* Reset the retry count at the start of each request.
|
||||
* If the retry count is not reset, when the connection drops,
|
||||
* it will not enter the retry mechanism on CONN_MAX_RETRIES + 1 attempts
|
||||
* and will immediately throw
|
||||
* "Connection died, tried CONN_MAX_RETRIES times before giving up".
|
||||
* By resetting it here, we ensure each new request starts fresh. */
|
||||
data->state.retrycount = 0;
|
||||
|
||||
if(!data->set.str[STRING_SET_URL] && !data->set.uh) {
|
||||
/* we cannot do anything without URL */
|
||||
failf(data, "No URL set");
|
||||
return CURLE_URL_MALFORMAT;
|
||||
}
|
||||
|
||||
/* CURLOPT_CURLU overrides CURLOPT_URL and the contents of the CURLU handle
|
||||
is allowed to be changed by the user between transfers */
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/* since the URL may have been redirected in a previous use of this handle */
|
||||
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) {
|
||||
/* we cannot */
|
||||
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; /* reset the location-follow counter */
|
||||
data->state.this_is_a_follow = FALSE; /* reset this */
|
||||
data->state.errorbuf = FALSE; /* no error has occurred */
|
||||
#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;
|
||||
|
||||
/* If there is a list of cookie files to read, do it now! */
|
||||
Curl_cookie_loadfiles(data);
|
||||
|
||||
/* If there is a list of host pairs to deal with */
|
||||
if(data->state.resolve)
|
||||
result = Curl_loadhostpairs(data);
|
||||
|
||||
/* If there is a list of hsts files to read */
|
||||
Curl_hsts_loadfiles(data);
|
||||
|
||||
if(!result) {
|
||||
/* Allow data->set.use_port to set which port to use. This needs to be
|
||||
* disabled for example when we follow Location: headers to URLs using
|
||||
* different ports! */
|
||||
data->state.allow_port = TRUE;
|
||||
|
||||
#if defined(HAVE_SIGNAL) && defined(SIGPIPE) && !defined(HAVE_MSG_NOSIGNAL)
|
||||
/*************************************************************
|
||||
* Tell signal handler to ignore SIGPIPE
|
||||
*************************************************************/
|
||||
if(!data->set.no_signal)
|
||||
data->state.prev_signal = signal(SIGPIPE, SIG_IGN);
|
||||
#endif
|
||||
|
||||
Curl_initinfo(data); /* reset session-specific information "variables" */
|
||||
Curl_pgrsResetTransferSizes(data);
|
||||
Curl_pgrsStartNow(data);
|
||||
|
||||
/* In case the handle is reused and an authentication method was picked
|
||||
in the session we need to make sure we only use the one(s) we now
|
||||
consider to be fine */
|
||||
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); /* init wildcard structures */
|
||||
if(result)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
result = Curl_hsts_loadcb(data, data->hsts);
|
||||
}
|
||||
|
||||
/*
|
||||
* Set user-agent. Used for HTTP, but since we can attempt to tunnel
|
||||
* basically anything through an HTTP proxy we cannot limit this based on
|
||||
* protocol.
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/* Returns CURLE_OK *and* sets '*url' if a request retry is wanted.
|
||||
|
||||
NOTE: that the *url is malloc()ed. */
|
||||
CURLcode Curl_retry_request(struct Curl_easy *data, char **url)
|
||||
{
|
||||
struct connectdata *conn = data->conn;
|
||||
bool retry = FALSE;
|
||||
*url = NULL;
|
||||
|
||||
/* if we are talking upload, we cannot do the checks below, unless the
|
||||
protocol is HTTP as when uploading over HTTP we will still get a
|
||||
response */
|
||||
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
|
||||
)
|
||||
/* We got no data, we attempted to reuse a connection. For HTTP this
|
||||
can be a retry so we try again regardless if we expected a body.
|
||||
For other protocols we only try again only if we expected a body.
|
||||
|
||||
This might happen if the connection was left alive when we were
|
||||
done using it before, but that was closed when we wanted to read from
|
||||
it again. Bad luck. Retry the same request on a fresh connect! */
|
||||
retry = TRUE;
|
||||
else if(data->state.refused_stream &&
|
||||
(data->req.bytecount + data->req.headerbytecount == 0) ) {
|
||||
/* This was sent on a refused stream, safe to rerun. A refused stream
|
||||
error can typically only happen on HTTP/2 level if the stream is safe
|
||||
to issue again, but the nghttp2 API can deliver the message to other
|
||||
streams as well, which is why this adds the check the data counters
|
||||
too. */
|
||||
infof(data, "REFUSED_STREAM, retrying a fresh connect");
|
||||
data->state.refused_stream = FALSE; /* clear again */
|
||||
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"); /* close this connection */
|
||||
conn->bits.retry = TRUE; /* mark this as a connection we are about
|
||||
to retry. Marking it this way should
|
||||
prevent i.e HTTP transfers to return
|
||||
error just because nothing has been
|
||||
transferred! */
|
||||
Curl_creader_set_rewind(data, TRUE);
|
||||
}
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
static void xfer_setup(
|
||||
struct Curl_easy *data, /* transfer */
|
||||
int send_idx, /* sockindex to send on or -1 */
|
||||
int recv_idx, /* sockindex to receive on or -1 */
|
||||
curl_off_t recv_size /* how much to receive, -1 if unknown */
|
||||
)
|
||||
{
|
||||
struct SingleRequest *k = &data->req;
|
||||
struct connectdata *conn = data->conn;
|
||||
|
||||
DEBUGASSERT(conn != NULL);
|
||||
/* indexes are in range */
|
||||
DEBUGASSERT((send_idx <= 1) && (send_idx >= -1));
|
||||
DEBUGASSERT((recv_idx <= 1) && (recv_idx >= -1));
|
||||
/* if request wants to send, switching off the send direction is wrong */
|
||||
DEBUGASSERT((send_idx >= 0) || !Curl_req_want_send(data));
|
||||
|
||||
conn->send_idx = send_idx;
|
||||
conn->recv_idx = recv_idx;
|
||||
|
||||
/* without receiving, there should be not recv_size */
|
||||
DEBUGASSERT((conn->recv_idx >= 0) || (recv_size == -1));
|
||||
k->size = recv_size;
|
||||
k->header = !!conn->handler->write_resp_hd;
|
||||
/* by default, we do not shutdown at the end of the transfer */
|
||||
k->shutdown = FALSE;
|
||||
k->shutdown_err_ignore = FALSE;
|
||||
|
||||
/* The code sequence below is placed in this function just because all
|
||||
necessary input is not always known in do_complete() as this function may
|
||||
be called after that */
|
||||
if(!k->header && (recv_size > 0))
|
||||
Curl_pgrsSetDownloadSize(data, recv_size);
|
||||
|
||||
/* we want header and/or body, if neither then do not do this! */
|
||||
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)
|
||||
{
|
||||
/* Shutdown should only be set when the transfer only sends or receives. */
|
||||
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) {
|
||||
/* protocol handlers offering this function take full responsibility
|
||||
* for writing all received download data to the client. */
|
||||
result = data->conn->handler->write_resp(data, buf, blen, is_eos);
|
||||
}
|
||||
else {
|
||||
/* No special handling by protocol handler, write all received data
|
||||
* as BODY to the client. */
|
||||
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) {
|
||||
/* If we wrote the EOS, we are definitely done */
|
||||
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) {
|
||||
/* protocol handlers offering this function take full responsibility
|
||||
* for writing all received download data to the client. */
|
||||
return data->conn->handler->write_resp_hd(data, hd0, hdlen, is_eos);
|
||||
}
|
||||
/* No special handling by protocol handler, write as response bytes */
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user