1import inspect
2from datetime import datetime
3
4import pytest
5
6from werkzeug import utils
7from werkzeug.datastructures import Headers
8from werkzeug.http import http_date
9from werkzeug.http import parse_date
10from werkzeug.test import Client
11from werkzeug.wrappers import Response
12
13
14def test_redirect():
15    resp = utils.redirect("/füübär")
16    assert b"/f%C3%BC%C3%BCb%C3%A4r" in resp.get_data()
17    assert resp.headers["Location"] == "/f%C3%BC%C3%BCb%C3%A4r"
18    assert resp.status_code == 302
19
20    resp = utils.redirect("http://☃.net/", 307)
21    assert b"http://xn--n3h.net/" in resp.get_data()
22    assert resp.headers["Location"] == "http://xn--n3h.net/"
23    assert resp.status_code == 307
24
25    resp = utils.redirect("http://example.com/", 305)
26    assert resp.headers["Location"] == "http://example.com/"
27    assert resp.status_code == 305
28
29
30def test_redirect_xss():
31    location = 'http://example.com/?xss="><script>alert(1)</script>'
32    resp = utils.redirect(location)
33    assert b"<script>alert(1)</script>" not in resp.get_data()
34
35    location = 'http://example.com/?xss="onmouseover="alert(1)'
36    resp = utils.redirect(location)
37    assert (
38        b'href="http://example.com/?xss="onmouseover="alert(1)"' not in resp.get_data()
39    )
40
41
42def test_redirect_with_custom_response_class():
43    class MyResponse(Response):
44        pass
45
46    location = "http://example.com/redirect"
47    resp = utils.redirect(location, Response=MyResponse)
48
49    assert isinstance(resp, MyResponse)
50    assert resp.headers["Location"] == location
51
52
53def test_cached_property():
54    foo = []
55
56    class A:
57        def prop(self):
58            foo.append(42)
59            return 42
60
61        prop = utils.cached_property(prop)
62
63    a = A()
64    p = a.prop
65    q = a.prop
66    assert p == q == 42
67    assert foo == [42]
68
69    foo = []
70
71    class A:
72        def _prop(self):
73            foo.append(42)
74            return 42
75
76        prop = utils.cached_property(_prop, name="prop")
77        del _prop
78
79    a = A()
80    p = a.prop
81    q = a.prop
82    assert p == q == 42
83    assert foo == [42]
84
85
86def test_can_set_cached_property():
87    class A:
88        @utils.cached_property
89        def _prop(self):
90            return "cached_property return value"
91
92    a = A()
93    a._prop = "value"
94    assert a._prop == "value"
95
96
97def test_invalidate_cached_property():
98    accessed = 0
99
100    class A:
101        @utils.cached_property
102        def prop(self):
103            nonlocal accessed
104            accessed += 1
105            return 42
106
107    a = A()
108    p = a.prop
109    q = a.prop
110    assert p == q == 42
111    assert accessed == 1
112
113    a.prop = 16
114    assert a.prop == 16
115    assert accessed == 1
116
117    del a.prop
118    r = a.prop
119    assert r == 42
120    assert accessed == 2
121
122
123def test_inspect_treats_cached_property_as_property():
124    class A:
125        @utils.cached_property
126        def _prop(self):
127            return "cached_property return value"
128
129    attrs = inspect.classify_class_attrs(A)
130    for attr in attrs:
131        if attr.name == "_prop":
132            break
133    assert attr.kind == "property"
134
135
136def test_environ_property():
137    class A:
138        environ = {"string": "abc", "number": "42"}
139
140        string = utils.environ_property("string")
141        missing = utils.environ_property("missing", "spam")
142        read_only = utils.environ_property("number")
143        number = utils.environ_property("number", load_func=int)
144        broken_number = utils.environ_property("broken_number", load_func=int)
145        date = utils.environ_property(
146            "date", None, parse_date, http_date, read_only=False
147        )
148        foo = utils.environ_property("foo")
149
150    a = A()
151    assert a.string == "abc"
152    assert a.missing == "spam"
153
154    def test_assign():
155        a.read_only = "something"
156
157    pytest.raises(AttributeError, test_assign)
158    assert a.number == 42
159    assert a.broken_number is None
160    assert a.date is None
161    a.date = datetime(2008, 1, 22, 10, 0, 0, 0)
162    assert a.environ["date"] == "Tue, 22 Jan 2008 10:00:00 GMT"
163
164
165def test_import_string():
166    from datetime import date
167    from werkzeug.debug import DebuggedApplication
168
169    assert utils.import_string("datetime.date") is date
170    assert utils.import_string("datetime.date") is date
171    assert utils.import_string("datetime:date") is date
172    assert utils.import_string("XXXXXXXXXXXX", True) is None
173    assert utils.import_string("datetime.XXXXXXXXXXXX", True) is None
174    assert (
175        utils.import_string("werkzeug.debug.DebuggedApplication") is DebuggedApplication
176    )
177    pytest.raises(ImportError, utils.import_string, "XXXXXXXXXXXXXXXX")
178    pytest.raises(ImportError, utils.import_string, "datetime.XXXXXXXXXX")
179
180
181def test_import_string_provides_traceback(tmpdir, monkeypatch):
182    monkeypatch.syspath_prepend(str(tmpdir))
183    # Couple of packages
184    dir_a = tmpdir.mkdir("a")
185    dir_b = tmpdir.mkdir("b")
186    # Totally packages, I promise
187    dir_a.join("__init__.py").write("")
188    dir_b.join("__init__.py").write("")
189    # 'aa.a' that depends on 'bb.b', which in turn has a broken import
190    dir_a.join("aa.py").write("from b import bb")
191    dir_b.join("bb.py").write("from os import a_typo")
192
193    # Do we get all the useful information in the traceback?
194    with pytest.raises(ImportError) as baz_exc:
195        utils.import_string("a.aa")
196    traceback = "".join(str(line) for line in baz_exc.traceback)
197    assert "bb.py':1" in traceback  # a bit different than typical python tb
198    assert "from os import a_typo" in traceback
199
200
201def test_import_string_attribute_error(tmpdir, monkeypatch):
202    monkeypatch.syspath_prepend(str(tmpdir))
203    tmpdir.join("foo_test.py").write("from bar_test import value")
204    tmpdir.join("bar_test.py").write("raise AttributeError('bad')")
205
206    with pytest.raises(AttributeError) as info:
207        utils.import_string("foo_test")
208
209    assert "bad" in str(info.value)
210
211    with pytest.raises(AttributeError) as info:
212        utils.import_string("bar_test")
213
214    assert "bad" in str(info.value)
215
216
217def test_find_modules():
218    assert list(utils.find_modules("werkzeug.debug")) == [
219        "werkzeug.debug.console",
220        "werkzeug.debug.repr",
221        "werkzeug.debug.tbtools",
222    ]
223
224
225def test_header_set_duplication_bug():
226    headers = Headers([("Content-Type", "text/html"), ("Foo", "bar"), ("Blub", "blah")])
227    headers["blub"] = "hehe"
228    headers["blafasel"] = "humm"
229    assert headers == Headers(
230        [
231            ("Content-Type", "text/html"),
232            ("Foo", "bar"),
233            ("blub", "hehe"),
234            ("blafasel", "humm"),
235        ]
236    )
237
238
239def test_append_slash_redirect():
240    def app(env, sr):
241        return utils.append_slash_redirect(env)(env, sr)
242
243    client = Client(app)
244    response = client.get("foo", base_url="http://example.org/app")
245    assert response.status_code == 301
246    assert response.headers["Location"] == "http://example.org/app/foo/"
247
248
249def test_cached_property_doc():
250    @utils.cached_property
251    def foo():
252        """testing"""
253        return 42
254
255    assert foo.__doc__ == "testing"
256    assert foo.__name__ == "foo"
257    assert foo.__module__ == __name__
258
259
260def test_secure_filename():
261    assert utils.secure_filename("My cool movie.mov") == "My_cool_movie.mov"
262    assert utils.secure_filename("../../../etc/passwd") == "etc_passwd"
263    assert (
264        utils.secure_filename("i contain cool \xfcml\xe4uts.txt")
265        == "i_contain_cool_umlauts.txt"
266    )
267    assert utils.secure_filename("__filename__") == "filename"
268    assert utils.secure_filename("foo$&^*)bar") == "foobar"
269