/*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | * / __| | | | |_) | | * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * * Copyright (C) Daniel Stenberg, , 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 #endif #ifdef HAVE_LINUX_TCP_H #include #elif defined(HAVE_NETINET_TCP_H) #include #endif #include #include "urldata.h" #include "sendf.h" #include "cfilters.h" #include "connect.h" #include "content_encoding.h" #include "cw-out.h" #include "vtls/vtls.h" #include "vssh/ssh.h" #include "easyif.h" #include "multiif.h" #include "strerror.h" #include "select.h" #include "strdup.h" #include "http2.h" #include "progress.h" #include "ws.h" /* The last 3 #include files should be in this order */ #include "curl_printf.h" #include "curl_memory.h" #include "memdebug.h" static CURLcode do_init_stack(struct Curl_easy *data); /* Curl_client_write() sends data to the write callback(s) The bit pattern defines to what "streams" to write to. Body and/or header. The defines are in sendf.h of course. */ CURLcode Curl_client_write(struct Curl_easy *data, int type, const char *buf, size_t blen) { CURLcode result; /* it is one of those, at least */ DEBUGASSERT(type & (CLIENTWRITE_BODY|CLIENTWRITE_HEADER|CLIENTWRITE_INFO)); /* BODY is only BODY (with optional EOS) */ DEBUGASSERT(!(type & CLIENTWRITE_BODY) || ((type & ~(CLIENTWRITE_BODY|CLIENTWRITE_EOS)) == 0)); /* INFO is only INFO (with optional EOS) */ DEBUGASSERT(!(type & CLIENTWRITE_INFO) || ((type & ~(CLIENTWRITE_INFO|CLIENTWRITE_EOS)) == 0)); if(!data->req.writer_stack) { result = do_init_stack(data); if(result) return result; DEBUGASSERT(data->req.writer_stack); } return Curl_cwriter_write(data, data->req.writer_stack, type, buf, blen); } void Curl_cw_reset(struct Curl_easy *data) { struct Curl_cwriter *writer = data->req.writer_stack; while(writer) { data->req.writer_stack = writer->next; writer->cwt->do_close(data, writer); free(writer); writer = data->req.writer_stack; } data->req.bytecount = 0; data->req.headerline = 0; } /* Write data using an unencoding writer stack. "nbytes" is not allowed to be 0. */ CURLcode Curl_cwriter_write(struct Curl_easy *data, struct Curl_cwriter *writer, int type, const char *buf, size_t nbytes) { if(!writer) return CURLE_WRITE_ERROR; return writer->cwt->do_write(data, writer, type, buf, nbytes); } CURLcode Curl_cwriter_def_init(struct Curl_easy *data, struct Curl_cwriter *writer) { (void)data; (void)writer; return CURLE_OK; } CURLcode Curl_cwriter_def_write(struct Curl_easy *data, struct Curl_cwriter *writer, int type, const char *buf, size_t nbytes) { return Curl_cwriter_write(data, writer->next, type, buf, nbytes); } void Curl_cwriter_def_close(struct Curl_easy *data, struct Curl_cwriter *writer) { (void) data; (void) writer; } static size_t get_max_body_write_len(struct Curl_easy *data, curl_off_t limit) { if(limit != -1) { /* How much more are we allowed to write? */ curl_off_t remain_diff; remain_diff = limit - data->req.bytecount; if(remain_diff < 0) { /* already written too much! */ return 0; } #if SIZEOF_CURL_OFF_T > SIZEOF_SIZE_T else if(remain_diff > SSIZE_T_MAX) { return SIZE_T_MAX; } #endif else { return (size_t)remain_diff; } } return SIZE_T_MAX; } /* Download client writer in phase CURL_CW_PROTOCOL that * sees the "real" download body data. */ static CURLcode cw_download_write(struct Curl_easy *data, struct Curl_cwriter *writer, int type, const char *buf, size_t nbytes) { CURLcode result; size_t nwrite, excess_len = 0; if(!(type & CLIENTWRITE_BODY)) { if((type & CLIENTWRITE_CONNECT) && data->set.suppress_connect_headers) return CURLE_OK; return Curl_cwriter_write(data, writer->next, type, buf, nbytes); } if(!data->req.bytecount) { Curl_pgrsTime(data, TIMER_STARTTRANSFER); if(data->req.exp100 > EXP100_SEND_DATA) /* set time stamp to compare with when waiting for the 100 */ data->req.start100 = Curl_now(); } /* Here, we deal with REAL BODY bytes. All filtering and transfer * encodings have been applied and only the true content, e.g. BODY, * bytes are passed here. * This allows us to check sizes, update stats, etc. independent * from the protocol in play. */ if(data->req.no_body && nbytes > 0) { /* BODY arrives although we want none, bail out */ streamclose(data->conn, "ignoring body"); DEBUGF(infof(data, "did not want a BODY, but seeing %zu bytes", nbytes)); data->req.download_done = TRUE; if(data->info.header_size) /* if headers have been received, this is fine */ return CURLE_OK; return CURLE_WEIRD_SERVER_REPLY; } /* Determine if we see any bytes in excess to what is allowed. * We write the allowed bytes and handle excess further below. * This gives deterministic BODY writes on varying buffer receive * lengths. */ nwrite = nbytes; if(-1 != data->req.maxdownload) { size_t wmax = get_max_body_write_len(data, data->req.maxdownload); if(nwrite > wmax) { excess_len = nbytes - wmax; nwrite = wmax; } if(nwrite == wmax) { data->req.download_done = TRUE; } } /* Error on too large filesize is handled below, after writing * the permitted bytes */ if(data->set.max_filesize) { size_t wmax = get_max_body_write_len(data, data->set.max_filesize); if(nwrite > wmax) { nwrite = wmax; } } if(!data->req.ignorebody && (nwrite || (type & CLIENTWRITE_EOS))) { result = Curl_cwriter_write(data, writer->next, type, buf, nwrite); if(result) return result; } /* Update stats, write and report progress */ data->req.bytecount += nwrite; ++data->req.bodywrites; result = Curl_pgrsSetDownloadCounter(data, data->req.bytecount); if(result) return result; if(excess_len) { if(!data->req.ignorebody) { infof(data, "Excess found writing body:" " excess = %zu" ", size = %" CURL_FORMAT_CURL_OFF_T ", maxdownload = %" CURL_FORMAT_CURL_OFF_T ", bytecount = %" CURL_FORMAT_CURL_OFF_T, excess_len, data->req.size, data->req.maxdownload, data->req.bytecount); connclose(data->conn, "excess found in a read"); } } else if(nwrite < nbytes) { failf(data, "Exceeded the maximum allowed file size " "(%" CURL_FORMAT_CURL_OFF_T ") with %" CURL_FORMAT_CURL_OFF_T " bytes", data->set.max_filesize, data->req.bytecount); return CURLE_FILESIZE_EXCEEDED; } return CURLE_OK; } static const struct Curl_cwtype cw_download = { "download", NULL, Curl_cwriter_def_init, cw_download_write, Curl_cwriter_def_close, sizeof(struct Curl_cwriter) }; /* RAW client writer in phase CURL_CW_RAW that * enabled tracing of raw data. */ static CURLcode cw_raw_write(struct Curl_easy *data, struct Curl_cwriter *writer, int type, const char *buf, size_t nbytes) { if(type & CLIENTWRITE_BODY && data->set.verbose && !data->req.ignorebody) { Curl_debug(data, CURLINFO_DATA_IN, (char *)buf, nbytes); } return Curl_cwriter_write(data, writer->next, type, buf, nbytes); } static const struct Curl_cwtype cw_raw = { "raw", NULL, Curl_cwriter_def_init, cw_raw_write, Curl_cwriter_def_close, sizeof(struct Curl_cwriter) }; /* Create an unencoding writer stage using the given handler. */ CURLcode Curl_cwriter_create(struct Curl_cwriter **pwriter, struct Curl_easy *data, const struct Curl_cwtype *cwt, Curl_cwriter_phase phase) { struct Curl_cwriter *writer; CURLcode result = CURLE_OUT_OF_MEMORY; DEBUGASSERT(cwt->cwriter_size >= sizeof(struct Curl_cwriter)); writer = (struct Curl_cwriter *) calloc(1, cwt->cwriter_size); if(!writer) goto out; writer->cwt = cwt; writer->phase = phase; result = cwt->do_init(data, writer); out: *pwriter = result? NULL : writer; if(result) free(writer); return result; } void Curl_cwriter_free(struct Curl_easy *data, struct Curl_cwriter *writer) { if(writer) { writer->cwt->do_close(data, writer); free(writer); } } size_t Curl_cwriter_count(struct Curl_easy *data, Curl_cwriter_phase phase) { struct Curl_cwriter *w; size_t n = 0; for(w = data->req.writer_stack; w; w = w->next) { if(w->phase == phase) ++n; } return n; } static CURLcode do_init_stack(struct Curl_easy *data) { struct Curl_cwriter *writer; CURLcode result; DEBUGASSERT(!data->req.writer_stack); result = Curl_cwriter_create(&data->req.writer_stack, data, &Curl_cwt_out, CURL_CW_CLIENT); if(result) return result; result = Curl_cwriter_create(&writer, data, &cw_download, CURL_CW_PROTOCOL); if(result) return result; result = Curl_cwriter_add(data, writer); if(result) { Curl_cwriter_free(data, writer); } result = Curl_cwriter_create(&writer, data, &cw_raw, CURL_CW_RAW); if(result) return result; result = Curl_cwriter_add(data, writer); if(result) { Curl_cwriter_free(data, writer); } return result; } CURLcode Curl_cwriter_add(struct Curl_easy *data, struct Curl_cwriter *writer) { CURLcode result; struct Curl_cwriter **anchor = &data->req.writer_stack; if(!*anchor) { result = do_init_stack(data); if(result) return result; } /* Insert the writer as first in its phase. * Skip existing writers of lower phases. */ while(*anchor && (*anchor)->phase < writer->phase) anchor = &((*anchor)->next); writer->next = *anchor; *anchor = writer; return CURLE_OK; } struct Curl_cwriter *Curl_cwriter_get_by_name(struct Curl_easy *data, const char *name) { struct Curl_cwriter *writer; for(writer = data->req.writer_stack; writer; writer = writer->next) { if(!strcmp(name, writer->cwt->name)) return writer; } return NULL; } struct Curl_cwriter *Curl_cwriter_get_by_type(struct Curl_easy *data, const struct Curl_cwtype *cwt) { struct Curl_cwriter *writer; for(writer = data->req.writer_stack; writer; writer = writer->next) { if(writer->cwt == cwt) return writer; } return NULL; } void Curl_cwriter_remove_by_name(struct Curl_easy *data, const char *name) { struct Curl_cwriter **anchor = &data->req.writer_stack; while(*anchor) { if(!strcmp(name, (*anchor)->cwt->name)) { struct Curl_cwriter *w = (*anchor); *anchor = w->next; Curl_cwriter_free(data, w); continue; } anchor = &((*anchor)->next); } }