curl-curl/lib/content_encoding.c
Viktor Szakats 193cb00ce9
build: stop overriding standard memory allocation functions
Before this patch curl used the C preprocessor to override standard
memory allocation symbols: malloc, calloc, strdup, realloc, free.
The goal of these is to replace them with curl's debug wrappers in
`CURLDEBUG` builds, another was to replace them with the wrappers
calling user-defined allocators in libcurl. This solution needed a bunch
of workarounds to avoid breaking external headers: it relied on include
order to do the overriding last. For "unity" builds it needed to reset
overrides before external includes. Also in test apps, which are always
built as single source files. It also needed the `(symbol)` trick
to avoid overrides in some places. This would still not fix cases where
the standard symbols were macros. It was also fragile and difficult
to figure out which was the actual function behind an alloc or free call
in a specific piece of code. This in turn caused bugs where the wrong
allocator was accidentally called.

To avoid these problems, this patch replaces this solution with
`curlx_`-prefixed allocator macros, and mapping them _once_ to either
the libcurl wrappers, the debug wrappers or the standard ones, matching
the rest of the code in libtests.

This concludes the long journey to avoid redefining standard functions
in the curl codebase.

Note: I did not update `packages/OS400/*.c` sources. They did not
`#include` `curl_setup.h`, `curl_memory.h` or `memdebug.h`, meaning
the overrides were never applied to them. This may or may not have been
correct. For now I suppressed the direct use of standard allocators
via a local `.checksrc`. Probably they (except for `curlcl.c`) should be
updated to include `curl_setup.h` and use the `curlx_` macros.

This patch changes mappings in two places:
- `lib/curl_threads.c` in libtests: Before this patch it mapped to
  libcurl allocators. After, it maps to standard allocators, like
  the rest of libtests code.
- `units`: before this patch it mapped to standard allocators. After, it
  maps to libcurl allocators.

Also:
- drop all position-dependent `curl_memory.h` and `memdebug.h` includes,
  and delete the now unnecessary headers.
- rename `Curl_tcsdup` macro to `curlx_tcsdup` and define like the other
  allocators.
- map `curlx_strdup()` to `_strdup()` on Windows (was: `strdup()`).
  To fix warnings silenced via `_CRT_NONSTDC_NO_DEPRECATE`.
- multibyte: map `curlx_convert_*()` to `_strdup()` on Windows
  (was: `strdup()`).
- src: do not reuse the `strdup` name for the local replacement.
- lib509: call `_strdup()` on Windows (was: `strdup()`).
- test1132: delete test obsoleted by this patch.
- CHECKSRC.md: update text for `SNPRINTF`.
- checksrc: ban standard allocator symbols.

Follow-up to b12da22db1 #18866
Follow-up to db98daab05 #18844
Follow-up to 4deea9396b #18814
Follow-up to 9678ff5b1b #18776
Follow-up to 10bac43b87 #18774
Follow-up to 20142f5d06 #18634
Follow-up to bf7375ecc5 #18503
Follow-up to 9863599d69 #18502
Follow-up to 3bb5e58c10 #17827

Closes #19626
2025-11-28 10:44:26 +01:00

855 lines
24 KiB
C

/***************************************************************************
* _ _ ____ _
* 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"
#include "urldata.h"
#include <curl/curl.h>
#include <stddef.h>
#ifdef HAVE_LIBZ
#include <zlib.h>
#endif
#ifdef HAVE_BROTLI
#if defined(__GNUC__) || defined(__clang__)
/* Ignore -Wvla warnings in brotli headers */
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wvla"
#endif
#include <brotli/decode.h>
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic pop
#endif
#endif
#ifdef HAVE_ZSTD
#include <zstd.h>
#endif
#include "sendf.h"
#include "http.h"
#include "content_encoding.h"
#define CONTENT_ENCODING_DEFAULT "identity"
#ifndef CURL_DISABLE_HTTP
/* allow no more than 5 "chained" compression steps */
#define MAX_ENCODE_STACK 5
#if defined(HAVE_LIBZ) || defined(HAVE_BROTLI) || defined(HAVE_ZSTD)
#define DECOMPRESS_BUFFER_SIZE 16384 /* buffer size for decompressed data */
#endif
#ifdef HAVE_LIBZ
#if !defined(ZLIB_VERNUM) || (ZLIB_VERNUM < 0x1252)
#error "requires zlib 1.2.5.2 or newer"
#endif
typedef enum {
ZLIB_UNINIT, /* uninitialized */
ZLIB_INIT, /* initialized */
ZLIB_INFLATING, /* inflating started. */
ZLIB_EXTERNAL_TRAILER, /* reading external trailer */
ZLIB_INIT_GZIP /* initialized in transparent gzip mode */
} zlibInitState;
/* Deflate and gzip writer. */
struct zlib_writer {
struct Curl_cwriter super;
zlibInitState zlib_init; /* zlib init state */
char buffer[DECOMPRESS_BUFFER_SIZE]; /* Put the decompressed data here. */
uInt trailerlen; /* Remaining trailer byte count. */
z_stream z; /* State structure for zlib. */
};
static voidpf
zalloc_cb(voidpf opaque, unsigned int items, unsigned int size)
{
(void)opaque;
/* not a typo, keep it curlx_calloc() */
return (voidpf)curlx_calloc(items, size);
}
static void
zfree_cb(voidpf opaque, voidpf ptr)
{
(void)opaque;
curlx_free(ptr);
}
static CURLcode
process_zlib_error(struct Curl_easy *data, z_stream *z)
{
if(z->msg)
failf(data, "Error while processing content unencoding: %s",
z->msg);
else
failf(data, "Error while processing content unencoding: "
"Unknown failure within decompression software.");
return CURLE_BAD_CONTENT_ENCODING;
}
static CURLcode
exit_zlib(struct Curl_easy *data,
z_stream *z, zlibInitState *zlib_init, CURLcode result)
{
if(*zlib_init != ZLIB_UNINIT) {
if(inflateEnd(z) != Z_OK && result == CURLE_OK)
result = process_zlib_error(data, z);
*zlib_init = ZLIB_UNINIT;
}
return result;
}
static CURLcode process_trailer(struct Curl_easy *data,
struct zlib_writer *zp)
{
z_stream *z = &zp->z;
CURLcode result = CURLE_OK;
uInt len = z->avail_in < zp->trailerlen ? z->avail_in : zp->trailerlen;
/* Consume expected trailer bytes. Terminate stream if exhausted.
Issue an error if unexpected bytes follow. */
zp->trailerlen -= len;
z->avail_in -= len;
z->next_in += len;
if(z->avail_in)
result = CURLE_WRITE_ERROR;
if(result || !zp->trailerlen)
result = exit_zlib(data, z, &zp->zlib_init, result);
else {
/* Only occurs for gzip with zlib < 1.2.0.4 or raw deflate. */
zp->zlib_init = ZLIB_EXTERNAL_TRAILER;
}
return result;
}
static CURLcode inflate_stream(struct Curl_easy *data,
struct Curl_cwriter *writer, int type,
zlibInitState started)
{
struct zlib_writer *zp = (struct zlib_writer *) writer;
z_stream *z = &zp->z; /* zlib state structure */
uInt nread = z->avail_in;
z_const Bytef *orig_in = z->next_in;
bool done = FALSE;
CURLcode result = CURLE_OK; /* Curl_client_write status */
/* Check state. */
if(zp->zlib_init != ZLIB_INIT &&
zp->zlib_init != ZLIB_INFLATING &&
zp->zlib_init != ZLIB_INIT_GZIP)
return exit_zlib(data, z, &zp->zlib_init, CURLE_WRITE_ERROR);
/* because the buffer size is fixed, iteratively decompress and transfer to
the client via next_write function. */
while(!done) {
int status; /* zlib status */
done = TRUE;
/* (re)set buffer for decompressed output for every iteration */
z->next_out = (Bytef *) zp->buffer;
z->avail_out = DECOMPRESS_BUFFER_SIZE;
status = inflate(z, Z_BLOCK);
/* Flush output data if some. */
if(z->avail_out != DECOMPRESS_BUFFER_SIZE) {
if(status == Z_OK || status == Z_STREAM_END) {
zp->zlib_init = started; /* Data started. */
result = Curl_cwriter_write(data, writer->next, type, zp->buffer,
DECOMPRESS_BUFFER_SIZE - z->avail_out);
if(result) {
exit_zlib(data, z, &zp->zlib_init, result);
break;
}
}
}
/* Dispatch by inflate() status. */
switch(status) {
case Z_OK:
/* Always loop: there may be unflushed latched data in zlib state. */
done = FALSE;
break;
case Z_BUF_ERROR:
/* No more data to flush: just exit loop. */
break;
case Z_STREAM_END:
result = process_trailer(data, zp);
break;
case Z_DATA_ERROR:
/* some servers seem to not generate zlib headers, so this is an attempt
to fix and continue anyway */
if(zp->zlib_init == ZLIB_INIT) {
if(inflateReset2(z, -MAX_WBITS) == Z_OK) {
z->next_in = orig_in;
z->avail_in = nread;
zp->zlib_init = ZLIB_INFLATING;
zp->trailerlen = 4; /* Tolerate up to 4 unknown trailer bytes. */
done = FALSE;
break;
}
zp->zlib_init = ZLIB_UNINIT; /* inflateEnd() already called. */
}
result = exit_zlib(data, z, &zp->zlib_init, process_zlib_error(data, z));
break;
default:
result = exit_zlib(data, z, &zp->zlib_init, process_zlib_error(data, z));
break;
}
}
/* We are about to leave this call so the `nread' data bytes will not be seen
again. If we are in a state that would wrongly allow restart in raw mode
at the next call, assume output has already started. */
if(nread && zp->zlib_init == ZLIB_INIT)
zp->zlib_init = started; /* Cannot restart anymore. */
return result;
}
/* Deflate handler. */
static CURLcode deflate_do_init(struct Curl_easy *data,
struct Curl_cwriter *writer)
{
struct zlib_writer *zp = (struct zlib_writer *) writer;
z_stream *z = &zp->z; /* zlib state structure */
/* Initialize zlib */
z->zalloc = (alloc_func) zalloc_cb;
z->zfree = (free_func) zfree_cb;
if(inflateInit(z) != Z_OK)
return process_zlib_error(data, z);
zp->zlib_init = ZLIB_INIT;
return CURLE_OK;
}
static CURLcode deflate_do_write(struct Curl_easy *data,
struct Curl_cwriter *writer, int type,
const char *buf, size_t nbytes)
{
struct zlib_writer *zp = (struct zlib_writer *) writer;
z_stream *z = &zp->z; /* zlib state structure */
if(!(type & CLIENTWRITE_BODY) || !nbytes)
return Curl_cwriter_write(data, writer->next, type, buf, nbytes);
/* Set the compressed input when this function is called */
z->next_in = (z_const Bytef *)buf;
z->avail_in = (uInt)nbytes;
if(zp->zlib_init == ZLIB_EXTERNAL_TRAILER)
return process_trailer(data, zp);
/* Now uncompress the data */
return inflate_stream(data, writer, type, ZLIB_INFLATING);
}
static void deflate_do_close(struct Curl_easy *data,
struct Curl_cwriter *writer)
{
struct zlib_writer *zp = (struct zlib_writer *) writer;
z_stream *z = &zp->z; /* zlib state structure */
exit_zlib(data, z, &zp->zlib_init, CURLE_OK);
}
static const struct Curl_cwtype deflate_encoding = {
"deflate",
NULL,
deflate_do_init,
deflate_do_write,
deflate_do_close,
sizeof(struct zlib_writer)
};
/* Gzip handler. */
static CURLcode gzip_do_init(struct Curl_easy *data,
struct Curl_cwriter *writer)
{
struct zlib_writer *zp = (struct zlib_writer *) writer;
z_stream *z = &zp->z; /* zlib state structure */
/* Initialize zlib */
z->zalloc = (alloc_func) zalloc_cb;
z->zfree = (free_func) zfree_cb;
if(inflateInit2(z, MAX_WBITS + 32) != Z_OK)
return process_zlib_error(data, z);
zp->zlib_init = ZLIB_INIT_GZIP; /* Transparent gzip decompress state */
return CURLE_OK;
}
static CURLcode gzip_do_write(struct Curl_easy *data,
struct Curl_cwriter *writer, int type,
const char *buf, size_t nbytes)
{
struct zlib_writer *zp = (struct zlib_writer *) writer;
z_stream *z = &zp->z; /* zlib state structure */
if(!(type & CLIENTWRITE_BODY) || !nbytes)
return Curl_cwriter_write(data, writer->next, type, buf, nbytes);
if(zp->zlib_init == ZLIB_INIT_GZIP) {
/* Let zlib handle the gzip decompression entirely */
z->next_in = (z_const Bytef *)buf;
z->avail_in = (uInt)nbytes;
/* Now uncompress the data */
return inflate_stream(data, writer, type, ZLIB_INIT_GZIP);
}
/* We are running with an old version: return error. */
return exit_zlib(data, z, &zp->zlib_init, CURLE_WRITE_ERROR);
}
static void gzip_do_close(struct Curl_easy *data,
struct Curl_cwriter *writer)
{
struct zlib_writer *zp = (struct zlib_writer *) writer;
z_stream *z = &zp->z; /* zlib state structure */
exit_zlib(data, z, &zp->zlib_init, CURLE_OK);
}
static const struct Curl_cwtype gzip_encoding = {
"gzip",
"x-gzip",
gzip_do_init,
gzip_do_write,
gzip_do_close,
sizeof(struct zlib_writer)
};
#endif /* HAVE_LIBZ */
#ifdef HAVE_BROTLI
/* Brotli writer. */
struct brotli_writer {
struct Curl_cwriter super;
char buffer[DECOMPRESS_BUFFER_SIZE];
BrotliDecoderState *br; /* State structure for brotli. */
};
static CURLcode brotli_map_error(BrotliDecoderErrorCode be)
{
switch(be) {
case BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_NIBBLE:
case BROTLI_DECODER_ERROR_FORMAT_EXUBERANT_META_NIBBLE:
case BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_ALPHABET:
case BROTLI_DECODER_ERROR_FORMAT_SIMPLE_HUFFMAN_SAME:
case BROTLI_DECODER_ERROR_FORMAT_CL_SPACE:
case BROTLI_DECODER_ERROR_FORMAT_HUFFMAN_SPACE:
case BROTLI_DECODER_ERROR_FORMAT_CONTEXT_MAP_REPEAT:
case BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_1:
case BROTLI_DECODER_ERROR_FORMAT_BLOCK_LENGTH_2:
case BROTLI_DECODER_ERROR_FORMAT_TRANSFORM:
case BROTLI_DECODER_ERROR_FORMAT_DICTIONARY:
case BROTLI_DECODER_ERROR_FORMAT_WINDOW_BITS:
case BROTLI_DECODER_ERROR_FORMAT_PADDING_1:
case BROTLI_DECODER_ERROR_FORMAT_PADDING_2:
#ifdef BROTLI_DECODER_ERROR_COMPOUND_DICTIONARY /* brotli v1.1.0+ */
case BROTLI_DECODER_ERROR_COMPOUND_DICTIONARY:
#endif
case BROTLI_DECODER_ERROR_DICTIONARY_NOT_SET:
case BROTLI_DECODER_ERROR_INVALID_ARGUMENTS:
return CURLE_BAD_CONTENT_ENCODING;
case BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MODES:
case BROTLI_DECODER_ERROR_ALLOC_TREE_GROUPS:
case BROTLI_DECODER_ERROR_ALLOC_CONTEXT_MAP:
case BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_1:
case BROTLI_DECODER_ERROR_ALLOC_RING_BUFFER_2:
case BROTLI_DECODER_ERROR_ALLOC_BLOCK_TYPE_TREES:
return CURLE_OUT_OF_MEMORY;
default:
break;
}
return CURLE_WRITE_ERROR;
}
static CURLcode brotli_do_init(struct Curl_easy *data,
struct Curl_cwriter *writer)
{
struct brotli_writer *bp = (struct brotli_writer *) writer;
(void)data;
bp->br = BrotliDecoderCreateInstance(NULL, NULL, NULL);
return bp->br ? CURLE_OK : CURLE_OUT_OF_MEMORY;
}
static CURLcode brotli_do_write(struct Curl_easy *data,
struct Curl_cwriter *writer, int type,
const char *buf, size_t nbytes)
{
struct brotli_writer *bp = (struct brotli_writer *) writer;
const uint8_t *src = (const uint8_t *) buf;
uint8_t *dst;
size_t dstleft;
CURLcode result = CURLE_OK;
BrotliDecoderResult r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;
if(!(type & CLIENTWRITE_BODY) || !nbytes)
return Curl_cwriter_write(data, writer->next, type, buf, nbytes);
if(!bp->br)
return CURLE_WRITE_ERROR; /* Stream already ended. */
while((nbytes || r == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) &&
result == CURLE_OK) {
dst = (uint8_t *) bp->buffer;
dstleft = DECOMPRESS_BUFFER_SIZE;
r = BrotliDecoderDecompressStream(bp->br,
&nbytes, &src, &dstleft, &dst, NULL);
result = Curl_cwriter_write(data, writer->next, type,
bp->buffer, DECOMPRESS_BUFFER_SIZE - dstleft);
if(result)
break;
switch(r) {
case BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT:
case BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT:
break;
case BROTLI_DECODER_RESULT_SUCCESS:
BrotliDecoderDestroyInstance(bp->br);
bp->br = NULL;
if(nbytes)
result = CURLE_WRITE_ERROR;
break;
default:
result = brotli_map_error(BrotliDecoderGetErrorCode(bp->br));
break;
}
}
return result;
}
static void brotli_do_close(struct Curl_easy *data,
struct Curl_cwriter *writer)
{
struct brotli_writer *bp = (struct brotli_writer *) writer;
(void)data;
if(bp->br) {
BrotliDecoderDestroyInstance(bp->br);
bp->br = NULL;
}
}
static const struct Curl_cwtype brotli_encoding = {
"br",
NULL,
brotli_do_init,
brotli_do_write,
brotli_do_close,
sizeof(struct brotli_writer)
};
#endif
#ifdef HAVE_ZSTD
/* Zstd writer. */
struct zstd_writer {
struct Curl_cwriter super;
ZSTD_DStream *zds; /* State structure for zstd. */
char buffer[DECOMPRESS_BUFFER_SIZE];
};
#ifdef ZSTD_STATIC_LINKING_ONLY
static void *Curl_zstd_alloc(void *opaque, size_t size)
{
(void)opaque;
return Curl_cmalloc(size);
}
static void Curl_zstd_free(void *opaque, void *address)
{
(void)opaque;
Curl_cfree(address);
}
#endif
static CURLcode zstd_do_init(struct Curl_easy *data,
struct Curl_cwriter *writer)
{
struct zstd_writer *zp = (struct zstd_writer *) writer;
(void)data;
#ifdef ZSTD_STATIC_LINKING_ONLY
zp->zds = ZSTD_createDStream_advanced((ZSTD_customMem) {
.customAlloc = Curl_zstd_alloc,
.customFree = Curl_zstd_free,
.opaque = NULL
});
#else
zp->zds = ZSTD_createDStream();
#endif
return zp->zds ? CURLE_OK : CURLE_OUT_OF_MEMORY;
}
static CURLcode zstd_do_write(struct Curl_easy *data,
struct Curl_cwriter *writer, int type,
const char *buf, size_t nbytes)
{
CURLcode result = CURLE_OK;
struct zstd_writer *zp = (struct zstd_writer *) writer;
ZSTD_inBuffer in;
ZSTD_outBuffer out;
size_t errorCode;
if(!(type & CLIENTWRITE_BODY) || !nbytes)
return Curl_cwriter_write(data, writer->next, type, buf, nbytes);
in.pos = 0;
in.src = buf;
in.size = nbytes;
for(;;) {
out.pos = 0;
out.dst = zp->buffer;
out.size = DECOMPRESS_BUFFER_SIZE;
errorCode = ZSTD_decompressStream(zp->zds, &out, &in);
if(ZSTD_isError(errorCode)) {
return CURLE_BAD_CONTENT_ENCODING;
}
if(out.pos > 0) {
result = Curl_cwriter_write(data, writer->next, type,
zp->buffer, out.pos);
if(result)
break;
}
if((in.pos == nbytes) && (out.pos < out.size))
break;
}
return result;
}
static void zstd_do_close(struct Curl_easy *data,
struct Curl_cwriter *writer)
{
struct zstd_writer *zp = (struct zstd_writer *) writer;
(void)data;
if(zp->zds) {
ZSTD_freeDStream(zp->zds);
zp->zds = NULL;
}
}
static const struct Curl_cwtype zstd_encoding = {
"zstd",
NULL,
zstd_do_init,
zstd_do_write,
zstd_do_close,
sizeof(struct zstd_writer)
};
#endif
/* Identity handler. */
static const struct Curl_cwtype identity_encoding = {
"identity",
"none",
Curl_cwriter_def_init,
Curl_cwriter_def_write,
Curl_cwriter_def_close,
sizeof(struct Curl_cwriter)
};
/* supported general content decoders. */
static const struct Curl_cwtype * const general_unencoders[] = {
&identity_encoding,
#ifdef HAVE_LIBZ
&deflate_encoding,
&gzip_encoding,
#endif
#ifdef HAVE_BROTLI
&brotli_encoding,
#endif
#ifdef HAVE_ZSTD
&zstd_encoding,
#endif
NULL
};
/* supported content decoders only for transfer encodings */
static const struct Curl_cwtype * const transfer_unencoders[] = {
#ifndef CURL_DISABLE_HTTP
&Curl_httpchunk_unencoder,
#endif
NULL
};
/* Provide a list of comma-separated names of supported encodings.
*/
void Curl_all_content_encodings(char *buf, size_t blen)
{
size_t len = 0;
const struct Curl_cwtype * const *cep;
const struct Curl_cwtype *ce;
DEBUGASSERT(buf);
DEBUGASSERT(blen);
buf[0] = 0;
for(cep = general_unencoders; *cep; cep++) {
ce = *cep;
if(!curl_strequal(ce->name, CONTENT_ENCODING_DEFAULT))
len += strlen(ce->name) + 2;
}
if(!len) {
if(blen >= sizeof(CONTENT_ENCODING_DEFAULT))
strcpy(buf, CONTENT_ENCODING_DEFAULT);
}
else if(blen > len) {
char *p = buf;
for(cep = general_unencoders; *cep; cep++) {
ce = *cep;
if(!curl_strequal(ce->name, CONTENT_ENCODING_DEFAULT)) {
strcpy(p, ce->name);
p += strlen(p);
*p++ = ',';
*p++ = ' ';
}
}
p[-2] = '\0';
}
}
/* Deferred error dummy writer. */
static CURLcode error_do_init(struct Curl_easy *data,
struct Curl_cwriter *writer)
{
(void)data;
(void)writer;
return CURLE_OK;
}
static CURLcode error_do_write(struct Curl_easy *data,
struct Curl_cwriter *writer, int type,
const char *buf, size_t nbytes)
{
(void)writer;
(void)buf;
(void)nbytes;
if(!(type & CLIENTWRITE_BODY) || !nbytes)
return Curl_cwriter_write(data, writer->next, type, buf, nbytes);
else {
char all[256];
(void)Curl_all_content_encodings(all, sizeof(all));
failf(data, "Unrecognized content encoding type. "
"libcurl understands %s content encodings.", all);
}
return CURLE_BAD_CONTENT_ENCODING;
}
static void error_do_close(struct Curl_easy *data,
struct Curl_cwriter *writer)
{
(void)data;
(void)writer;
}
static const struct Curl_cwtype error_writer = {
"ce-error",
NULL,
error_do_init,
error_do_write,
error_do_close,
sizeof(struct Curl_cwriter)
};
/* Find the content encoding by name. */
static const struct Curl_cwtype *find_unencode_writer(const char *name,
size_t len,
Curl_cwriter_phase phase)
{
const struct Curl_cwtype * const *cep;
if(phase == CURL_CW_TRANSFER_DECODE) {
for(cep = transfer_unencoders; *cep; cep++) {
const struct Curl_cwtype *ce = *cep;
if((curl_strnequal(name, ce->name, len) && !ce->name[len]) ||
(ce->alias && curl_strnequal(name, ce->alias, len)
&& !ce->alias[len]))
return ce;
}
}
/* look among the general decoders */
for(cep = general_unencoders; *cep; cep++) {
const struct Curl_cwtype *ce = *cep;
if((curl_strnequal(name, ce->name, len) && !ce->name[len]) ||
(ce->alias && curl_strnequal(name, ce->alias, len) && !ce->alias[len]))
return ce;
}
return NULL;
}
/* Setup the unencoding stack from the Content-Encoding header value.
* See RFC 7231 section 3.1.2.2. */
CURLcode Curl_build_unencoding_stack(struct Curl_easy *data,
const char *enclist, int is_transfer)
{
Curl_cwriter_phase phase = is_transfer ?
CURL_CW_TRANSFER_DECODE : CURL_CW_CONTENT_DECODE;
CURLcode result;
bool has_chunked = FALSE;
do {
const char *name;
size_t namelen;
bool is_chunked = FALSE;
/* Parse a single encoding name. */
while(ISBLANK(*enclist) || *enclist == ',')
enclist++;
name = enclist;
for(namelen = 0; *enclist && *enclist != ','; enclist++)
if(*enclist > ' ')
namelen = enclist - name + 1;
if(namelen) {
const struct Curl_cwtype *cwt;
struct Curl_cwriter *writer;
CURL_TRC_WRITE(data, "looking for %s decoder: %.*s",
is_transfer ? "transfer" : "content", (int)namelen, name);
is_chunked = (is_transfer && (namelen == 7) &&
curl_strnequal(name, "chunked", 7));
/* if we skip the decoding in this phase, do not look further.
* Exception is "chunked" transfer-encoding which always must happen */
if((is_transfer && !data->set.http_transfer_encoding && !is_chunked) ||
(!is_transfer && data->set.http_ce_skip)) {
bool is_identity = curl_strnequal(name, "identity", 8);
/* not requested, ignore */
CURL_TRC_WRITE(data, "decoder not requested, ignored: %.*s",
(int)namelen, name);
if(is_transfer && !data->set.http_te_skip) {
if(has_chunked)
failf(data, "A Transfer-Encoding (%.*s) was listed after chunked",
(int)namelen, name);
else if(is_identity)
continue;
else
failf(data, "Unsolicited Transfer-Encoding (%.*s) found",
(int)namelen, name);
return CURLE_BAD_CONTENT_ENCODING;
}
return CURLE_OK;
}
if(Curl_cwriter_count(data, phase) + 1 >= MAX_ENCODE_STACK) {
failf(data, "Reject response due to more than %u content encodings",
MAX_ENCODE_STACK);
return CURLE_BAD_CONTENT_ENCODING;
}
cwt = find_unencode_writer(name, namelen, phase);
if(cwt && is_chunked && Curl_cwriter_get_by_type(data, cwt)) {
/* A 'chunked' transfer encoding has already been added.
* Ignore duplicates. See #13451.
* Also RFC 9112, ch. 6.1:
* "A sender MUST NOT apply the chunked transfer coding more than
* once to a message body."
*/
CURL_TRC_WRITE(data, "ignoring duplicate 'chunked' decoder");
return CURLE_OK;
}
if(is_transfer && !is_chunked &&
Curl_cwriter_get_by_name(data, "chunked")) {
/* RFC 9112, ch. 6.1:
* "If any transfer coding other than chunked is applied to a
* response's content, the sender MUST either apply chunked as the
* final transfer coding or terminate the message by closing the
* connection."
* "chunked" must be the last added to be the first in its phase,
* reject this.
*/
failf(data, "Reject response due to 'chunked' not being the last "
"Transfer-Encoding");
return CURLE_BAD_CONTENT_ENCODING;
}
if(!cwt)
cwt = &error_writer; /* Defer error at use. */
result = Curl_cwriter_create(&writer, data, cwt, phase);
CURL_TRC_WRITE(data, "added %s decoder %s -> %d",
is_transfer ? "transfer" : "content", cwt->name, result);
if(result)
return result;
result = Curl_cwriter_add(data, writer);
if(result) {
Curl_cwriter_free(data, writer);
return result;
}
if(is_chunked)
has_chunked = TRUE;
}
} while(*enclist);
return CURLE_OK;
}
#else
/* Stubs for builds without HTTP. */
CURLcode Curl_build_unencoding_stack(struct Curl_easy *data,
const char *enclist, int is_transfer)
{
(void)data;
(void)enclist;
(void)is_transfer;
return CURLE_NOT_BUILT_IN;
}
void Curl_all_content_encodings(char *buf, size_t blen)
{
DEBUGASSERT(buf);
DEBUGASSERT(blen);
if(blen < sizeof(CONTENT_ENCODING_DEFAULT))
buf[0] = 0;
else
strcpy(buf, CONTENT_ENCODING_DEFAULT);
}
#endif /* CURL_DISABLE_HTTP */