New upstream version 0.15.6+dfsg1
Ondřej Nový
4 years ago
0 | environment: | |
1 | global: | |
2 | TOXENV: py,codecov | |
3 | ||
4 | matrix: | |
5 | - PYTHON: C:\Python37-x64 | |
6 | - PYTHON: C:\Python27-x64 | |
7 | ||
8 | init: | |
9 | - SET PATH=%PYTHON%;%PATH% | |
10 | ||
11 | install: | |
12 | - python -m pip install -U tox | |
13 | ||
14 | build: false | |
15 | ||
16 | test_script: | |
17 | - python -m tox --skip-missing-interpreters false | |
18 | ||
19 | branches: | |
20 | only: | |
21 | - master | |
22 | - /^\d+(\.\d+)*(\.x)?$/ | |
23 | ||
24 | cache: | |
25 | - '%LOCALAPPDATA%\pip\Cache' |
0 | trigger: | |
1 | - master | |
2 | - '*.x' | |
3 | ||
4 | variables: | |
5 | vmImage: ubuntu-latest | |
6 | python.version: 3.7 | |
7 | TOXENV: py,coverage-ci | |
8 | hasTestResults: true | |
9 | ||
10 | strategy: | |
11 | matrix: | |
12 | Python 3.7 Linux: | |
13 | vmImage: ubuntu-latest | |
14 | Python 3.7 Windows: | |
15 | vmImage: windows-latest | |
16 | Python 3.7 Mac: | |
17 | vmImage: macos-latest | |
18 | PyPy 3 Linux: | |
19 | python.version: pypy3 | |
20 | Python 3.6 Linux: | |
21 | python.version: 3.6 | |
22 | Python 3.5 Linux: | |
23 | python.version: 3.5 | |
24 | Python 2.7 Linux: | |
25 | python.version: 2.7 | |
26 | Python 2.7 Windows: | |
27 | python.version: 2.7 | |
28 | vmImage: windows-latest | |
29 | Docs: | |
30 | TOXENV: docs-html | |
31 | hasTestResults: false | |
32 | Style: | |
33 | TOXENV: style | |
34 | hasTestResults: false | |
35 | ||
36 | pool: | |
37 | vmImage: $[ variables.vmImage ] | |
38 | ||
39 | steps: | |
40 | - task: UsePythonVersion@0 | |
41 | inputs: | |
42 | versionSpec: $(python.version) | |
43 | displayName: Use Python $(python.version) | |
44 | ||
45 | - script: pip --disable-pip-version-check install -U tox | |
46 | displayName: Install tox | |
47 | ||
48 | - script: tox -s false -- --junit-xml=test-results.xml | |
49 | displayName: Run tox | |
50 | ||
51 | - task: PublishTestResults@2 | |
52 | inputs: | |
53 | testResultsFiles: test-results.xml | |
54 | testRunTitle: $(Agent.JobName) | |
55 | condition: eq(variables['hasTestResults'], 'true') | |
56 | displayName: Publish test results | |
57 | ||
58 | - task: PublishCodeCoverageResults@1 | |
59 | inputs: | |
60 | codeCoverageTool: Cobertura | |
61 | summaryFileLocation: coverage.xml | |
62 | condition: eq(variables['hasTestResults'], 'true') | |
63 | displayName: Publish coverage results |
0 | # Normalize CRLF to LF for all text files | |
1 | * text=auto | |
2 | ||
3 | # Declare binary file types so they won't be normalized | |
4 | *.png binary | |
5 | *.jpg binary | |
0 | 6 | tests/**/*.http binary |
7 | tests/res/test.txt binary |
0 | os: linux | |
1 | dist: xenial | |
2 | language: python | |
3 | python: | |
4 | - "3.7" | |
5 | - "3.6" | |
6 | - "3.5" | |
7 | - "3.4" | |
8 | - "2.7" | |
9 | - "nightly" | |
10 | - "pypy3.5-6.0" | |
11 | env: TOXENV=py,codecov | |
12 | ||
13 | matrix: | |
14 | include: | |
15 | - env: TOXENV=stylecheck,docs-html | |
16 | - os: osx | |
17 | language: generic | |
18 | env: TOXENV=py3,codecov | |
19 | cache: | |
20 | directories: | |
21 | - $HOME/Library/Caches/Homebrew | |
22 | - $HOME/Library/Caches/pip | |
23 | allow_failures: | |
24 | - python: nightly | |
25 | - python: pypy3.5-6.0 | |
26 | - os: osx | |
27 | fast_finish: true | |
28 | ||
29 | install: | |
30 | - pip install -U tox | |
31 | ||
32 | script: | |
33 | - tox --skip-missing-interpreters false | |
34 | ||
35 | cache: | |
36 | directories: | |
37 | - $HOME/.cache/pip | |
38 | - $HOME/.cache/pre-commit | |
39 | ||
40 | branches: | |
41 | only: | |
42 | - master | |
43 | - /^\d+(\.\d+)*(\.x)?$/ | |
44 | ||
45 | notifications: | |
46 | email: false |
0 | 0 | .. currentmodule:: werkzeug |
1 | ||
2 | Version 0.15.6 | |
3 | -------------- | |
4 | ||
5 | Released 2019-09-04 | |
6 | ||
7 | - Work around a bug in pip that caused the reloader to fail on | |
8 | Windows when the script was an entry point. This fixes the issue | |
9 | with Flask's `flask run` command failing with "No module named | |
10 | Scripts\flask". :issue:`1614` | |
11 | - ``ProxyFix`` trusts the ``X-Forwarded-Proto`` header by default. | |
12 | :issue:`1630` | |
13 | - The deprecated ``num_proxies`` argument to ``ProxyFix`` sets | |
14 | ``x_for``, ``x_proto``, and ``x_host`` to match 0.14 behavior. This | |
15 | is intended to make intermediate upgrades less disruptive, but the | |
16 | argument will still be removed in 1.0. :issue:`1630` | |
17 | ||
18 | ||
19 | Version 0.15.5 | |
20 | -------------- | |
21 | ||
22 | Released 2019-07-17 | |
23 | ||
24 | - Fix a ``TypeError`` due to changes to ``ast.Module`` in Python 3.8. | |
25 | :issue:`1551` | |
26 | - Fix a C assertion failure in debug builds of some Python 2.7 | |
27 | releases. :issue:`1553` | |
28 | - :class:`~exceptions.BadRequestKeyError` adds the ``KeyError`` | |
29 | message to the description if ``e.show_exception`` is set to | |
30 | ``True``. This is a more secure default than the original 0.15.0 | |
31 | behavior and makes it easier to control without losing information. | |
32 | :pr:`1592` | |
33 | - Upgrade the debugger to jQuery 3.4.1. :issue:`1581` | |
34 | - Work around an issue in some external debuggers that caused the | |
35 | reloader to fail. :issue:`1607` | |
36 | - Work around an issue where the reloader couldn't introspect a | |
37 | setuptools script installed as an egg. :issue:`1600` | |
38 | - The reloader will use ``sys.executable`` even if the script is | |
39 | marked executable, reverting a behavior intended for NixOS | |
40 | introduced in 0.15. The reloader should no longer cause | |
41 | ``OSError: [Errno 8] Exec format error``. :issue:`1482`, | |
42 | :issue:`1580` | |
43 | - ``SharedDataMiddleware`` safely handles paths with Windows drive | |
44 | names. :issue:`1589` | |
45 | ||
1 | 46 | |
2 | 47 | Version 0.15.4 |
3 | 48 | -------------- |
64 | 64 | Links |
65 | 65 | ----- |
66 | 66 | |
67 | - Website: https://www.palletsprojects.com/p/werkzeug/ | |
67 | - Website: https://palletsprojects.com/p/werkzeug/ | |
68 | 68 | - Documentation: https://werkzeug.palletsprojects.com/ |
69 | 69 | - Releases: https://pypi.org/project/Werkzeug/ |
70 | 70 | - Code: https://github.com/pallets/werkzeug |
71 | 71 | - Issue tracker: https://github.com/pallets/werkzeug/issues |
72 | - Test status: | |
73 | ||
74 | - Linux, Mac: https://travis-ci.org/pallets/werkzeug | |
75 | - Windows: https://ci.appveyor.com/project/pallets/werkzeug | |
76 | ||
77 | - Test coverage: https://codecov.io/gh/pallets/werkzeug | |
72 | - Test status: https://dev.azure.com/pallets/werkzeug/_build | |
78 | 73 | - Official chat: https://discord.gg/t6rrQZH |
79 | 74 | |
80 | 75 | .. _WSGI: https://wsgi.readthedocs.io/en/latest/ |
2 | 2 | |
3 | 3 | .. warning:: |
4 | 4 | ``werkzeug.contrib.profiler`` has moved to |
5 | :mod:`werkzeug.middleware.profile`. The old import is deprecated as | |
5 | :mod:`werkzeug.middleware.profiler`. The old import is deprecated as | |
6 | 6 | of version 0.15 and will be removed in version 1.0. |
7 | 7 | |
8 | 8 | .. autoclass:: werkzeug.contrib.profiler.MergeStream |
1 | 1 | API Levels |
2 | 2 | ========== |
3 | 3 | |
4 | .. module:: werkzeug | |
4 | .. currentmodule:: werkzeug | |
5 | 5 | |
6 | 6 | Werkzeug is intended to be a utility rather than a framework. Because of that |
7 | 7 | the user-friendly API is separated from the lower-level API so that Werkzeug |
1 | 1 | Quickstart |
2 | 2 | ========== |
3 | 3 | |
4 | .. module:: werkzeug | |
4 | .. currentmodule:: werkzeug | |
5 | 5 | |
6 | 6 | This part of the documentation shows how to use the most important parts of |
7 | 7 | Werkzeug. It's intended as a starting point for developers with basic |
2 | 2 | Dealing with Request Data |
3 | 3 | ========================= |
4 | 4 | |
5 | .. module:: werkzeug | |
5 | .. currentmodule:: werkzeug | |
6 | 6 | |
7 | 7 | The most important rule about web development is "Do not trust the user". |
8 | 8 | This is especially true for incoming request data on the input stream. |
1 | 1 | Important Terms |
2 | 2 | =============== |
3 | 3 | |
4 | .. module:: werkzeug | |
4 | .. currentmodule:: werkzeug | |
5 | 5 | |
6 | 6 | This page covers important terms used in the documentation and Werkzeug |
7 | 7 | itself. |
1 | 1 | Werkzeug Tutorial |
2 | 2 | ================= |
3 | 3 | |
4 | .. module:: werkzeug | |
4 | .. currentmodule:: werkzeug | |
5 | 5 | |
6 | 6 | Welcome to the Werkzeug tutorial in which we will create a `TinyURL`_ clone |
7 | 7 | that stores URLs in a redis instance. The libraries we will use for this |
3 | 3 | Unicode |
4 | 4 | ======= |
5 | 5 | |
6 | .. module:: werkzeug | |
6 | .. currentmodule:: werkzeug | |
7 | 7 | |
8 | 8 | Since early Python 2 days unicode was part of all default Python builds. It |
9 | 9 | allows developers to write applications that deal with non-ASCII characters |
21 | 21 | license="BSD-3-Clause", |
22 | 22 | author="Armin Ronacher", |
23 | 23 | author_email="armin.ronacher@active-4.com", |
24 | maintainer="The Pallets Team", | |
24 | maintainer="Pallets", | |
25 | 25 | maintainer_email="contact@palletsprojects.com", |
26 | 26 | description="The comprehensive WSGI web application library.", |
27 | 27 | long_description=readme, |
16 | 16 | import sys |
17 | 17 | from types import ModuleType |
18 | 18 | |
19 | __version__ = "0.15.4" | |
19 | __version__ = "0.15.6" | |
20 | 20 | |
21 | 21 | # This import magic raises concerns quite often which is why the implementation |
22 | 22 | # and motivation is explained here in detail now. |
65 | 65 | "redirect", |
66 | 66 | "cached_property", |
67 | 67 | "import_string", |
68 | "dump_cookie", | |
69 | "parse_cookie", | |
70 | 68 | "unescape", |
71 | 69 | "format_string", |
72 | 70 | "find_modules", |
145 | 143 | "unquote_header_value", |
146 | 144 | "quote_header_value", |
147 | 145 | "HTTP_STATUS_CODES", |
146 | "dump_cookie", | |
147 | "parse_cookie", | |
148 | 148 | ], |
149 | 149 | "werkzeug.wrappers": [ |
150 | 150 | "BaseResponse", |
60 | 60 | |
61 | 61 | |
62 | 62 | def _get_args_for_reloading(): |
63 | """Returns the executable. This contains a workaround for windows | |
64 | if the executable is incorrectly reported to not have the .exe | |
65 | extension which can cause bugs on reloading. This also contains | |
66 | a workaround for linux where the file is executable (possibly with | |
67 | a program other than python) | |
63 | """Determine how the script was executed, and return the args needed | |
64 | to execute it again in a new process. | |
68 | 65 | """ |
69 | 66 | rv = [sys.executable] |
70 | py_script = os.path.abspath(sys.argv[0]) | |
67 | py_script = sys.argv[0] | |
71 | 68 | args = sys.argv[1:] |
72 | 69 | # Need to look at main module to determine how it was executed. |
73 | 70 | __main__ = sys.modules["__main__"] |
74 | 71 | |
75 | if __main__.__package__ is None: | |
72 | # The value of __package__ indicates how Python was called. It may | |
73 | # not exist if a setuptools script is installed as an egg. It may be | |
74 | # set incorrectly for entry points created with pip on Windows. | |
75 | if getattr(__main__, "__package__", None) is None or ( | |
76 | os.name == "nt" | |
77 | and __main__.__package__ == "" | |
78 | and not os.path.exists(py_script) | |
79 | and os.path.exists(py_script + ".exe") | |
80 | ): | |
76 | 81 | # Executed a file, like "python app.py". |
82 | py_script = os.path.abspath(py_script) | |
83 | ||
77 | 84 | if os.name == "nt": |
78 | 85 | # Windows entry points have ".exe" extension and should be |
79 | 86 | # called directly. |
81 | 88 | py_script += ".exe" |
82 | 89 | |
83 | 90 | if ( |
84 | os.path.splitext(rv[0])[1] == ".exe" | |
91 | os.path.splitext(sys.executable)[1] == ".exe" | |
85 | 92 | and os.path.splitext(py_script)[1] == ".exe" |
86 | 93 | ): |
87 | 94 | rv.pop(0) |
88 | ||
89 | elif os.path.isfile(py_script) and os.access(py_script, os.X_OK): | |
90 | # The file is marked as executable. Nix adds a wrapper that | |
91 | # shouldn't be called with the Python executable. | |
92 | rv.pop(0) | |
93 | 95 | |
94 | 96 | rv.append(py_script) |
95 | 97 | else: |
100 | 102 | # TODO remove this once Flask no longer misbehaves |
101 | 103 | args = sys.argv |
102 | 104 | else: |
103 | py_module = __main__.__package__ | |
104 | name = os.path.splitext(os.path.basename(py_script))[0] | |
105 | ||
106 | if name != "__main__": | |
107 | py_module += "." + name | |
105 | if os.path.isfile(py_script): | |
106 | # Rewritten by Python from "-m script" to "/path/to/script.py". | |
107 | py_module = __main__.__package__ | |
108 | name = os.path.splitext(os.path.basename(py_script))[0] | |
109 | ||
110 | if name != "__main__": | |
111 | py_module += "." + name | |
112 | else: | |
113 | # Incorrectly rewritten by pydevd debugger from "-m script" to "script". | |
114 | py_module = py_script | |
108 | 115 | |
109 | 116 | rv.extend(("-m", py_module.lstrip("."))) |
110 | 117 |
61 | 61 | display: inline; |
62 | 62 | } |
63 | 63 | |
64 | div.traceback blockquote { margin: 1em 0 0 0; padding: 0; } | |
64 | div.traceback blockquote { margin: 1em 0 0 0; padding: 0; white-space: pre-line; } | |
65 | 65 | div.traceback img { float: right; padding: 2px; margin: -3px 2px 0 0; display: none; } |
66 | 66 | div.traceback img:hover { background-color: #ddd; cursor: pointer; |
67 | 67 | border-color: #BFDDE0; } |
83 | 83 | description = None |
84 | 84 | |
85 | 85 | def __init__(self, description=None, response=None): |
86 | super(Exception, self).__init__() | |
86 | super(HTTPException, self).__init__() | |
87 | 87 | if description is not None: |
88 | 88 | self.description = description |
89 | 89 | self.response = response |
95 | 95 | |
96 | 96 | The first argument to the class will be passed to the |
97 | 97 | wrapped ``exception``, the rest to the HTTP exception. If |
98 | ``self.args`` is not empty, the wrapped exception message is | |
99 | added to the HTTP exception description. | |
100 | ||
101 | .. versionchanged:: 0.15 | |
98 | ``e.args`` is not empty and ``e.show_exception`` is ``True``, | |
99 | the wrapped exception message is added to the HTTP error | |
100 | description. | |
101 | ||
102 | .. versionchanged:: 0.15.5 | |
103 | The ``show_exception`` attribute controls whether the | |
104 | description includes the wrapped exception message. | |
105 | ||
106 | .. versionchanged:: 0.15.0 | |
102 | 107 | The description includes the wrapped exception message. |
103 | 108 | """ |
104 | 109 | |
105 | 110 | class newcls(cls, exception): |
111 | _description = cls.description | |
112 | show_exception = False | |
113 | ||
106 | 114 | def __init__(self, arg=None, *args, **kwargs): |
107 | 115 | super(cls, self).__init__(*args, **kwargs) |
108 | 116 | |
111 | 119 | else: |
112 | 120 | exception.__init__(self, arg) |
113 | 121 | |
114 | def get_description(self, environ=None): | |
115 | out = super(cls, self).get_description(environ=environ) | |
116 | ||
117 | if self.args: | |
118 | out += "<p><pre><code>{}: {}</code></pre></p>".format( | |
119 | exception.__name__, escape(exception.__str__(self)) | |
122 | @property | |
123 | def description(self): | |
124 | if self.show_exception: | |
125 | return "{}\n{}: {}".format( | |
126 | self._description, exception.__name__, exception.__str__(self) | |
120 | 127 | ) |
121 | 128 | |
122 | return out | |
129 | return self._description | |
130 | ||
131 | @description.setter | |
132 | def description(self, value): | |
133 | self._description = value | |
123 | 134 | |
124 | 135 | newcls.__module__ = sys._getframe(1).f_globals.get("__name__") |
125 | newcls.__name__ = name or cls.__name__ + exception.__name__ | |
136 | name = name or cls.__name__ + exception.__name__ | |
137 | newcls.__name__ = newcls.__qualname__ = name | |
126 | 138 | return newcls |
127 | 139 | |
128 | 140 | @property |
132 | 144 | |
133 | 145 | def get_description(self, environ=None): |
134 | 146 | """Get the description.""" |
135 | return u"<p>%s</p>" % escape(self.description) | |
147 | return u"<p>%s</p>" % escape(self.description).replace("\n", "<br>") | |
136 | 148 | |
137 | 149 | def get_body(self, environ=None): |
138 | 150 | """Get the HTML body.""" |
764 | 776 | _aborter = Aborter() |
765 | 777 | |
766 | 778 | |
767 | #: an exception that is used internally to signal both a key error and a | |
768 | #: bad request. Used by a lot of the datastructures. | |
779 | #: An exception that is used to signal both a :exc:`KeyError` and a | |
780 | #: :exc:`BadRequest`. Used by many of the datastructures. | |
769 | 781 | BadRequestKeyError = BadRequest.wrap(KeyError) |
770 | 782 | |
771 | 783 | # imported here because of circular dependencies of werkzeug.utils |
76 | 76 | """ |
77 | 77 | |
78 | 78 | def __init__( |
79 | self, app, num_proxies=None, x_for=1, x_proto=0, x_host=0, x_port=0, x_prefix=0 | |
79 | self, app, num_proxies=None, x_for=1, x_proto=1, x_host=0, x_port=0, x_prefix=0 | |
80 | 80 | ): |
81 | 81 | self.app = app |
82 | 82 | self.x_for = x_for |
111 | 111 | if value is not None: |
112 | 112 | warnings.warn( |
113 | 113 | "'num_proxies' is deprecated as of version 0.15 and" |
114 | " will be removed in version 1.0. Use 'x_for' instead.", | |
114 | " will be removed in version 1.0. Use" | |
115 | " 'x_for={value}, x_proto={value}, x_host={value}'" | |
116 | " instead.".format(value=value), | |
115 | 117 | DeprecationWarning, |
116 | 118 | stacklevel=2, |
117 | 119 | ) |
118 | 120 | self.x_for = value |
121 | self.x_proto = value | |
122 | self.x_host = value | |
119 | 123 | |
120 | 124 | def get_remote_addr(self, forwarded_for): |
121 | 125 | """Get the real ``remote_addr`` by looking backwards ``x_for`` |
21 | 21 | from ..filesystem import get_filesystem_encoding |
22 | 22 | from ..http import http_date |
23 | 23 | from ..http import is_resource_modified |
24 | from ..security import safe_join | |
24 | 25 | from ..wsgi import get_path_info |
25 | 26 | from ..wsgi import wrap_file |
26 | 27 | |
148 | 149 | if path is None: |
149 | 150 | return None, None |
150 | 151 | |
151 | path = posixpath.join(package_path, path) | |
152 | path = safe_join(package_path, path) | |
152 | 153 | |
153 | 154 | if not provider.has_resource(path): |
154 | 155 | return None, None |
169 | 170 | def get_directory_loader(self, directory): |
170 | 171 | def loader(path): |
171 | 172 | if path is not None: |
172 | path = os.path.join(directory, path) | |
173 | path = safe_join(directory, path) | |
173 | 174 | else: |
174 | 175 | path = directory |
175 | 176 | |
191 | 192 | ) |
192 | 193 | |
193 | 194 | def __call__(self, environ, start_response): |
194 | cleaned_path = get_path_info(environ) | |
195 | path = get_path_info(environ) | |
195 | 196 | |
196 | 197 | if PY2: |
197 | cleaned_path = cleaned_path.encode(get_filesystem_encoding()) | |
198 | ||
199 | # sanitize the path for non unix systems | |
200 | cleaned_path = cleaned_path.strip("/") | |
201 | ||
202 | for sep in os.sep, os.altsep: | |
203 | if sep and sep != "/": | |
204 | cleaned_path = cleaned_path.replace(sep, "/") | |
205 | ||
206 | path = "/" + "/".join(x for x in cleaned_path.split("/") if x and x != "..") | |
198 | path = path.encode(get_filesystem_encoding()) | |
199 | ||
207 | 200 | file_loader = None |
208 | 201 | |
209 | 202 | for search_path, loader in self.exports: |
938 | 938 | func_ast.args.args.append(ast.arg(arg, None)) |
939 | 939 | func_ast.args.kwarg = ast.arg(".kwargs", None) |
940 | 940 | else: |
941 | func_ast.args.args.append(ast.Name(".self", ast.Load())) | |
941 | func_ast.args.args.append(ast.Name(".self", ast.Param())) | |
942 | 942 | for arg in pargs + kargs: |
943 | func_ast.args.args.append(ast.Name(arg, ast.Load())) | |
943 | func_ast.args.args.append(ast.Name(arg, ast.Param())) | |
944 | 944 | func_ast.args.kwarg = ".kwargs" |
945 | 945 | for _ in kargs: |
946 | 946 | func_ast.args.defaults.append(ast.Str("")) |
947 | 947 | func_ast.body = body |
948 | 948 | |
949 | module = ast.fix_missing_locations(ast.Module([func_ast])) | |
949 | # use `ast.parse` instead of `ast.Module` for better portability | |
950 | # python3.8 changes the signature of `ast.Module` | |
951 | module = ast.parse("") | |
952 | module.body = [func_ast] | |
953 | ||
954 | # mark everything as on line 1, offset 0 | |
955 | # less error-prone than `ast.fix_missing_locations` | |
956 | # bad line numbers cause an assert to fail in debug builds | |
957 | for node in ast.walk(module): | |
958 | if "lineno" in node._attributes: | |
959 | node.lineno = 1 | |
960 | if "col_offset" in node._attributes: | |
961 | node.col_offset = 0 | |
962 | ||
950 | 963 | code = compile(module, "<werkzeug routing>", "exec") |
951 | 964 | return self._get_func_code(code, func_ast.name) |
952 | 965 |
221 | 221 | |
222 | 222 | |
223 | 223 | def safe_join(directory, *pathnames): |
224 | """Safely join `directory` and one or more untrusted `pathnames`. If this | |
225 | cannot be done, this function returns ``None``. | |
226 | ||
227 | :param directory: the base directory. | |
228 | :param pathnames: the untrusted pathnames relative to that directory. | |
224 | """Safely join zero or more untrusted path components to a base | |
225 | directory to avoid escaping the base directory. | |
226 | ||
227 | :param directory: The trusted base directory. | |
228 | :param pathnames: The untrusted path components relative to the | |
229 | base directory. | |
230 | :return: A safe path, otherwise ``None``. | |
229 | 231 | """ |
230 | 232 | parts = [directory] |
233 | ||
231 | 234 | for filename in pathnames: |
232 | 235 | if filename != "": |
233 | 236 | filename = posixpath.normpath(filename) |
234 | for sep in _os_alt_seps: | |
235 | if sep in filename: | |
236 | return None | |
237 | if os.path.isabs(filename) or filename == ".." or filename.startswith("../"): | |
237 | ||
238 | if ( | |
239 | any(sep in filename for sep in _os_alt_seps) | |
240 | or os.path.isabs(filename) | |
241 | or filename == ".." | |
242 | or filename.startswith("../") | |
243 | ): | |
238 | 244 | return None |
245 | ||
239 | 246 | parts.append(filename) |
247 | ||
240 | 248 | return posixpath.join(*parts) |
205 | 205 | |
206 | 206 | @classmethod |
207 | 207 | def application(cls, f): |
208 | """Decorate a function as responder that accepts the request as first | |
209 | argument. This works like the :func:`responder` decorator but the | |
210 | function is passed the request object as first argument and the | |
211 | request object will be closed automatically:: | |
208 | """Decorate a function as responder that accepts the request as | |
209 | the last argument. This works like the :func:`responder` | |
210 | decorator but the function is passed the request object as the | |
211 | last argument and the request object will be closed | |
212 | automatically:: | |
212 | 213 | |
213 | 214 | @Request.application |
214 | 215 | def my_wsgi_app(request): |
224 | 225 | #: and calls the function with all the arguments up to that one and |
225 | 226 | #: the request. The return value is then called with the latest |
226 | 227 | #: two arguments. This makes it possible to use this decorator for |
227 | #: both methods and standalone WSGI functions. | |
228 | #: both standalone WSGI functions as well as bound methods and | |
229 | #: partially applied functions. | |
228 | 230 | from ..exceptions import HTTPException |
229 | 231 | |
230 | 232 | def application(*args): |
17 | 17 | "REMOTE_ADDR": "192.168.0.2", |
18 | 18 | "HTTP_HOST": "spam", |
19 | 19 | "HTTP_X_FORWARDED_FOR": "192.168.0.1", |
20 | "HTTP_X_FORWARDED_PROTO": "https", | |
20 | 21 | }, |
21 | "http://spam/", | |
22 | "https://spam/", | |
22 | 23 | id="for", |
23 | 24 | ), |
24 | 25 | pytest.param( |
177 | 178 | def test_proxy_fix_deprecations(): |
178 | 179 | app = pytest.deprecated_call(ProxyFix, None, 2) |
179 | 180 | assert app.x_for == 2 |
181 | assert app.x_proto == 2 | |
182 | assert app.x_host == 2 | |
180 | 183 | |
181 | 184 | with pytest.deprecated_call(): |
182 | 185 | assert app.num_proxies == 2 |
0 | 0 | # -*- coding: utf-8 -*- |
1 | 1 | import os |
2 | import sys | |
2 | 3 | from contextlib import closing |
4 | ||
5 | import pytest | |
3 | 6 | |
4 | 7 | from werkzeug._compat import to_native |
5 | 8 | from werkzeug.middleware.shared_data import SharedDataMiddleware |
12 | 15 | assert callable(app.get_file_loader("foo")) |
13 | 16 | |
14 | 17 | |
18 | @pytest.mark.xfail( | |
19 | sys.version_info.major == 2 and sys.platform == "win32", | |
20 | reason="TODO fix test for Python 2 on Windows", | |
21 | ) | |
15 | 22 | def test_shared_data_middleware(tmpdir): |
16 | 23 | def null_application(environ, start_response): |
17 | 24 | start_response("404 NOT FOUND", [("Content-Type", "text/plain")]) |
582 | 582 | with pytest.raises(BadRequestKeyError) as exc_info: |
583 | 583 | data["baz"] |
584 | 584 | |
585 | assert "baz" not in exc_info.value.get_description() | |
586 | exc_info.value.show_exception = True | |
585 | 587 | assert "baz" in exc_info.value.get_description() |
586 | 588 | |
587 | 589 | with pytest.raises(BadRequestKeyError) as exc_info: |
588 | 590 | data.pop("baz") |
589 | 591 | |
592 | exc_info.value.show_exception = True | |
590 | 593 | assert "baz" in exc_info.value.get_description() |
591 | 594 | exc_info.value.args = () |
592 | 595 | assert "baz" not in exc_info.value.get_description() |
146 | 146 | |
147 | 147 | |
148 | 148 | @pytest.mark.skipif(watchdog is None, reason="Watchdog not installed.") |
149 | @pytest.mark.xfail( | |
150 | sys.version_info.major == 2 and sys.platform == "win32", | |
151 | reason="TODO fix test for Python 2 on Windows", | |
152 | ) | |
149 | 153 | def test_reloader_broken_imports(tmpdir, dev_server): |
150 | 154 | # We explicitly assert that the server reloads on change, even though in |
151 | 155 | # this case the import could've just been retried. This is to assert |
236 | 240 | |
237 | 241 | |
238 | 242 | @pytest.mark.skipif(watchdog is None, reason="Watchdog not installed.") |
243 | @pytest.mark.xfail( | |
244 | sys.version_info.major == 2 and sys.platform == "win32", | |
245 | reason="TODO fix test for Python 2 on Windows", | |
246 | ) | |
239 | 247 | def test_reloader_reports_correct_file(tmpdir, dev_server): |
240 | 248 | real_app = tmpdir.join("real_app.py") |
241 | 249 | real_app.write( |
202 | 202 | def test_import_string_attribute_error(tmpdir, monkeypatch): |
203 | 203 | monkeypatch.syspath_prepend(str(tmpdir)) |
204 | 204 | tmpdir.join("foo_test.py").write("from bar_test import value") |
205 | tmpdir.join("bar_test.py").write('raise AttributeError("screw you!")') | |
206 | with pytest.raises(AttributeError) as foo_exc: | |
205 | tmpdir.join("bar_test.py").write("raise AttributeError('bad')") | |
206 | ||
207 | with pytest.raises(AttributeError) as info: | |
207 | 208 | utils.import_string("foo_test") |
208 | assert "screw you!" in str(foo_exc) | |
209 | ||
210 | with pytest.raises(AttributeError) as bar_exc: | |
209 | ||
210 | assert "bad" in str(info.value) | |
211 | ||
212 | with pytest.raises(AttributeError) as info: | |
211 | 213 | utils.import_string("bar_test") |
212 | assert "screw you!" in str(bar_exc) | |
214 | ||
215 | assert "bad" in str(info.value) | |
213 | 216 | |
214 | 217 | |
215 | 218 | def test_find_modules(): |
362 | 362 | strict_eq(response.status, "0 wtf") |
363 | 363 | |
364 | 364 | # invalid status codes |
365 | with pytest.raises(ValueError) as empty_string_error: | |
365 | with pytest.raises(ValueError) as info: | |
366 | 366 | wrappers.BaseResponse(None, "") |
367 | assert "Empty status argument" in str(empty_string_error) | |
368 | ||
369 | with pytest.raises(TypeError) as invalid_type_error: | |
367 | ||
368 | assert "Empty status argument" in str(info.value) | |
369 | ||
370 | with pytest.raises(TypeError) as info: | |
370 | 371 | wrappers.BaseResponse(None, tuple()) |
371 | assert "Invalid status argument" in str(invalid_type_error) | |
372 | ||
373 | assert "Invalid status argument" in str(info.value) | |
372 | 374 | |
373 | 375 | |
374 | 376 | def test_type_forcing(): |
0 | 0 | [tox] |
1 | 1 | envlist = |
2 | py{37,36,35,34,27,py3,py} | |
3 | stylecheck | |
2 | py{37,36,35,27,py3,py} | |
3 | style | |
4 | 4 | docs-html |
5 | coverage-report | |
5 | coverage | |
6 | 6 | skip_missing_interpreters = true |
7 | 7 | |
8 | 8 | [testenv] |
18 | 18 | watchdog |
19 | 19 | commands = coverage run -p -m pytest --tb=short --basetemp={envtmpdir} {posargs} |
20 | 20 | |
21 | [testenv:stylecheck] | |
21 | [testenv:style] | |
22 | 22 | deps = pre-commit |
23 | 23 | skip_install = true |
24 | 24 | commands = pre-commit run --all-files --show-diff-on-failure |
30 | 30 | sphinx-issues |
31 | 31 | commands = sphinx-build -W -b html -d {envtmpdir}/doctrees docs {envtmpdir}/html |
32 | 32 | |
33 | [testenv:coverage-report] | |
33 | [testenv:coverage] | |
34 | 34 | deps = coverage |
35 | 35 | skip_install = true |
36 | 36 | commands = |
38 | 38 | coverage html |
39 | 39 | coverage report |
40 | 40 | |
41 | [testenv:codecov] | |
42 | passenv = CI TRAVIS TRAVIS_* APPVEYOR APPVEYOR_* | |
43 | deps = codecov | |
41 | [testenv:coverage-ci] | |
42 | deps = coverage | |
44 | 43 | skip_install = true |
45 | 44 | commands = |
46 | 45 | coverage combine |
47 | codecov | |
46 | coverage xml | |
48 | 47 | coverage report |