Skip to content

Commit

Permalink
curl: Fix brotli
Browse files Browse the repository at this point in the history
Signed-off-by: Reilly Brogan <reilly@reillybrogan.com>
  • Loading branch information
ReillyBrogan committed Mar 28, 2024
1 parent 1396ae8 commit 5b6b873
Show file tree
Hide file tree
Showing 3 changed files with 176 additions and 7 deletions.
168 changes: 168 additions & 0 deletions packages/c/curl/files/fix-brotli.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
From b30d694a027eb771c02a3db0dee0ca03ccab7377 Mon Sep 17 00:00:00 2001
From: Stefan Eissing <stefan@eissing.org>
Date: Thu, 28 Mar 2024 11:08:15 +0100
Subject: [PATCH] content_encoding: brotli and others, pass through 0-length
writes

- curl's transfer handling may write 0-length chunks at the end of the
download with an EOS flag. (HTTP/2 does this commonly)

- content encoders need to pass-through such a write and not count this
as error in case they are finished decoding

Fixes #13209
Fixes #13212
Closes #13219
---
lib/content_encoding.c | 10 +++++-----
tests/http/test_02_download.py | 13 +++++++++++++
tests/http/testenv/env.py | 7 ++++++-
tests/http/testenv/httpd.py | 20 ++++++++++++++++++++
4 files changed, 44 insertions(+), 6 deletions(-)

diff --git a/lib/content_encoding.c b/lib/content_encoding.c
index c1abf24e8c027c..8e926dd2ecd5ad 100644
--- a/lib/content_encoding.c
+++ b/lib/content_encoding.c
@@ -300,7 +300,7 @@ static CURLcode deflate_do_write(struct Curl_easy *data,
struct zlib_writer *zp = (struct zlib_writer *) writer;
z_stream *z = &zp->z; /* zlib state structure */

- if(!(type & CLIENTWRITE_BODY))
+ if(!(type & CLIENTWRITE_BODY) || !nbytes)
return Curl_cwriter_write(data, writer->next, type, buf, nbytes);

/* Set the compressed input when this function is called */
@@ -457,7 +457,7 @@ static CURLcode gzip_do_write(struct Curl_easy *data,
struct zlib_writer *zp = (struct zlib_writer *) writer;
z_stream *z = &zp->z; /* zlib state structure */

- if(!(type & CLIENTWRITE_BODY))
+ if(!(type & CLIENTWRITE_BODY) || !nbytes)
return Curl_cwriter_write(data, writer->next, type, buf, nbytes);

if(zp->zlib_init == ZLIB_INIT_GZIP) {
@@ -669,7 +669,7 @@ static CURLcode brotli_do_write(struct Curl_easy *data,
CURLcode result = CURLE_OK;
BrotliDecoderResult r = BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT;

- if(!(type & CLIENTWRITE_BODY))
+ if(!(type & CLIENTWRITE_BODY) || !nbytes)
return Curl_cwriter_write(data, writer->next, type, buf, nbytes);

if(!bp->br)
@@ -762,7 +762,7 @@ static CURLcode zstd_do_write(struct Curl_easy *data,
ZSTD_outBuffer out;
size_t errorCode;

- if(!(type & CLIENTWRITE_BODY))
+ if(!(type & CLIENTWRITE_BODY) || !nbytes)
return Curl_cwriter_write(data, writer->next, type, buf, nbytes);

if(!zp->decomp) {
@@ -916,7 +916,7 @@ static CURLcode error_do_write(struct Curl_easy *data,
(void) buf;
(void) nbytes;

- if(!(type & CLIENTWRITE_BODY))
+ if(!(type & CLIENTWRITE_BODY) || !nbytes)
return Curl_cwriter_write(data, writer->next, type, buf, nbytes);

failf(data, "Unrecognized content encoding type. "
diff --git a/tests/http/test_02_download.py b/tests/http/test_02_download.py
index 4db9c9d36e9ed5..395fc862f2f839 100644
--- a/tests/http/test_02_download.py
+++ b/tests/http/test_02_download.py
@@ -394,6 +394,19 @@ def test_02_27_paused_no_cl(self, env: Env, httpd, nghttpx, repeat):
r = client.run(args=[url])
r.check_exit_code(0)

+ @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
+ def test_02_28_get_compressed(self, env: Env, httpd, nghttpx, repeat, proto):
+ if proto == 'h3' and not env.have_h3():
+ pytest.skip("h3 not supported")
+ count = 1
+ urln = f'https://{env.authority_for(env.domain1brotli, proto)}/data-100k?[0-{count-1}]'
+ curl = CurlClient(env=env)
+ r = curl.http_download(urls=[urln], alpn_proto=proto, extra_args=[
+ '--compressed'
+ ])
+ r.check_exit_code(code=0)
+ r.check_response(count=count, http_status=200)
+
def check_downloads(self, client, srcfile: str, count: int,
complete: bool = True):
for i in range(count):
diff --git a/tests/http/testenv/env.py b/tests/http/testenv/env.py
index a207059dcd57c5..13c5d6bd46ee57 100644
--- a/tests/http/testenv/env.py
+++ b/tests/http/testenv/env.py
@@ -129,10 +129,11 @@ def __init__(self):
self.htdocs_dir = os.path.join(self.gen_dir, 'htdocs')
self.tld = 'http.curl.se'
self.domain1 = f"one.{self.tld}"
+ self.domain1brotli = f"brotli.one.{self.tld}"
self.domain2 = f"two.{self.tld}"
self.proxy_domain = f"proxy.{self.tld}"
self.cert_specs = [
- CertificateSpec(domains=[self.domain1, 'localhost'], key_type='rsa2048'),
+ CertificateSpec(domains=[self.domain1, self.domain1brotli, 'localhost'], key_type='rsa2048'),
CertificateSpec(domains=[self.domain2], key_type='rsa2048'),
CertificateSpec(domains=[self.proxy_domain, '127.0.0.1'], key_type='rsa2048'),
CertificateSpec(name="clientsX", sub_specs=[
@@ -376,6 +377,10 @@ def htdocs_dir(self) -> str:
def domain1(self) -> str:
return self.CONFIG.domain1

+ @property
+ def domain1brotli(self) -> str:
+ return self.CONFIG.domain1brotli
+
@property
def domain2(self) -> str:
return self.CONFIG.domain2
diff --git a/tests/http/testenv/httpd.py b/tests/http/testenv/httpd.py
index c04c22699a62c4..b8615875a9a558 100644
--- a/tests/http/testenv/httpd.py
+++ b/tests/http/testenv/httpd.py
@@ -50,6 +50,7 @@ class Httpd:
'alias', 'env', 'filter', 'headers', 'mime', 'setenvif',
'socache_shmcb',
'rewrite', 'http2', 'ssl', 'proxy', 'proxy_http', 'proxy_connect',
+ 'brotli',
'mpm_event',
]
COMMON_MODULES_DIRS = [
@@ -203,6 +204,7 @@ def _mkpath(self, path):

def _write_config(self):
domain1 = self.env.domain1
+ domain1brotli = self.env.domain1brotli
creds1 = self.env.get_credentials(domain1)
domain2 = self.env.domain2
creds2 = self.env.get_credentials(domain2)
@@ -285,6 +287,24 @@ def _write_config(self):
f'</VirtualHost>',
f'',
])
+ # Alternate to domain1 with BROTLI compression
+ conf.extend([ # https host for domain1, h1 + h2
+ f'<VirtualHost *:{self.env.https_port}>',
+ f' ServerName {domain1brotli}',
+ f' Protocols h2 http/1.1',
+ f' SSLEngine on',
+ f' SSLCertificateFile {creds1.cert_file}',
+ f' SSLCertificateKeyFile {creds1.pkey_file}',
+ f' DocumentRoot "{self._docs_dir}"',
+ f' SetOutputFilter BROTLI_COMPRESS',
+ ])
+ conf.extend(self._curltest_conf(domain1))
+ if domain1 in self._extra_configs:
+ conf.extend(self._extra_configs[domain1])
+ conf.extend([
+ f'</VirtualHost>',
+ f'',
+ ])
conf.extend([ # https host for domain2, no h2
f'<VirtualHost *:{self.env.https_port}>',
f' ServerName {domain2}',
5 changes: 3 additions & 2 deletions packages/c/curl/package.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name : curl
version : 8.7.1
release : 87
release : 88
source :
- https://github.com/curl/curl/releases/download/curl-8_7_1/curl-8.7.1.tar.gz#curl.tar.gz : f91249c87f68ea00cf27c44fdfa5a78423e41e71b7d408e5901a9896d905c495
extract : no
Expand Down Expand Up @@ -53,7 +53,7 @@ setup : |
--with-ca-path=/etc/ssl/certs"
pushd main
# cp $sources/errorcodes.pl tests/ && chmod +x tests/errorcodes.pl
patch -p1 -i $pkgfiles/fix-brotli.patch
%configure $common \
--without-gnutls \
--without-libpsl \
Expand All @@ -66,6 +66,7 @@ setup : |
popd
pushd gnutls
patch -p1 -i $pkgfiles/fix-brotli.patch
%configure $common \
--disable-libcurl-option \
--with-gnutls \
Expand Down
10 changes: 5 additions & 5 deletions packages/c/curl/pspec_x86_64.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<Description xml:lang="en">curl is a client to get files from servers using any of the supported protocols. The command is designed to work without user interaction or any kind of interactivity. curl offers a busload of useful tricks like proxy support, user authentication, ftp upload, HTTP post, file transfer resume and more.</Description>
<PartOf>emul32</PartOf>
<RuntimeDependencies>
<Dependency release="87">curl</Dependency>
<Dependency release="88">curl</Dependency>
</RuntimeDependencies>
<Files>
<Path fileType="library">/usr/lib32/libcurl.so.4</Path>
Expand All @@ -47,8 +47,8 @@
<Description xml:lang="en">curl is a client to get files from servers using any of the supported protocols. The command is designed to work without user interaction or any kind of interactivity. curl offers a busload of useful tricks like proxy support, user authentication, ftp upload, HTTP post, file transfer resume and more.</Description>
<PartOf>programming.devel</PartOf>
<RuntimeDependencies>
<Dependency release="87">curl-32bit</Dependency>
<Dependency release="87">curl-devel</Dependency>
<Dependency release="88">curl-32bit</Dependency>
<Dependency release="88">curl-devel</Dependency>
</RuntimeDependencies>
<Files>
<Path fileType="library">/usr/lib32/libcurl.so</Path>
Expand All @@ -61,7 +61,7 @@
<Description xml:lang="en">curl is a client to get files from servers using any of the supported protocols. The command is designed to work without user interaction or any kind of interactivity. curl offers a busload of useful tricks like proxy support, user authentication, ftp upload, HTTP post, file transfer resume and more.</Description>
<PartOf>programming.devel</PartOf>
<RuntimeDependencies>
<Dependency release="87">curl</Dependency>
<Dependency release="88">curl</Dependency>
</RuntimeDependencies>
<Files>
<Path fileType="header">/usr/include/curl/curl.h</Path>
Expand Down Expand Up @@ -598,7 +598,7 @@
</Files>
</Package>
<History>
<Update release="87">
<Update release="88">
<Date>2024-03-28</Date>
<Version>8.7.1</Version>
<Comment>Packaging update</Comment>
Expand Down

0 comments on commit 5b6b873

Please sign in to comment.