1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
9 *
10 * This software is licensed as described in the file COPYING, which
11 * you should have received as part of this distribution. The terms
12 * are also available at https://curl.se/docs/copyright.html.
13 *
14 * You may opt to use, copy, modify, merge, publish, distribute and/or sell
15 * copies of the Software, and permit persons to whom the Software is
16 * furnished to do so, under the terms of the COPYING file.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 * SPDX-License-Identifier: curl
22 *
23 ***************************************************************************/
24
25 #include "curl_setup.h"
26
27 #include <curl/curl.h>
28
29 #include "urldata.h"
30 #include "cfilters.h"
31 #include "headers.h"
32 #include "multiif.h"
33 #include "sendf.h"
34 #include "cw-out.h"
35
36 /* The last 3 #include files should be in this order */
37 #include "curl_printf.h"
38 #include "curl_memory.h"
39 #include "memdebug.h"
40
41
42 /**
43 * OVERALL DESIGN of this client writer
44 *
45 * The 'cw-out' writer is supposed to be the last writer in a transfer's
46 * stack. It is always added when that stack is initialized. Its purpose
47 * is to pass BODY and HEADER bytes to the client-installed callback
48 * functions.
49 *
50 * These callback may return `CURL_WRITEFUNC_PAUSE` to indicate that the
51 * data had not been written and the whole transfer should stop receiving
52 * new data. Or at least, stop calling the functions. When the transfer
53 * is "unpaused" by the client, the previous data shall be passed as
54 * if nothing happened.
55 *
56 * The `cw-out` writer therefore manages buffers for bytes that could
57 * not be written. Data that was already in flight from the server also
58 * needs buffering on paused transfer when it arrives.
59 *
60 * In addition, the writer allows buffering of "small" body writes,
61 * so client functions are called less often. That is only enabled on a
62 * number of conditions.
63 *
64 * HEADER and BODY data may arrive in any order. For paused transfers,
65 * a list of `struct cw_out_buf` is kept for `cw_out_type` types. The
66 * list may be: [BODY]->[HEADER]->[BODY]->[HEADER]....
67 * When unpausing, this list is "played back" to the client callbacks.
68 *
69 * The amount of bytes being buffered is limited by `DYN_PAUSE_BUFFER`
70 * and when that is exceeded `CURLE_TOO_LARGE` is returned as error.
71 */
72 typedef enum {
73 CW_OUT_NONE,
74 CW_OUT_BODY,
75 CW_OUT_HDS
76 } cw_out_type;
77
78 struct cw_out_buf {
79 struct cw_out_buf *next;
80 struct dynbuf b;
81 cw_out_type type;
82 };
83
cw_out_buf_create(cw_out_type otype)84 static struct cw_out_buf *cw_out_buf_create(cw_out_type otype)
85 {
86 struct cw_out_buf *cwbuf = calloc(1, sizeof(*cwbuf));
87 if(cwbuf) {
88 cwbuf->type = otype;
89 Curl_dyn_init(&cwbuf->b, DYN_PAUSE_BUFFER);
90 }
91 return cwbuf;
92 }
93
cw_out_buf_free(struct cw_out_buf * cwbuf)94 static void cw_out_buf_free(struct cw_out_buf *cwbuf)
95 {
96 if(cwbuf) {
97 Curl_dyn_free(&cwbuf->b);
98 free(cwbuf);
99 }
100 }
101
102 struct cw_out_ctx {
103 struct Curl_cwriter super;
104 struct cw_out_buf *buf;
105 BIT(paused);
106 BIT(errored);
107 };
108
109 static CURLcode cw_out_write(struct Curl_easy *data,
110 struct Curl_cwriter *writer, int type,
111 const char *buf, size_t nbytes);
112 static void cw_out_close(struct Curl_easy *data, struct Curl_cwriter *writer);
113 static CURLcode cw_out_init(struct Curl_easy *data,
114 struct Curl_cwriter *writer);
115
116 struct Curl_cwtype Curl_cwt_out = {
117 "cw-out",
118 NULL,
119 cw_out_init,
120 cw_out_write,
121 cw_out_close,
122 sizeof(struct cw_out_ctx)
123 };
124
cw_out_init(struct Curl_easy * data,struct Curl_cwriter * writer)125 static CURLcode cw_out_init(struct Curl_easy *data,
126 struct Curl_cwriter *writer)
127 {
128 struct cw_out_ctx *ctx = writer->ctx;
129 (void)data;
130 ctx->buf = NULL;
131 return CURLE_OK;
132 }
133
cw_out_bufs_free(struct cw_out_ctx * ctx)134 static void cw_out_bufs_free(struct cw_out_ctx *ctx)
135 {
136 while(ctx->buf) {
137 struct cw_out_buf *next = ctx->buf->next;
138 cw_out_buf_free(ctx->buf);
139 ctx->buf = next;
140 }
141 }
142
cw_out_bufs_len(struct cw_out_ctx * ctx)143 static size_t cw_out_bufs_len(struct cw_out_ctx *ctx)
144 {
145 struct cw_out_buf *cwbuf = ctx->buf;
146 size_t len = 0;
147 while(cwbuf) {
148 len += Curl_dyn_len(&cwbuf->b);
149 cwbuf = cwbuf->next;
150 }
151 return len;
152 }
153
cw_out_close(struct Curl_easy * data,struct Curl_cwriter * writer)154 static void cw_out_close(struct Curl_easy *data, struct Curl_cwriter *writer)
155 {
156 struct cw_out_ctx *ctx = writer->ctx;
157
158 (void)data;
159 cw_out_bufs_free(ctx);
160 }
161
162 /**
163 * Return the current curl_write_callback and user_data for the buf type
164 */
cw_get_writefunc(struct Curl_easy * data,cw_out_type otype,curl_write_callback * pwcb,void ** pwcb_data,size_t * pmax_write,size_t * pmin_write)165 static void cw_get_writefunc(struct Curl_easy *data, cw_out_type otype,
166 curl_write_callback *pwcb, void **pwcb_data,
167 size_t *pmax_write, size_t *pmin_write)
168 {
169 switch(otype) {
170 case CW_OUT_BODY:
171 *pwcb = data->set.fwrite_func;
172 *pwcb_data = data->set.out;
173 *pmax_write = CURL_MAX_WRITE_SIZE;
174 /* if we ever want buffering of BODY output, we can set `min_write`
175 * the preferred size. The default should always be to pass data
176 * to the client as it comes without delay */
177 *pmin_write = 0;
178 break;
179 case CW_OUT_HDS:
180 *pwcb = data->set.fwrite_header ? data->set.fwrite_header :
181 (data->set.writeheader ? data->set.fwrite_func : NULL);
182 *pwcb_data = data->set.writeheader;
183 *pmax_write = 0; /* do not chunk-write headers, write them as they are */
184 *pmin_write = 0;
185 break;
186 default:
187 *pwcb = NULL;
188 *pwcb_data = NULL;
189 *pmax_write = CURL_MAX_WRITE_SIZE;
190 *pmin_write = 0;
191 }
192 }
193
cw_out_ptr_flush(struct cw_out_ctx * ctx,struct Curl_easy * data,cw_out_type otype,bool flush_all,const char * buf,size_t blen,size_t * pconsumed)194 static CURLcode cw_out_ptr_flush(struct cw_out_ctx *ctx,
195 struct Curl_easy *data,
196 cw_out_type otype,
197 bool flush_all,
198 const char *buf, size_t blen,
199 size_t *pconsumed)
200 {
201 curl_write_callback wcb;
202 void *wcb_data;
203 size_t max_write, min_write;
204 size_t wlen, nwritten;
205
206 /* If we errored once, we do not invoke the client callback again */
207 if(ctx->errored)
208 return CURLE_WRITE_ERROR;
209
210 /* write callbacks may get NULLed by the client between calls. */
211 cw_get_writefunc(data, otype, &wcb, &wcb_data, &max_write, &min_write);
212 if(!wcb) {
213 *pconsumed = blen;
214 return CURLE_OK;
215 }
216
217 *pconsumed = 0;
218 while(blen && !ctx->paused) {
219 if(!flush_all && blen < min_write)
220 break;
221 wlen = max_write ? CURLMIN(blen, max_write) : blen;
222 Curl_set_in_callback(data, TRUE);
223 nwritten = wcb((char *)buf, 1, wlen, wcb_data);
224 Curl_set_in_callback(data, FALSE);
225 CURL_TRC_WRITE(data, "cw_out, wrote %zu %s bytes -> %zu",
226 wlen, (otype == CW_OUT_BODY) ? "body" : "header",
227 nwritten);
228 if(CURL_WRITEFUNC_PAUSE == nwritten) {
229 if(data->conn && data->conn->handler->flags & PROTOPT_NONETWORK) {
230 /* Protocols that work without network cannot be paused. This is
231 actually only FILE:// just now, and it cannot pause since the
232 transfer is not done using the "normal" procedure. */
233 failf(data, "Write callback asked for PAUSE when not supported");
234 return CURLE_WRITE_ERROR;
235 }
236 /* mark the connection as RECV paused */
237 data->req.keepon |= KEEP_RECV_PAUSE;
238 ctx->paused = TRUE;
239 CURL_TRC_WRITE(data, "cw_out, PAUSE requested by client");
240 break;
241 }
242 else if(CURL_WRITEFUNC_ERROR == nwritten) {
243 failf(data, "client returned ERROR on write of %zu bytes", wlen);
244 return CURLE_WRITE_ERROR;
245 }
246 else if(nwritten != wlen) {
247 failf(data, "Failure writing output to destination, "
248 "passed %zu returned %zd", wlen, nwritten);
249 return CURLE_WRITE_ERROR;
250 }
251 *pconsumed += nwritten;
252 blen -= nwritten;
253 buf += nwritten;
254 }
255 return CURLE_OK;
256 }
257
cw_out_buf_flush(struct cw_out_ctx * ctx,struct Curl_easy * data,struct cw_out_buf * cwbuf,bool flush_all)258 static CURLcode cw_out_buf_flush(struct cw_out_ctx *ctx,
259 struct Curl_easy *data,
260 struct cw_out_buf *cwbuf,
261 bool flush_all)
262 {
263 CURLcode result = CURLE_OK;
264
265 if(Curl_dyn_len(&cwbuf->b)) {
266 size_t consumed;
267
268 result = cw_out_ptr_flush(ctx, data, cwbuf->type, flush_all,
269 Curl_dyn_ptr(&cwbuf->b),
270 Curl_dyn_len(&cwbuf->b),
271 &consumed);
272 if(result)
273 return result;
274
275 if(consumed) {
276 if(consumed == Curl_dyn_len(&cwbuf->b)) {
277 Curl_dyn_free(&cwbuf->b);
278 }
279 else {
280 DEBUGASSERT(consumed < Curl_dyn_len(&cwbuf->b));
281 result = Curl_dyn_tail(&cwbuf->b, Curl_dyn_len(&cwbuf->b) - consumed);
282 if(result)
283 return result;
284 }
285 }
286 }
287 return result;
288 }
289
cw_out_flush_chain(struct cw_out_ctx * ctx,struct Curl_easy * data,struct cw_out_buf ** pcwbuf,bool flush_all)290 static CURLcode cw_out_flush_chain(struct cw_out_ctx *ctx,
291 struct Curl_easy *data,
292 struct cw_out_buf **pcwbuf,
293 bool flush_all)
294 {
295 struct cw_out_buf *cwbuf = *pcwbuf;
296 CURLcode result;
297
298 if(!cwbuf)
299 return CURLE_OK;
300 if(ctx->paused)
301 return CURLE_OK;
302
303 /* write the end of the chain until it blocks or gets empty */
304 while(cwbuf->next) {
305 struct cw_out_buf **plast = &cwbuf->next;
306 while((*plast)->next)
307 plast = &(*plast)->next;
308 result = cw_out_flush_chain(ctx, data, plast, flush_all);
309 if(result)
310 return result;
311 if(*plast) {
312 /* could not write last, paused again? */
313 DEBUGASSERT(ctx->paused);
314 return CURLE_OK;
315 }
316 }
317
318 result = cw_out_buf_flush(ctx, data, cwbuf, flush_all);
319 if(result)
320 return result;
321 if(!Curl_dyn_len(&cwbuf->b)) {
322 cw_out_buf_free(cwbuf);
323 *pcwbuf = NULL;
324 }
325 return CURLE_OK;
326 }
327
cw_out_append(struct cw_out_ctx * ctx,cw_out_type otype,const char * buf,size_t blen)328 static CURLcode cw_out_append(struct cw_out_ctx *ctx,
329 cw_out_type otype,
330 const char *buf, size_t blen)
331 {
332 if(cw_out_bufs_len(ctx) + blen > DYN_PAUSE_BUFFER)
333 return CURLE_TOO_LARGE;
334
335 /* if we do not have a buffer, or it is of another type, make a new one.
336 * And for CW_OUT_HDS always make a new one, so we "replay" headers
337 * exactly as they came in */
338 if(!ctx->buf || (ctx->buf->type != otype) || (otype == CW_OUT_HDS)) {
339 struct cw_out_buf *cwbuf = cw_out_buf_create(otype);
340 if(!cwbuf)
341 return CURLE_OUT_OF_MEMORY;
342 cwbuf->next = ctx->buf;
343 ctx->buf = cwbuf;
344 }
345 DEBUGASSERT(ctx->buf && (ctx->buf->type == otype));
346 return Curl_dyn_addn(&ctx->buf->b, buf, blen);
347 }
348
cw_out_do_write(struct cw_out_ctx * ctx,struct Curl_easy * data,cw_out_type otype,bool flush_all,const char * buf,size_t blen)349 static CURLcode cw_out_do_write(struct cw_out_ctx *ctx,
350 struct Curl_easy *data,
351 cw_out_type otype,
352 bool flush_all,
353 const char *buf, size_t blen)
354 {
355 CURLcode result = CURLE_OK;
356
357 /* if we have buffered data and it is a different type than what
358 * we are writing now, try to flush all */
359 if(ctx->buf && ctx->buf->type != otype) {
360 result = cw_out_flush_chain(ctx, data, &ctx->buf, TRUE);
361 if(result)
362 goto out;
363 }
364
365 if(ctx->buf) {
366 /* still have buffered data, append and flush */
367 result = cw_out_append(ctx, otype, buf, blen);
368 if(result)
369 return result;
370 result = cw_out_flush_chain(ctx, data, &ctx->buf, flush_all);
371 if(result)
372 goto out;
373 }
374 else {
375 /* nothing buffered, try direct write */
376 size_t consumed;
377 result = cw_out_ptr_flush(ctx, data, otype, flush_all,
378 buf, blen, &consumed);
379 if(result)
380 return result;
381 if(consumed < blen) {
382 /* did not write all, append the rest */
383 result = cw_out_append(ctx, otype, buf + consumed, blen - consumed);
384 if(result)
385 goto out;
386 }
387 }
388
389 out:
390 if(result) {
391 /* We do not want to invoked client callbacks a second time after
392 * encountering an error. See issue #13337 */
393 ctx->errored = TRUE;
394 cw_out_bufs_free(ctx);
395 }
396 return result;
397 }
398
cw_out_write(struct Curl_easy * data,struct Curl_cwriter * writer,int type,const char * buf,size_t blen)399 static CURLcode cw_out_write(struct Curl_easy *data,
400 struct Curl_cwriter *writer, int type,
401 const char *buf, size_t blen)
402 {
403 struct cw_out_ctx *ctx = writer->ctx;
404 CURLcode result;
405 bool flush_all = !!(type & CLIENTWRITE_EOS);
406
407 if((type & CLIENTWRITE_BODY) ||
408 ((type & CLIENTWRITE_HEADER) && data->set.include_header)) {
409 result = cw_out_do_write(ctx, data, CW_OUT_BODY, flush_all, buf, blen);
410 if(result)
411 return result;
412 }
413
414 if(type & (CLIENTWRITE_HEADER|CLIENTWRITE_INFO)) {
415 result = cw_out_do_write(ctx, data, CW_OUT_HDS, flush_all, buf, blen);
416 if(result)
417 return result;
418 }
419
420 return CURLE_OK;
421 }
422
Curl_cw_out_is_paused(struct Curl_easy * data)423 bool Curl_cw_out_is_paused(struct Curl_easy *data)
424 {
425 struct Curl_cwriter *cw_out;
426 struct cw_out_ctx *ctx;
427
428 cw_out = Curl_cwriter_get_by_type(data, &Curl_cwt_out);
429 if(!cw_out)
430 return FALSE;
431
432 ctx = (struct cw_out_ctx *)cw_out;
433 CURL_TRC_WRITE(data, "cw-out is%spaused", ctx->paused ? "" : " not");
434 return ctx->paused;
435 }
436
cw_out_flush(struct Curl_easy * data,bool unpause,bool flush_all)437 static CURLcode cw_out_flush(struct Curl_easy *data,
438 bool unpause, bool flush_all)
439 {
440 struct Curl_cwriter *cw_out;
441 CURLcode result = CURLE_OK;
442
443 cw_out = Curl_cwriter_get_by_type(data, &Curl_cwt_out);
444 if(cw_out) {
445 struct cw_out_ctx *ctx = (struct cw_out_ctx *)cw_out;
446 if(ctx->errored)
447 return CURLE_WRITE_ERROR;
448 if(unpause && ctx->paused)
449 ctx->paused = FALSE;
450 if(ctx->paused)
451 return CURLE_OK; /* not doing it */
452
453 result = cw_out_flush_chain(ctx, data, &ctx->buf, flush_all);
454 if(result) {
455 ctx->errored = TRUE;
456 cw_out_bufs_free(ctx);
457 return result;
458 }
459 }
460 return result;
461 }
462
Curl_cw_out_unpause(struct Curl_easy * data)463 CURLcode Curl_cw_out_unpause(struct Curl_easy *data)
464 {
465 CURL_TRC_WRITE(data, "cw-out unpause");
466 return cw_out_flush(data, TRUE, FALSE);
467 }
468
Curl_cw_out_done(struct Curl_easy * data)469 CURLcode Curl_cw_out_done(struct Curl_easy *data)
470 {
471 CURL_TRC_WRITE(data, "cw-out done");
472 return cw_out_flush(data, FALSE, TRUE);
473 }
474