if(!stream->send_closed) {
stream->send_closed = TRUE;
if(stream->upload_left) {
- /* If we operated with unknown length, we now know that everything
- * that is buffered is all we have to send. */
- if(stream->upload_left == -1)
- stream->upload_left = Curl_bufq_len(&stream->sendbuf);
+ /* we now know that everything that is buffered is all there is. */
+ stream->upload_left = Curl_bufq_len(&stream->sendbuf);
/* resume sending here to trigger the callback to get called again so
that it can signal EOF to nghttp2 */
(void)nghttp2_session_resume_data(ctx->h2, stream->id);
assert r.exit_code == 0, r.dump_logs()
r.check_stats(1, 200)
+ def test_07_34_issue_11194(self, env: Env, httpd, nghttpx, repeat):
+ proto = 'h2'
+ fdata = os.path.join(env.gen_dir, 'data-10m')
+ # tell our test PUT handler to read the upload more slowly, so
+ # that the send buffering and transfer loop needs to wait
+ fdata = os.path.join(env.gen_dir, 'data-100k')
+ url = f'https://{env.authority_for(env.domain1, proto)}/curltest/put'
+ curl = CurlClient(env=env)
+ r = curl.run_direct(with_stats=True, args=[
+ '--verbose',
+ '--resolve', f'{env.authority_for(env.domain1, proto)}:127.0.0.1',
+ '--cacert', env.ca.cert_file,
+ '--request', 'PUT',
+ '--digest', '--user', 'test:test',
+ '--data-binary', f'@{fdata}'
+ '--url', url,
+ ])
+ assert r.exit_code == 0, r.dump_logs()
+ r.check_stats(1, 200)
+
def check_download(self, count, srcfile, curl):
for i in range(count):
dfile = curl.download_file(i)
--- /dev/null
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#***************************************************************************
+# _ _ ____ _
+# 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
+#
+###########################################################################
+#
+import difflib
+import filecmp
+import logging
+import os
+import pytest
+
+from testenv import Env, CurlClient, LocalClient
+
+
+log = logging.getLogger(__name__)
+
+
+class TestAuth:
+
+ @pytest.fixture(autouse=True, scope='class')
+ def _class_scope(self, env, httpd, nghttpx):
+ if env.have_h3():
+ nghttpx.start_if_needed()
+ httpd.clear_extra_configs()
+ httpd.reload()
+
+ # download 1 file, not authenticated
+ @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
+ def test_14_01_digest_get_noauth(self, env: Env, httpd, nghttpx, repeat, proto):
+ if proto == 'h3' and not env.have_h3():
+ pytest.skip("h3 not supported")
+ curl = CurlClient(env=env)
+ url = f'https://{env.authority_for(env.domain1, proto)}/restricted/digest/data.json'
+ r = curl.http_download(urls=[url], alpn_proto=proto)
+ r.check_response(http_status=401)
+
+ # download 1 file, authenticated
+ @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
+ def test_14_02_digest_get_auth(self, env: Env, httpd, nghttpx, repeat, proto):
+ if proto == 'h3' and not env.have_h3():
+ pytest.skip("h3 not supported")
+ curl = CurlClient(env=env)
+ url = f'https://{env.authority_for(env.domain1, proto)}/restricted/digest/data.json'
+ r = curl.http_download(urls=[url], alpn_proto=proto, extra_args=[
+ '--digest', '--user', 'test:test'
+ ])
+ r.check_response(http_status=200)
+
+ # PUT data, authenticated
+ @pytest.mark.parametrize("proto", ['http/1.1', 'h2', 'h3'])
+ def test_14_03_digest_put_auth(self, env: Env, httpd, nghttpx, repeat, proto):
+ if proto == 'h3' and not env.have_h3():
+ pytest.skip("h3 not supported")
+ data='0123456789'
+ curl = CurlClient(env=env)
+ url = f'https://{env.authority_for(env.domain1, proto)}/restricted/digest/data.json'
+ r = curl.http_upload(urls=[url], data=data, alpn_proto=proto, extra_args=[
+ '--digest', '--user', 'test:test'
+ ])
+ r.check_response(http_status=200)
self._logs_dir = os.path.join(self._apache_dir, 'logs')
self._error_log = os.path.join(self._logs_dir, 'error_log')
self._tmp_dir = os.path.join(self._apache_dir, 'tmp')
- self._passwords = os.path.join(self._conf_dir, 'passwords')
+ self._basic_passwords = os.path.join(self._conf_dir, 'basic.passwords')
+ self._digest_passwords = os.path.join(self._conf_dir, 'digest.passwords')
self._mods_dir = None
- self._proxy_auth = proxy_auth
+ self._auth_digest = True
+ self._proxy_auth_basic = proxy_auth
self._extra_configs = {}
assert env.apxs
p = subprocess.run(args=[env.apxs, '-q', 'libexecdir'],
self._extra_configs = {}
def set_proxy_auth(self, active: bool):
- self._proxy_auth = active
+ self._proxy_auth_basic = active
def _run(self, args, intext=''):
env = {}
'server': f'{domain2}',
}
fd.write(JSONEncoder().encode(data))
- if self._proxy_auth:
- with open(self._passwords, 'w') as fd:
+ if self._proxy_auth_basic:
+ with open(self._basic_passwords, 'w') as fd:
fd.write('proxy:$apr1$FQfeInbs$WQZbODJlVg60j0ogEIlTW/\n')
+ if self._auth_digest:
+ with open(self._digest_passwords, 'w') as fd:
+ fd.write('test:restricted area:57123e269fd73d71ae0656594e938e2f\n')
+ self._mkpath(os.path.join(self.docs_dir, 'restricted/digest'))
+ with open(os.path.join(self.docs_dir, 'restricted/digest/data.json'), 'w') as fd:
+ fd.write('{"area":"digest"}\n')
with open(self._conf_file, 'w') as fd:
for m in self.MODULES:
if os.path.exists(os.path.join(self._mods_dir, f'mod_{m}.so')):
f' DocumentRoot "{self._docs_dir}"',
f' Protocols h2c http/1.1',
])
- conf.extend(self._curltest_conf())
+ conf.extend(self._curltest_conf(domain1))
conf.extend([
f'</VirtualHost>',
f'',
f' SSLCertificateKeyFile {creds1.pkey_file}',
f' DocumentRoot "{self._docs_dir}"',
])
- conf.extend(self._curltest_conf())
+ conf.extend(self._curltest_conf(domain1))
if domain1 in self._extra_configs:
conf.extend(self._extra_configs[domain1])
conf.extend([
f' SSLCertificateKeyFile {creds2.pkey_file}',
f' DocumentRoot "{self._docs_dir}/two"',
])
- conf.extend(self._curltest_conf())
+ conf.extend(self._curltest_conf(domain2))
if domain2 in self._extra_configs:
conf.extend(self._extra_configs[domain2])
conf.extend([
]))
def _get_proxy_conf(self):
- if self._proxy_auth:
+ if self._proxy_auth_basic:
return [
f' <Proxy "*">',
f' AuthType Basic',
f' AuthName "Restricted Proxy"',
f' AuthBasicProvider file',
- f' AuthUserFile "{self._passwords}"',
+ f' AuthUserFile "{self._basic_passwords}"',
f' Require user proxy',
f' </Proxy>',
]
return 'debug'
return 'info'
- def _curltest_conf(self) -> List[str]:
+ def _curltest_conf(self, servername) -> List[str]:
+ lines = []
if Httpd.MOD_CURLTEST is not None:
- return [
+ lines.extend([
f' <Location /curltest/echo>',
f' SetHandler curltest-echo',
f' </Location>',
f' <Location /curltest/tweak>',
f' SetHandler curltest-tweak',
f' </Location>',
- ]
- return []
+ ])
+ if self._auth_digest:
+ lines.extend([
+ f' <Directory {self.docs_dir}/restricted/digest>',
+ f' AuthType Digest',
+ f' AuthName "restricted area"',
+ f' AuthDigestDomain "https://{servername}"',
+ f' AuthBasicProvider file',
+ f' AuthUserFile "{self._digest_passwords}"',
+ f' Require valid-user',
+ f' </Directory>',
+
+ ])
+ return lines
def _init_curltest(self):
if Httpd.MOD_CURLTEST is not None: