Backport fix for CVE-2023-23934
Fix for leading = in cookies from upstream patch on 2.2.3:
cf275f42ac
Resolves: rhbz#2170317
This commit is contained in:
parent
f9244201cb
commit
6e6cf25aba
@ -1,7 +1,7 @@
|
||||
From 722f58f8221c013e2dd6cf1fde59fd686619f483 Mon Sep 17 00:00:00 2001
|
||||
From: "Brian C. Lane" <bcl@redhat.com>
|
||||
Date: Tue, 18 Apr 2023 15:57:50 -0700
|
||||
Subject: [PATCH] Backport limits for multiple form parts
|
||||
Subject: [PATCH 1/2] Backport limits for multiple form parts
|
||||
|
||||
This fixes CVE-2023-25577, and is backported from the fix for 2.2.3
|
||||
here:
|
||||
|
74
0002-Backport-fix-for-cookie-prefixed-with.patch
Normal file
74
0002-Backport-fix-for-cookie-prefixed-with.patch
Normal file
@ -0,0 +1,74 @@
|
||||
From ab00d73bdc48b7a2d06a44b989b4f161310768a6 Mon Sep 17 00:00:00 2001
|
||||
From: "Brian C. Lane" <bcl@redhat.com>
|
||||
Date: Tue, 18 Apr 2023 16:44:22 -0700
|
||||
Subject: [PATCH 2/2] Backport fix for cookie prefixed with =
|
||||
|
||||
This fixes CVE-2023-23934 by backporting the fix from upstream:
|
||||
https://github.com/pallets/werkzeug/commit/cf275f42acad1b5950c50ffe8ef58fe62cdce028
|
||||
|
||||
Resolves: rhbz#2170317
|
||||
---
|
||||
tests/test_http.py | 6 ++++--
|
||||
werkzeug/_internal.py | 11 +++++++----
|
||||
2 files changed, 11 insertions(+), 6 deletions(-)
|
||||
|
||||
diff --git a/tests/test_http.py b/tests/test_http.py
|
||||
index b77e3c38..c1582fd6 100644
|
||||
--- a/tests/test_http.py
|
||||
+++ b/tests/test_http.py
|
||||
@@ -354,13 +354,15 @@ class TestHTTPUtility(object):
|
||||
def test_cookies(self):
|
||||
strict_eq(
|
||||
dict(http.parse_cookie('dismiss-top=6; CP=null*; PHPSESSID=0a539d42abc001cd'
|
||||
- 'c762809248d4beed; a=42; b="\\\";"')),
|
||||
+ 'c762809248d4beed; a=42; b="\\\";";'
|
||||
+ '==__Host-eq=bad;__Host-eq=good;')),
|
||||
{
|
||||
'CP': u'null*',
|
||||
'PHPSESSID': u'0a539d42abc001cdc762809248d4beed',
|
||||
'a': u'42',
|
||||
'dismiss-top': u'6',
|
||||
- 'b': u'\";'
|
||||
+ 'b': u'\";',
|
||||
+ '__Host-eq': u'good',
|
||||
}
|
||||
)
|
||||
rv = http.dump_cookie('foo', 'bar baz blub', 360, httponly=True,
|
||||
diff --git a/werkzeug/_internal.py b/werkzeug/_internal.py
|
||||
index 3d1ee090..0bf9fb2a 100644
|
||||
--- a/werkzeug/_internal.py
|
||||
+++ b/werkzeug/_internal.py
|
||||
@@ -44,7 +44,7 @@ _octal_re = re.compile(b'\\\\[0-3][0-7][0-7]')
|
||||
_quote_re = re.compile(b'[\\\\].')
|
||||
_legal_cookie_chars_re = b'[\w\d!#%&\'~_`><@,:/\$\*\+\-\.\^\|\)\(\?\}\{\=]'
|
||||
_cookie_re = re.compile(b"""
|
||||
- (?P<key>[^=]+)
|
||||
+ (?P<key>[^=]*)
|
||||
\s*=\s*
|
||||
(?P<val>
|
||||
"(?:[^\\\\"]|\\\\.)*" |
|
||||
@@ -276,15 +276,18 @@ def _cookie_parse_impl(b):
|
||||
"""Lowlevel cookie parsing facility that operates on bytes."""
|
||||
i = 0
|
||||
n = len(b)
|
||||
+ b += b";"
|
||||
|
||||
while i < n:
|
||||
- match = _cookie_re.search(b + b';', i)
|
||||
+ match = _cookie_re.match(b, i)
|
||||
if not match:
|
||||
break
|
||||
|
||||
- key = match.group('key').strip()
|
||||
- value = match.group('val')
|
||||
i = match.end(0)
|
||||
+ key = match.group('key').strip()
|
||||
+ if not key:
|
||||
+ continue
|
||||
+ value = match.group('val') or b""
|
||||
|
||||
# Ignore parameters. We have no interest in them.
|
||||
if key.lower() not in _cookie_params:
|
||||
--
|
||||
2.40.0
|
||||
|
@ -21,6 +21,7 @@ Source0: https://files.pythonhosted.org/packages/source/W/Werkzeug/%{srcn
|
||||
Source1: werkzeug-sphinx-theme.tar.gz
|
||||
|
||||
Patch0001: 0001-Backport-limits-for-multiple-form-parts.patch
|
||||
Patch0002: 0002-Backport-fix-for-cookie-prefixed-with.patch
|
||||
|
||||
BuildArch: noarch
|
||||
|
||||
@ -145,6 +146,8 @@ popd
|
||||
- Add new test for formdata
|
||||
- Backport fix for CVE-2023-25577
|
||||
Resolves: rhbz#2188442
|
||||
- Backport fix for CVE-2023-23934
|
||||
Resolves: rhbz#2170317
|
||||
|
||||
* Fri Jun 22 2018 Charalampos Stratakis <cstratak@redhat.com> - 0.12.2-4
|
||||
- Use python3-sphinx for the docs
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/bash
|
||||
set -eux
|
||||
|
||||
pytest-3 ./test_wsgi.py ./test_formparser.py
|
||||
pytest-3 ./test_wsgi.py ./test_formparser.py ./test_http.py
|
||||
|
545
tests/scripts/test_http.py
Normal file
545
tests/scripts/test_http.py
Normal file
@ -0,0 +1,545 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
tests.http
|
||||
~~~~~~~~~~
|
||||
|
||||
HTTP parsing utilities.
|
||||
|
||||
:copyright: (c) 2014 by Armin Ronacher.
|
||||
:license: BSD, see LICENSE for more details.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
#from tests import strict_eq
|
||||
from werkzeug._compat import itervalues, wsgi_encoding_dance
|
||||
|
||||
from werkzeug import http, datastructures
|
||||
from werkzeug.test import create_environ
|
||||
|
||||
|
||||
def strict_eq(x, y):
|
||||
'''Equality test bypassing the implicit string conversion in Python 2'''
|
||||
__tracebackhide__ = True
|
||||
assert x == y
|
||||
assert issubclass(type(x), type(y)) or issubclass(type(y), type(x))
|
||||
if isinstance(x, dict) and isinstance(y, dict):
|
||||
x = sorted(x.items())
|
||||
y = sorted(y.items())
|
||||
elif isinstance(x, set) and isinstance(y, set):
|
||||
x = sorted(x)
|
||||
y = sorted(y)
|
||||
assert repr(x) == repr(y)
|
||||
|
||||
|
||||
class TestHTTPUtility(object):
|
||||
|
||||
def test_accept(self):
|
||||
a = http.parse_accept_header('en-us,ru;q=0.5')
|
||||
assert list(itervalues(a)) == ['en-us', 'ru']
|
||||
assert a.best == 'en-us'
|
||||
assert a.find('ru') == 1
|
||||
pytest.raises(ValueError, a.index, 'de')
|
||||
assert a.to_header() == 'en-us,ru;q=0.5'
|
||||
|
||||
def test_mime_accept(self):
|
||||
a = http.parse_accept_header('text/xml,application/xml,'
|
||||
'application/xhtml+xml,'
|
||||
'application/foo;quiet=no; bar=baz;q=0.6,'
|
||||
'text/html;q=0.9,text/plain;q=0.8,'
|
||||
'image/png,*/*;q=0.5',
|
||||
datastructures.MIMEAccept)
|
||||
pytest.raises(ValueError, lambda: a['missing'])
|
||||
assert a['image/png'] == 1
|
||||
assert a['text/plain'] == 0.8
|
||||
assert a['foo/bar'] == 0.5
|
||||
assert a['application/foo;quiet=no; bar=baz'] == 0.6
|
||||
assert a[a.find('foo/bar')] == ('*/*', 0.5)
|
||||
|
||||
def test_accept_matches(self):
|
||||
a = http.parse_accept_header('text/xml,application/xml,application/xhtml+xml,'
|
||||
'text/html;q=0.9,text/plain;q=0.8,'
|
||||
'image/png', datastructures.MIMEAccept)
|
||||
assert a.best_match(['text/html', 'application/xhtml+xml']) == \
|
||||
'application/xhtml+xml'
|
||||
assert a.best_match(['text/html']) == 'text/html'
|
||||
assert a.best_match(['foo/bar']) is None
|
||||
assert a.best_match(['foo/bar', 'bar/foo'], default='foo/bar') == 'foo/bar'
|
||||
assert a.best_match(['application/xml', 'text/xml']) == 'application/xml'
|
||||
|
||||
def test_charset_accept(self):
|
||||
a = http.parse_accept_header('ISO-8859-1,utf-8;q=0.7,*;q=0.7',
|
||||
datastructures.CharsetAccept)
|
||||
assert a['iso-8859-1'] == a['iso8859-1']
|
||||
assert a['iso-8859-1'] == 1
|
||||
assert a['UTF8'] == 0.7
|
||||
assert a['ebcdic'] == 0.7
|
||||
|
||||
def test_language_accept(self):
|
||||
a = http.parse_accept_header('de-AT,de;q=0.8,en;q=0.5',
|
||||
datastructures.LanguageAccept)
|
||||
assert a.best == 'de-AT'
|
||||
assert 'de_AT' in a
|
||||
assert 'en' in a
|
||||
assert a['de-at'] == 1
|
||||
assert a['en'] == 0.5
|
||||
|
||||
def test_set_header(self):
|
||||
hs = http.parse_set_header('foo, Bar, "Blah baz", Hehe')
|
||||
assert 'blah baz' in hs
|
||||
assert 'foobar' not in hs
|
||||
assert 'foo' in hs
|
||||
assert list(hs) == ['foo', 'Bar', 'Blah baz', 'Hehe']
|
||||
hs.add('Foo')
|
||||
assert hs.to_header() == 'foo, Bar, "Blah baz", Hehe'
|
||||
|
||||
def test_list_header(self):
|
||||
hl = http.parse_list_header('foo baz, blah')
|
||||
assert hl == ['foo baz', 'blah']
|
||||
|
||||
def test_dict_header(self):
|
||||
d = http.parse_dict_header('foo="bar baz", blah=42')
|
||||
assert d == {'foo': 'bar baz', 'blah': '42'}
|
||||
|
||||
def test_cache_control_header(self):
|
||||
cc = http.parse_cache_control_header('max-age=0, no-cache')
|
||||
assert cc.max_age == 0
|
||||
assert cc.no_cache
|
||||
cc = http.parse_cache_control_header('private, community="UCI"', None,
|
||||
datastructures.ResponseCacheControl)
|
||||
assert cc.private
|
||||
assert cc['community'] == 'UCI'
|
||||
|
||||
c = datastructures.ResponseCacheControl()
|
||||
assert c.no_cache is None
|
||||
assert c.private is None
|
||||
c.no_cache = True
|
||||
assert c.no_cache == '*'
|
||||
c.private = True
|
||||
assert c.private == '*'
|
||||
del c.private
|
||||
assert c.private is None
|
||||
assert c.to_header() == 'no-cache'
|
||||
|
||||
def test_authorization_header(self):
|
||||
a = http.parse_authorization_header('Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==')
|
||||
assert a.type == 'basic'
|
||||
assert a.username == 'Aladdin'
|
||||
assert a.password == 'open sesame'
|
||||
|
||||
a = http.parse_authorization_header('''Digest username="Mufasa",
|
||||
realm="testrealm@host.invalid",
|
||||
nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
|
||||
uri="/dir/index.html",
|
||||
qop=auth,
|
||||
nc=00000001,
|
||||
cnonce="0a4f113b",
|
||||
response="6629fae49393a05397450978507c4ef1",
|
||||
opaque="5ccc069c403ebaf9f0171e9517f40e41"''')
|
||||
assert a.type == 'digest'
|
||||
assert a.username == 'Mufasa'
|
||||
assert a.realm == 'testrealm@host.invalid'
|
||||
assert a.nonce == 'dcd98b7102dd2f0e8b11d0f600bfb0c093'
|
||||
assert a.uri == '/dir/index.html'
|
||||
assert 'auth' in a.qop
|
||||
assert a.nc == '00000001'
|
||||
assert a.cnonce == '0a4f113b'
|
||||
assert a.response == '6629fae49393a05397450978507c4ef1'
|
||||
assert a.opaque == '5ccc069c403ebaf9f0171e9517f40e41'
|
||||
|
||||
a = http.parse_authorization_header('''Digest username="Mufasa",
|
||||
realm="testrealm@host.invalid",
|
||||
nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
|
||||
uri="/dir/index.html",
|
||||
response="e257afa1414a3340d93d30955171dd0e",
|
||||
opaque="5ccc069c403ebaf9f0171e9517f40e41"''')
|
||||
assert a.type == 'digest'
|
||||
assert a.username == 'Mufasa'
|
||||
assert a.realm == 'testrealm@host.invalid'
|
||||
assert a.nonce == 'dcd98b7102dd2f0e8b11d0f600bfb0c093'
|
||||
assert a.uri == '/dir/index.html'
|
||||
assert a.response == 'e257afa1414a3340d93d30955171dd0e'
|
||||
assert a.opaque == '5ccc069c403ebaf9f0171e9517f40e41'
|
||||
|
||||
assert http.parse_authorization_header('') is None
|
||||
assert http.parse_authorization_header(None) is None
|
||||
assert http.parse_authorization_header('foo') is None
|
||||
|
||||
def test_www_authenticate_header(self):
|
||||
wa = http.parse_www_authenticate_header('Basic realm="WallyWorld"')
|
||||
assert wa.type == 'basic'
|
||||
assert wa.realm == 'WallyWorld'
|
||||
wa.realm = 'Foo Bar'
|
||||
assert wa.to_header() == 'Basic realm="Foo Bar"'
|
||||
|
||||
wa = http.parse_www_authenticate_header('''Digest
|
||||
realm="testrealm@host.com",
|
||||
qop="auth,auth-int",
|
||||
nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
|
||||
opaque="5ccc069c403ebaf9f0171e9517f40e41"''')
|
||||
assert wa.type == 'digest'
|
||||
assert wa.realm == 'testrealm@host.com'
|
||||
assert 'auth' in wa.qop
|
||||
assert 'auth-int' in wa.qop
|
||||
assert wa.nonce == 'dcd98b7102dd2f0e8b11d0f600bfb0c093'
|
||||
assert wa.opaque == '5ccc069c403ebaf9f0171e9517f40e41'
|
||||
|
||||
wa = http.parse_www_authenticate_header('broken')
|
||||
assert wa.type == 'broken'
|
||||
|
||||
assert not http.parse_www_authenticate_header('').type
|
||||
assert not http.parse_www_authenticate_header('')
|
||||
|
||||
def test_etags(self):
|
||||
assert http.quote_etag('foo') == '"foo"'
|
||||
assert http.quote_etag('foo', True) == 'W/"foo"'
|
||||
assert http.unquote_etag('"foo"') == ('foo', False)
|
||||
assert http.unquote_etag('W/"foo"') == ('foo', True)
|
||||
es = http.parse_etags('"foo", "bar", W/"baz", blar')
|
||||
assert sorted(es) == ['bar', 'blar', 'foo']
|
||||
assert 'foo' in es
|
||||
assert 'baz' not in es
|
||||
assert es.contains_weak('baz')
|
||||
assert 'blar' in es
|
||||
assert es.contains_raw('W/"baz"')
|
||||
assert es.contains_raw('"foo"')
|
||||
assert sorted(es.to_header().split(', ')) == ['"bar"', '"blar"', '"foo"', 'W/"baz"']
|
||||
|
||||
def test_etags_nonzero(self):
|
||||
etags = http.parse_etags('W/"foo"')
|
||||
assert bool(etags)
|
||||
assert etags.contains_raw('W/"foo"')
|
||||
|
||||
def test_parse_date(self):
|
||||
assert http.parse_date('Sun, 06 Nov 1994 08:49:37 GMT ') == datetime(
|
||||
1994, 11, 6, 8, 49, 37)
|
||||
assert http.parse_date('Sunday, 06-Nov-94 08:49:37 GMT') == datetime(1994, 11, 6, 8, 49, 37)
|
||||
assert http.parse_date(' Sun Nov 6 08:49:37 1994') == datetime(1994, 11, 6, 8, 49, 37)
|
||||
assert http.parse_date('foo') is None
|
||||
|
||||
def test_parse_date_overflows(self):
|
||||
assert http.parse_date(' Sun 02 Feb 1343 08:49:37 GMT') == datetime(1343, 2, 2, 8, 49, 37)
|
||||
assert http.parse_date('Thu, 01 Jan 1970 00:00:00 GMT') == datetime(1970, 1, 1, 0, 0)
|
||||
assert http.parse_date('Thu, 33 Jan 1970 00:00:00 GMT') is None
|
||||
|
||||
def test_remove_entity_headers(self):
|
||||
now = http.http_date()
|
||||
headers1 = [('Date', now), ('Content-Type', 'text/html'), ('Content-Length', '0')]
|
||||
headers2 = datastructures.Headers(headers1)
|
||||
|
||||
http.remove_entity_headers(headers1)
|
||||
assert headers1 == [('Date', now)]
|
||||
|
||||
http.remove_entity_headers(headers2)
|
||||
assert headers2 == datastructures.Headers([(u'Date', now)])
|
||||
|
||||
def test_remove_hop_by_hop_headers(self):
|
||||
headers1 = [('Connection', 'closed'), ('Foo', 'bar'),
|
||||
('Keep-Alive', 'wtf')]
|
||||
headers2 = datastructures.Headers(headers1)
|
||||
|
||||
http.remove_hop_by_hop_headers(headers1)
|
||||
assert headers1 == [('Foo', 'bar')]
|
||||
|
||||
http.remove_hop_by_hop_headers(headers2)
|
||||
assert headers2 == datastructures.Headers([('Foo', 'bar')])
|
||||
|
||||
def test_parse_options_header(self):
|
||||
assert http.parse_options_header(None) == \
|
||||
('', {})
|
||||
assert http.parse_options_header("") == \
|
||||
('', {})
|
||||
assert http.parse_options_header(r'something; foo="other\"thing"') == \
|
||||
('something', {'foo': 'other"thing'})
|
||||
assert http.parse_options_header(r'something; foo="other\"thing"; meh=42') == \
|
||||
('something', {'foo': 'other"thing', 'meh': '42'})
|
||||
assert http.parse_options_header(r'something; foo="other\"thing"; meh=42; bleh') == \
|
||||
('something', {'foo': 'other"thing', 'meh': '42', 'bleh': None})
|
||||
assert http.parse_options_header('something; foo="other;thing"; meh=42; bleh') == \
|
||||
('something', {'foo': 'other;thing', 'meh': '42', 'bleh': None})
|
||||
assert http.parse_options_header('something; foo="otherthing"; meh=; bleh') == \
|
||||
('something', {'foo': 'otherthing', 'meh': None, 'bleh': None})
|
||||
# Issue #404
|
||||
assert http.parse_options_header('multipart/form-data; name="foo bar"; '
|
||||
'filename="bar foo"') == \
|
||||
('multipart/form-data', {'name': 'foo bar', 'filename': 'bar foo'})
|
||||
# Examples from RFC
|
||||
assert http.parse_options_header('audio/*; q=0.2, audio/basic') == \
|
||||
('audio/*', {'q': '0.2'})
|
||||
assert http.parse_options_header('audio/*; q=0.2, audio/basic', multiple=True) == \
|
||||
('audio/*', {'q': '0.2'}, "audio/basic", {})
|
||||
assert http.parse_options_header(
|
||||
'text/plain; q=0.5, text/html\n '
|
||||
'text/x-dvi; q=0.8, text/x-c',
|
||||
multiple=True) == \
|
||||
('text/plain', {'q': '0.5'}, "text/html", {},
|
||||
"text/x-dvi", {'q': '0.8'}, "text/x-c", {})
|
||||
assert http.parse_options_header('text/plain; q=0.5, text/html\n'
|
||||
' '
|
||||
'text/x-dvi; q=0.8, text/x-c') == \
|
||||
('text/plain', {'q': '0.5'})
|
||||
# Issue #932
|
||||
assert http.parse_options_header(
|
||||
'form-data; '
|
||||
'name="a_file"; '
|
||||
'filename*=UTF-8\'\''
|
||||
'"%c2%a3%20and%20%e2%82%ac%20rates"') == \
|
||||
('form-data', {'name': 'a_file',
|
||||
'filename': u'\xa3 and \u20ac rates'})
|
||||
assert http.parse_options_header(
|
||||
'form-data; '
|
||||
'name*=UTF-8\'\'"%C5%AAn%C4%ADc%C5%8Dde%CC%BD"; '
|
||||
'filename="some_file.txt"') == \
|
||||
('form-data', {'name': u'\u016an\u012dc\u014dde\u033d',
|
||||
'filename': 'some_file.txt'})
|
||||
|
||||
def test_parse_options_header_broken_values(self):
|
||||
# Issue #995
|
||||
assert http.parse_options_header(' ') == ('', {})
|
||||
assert http.parse_options_header(' , ') == ('', {})
|
||||
assert http.parse_options_header(' ; ') == ('', {})
|
||||
assert http.parse_options_header(' ,; ') == ('', {})
|
||||
assert http.parse_options_header(' , a ') == ('', {})
|
||||
assert http.parse_options_header(' ; a ') == ('', {})
|
||||
|
||||
def test_dump_options_header(self):
|
||||
assert http.dump_options_header('foo', {'bar': 42}) == \
|
||||
'foo; bar=42'
|
||||
assert http.dump_options_header('foo', {'bar': 42, 'fizz': None}) in \
|
||||
('foo; bar=42; fizz', 'foo; fizz; bar=42')
|
||||
|
||||
def test_dump_header(self):
|
||||
assert http.dump_header([1, 2, 3]) == '1, 2, 3'
|
||||
assert http.dump_header([1, 2, 3], allow_token=False) == '"1", "2", "3"'
|
||||
assert http.dump_header({'foo': 'bar'}, allow_token=False) == 'foo="bar"'
|
||||
assert http.dump_header({'foo': 'bar'}) == 'foo=bar'
|
||||
|
||||
def test_is_resource_modified(self):
|
||||
env = create_environ()
|
||||
|
||||
# ignore POST
|
||||
env['REQUEST_METHOD'] = 'POST'
|
||||
assert not http.is_resource_modified(env, etag='testing')
|
||||
env['REQUEST_METHOD'] = 'GET'
|
||||
|
||||
# etagify from data
|
||||
pytest.raises(TypeError, http.is_resource_modified, env,
|
||||
data='42', etag='23')
|
||||
env['HTTP_IF_NONE_MATCH'] = http.generate_etag(b'awesome')
|
||||
assert not http.is_resource_modified(env, data=b'awesome')
|
||||
|
||||
env['HTTP_IF_MODIFIED_SINCE'] = http.http_date(datetime(2008, 1, 1, 12, 30))
|
||||
assert not http.is_resource_modified(env,
|
||||
last_modified=datetime(2008, 1, 1, 12, 00))
|
||||
assert http.is_resource_modified(env,
|
||||
last_modified=datetime(2008, 1, 1, 13, 00))
|
||||
|
||||
def test_is_resource_modified_for_range_requests(self):
|
||||
env = create_environ()
|
||||
|
||||
env['HTTP_IF_MODIFIED_SINCE'] = http.http_date(datetime(2008, 1, 1, 12, 30))
|
||||
env['HTTP_IF_RANGE'] = http.generate_etag(b'awesome_if_range')
|
||||
# Range header not present, so If-Range should be ignored
|
||||
assert not http.is_resource_modified(env, data=b'not_the_same',
|
||||
ignore_if_range=False,
|
||||
last_modified=datetime(2008, 1, 1, 12, 30))
|
||||
|
||||
env['HTTP_RANGE'] = ''
|
||||
assert not http.is_resource_modified(env, data=b'awesome_if_range',
|
||||
ignore_if_range=False)
|
||||
assert http.is_resource_modified(env, data=b'not_the_same',
|
||||
ignore_if_range=False)
|
||||
|
||||
env['HTTP_IF_RANGE'] = http.http_date(datetime(2008, 1, 1, 13, 30))
|
||||
assert http.is_resource_modified(env, last_modified=datetime(2008, 1, 1, 14, 00),
|
||||
ignore_if_range=False)
|
||||
assert not http.is_resource_modified(env, last_modified=datetime(2008, 1, 1, 13, 30),
|
||||
ignore_if_range=False)
|
||||
assert http.is_resource_modified(env, last_modified=datetime(2008, 1, 1, 13, 30),
|
||||
ignore_if_range=True)
|
||||
|
||||
def test_date_formatting(self):
|
||||
assert http.cookie_date(0) == 'Thu, 01-Jan-1970 00:00:00 GMT'
|
||||
assert http.cookie_date(datetime(1970, 1, 1)) == 'Thu, 01-Jan-1970 00:00:00 GMT'
|
||||
assert http.http_date(0) == 'Thu, 01 Jan 1970 00:00:00 GMT'
|
||||
assert http.http_date(datetime(1970, 1, 1)) == 'Thu, 01 Jan 1970 00:00:00 GMT'
|
||||
|
||||
def test_cookies(self):
|
||||
strict_eq(
|
||||
dict(http.parse_cookie('dismiss-top=6; CP=null*; PHPSESSID=0a539d42abc001cd'
|
||||
'c762809248d4beed; a=42; b="\\\";";'
|
||||
'==__Host-eq=bad;__Host-eq=good;')),
|
||||
{
|
||||
'CP': u'null*',
|
||||
'PHPSESSID': u'0a539d42abc001cdc762809248d4beed',
|
||||
'a': u'42',
|
||||
'dismiss-top': u'6',
|
||||
'b': u'\";',
|
||||
'__Host-eq': u'good',
|
||||
}
|
||||
)
|
||||
rv = http.dump_cookie('foo', 'bar baz blub', 360, httponly=True,
|
||||
sync_expires=False)
|
||||
assert type(rv) is str
|
||||
assert set(rv.split('; ')) == set(['HttpOnly', 'Max-Age=360',
|
||||
'Path=/', 'foo="bar baz blub"'])
|
||||
|
||||
strict_eq(dict(http.parse_cookie('fo234{=bar; blub=Blah')),
|
||||
{'fo234{': u'bar', 'blub': u'Blah'})
|
||||
|
||||
def test_cookie_quoting(self):
|
||||
val = http.dump_cookie("foo", "?foo")
|
||||
strict_eq(val, 'foo="?foo"; Path=/')
|
||||
strict_eq(dict(http.parse_cookie(val)), {'foo': u'?foo'})
|
||||
|
||||
strict_eq(dict(http.parse_cookie(r'foo="foo\054bar"')),
|
||||
{'foo': u'foo,bar'})
|
||||
|
||||
def test_cookie_domain_resolving(self):
|
||||
val = http.dump_cookie('foo', 'bar', domain=u'\N{SNOWMAN}.com')
|
||||
strict_eq(val, 'foo=bar; Domain=xn--n3h.com; Path=/')
|
||||
|
||||
def test_cookie_unicode_dumping(self):
|
||||
val = http.dump_cookie('foo', u'\N{SNOWMAN}')
|
||||
h = datastructures.Headers()
|
||||
h.add('Set-Cookie', val)
|
||||
assert h['Set-Cookie'] == 'foo="\\342\\230\\203"; Path=/'
|
||||
|
||||
cookies = http.parse_cookie(h['Set-Cookie'])
|
||||
assert cookies['foo'] == u'\N{SNOWMAN}'
|
||||
|
||||
def test_cookie_unicode_keys(self):
|
||||
# Yes, this is technically against the spec but happens
|
||||
val = http.dump_cookie(u'fö', u'fö')
|
||||
assert val == wsgi_encoding_dance(u'fö="f\\303\\266"; Path=/', 'utf-8')
|
||||
cookies = http.parse_cookie(val)
|
||||
assert cookies[u'fö'] == u'fö'
|
||||
|
||||
def test_cookie_unicode_parsing(self):
|
||||
# This is actually a correct test. This is what is being submitted
|
||||
# by firefox if you set an unicode cookie and we get the cookie sent
|
||||
# in on Python 3 under PEP 3333.
|
||||
cookies = http.parse_cookie(u'fö=fö')
|
||||
assert cookies[u'fö'] == u'fö'
|
||||
|
||||
def test_cookie_domain_encoding(self):
|
||||
val = http.dump_cookie('foo', 'bar', domain=u'\N{SNOWMAN}.com')
|
||||
strict_eq(val, 'foo=bar; Domain=xn--n3h.com; Path=/')
|
||||
|
||||
val = http.dump_cookie('foo', 'bar', domain=u'.\N{SNOWMAN}.com')
|
||||
strict_eq(val, 'foo=bar; Domain=.xn--n3h.com; Path=/')
|
||||
|
||||
val = http.dump_cookie('foo', 'bar', domain=u'.foo.com')
|
||||
strict_eq(val, 'foo=bar; Domain=.foo.com; Path=/')
|
||||
|
||||
|
||||
class TestRange(object):
|
||||
|
||||
def test_if_range_parsing(self):
|
||||
rv = http.parse_if_range_header('"Test"')
|
||||
assert rv.etag == 'Test'
|
||||
assert rv.date is None
|
||||
assert rv.to_header() == '"Test"'
|
||||
|
||||
# weak information is dropped
|
||||
rv = http.parse_if_range_header('W/"Test"')
|
||||
assert rv.etag == 'Test'
|
||||
assert rv.date is None
|
||||
assert rv.to_header() == '"Test"'
|
||||
|
||||
# broken etags are supported too
|
||||
rv = http.parse_if_range_header('bullshit')
|
||||
assert rv.etag == 'bullshit'
|
||||
assert rv.date is None
|
||||
assert rv.to_header() == '"bullshit"'
|
||||
|
||||
rv = http.parse_if_range_header('Thu, 01 Jan 1970 00:00:00 GMT')
|
||||
assert rv.etag is None
|
||||
assert rv.date == datetime(1970, 1, 1)
|
||||
assert rv.to_header() == 'Thu, 01 Jan 1970 00:00:00 GMT'
|
||||
|
||||
for x in '', None:
|
||||
rv = http.parse_if_range_header(x)
|
||||
assert rv.etag is None
|
||||
assert rv.date is None
|
||||
assert rv.to_header() == ''
|
||||
|
||||
def test_range_parsing(self):
|
||||
rv = http.parse_range_header('bytes=52')
|
||||
assert rv is None
|
||||
|
||||
rv = http.parse_range_header('bytes=52-')
|
||||
assert rv.units == 'bytes'
|
||||
assert rv.ranges == [(52, None)]
|
||||
assert rv.to_header() == 'bytes=52-'
|
||||
|
||||
rv = http.parse_range_header('bytes=52-99')
|
||||
assert rv.units == 'bytes'
|
||||
assert rv.ranges == [(52, 100)]
|
||||
assert rv.to_header() == 'bytes=52-99'
|
||||
|
||||
rv = http.parse_range_header('bytes=52-99,-1000')
|
||||
assert rv.units == 'bytes'
|
||||
assert rv.ranges == [(52, 100), (-1000, None)]
|
||||
assert rv.to_header() == 'bytes=52-99,-1000'
|
||||
|
||||
rv = http.parse_range_header('bytes = 1 - 100')
|
||||
assert rv.units == 'bytes'
|
||||
assert rv.ranges == [(1, 101)]
|
||||
assert rv.to_header() == 'bytes=1-100'
|
||||
|
||||
rv = http.parse_range_header('AWesomes=0-999')
|
||||
assert rv.units == 'awesomes'
|
||||
assert rv.ranges == [(0, 1000)]
|
||||
assert rv.to_header() == 'awesomes=0-999'
|
||||
|
||||
rv = http.parse_range_header('bytes=-')
|
||||
assert rv is None
|
||||
|
||||
rv = http.parse_range_header('bytes=bullshit')
|
||||
assert rv is None
|
||||
|
||||
rv = http.parse_range_header('bytes=bullshit-1')
|
||||
assert rv is None
|
||||
|
||||
rv = http.parse_range_header('bytes=-bullshit')
|
||||
assert rv is None
|
||||
|
||||
rv = http.parse_range_header('bytes=52-99, bullshit')
|
||||
assert rv is None
|
||||
|
||||
def test_content_range_parsing(self):
|
||||
rv = http.parse_content_range_header('bytes 0-98/*')
|
||||
assert rv.units == 'bytes'
|
||||
assert rv.start == 0
|
||||
assert rv.stop == 99
|
||||
assert rv.length is None
|
||||
assert rv.to_header() == 'bytes 0-98/*'
|
||||
|
||||
rv = http.parse_content_range_header('bytes 0-98/*asdfsa')
|
||||
assert rv is None
|
||||
|
||||
rv = http.parse_content_range_header('bytes 0-99/100')
|
||||
assert rv.to_header() == 'bytes 0-99/100'
|
||||
rv.start = None
|
||||
rv.stop = None
|
||||
assert rv.units == 'bytes'
|
||||
assert rv.to_header() == 'bytes */100'
|
||||
|
||||
rv = http.parse_content_range_header('bytes */100')
|
||||
assert rv.start is None
|
||||
assert rv.stop is None
|
||||
assert rv.length == 100
|
||||
assert rv.units == 'bytes'
|
||||
|
||||
|
||||
class TestRegression(object):
|
||||
|
||||
def test_best_match_works(self):
|
||||
# was a bug in 0.6
|
||||
rv = http.parse_accept_header('foo=,application/xml,application/xhtml+xml,'
|
||||
'text/html;q=0.9,text/plain;q=0.8,'
|
||||
'image/png,*/*;q=0.5',
|
||||
datastructures.MIMEAccept).best_match(['foo/bar'])
|
||||
assert rv == 'foo/bar'
|
Loading…
Reference in New Issue
Block a user