Codebase list python-werkzeug / e8c7062
Imported Upstream version 0.10.4+dfsg1 SVN-Git Migration 8 years ago
152 changed file(s) with 8169 addition(s) and 7726 deletion(s). Raw diff Collapse all Expand all
2525 - Ludvig Ericson
2626 - Kenneth Reitz
2727 - Daniel Neuhäuser
28 - Markus Unterwaditzer
29 - Joe Esposito <joe@joeyespo.com>
2830
2931 Contributors of code for werkzeug/examples are:
3032
00 Werkzeug Changelog
11 ==================
2
3 Version 0.10.4
4 --------------
5
6 (bugfix release, released on March 26th 2015)
7
8 - Re-release of 0.10.3 with packaging artifacts manually removed.
9
10 Version 0.10.3
11 --------------
12
13 (bugfix release, released on March 26th 2015)
14
15 - Re-release of 0.10.2 without packaging artifacts.
16
17 Version 0.10.2
18 --------------
19
20 (bugfix release, released on March 26th 2015)
21
22 - Fixed issue where ``empty`` could break third-party libraries that relied on
23 keyword arguments (pull request ``#675``)
24 - Improved ``Rule.empty`` by providing a ```get_empty_kwargs`` to allow setting
25 custom kwargs without having to override entire ``empty`` method. (pull
26 request ``#675``)
27 - Fixed ```extra_files``` parameter for reloader to not cause startup
28 to crash when included in server params
29 - Using `MultiDict` when building URLs is now not supported again. The behavior
30 introduced several regressions.
31 - Fix performance problems with stat-reloader (pull request ``#715``).
32
33 Version 0.10.1
34 --------------
35
36 (bugfix release, released on February 3rd 2015)
37
38 - Fixed regression with multiple query values for URLs (pull request ``#667``).
39 - Fix issues with eventlet's monkeypatching and the builtin server (pull
40 request ``#663``).
41
42 Version 0.10
43 ------------
44
45 Released on January 30th 2015, codename Bagger.
46
47 - Changed the error handling of and improved testsuite for the caches in
48 ``contrib.cache``.
49 - Fixed a bug on Python 3 when creating adhoc ssl contexts, due to `sys.maxint`
50 not being defined.
51 - Fixed a bug on Python 3, that caused
52 :func:`~werkzeug.serving.make_ssl_devcert` to fail with an exception.
53 - Added exceptions for 504 and 505.
54 - Added support for ChromeOS detection.
55 - Added UUID converter to the routing system.
56 - Added message that explains how to quit the server.
57 - Fixed a bug on Python 2, that caused ``len`` for
58 :class:`werkzeug.datastructures.CombinedMultiDict` to crash.
59 - Added support for stdlib pbkdf2 hmac if a compatible digest
60 is found.
61 - Ported testsuite to use ``py.test``.
62 - Minor optimizations to various middlewares (pull requests ``#496`` and
63 ``#571``).
64 - Use stdlib ``ssl`` module instead of ``OpenSSL`` for the builtin server
65 (issue ``#434``). This means that OpenSSL contexts are not supported anymore,
66 but instead ``ssl.SSLContext`` from the stdlib.
67 - Allow protocol-relative URLs when building external URLs.
68 - Fixed Atom syndication to print time zone offset for tz-aware datetime
69 objects (pull request ``#254``).
70 - Improved reloader to track added files and to recover from broken
71 sys.modules setups with syntax errors in packages.
72 - ``cache.RedisCache`` now supports arbitrary ``**kwargs`` for the redis
73 object.
74 - ``werkzeug.test.Client`` now uses the original request method when resolving
75 307 redirects (pull request ``#556``).
76 - ``werkzeug.datastructures.MIMEAccept`` now properly deals with mimetype
77 parameters (pull request ``#205``).
78 - ``werkzeug.datastructures.Accept`` now handles a quality of ``0`` as
79 intolerable, as per RFC 2616 (pull request ``#536``).
80 - ``werkzeug.urls.url_fix`` now properly encodes hostnames with ``idna``
81 encoding (issue ``#559``). It also doesn't crash on malformed URLs anymore
82 (issue ``#582``).
83 - ``werkzeug.routing.MapAdapter.match`` now recognizes the difference between
84 the path ``/`` and an empty one (issue ``#360``).
85 - The interactive debugger now tries to decode non-ascii filenames (issue
86 ``#469``).
87 - Increased default key size of generated SSL certificates to 1024 bits (issue
88 ``#611``).
89 - Added support for specifying a ``Response`` subclass to use when calling
90 :func:`~werkzeug.utils.redirect`\ .
91 - ``werkzeug.test.EnvironBuilder`` now doesn't use the request method anymore
92 to guess the content type, and purely relies on the ``form``, ``files`` and
93 ``input_stream`` properties (issue ``#620``).
94 - Added Symbian to the user agent platform list.
95 - Fixed make_conditional to respect automatically_set_content_length
96 - Unset ``Content-Length`` when writing to response.stream (issue ``#451``)
97 - ``wrappers.Request.method`` is now always uppercase, eliminating
98 inconsistencies of the WSGI environment (issue ``647``).
99 - ``routing.Rule.empty`` now works correctly with subclasses of ``Rule`` (pull
100 request ``#645``).
101 - Made map updating safe in light of concurrent updates.
102 - Allow multiple values for the same field for url building (issue ``#658``).
103
104 Version 0.9.7
105 -------------
106
107 (bugfix release, release date to be decided)
108
109 - Fix unicode problems in ``werkzeug.debug.tbtools``.
110 - Fix Python 3-compatibility problems in ``werkzeug.posixemulation``.
111 - Backport fix of fatal typo for ``ImmutableList`` (issue ``#492``).
112 - Make creation of the cache dir for ``FileSystemCache`` atomic (issue
113 ``#468``).
114 - Use native strings for memcached keys to work with Python 3 client (issue
115 ``#539``).
116 - Fix charset detection for ``werkzeug.debug.tbtools.Frame`` objects (issues
117 ``#547`` and ``#532``).
118 - Fix ``AttributeError`` masking in ``werkzeug.utils.import_string`` (issue
119 ``#182``).
120 - Explicitly shut down server (issue ``#519``).
121 - Fix timeouts greater than 2592000 being misinterpreted as UNIX timestamps in
122 ``werkzeug.contrib.cache.MemcachedCache`` (issue ``#533``).
123 - Fix bug where ``werkzeug.exceptions.abort`` would raise an arbitrary subclass
124 of the expected class (issue ``#422``).
125 - Fix broken ``jsrouting`` (due to removal of ``werkzeug.templates``)
126 - ``werkzeug.urls.url_fix`` now doesn't crash on malformed URLs anymore, but
127 returns them unmodified. This is a cheap workaround for ``#582``, the proper
128 fix is included in version 0.10.
129 - The repr of ``werkzeug.wrappers.Request`` doesn't crash on non-ASCII-values
130 anymore (pull request ``#466``).
131 - Fix bug in ``cache.RedisCache`` when combined with ``redis.StrictRedis``
132 object (pull request ``#583``).
133 - The ``qop`` parameter for ``WWW-Authenticate`` headers is now always quoted,
134 as required by RFC 2617 (issue ``#633``).
135 - Fix bug in ``werkzeug.contrib.cache.SimpleCache`` with Python 3 where add/set
136 may throw an exception when pruning old entries from the cache (pull request
137 ``#651``).
2138
3139 Version 0.9.6
4140 -------------
42178
43179 (bugfix release, released on July 25th 2013)
44180
45 - Restored beahvior of the ``data`` descriptor of the request class to pre 0.9
181 - Restored behavior of the ``data`` descriptor of the request class to pre 0.9
46182 behavior. This now also means that ``.data`` and ``.get_data()`` have
47183 different behavior. New code should use ``.get_data()`` always.
48184
70206 :func:`~werkzeug.local.release_local`.
71207 - Fixed an `AttributeError` that sometimes occurred when accessing the
72208 :attr:`werkzeug.wrappers.BaseResponse.is_streamed` attribute.
73
74209
75210 Version 0.9.1
76211 -------------
0 Copyright (c) 2011 by the Werkzeug Team, see AUTHORS for more details.
0 Copyright (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
11
22 Redistribution and use in source and binary forms, with or without
33 modification, are permitted provided that the following conditions are
00 include Makefile CHANGES LICENSE AUTHORS
11 recursive-include werkzeug/debug/shared *
2 recursive-include werkzeug/debug/templates *
3 recursive-include werkzeug/testsuite/res *
4 recursive-include werkzeug/testsuite/multipart *
52 recursive-include tests *
63 recursive-include docs *
4 recursive-include artwork *
75 recursive-include examples *
8 recursive-exclude docs *.pyc
9 recursive-exclude docs *.pyo
10 recursive-exclude tests *.pyc
11 recursive-exclude tests *.pyo
12 recursive-exclude examples *.pyc
13 recursive-exclude examples *.pyo
14 recursive-include artwork *
6
157 prune docs/_build
8 prune docs/_themes
9 global-exclude *.py[cdo] __pycache__ *.so *.pyd
1414 python scripts/make-release.py
1515
1616 test:
17 python run-tests.py
17 py.test --tb=native
1818
1919 tox-test:
2020 tox
2121
2222 coverage:
23 @(nosetests $(TEST_OPTIONS) --with-coverage --cover-package=werkzeug --cover-html --cover-html-dir=coverage_out $(TESTS))
23 @(coverage run --source=werkzeug --module py.test $(TEST_OPTIONS) $(TESTS))
2424
2525 doctest:
2626 @(cd docs; sphinx-build -b doctest . _build/doctest)
2929 $(MAKE) -C docs html dirhtml latex
3030 $(MAKE) -C docs/_build/latex all-pdf
3131 cd docs/_build/; mv html werkzeug-docs; zip -r werkzeug-docs.zip werkzeug-docs; mv werkzeug-docs html
32 rsync -a docs/_build/dirhtml/ pocoo.org:/var/www/werkzeug.pocoo.org/docs/
33 rsync -a docs/_build/latex/Werkzeug.pdf pocoo.org:/var/www/werkzeug.pocoo.org/docs/werkzeug-docs.pdf
34 rsync -a docs/_build/werkzeug-docs.zip pocoo.org:/var/www/werkzeug.pocoo.org/docs/werkzeug-docs.zip
32 rsync -a docs/_build/dirhtml/ flow.srv.pocoo.org:/srv/websites/werkzeug.pocoo.org/docs/
33 rsync -a docs/_build/latex/Werkzeug.pdf flow.srv.pocoo.org:/srv/websites/werkzeug.pocoo.org/docs/
34 rsync -a docs/_build/werkzeug-docs.zip flow.srv.pocoo.org:/srv/websites/werkzeug.pocoo.org/docs/werkzeug-docs.zip
0 Metadata-Version: 1.0
0 Metadata-Version: 1.1
11 Name: Werkzeug
2 Version: 0.9.6
2 Version: 0.10.4
33 Summary: The Swiss Army knife of Python web development
44 Home-page: http://werkzeug.pocoo.org/
55 Author: Armin Ronacher
1616
1717 Details and example applications are available on the
1818 `Werkzeug website <http://werkzeug.pocoo.org/>`_.
19
20
21 Branches
22 --------
23
24 +---------------------+--------------------------------------------------------------------------------+
25 | ``master`` | .. image:: https://travis-ci.org/mitsuhiko/werkzeug.svg?branch=master |
26 | | :target: https://travis-ci.org/mitsuhiko/werkzeug |
27 +---------------------+--------------------------------------------------------------------------------+
28 | ``0.9-maintenance`` | .. image:: https://travis-ci.org/mitsuhiko/werkzeug.svg?branch=0.9-maintenance |
29 | | :target: https://travis-ci.org/mitsuhiko/werkzeug |
30 +---------------------+--------------------------------------------------------------------------------+
+0
-72
Werkzeug.egg-info/PKG-INFO less more
0 Metadata-Version: 1.0
1 Name: Werkzeug
2 Version: 0.9.6
3 Summary: The Swiss Army knife of Python web development
4 Home-page: http://werkzeug.pocoo.org/
5 Author: Armin Ronacher
6 Author-email: armin.ronacher@active-4.com
7 License: BSD
8 Description:
9 Werkzeug
10 ========
11
12 Werkzeug started as simple collection of various utilities for WSGI
13 applications and has become one of the most advanced WSGI utility
14 modules. It includes a powerful debugger, full featured request and
15 response objects, HTTP utilities to handle entity tags, cache control
16 headers, HTTP dates, cookie handling, file uploads, a powerful URL
17 routing system and a bunch of community contributed addon modules.
18
19 Werkzeug is unicode aware and doesn't enforce a specific template
20 engine, database adapter or anything else. It doesn't even enforce
21 a specific way of handling requests and leaves all that up to the
22 developer. It's most useful for end user applications which should work
23 on as many server environments as possible (such as blogs, wikis,
24 bulletin boards, etc.).
25
26 Details and example applications are available on the
27 `Werkzeug website <http://werkzeug.pocoo.org/>`_.
28
29
30 Features
31 --------
32
33 - unicode awareness
34
35 - request and response objects
36
37 - various utility functions for dealing with HTTP headers such as
38 `Accept` and `Cache-Control` headers.
39
40 - thread local objects with proper cleanup at request end
41
42 - an interactive debugger
43
44 - A simple WSGI server with support for threading and forking
45 with an automatic reloader.
46
47 - a flexible URL routing system with REST support.
48
49 - fully WSGI compatible
50
51
52 Development Version
53 -------------------
54
55 The Werkzeug development version can be installed by cloning the git
56 repository from `github`_::
57
58 git clone git@github.com:mitsuhiko/werkzeug.git
59
60 .. _github: http://github.com/mitsuhiko/werkzeug
61
62 Platform: any
63 Classifier: Development Status :: 5 - Production/Stable
64 Classifier: Environment :: Web Environment
65 Classifier: Intended Audience :: Developers
66 Classifier: License :: OSI Approved :: BSD License
67 Classifier: Operating System :: OS Independent
68 Classifier: Programming Language :: Python
69 Classifier: Programming Language :: Python :: 3
70 Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
71 Classifier: Topic :: Software Development :: Libraries :: Python Modules
+0
-290
Werkzeug.egg-info/SOURCES.txt less more
0 AUTHORS
1 CHANGES
2 LICENSE
3 MANIFEST.in
4 Makefile
5 README.rst
6 setup.cfg
7 setup.py
8 Werkzeug.egg-info/PKG-INFO
9 Werkzeug.egg-info/SOURCES.txt
10 Werkzeug.egg-info/dependency_links.txt
11 Werkzeug.egg-info/not-zip-safe
12 Werkzeug.egg-info/top_level.txt
13 artwork/logo.png
14 artwork/logo.svg
15 docs/Makefile
16 docs/changes.rst
17 docs/conf.py
18 docs/contents.rst.inc
19 docs/datastructures.rst
20 docs/debug.rst
21 docs/exceptions.rst
22 docs/http.rst
23 docs/index.rst
24 docs/installation.rst
25 docs/latexindex.rst
26 docs/levels.rst
27 docs/local.rst
28 docs/logo.pdf
29 docs/make.bat
30 docs/makearchive.py
31 docs/middlewares.rst
32 docs/python3.rst
33 docs/quickstart.rst
34 docs/request_data.rst
35 docs/routing.rst
36 docs/serving.rst
37 docs/terms.rst
38 docs/test.rst
39 docs/transition.rst
40 docs/tutorial.rst
41 docs/unicode.rst
42 docs/utils.rst
43 docs/werkzeugext.py
44 docs/werkzeugstyle.sty
45 docs/wrappers.rst
46 docs/wsgi.rst
47 docs/_static/background.png
48 docs/_static/codebackground.png
49 docs/_static/contents.png
50 docs/_static/debug-screenshot.png
51 docs/_static/favicon.ico
52 docs/_static/header.png
53 docs/_static/navigation.png
54 docs/_static/navigation_active.png
55 docs/_static/shortly.png
56 docs/_static/shorty-screenshot.png
57 docs/_static/style.css
58 docs/_static/werkzeug.js
59 docs/_static/werkzeug.png
60 docs/_templates/sidebarintro.html
61 docs/_templates/sidebarlogo.html
62 docs/_themes/LICENSE
63 docs/_themes/README
64 docs/_themes/werkzeug_theme_support.py
65 docs/_themes/werkzeug/layout.html
66 docs/_themes/werkzeug/relations.html
67 docs/_themes/werkzeug/theme.conf
68 docs/_themes/werkzeug/static/werkzeug.css_t
69 docs/contrib/atom.rst
70 docs/contrib/cache.rst
71 docs/contrib/fixers.rst
72 docs/contrib/index.rst
73 docs/contrib/iterio.rst
74 docs/contrib/lint.rst
75 docs/contrib/profiler.rst
76 docs/contrib/securecookie.rst
77 docs/contrib/sessions.rst
78 docs/contrib/wrappers.rst
79 docs/deployment/cgi.rst
80 docs/deployment/fastcgi.rst
81 docs/deployment/index.rst
82 docs/deployment/mod_wsgi.rst
83 docs/deployment/proxying.rst
84 examples/README
85 examples/cookieauth.py
86 examples/httpbasicauth.py
87 examples/manage-coolmagic.py
88 examples/manage-couchy.py
89 examples/manage-cupoftee.py
90 examples/manage-i18nurls.py
91 examples/manage-plnt.py
92 examples/manage-shorty.py
93 examples/manage-simplewiki.py
94 examples/manage-webpylike.py
95 examples/upload.py
96 examples/contrib/README
97 examples/contrib/securecookie.py
98 examples/contrib/sessions.py
99 examples/coolmagic/__init__.py
100 examples/coolmagic/application.py
101 examples/coolmagic/helpers.py
102 examples/coolmagic/utils.py
103 examples/coolmagic/public/style.css
104 examples/coolmagic/templates/layout.html
105 examples/coolmagic/templates/static/about.html
106 examples/coolmagic/templates/static/index.html
107 examples/coolmagic/templates/static/not_found.html
108 examples/coolmagic/views/__init__.py
109 examples/coolmagic/views/static.py
110 examples/couchy/README
111 examples/couchy/__init__.py
112 examples/couchy/application.py
113 examples/couchy/models.py
114 examples/couchy/utils.py
115 examples/couchy/views.py
116 examples/couchy/static/style.css
117 examples/couchy/templates/display.html
118 examples/couchy/templates/layout.html
119 examples/couchy/templates/list.html
120 examples/couchy/templates/new.html
121 examples/couchy/templates/not_found.html
122 examples/cupoftee/__init__.py
123 examples/cupoftee/application.py
124 examples/cupoftee/db.py
125 examples/cupoftee/network.py
126 examples/cupoftee/pages.py
127 examples/cupoftee/utils.py
128 examples/cupoftee/shared/content.png
129 examples/cupoftee/shared/down.png
130 examples/cupoftee/shared/favicon.ico
131 examples/cupoftee/shared/header.png
132 examples/cupoftee/shared/logo.png
133 examples/cupoftee/shared/style.css
134 examples/cupoftee/shared/up.png
135 examples/cupoftee/templates/layout.html
136 examples/cupoftee/templates/missingpage.html
137 examples/cupoftee/templates/search.html
138 examples/cupoftee/templates/server.html
139 examples/cupoftee/templates/serverlist.html
140 examples/i18nurls/__init__.py
141 examples/i18nurls/application.py
142 examples/i18nurls/urls.py
143 examples/i18nurls/views.py
144 examples/i18nurls/templates/about.html
145 examples/i18nurls/templates/blog.html
146 examples/i18nurls/templates/index.html
147 examples/i18nurls/templates/layout.html
148 examples/partial/README
149 examples/partial/complex_routing.py
150 examples/plnt/__init__.py
151 examples/plnt/database.py
152 examples/plnt/sync.py
153 examples/plnt/utils.py
154 examples/plnt/views.py
155 examples/plnt/webapp.py
156 examples/plnt/shared/style.css
157 examples/plnt/templates/about.html
158 examples/plnt/templates/index.html
159 examples/plnt/templates/layout.html
160 examples/shortly/shortly.py
161 examples/shortly/static/style.css
162 examples/shortly/templates/404.html
163 examples/shortly/templates/layout.html
164 examples/shortly/templates/new_url.html
165 examples/shortly/templates/short_link_details.html
166 examples/shorty/__init__.py
167 examples/shorty/application.py
168 examples/shorty/models.py
169 examples/shorty/utils.py
170 examples/shorty/views.py
171 examples/shorty/static/style.css
172 examples/shorty/templates/display.html
173 examples/shorty/templates/layout.html
174 examples/shorty/templates/list.html
175 examples/shorty/templates/new.html
176 examples/shorty/templates/not_found.html
177 examples/simplewiki/__init__.py
178 examples/simplewiki/actions.py
179 examples/simplewiki/application.py
180 examples/simplewiki/database.py
181 examples/simplewiki/specialpages.py
182 examples/simplewiki/utils.py
183 examples/simplewiki/shared/style.css
184 examples/simplewiki/templates/action_diff.html
185 examples/simplewiki/templates/action_edit.html
186 examples/simplewiki/templates/action_log.html
187 examples/simplewiki/templates/action_revert.html
188 examples/simplewiki/templates/action_show.html
189 examples/simplewiki/templates/layout.html
190 examples/simplewiki/templates/macros.xml
191 examples/simplewiki/templates/missing_action.html
192 examples/simplewiki/templates/page_index.html
193 examples/simplewiki/templates/page_missing.html
194 examples/simplewiki/templates/recent_changes.html
195 examples/webpylike/example.py
196 examples/webpylike/webpylike.py
197 werkzeug/__init__.py
198 werkzeug/_compat.py
199 werkzeug/_internal.py
200 werkzeug/datastructures.py
201 werkzeug/exceptions.py
202 werkzeug/formparser.py
203 werkzeug/http.py
204 werkzeug/local.py
205 werkzeug/posixemulation.py
206 werkzeug/routing.py
207 werkzeug/script.py
208 werkzeug/security.py
209 werkzeug/serving.py
210 werkzeug/test.py
211 werkzeug/testapp.py
212 werkzeug/urls.py
213 werkzeug/useragents.py
214 werkzeug/utils.py
215 werkzeug/wrappers.py
216 werkzeug/wsgi.py
217 werkzeug/contrib/__init__.py
218 werkzeug/contrib/atom.py
219 werkzeug/contrib/cache.py
220 werkzeug/contrib/fixers.py
221 werkzeug/contrib/iterio.py
222 werkzeug/contrib/jsrouting.py
223 werkzeug/contrib/limiter.py
224 werkzeug/contrib/lint.py
225 werkzeug/contrib/profiler.py
226 werkzeug/contrib/securecookie.py
227 werkzeug/contrib/sessions.py
228 werkzeug/contrib/testtools.py
229 werkzeug/contrib/wrappers.py
230 werkzeug/debug/__init__.py
231 werkzeug/debug/console.py
232 werkzeug/debug/repr.py
233 werkzeug/debug/tbtools.py
234 werkzeug/debug/shared/FONT_LICENSE
235 werkzeug/debug/shared/console.png
236 werkzeug/debug/shared/debugger.js
237 werkzeug/debug/shared/jquery.js
238 werkzeug/debug/shared/less.png
239 werkzeug/debug/shared/more.png
240 werkzeug/debug/shared/source.png
241 werkzeug/debug/shared/style.css
242 werkzeug/debug/shared/ubuntu.ttf
243 werkzeug/testsuite/__init__.py
244 werkzeug/testsuite/compat.py
245 werkzeug/testsuite/datastructures.py
246 werkzeug/testsuite/debug.py
247 werkzeug/testsuite/exceptions.py
248 werkzeug/testsuite/formparser.py
249 werkzeug/testsuite/http.py
250 werkzeug/testsuite/internal.py
251 werkzeug/testsuite/local.py
252 werkzeug/testsuite/routing.py
253 werkzeug/testsuite/security.py
254 werkzeug/testsuite/serving.py
255 werkzeug/testsuite/test.py
256 werkzeug/testsuite/urls.py
257 werkzeug/testsuite/utils.py
258 werkzeug/testsuite/wrappers.py
259 werkzeug/testsuite/wsgi.py
260 werkzeug/testsuite/contrib/__init__.py
261 werkzeug/testsuite/contrib/cache.py
262 werkzeug/testsuite/contrib/fixers.py
263 werkzeug/testsuite/contrib/iterio.py
264 werkzeug/testsuite/contrib/securecookie.py
265 werkzeug/testsuite/contrib/sessions.py
266 werkzeug/testsuite/contrib/wrappers.py
267 werkzeug/testsuite/multipart/collect.py
268 werkzeug/testsuite/multipart/ie7_full_path_request.txt
269 werkzeug/testsuite/multipart/firefox3-2png1txt/file1.png
270 werkzeug/testsuite/multipart/firefox3-2png1txt/file2.png
271 werkzeug/testsuite/multipart/firefox3-2png1txt/request.txt
272 werkzeug/testsuite/multipart/firefox3-2png1txt/text.txt
273 werkzeug/testsuite/multipart/firefox3-2pnglongtext/file1.png
274 werkzeug/testsuite/multipart/firefox3-2pnglongtext/file2.png
275 werkzeug/testsuite/multipart/firefox3-2pnglongtext/request.txt
276 werkzeug/testsuite/multipart/firefox3-2pnglongtext/text.txt
277 werkzeug/testsuite/multipart/ie6-2png1txt/file1.png
278 werkzeug/testsuite/multipart/ie6-2png1txt/file2.png
279 werkzeug/testsuite/multipart/ie6-2png1txt/request.txt
280 werkzeug/testsuite/multipart/ie6-2png1txt/text.txt
281 werkzeug/testsuite/multipart/opera8-2png1txt/file1.png
282 werkzeug/testsuite/multipart/opera8-2png1txt/file2.png
283 werkzeug/testsuite/multipart/opera8-2png1txt/request.txt
284 werkzeug/testsuite/multipart/opera8-2png1txt/text.txt
285 werkzeug/testsuite/multipart/webkit3-2png1txt/file1.png
286 werkzeug/testsuite/multipart/webkit3-2png1txt/file2.png
287 werkzeug/testsuite/multipart/webkit3-2png1txt/request.txt
288 werkzeug/testsuite/multipart/webkit3-2png1txt/text.txt
289 werkzeug/testsuite/res/test.txt
+0
-1
Werkzeug.egg-info/dependency_links.txt less more
0
+0
-1
Werkzeug.egg-info/not-zip-safe less more
0
+0
-1
Werkzeug.egg-info/top_level.txt less more
0 werkzeug
3838 http
3939 datastructures
4040 utils
41 urls
4142 local
4243 middlewares
4344 exceptions
126126
127127 A few seconds later you are good to go.
128128
129 .. _download page: http://werkzeug.pocoo.org/download
129 .. _download page: https://pypi.python.org/pypi/Werkzeug
130130 .. _setuptools: http://peak.telecommunity.com/DevCenter/setuptools
131131 .. _easy_install: http://peak.telecommunity.com/DevCenter/EasyInstall
132132 .. _Git: http://git-scm.org/
254254 '400 BAD REQUEST'
255255
256256 As you can see attributes work in both directions. So you can set both
257 :attr:`~BaseResponse.status` and `~BaseResponse.status_code` and the
257 :attr:`~BaseResponse.status` and :attr:`~BaseResponse.status_code` and the
258258 change will be reflected to the other.
259259
260260 Also common headers are exposed as attributes or with methods to set /
102102
103103 .. autoclass:: FloatConverter
104104
105 .. autoclass:: UUIDConverter
106
105107
106108 Maps, Rules and Adapters
107109 ========================
2525
2626 .. autofunction:: run_simple
2727
28 .. autofunction:: is_running_from_reloader
29
2830 .. autofunction:: make_ssl_devcert
2931
3032 .. admonition:: Information
3335 It was designed especially for development purposes and performs poorly
3436 under high load. For deployment setups have a look at the
3537 :ref:`deployment` pages.
38
39 .. _reloader:
40
41 Reloader
42 --------
43
44 .. versionchanged:: 0.10
45
46 The Werkzeug reloader constantly monitors modules and paths of your web
47 application, and restarts the server if any of the observed files change.
48
49 Since version 0.10, there are two backends the reloader supports: ``stat`` and
50 ``watchdog``.
51
52 - The default ``stat`` backend simply checks the ``mtime`` of all files in a
53 regular interval. This is sufficient for most cases, however, it is known to
54 drain a laptop's battery.
55
56 - The ``watchdog`` backend uses filesystem events, and is much faster than
57 ``stat``. It requires the `watchdog <https://pypi.python.org/pypi/watchdog>`_
58 module to be installed.
59
60 If ``watchdog`` is installed and available it will automatically be used
61 instead of the builtin ``stat`` reloader.
62
63 To switch between the backends you can use the `reloader_type` parameter of the
64 :func:`run_simple` function. ``'stat'`` sets it to the default stat based
65 polling and ``'watchdog'`` forces it to the watchdog backend.
66
67 .. note::
68
69 Some edge cases, like modules that failed to import correctly, are not
70 handled by the stat reloader for performance reasons. The watchdog reloader
71 monitors such files too.
3672
3773 Virtual Hosts
3874 -------------
114150
115151 .. versionadded:: 0.6
116152
117 The builtin server supports SSL for testing purposes. If an SSL context
118 is provided it will be used. That means a server can either run in HTTP
119 or HTTPS mode, but not both. This feature requires the Python OpenSSL
120 library.
153 The builtin server supports SSL for testing purposes. If an SSL context is
154 provided it will be used. That means a server can either run in HTTP or HTTPS
155 mode, but not both.
121156
122157 Quickstart
123158 ``````````
134169 ('/path/to/the/key.crt', '/path/to/the/key.key')
135170
136171 2. Now this tuple can be passed as ``ssl_context`` to the
137 :func:`run_simple` method:
138
139 run_simple('localhost', 4000, application,
140 ssl_context=('/path/to/the/key.crt',
141 '/path/to/the/key.key'))
172 :func:`run_simple` method::
173
174 run_simple('localhost', 4000, application,
175 ssl_context=('/path/to/the/key.crt',
176 '/path/to/the/key.key'))
142177
143178 You will have to acknowledge the certificate in your browser once then.
144179
145180 Loading Contexts by Hand
146181 ````````````````````````
147182
148 Instead of using a tuple as ``ssl_context`` you can also create the
149 context programmatically. This way you have better control over it::
150
151 from OpenSSL import SSL
152 ctx = SSL.Context(SSL.SSLv23_METHOD)
153 ctx.use_privatekey_file('ssl.key')
154 ctx.use_certificate_file('ssl.cert')
183 In Python 2.7.9 and 3+ you also have the option to use a ``ssl.SSLContext``
184 object instead of a simple tuple. This way you have better control over the SSL
185 behavior of Werkzeug's builtin server::
186
187 import ssl
188 ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
189 ctx.load_cert_chain('ssl.cert', 'ssl.key')
155190 run_simple('localhost', 4000, application, ssl_context=ctx)
191
192
193 .. versionchanged 0.10:: ``OpenSSL`` contexts are not supported anymore.
156194
157195 Generating Certificates
158196 ```````````````````````
177215 certificate each time the server is reloaded. Adhoc certificates are
178216 discouraged because modern browsers do a bad job at supporting them for
179217 security reasons.
218
219 This feature requires the pyOpenSSL library to be installed.
1212
1313 WSGI a specification for Python web applications Werkzeug follows. It was
1414 specified in the :pep:`333` and is widely supported. Unlike previous solutions
15 it gurantees that web applications, servers and utilties can work together.
15 it guarantees that web applications, servers and utilties can work together.
1616
1717 Response Object
1818 ---------------
145145
146146 .. autoclass:: Client
147147
148 .. automethod:: open(options)
148 .. automethod:: open
149149
150 .. automethod:: get(options)
150 Shortcut methods are available for many HTTP methods:
151151
152 .. automethod:: post(options)
152 .. automethod:: get
153153
154 .. automethod:: put(options)
154 .. automethod:: patch
155155
156 .. automethod:: delete(options)
156 .. automethod:: post
157157
158 .. automethod:: head(options)
158 .. automethod:: head
159
160 .. automethod:: put
161
162 .. automethod:: delete
163
164 .. automethod:: options
165
166 .. automethod:: trace
167
159168
160169 .. autofunction:: create_environ([options])
161170
22
33 Werkzeug originally had a magical import system hook that enabled
44 everything to be imported from one module and still loading the actual
5 implementations lazily as necessary. Unfortunately this turned out be
5 implementations lazily as necessary. Unfortunately this turned out to be
66 slow and also unreliable on alternative Python implementations and
77 Google's App Engine.
88
1919
2020 If you are on Ubuntu or Debian, you can use apt-get::
2121
22 sudo apt-get install redis
22 sudo apt-get install redis-server
2323
2424 Redis was developed for UNIX systems and was never really designed to
2525 work on Windows. For development purposes, the unofficial ports however
0 ===========
1 URL Helpers
2 ===========
3
4 .. automodule:: werkzeug.urls
5 :members:
4848 URL Helpers
4949 ===========
5050
51 .. module:: werkzeug.urls
52
53 .. autoclass:: Href
54
55 .. autofunction:: url_decode
56
57 .. autofunction:: url_decode_stream
58
59 .. autofunction:: url_encode
60
61 .. autofunction:: url_encode_stream
62
63 .. autofunction:: url_quote
64
65 .. autofunction:: url_quote_plus
66
67 .. autofunction:: url_unquote
68
69 .. autofunction:: url_unquote_plus
70
71 .. autofunction:: url_fix
72
73 .. autofunction:: uri_to_iri
74
75 .. autofunction:: iri_to_uri
76
51 Please refer to :doc:`urls`.
7752
7853 UserAgent Parsing
7954 =================
55 [aliases]
66 release = egg_info -RDb ''
77
8 [pytest]
9 norecursedirs = .* _build *.eggs
10
11 [bdist_wheel]
12 universal = 1
13
5353 .. _github: http://github.com/mitsuhiko/werkzeug
5454 """
5555 try:
56 from setuptools import setup
56 from setuptools import setup, Command
5757 except ImportError:
58 from distutils.core import setup
58 from distutils.core import setup, Command
59
60
61 class TestCommand(Command):
62 user_options = []
63
64 def initialize_options(self):
65 pass
66 def finalize_options(self):
67 pass
68
69 def run(self):
70 import pytest
71 pytest.cmdline.main(args=[])
5972
6073
6174 setup(
6275 name='Werkzeug',
63 version='0.9.6',
76 version='0.10.4',
6477 url='http://werkzeug.pocoo.org/',
6578 license='BSD',
6679 author='Armin Ronacher',
7891 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
7992 'Topic :: Software Development :: Libraries :: Python Modules'
8093 ],
81 packages=['werkzeug', 'werkzeug.debug', 'werkzeug.contrib',
82 'werkzeug.testsuite', 'werkzeug.testsuite.contrib'],
94 packages=['werkzeug', 'werkzeug.debug', 'werkzeug.contrib'],
95 cmdclass=dict(test=TestCommand),
8396 include_package_data=True,
84 test_suite='werkzeug.testsuite.suite',
8597 zip_safe=False,
8698 platforms='any'
8799 )
0 # -*- coding: utf-8 -*-
1 """
2 tests
3 ~~~~~
4
5 Contains all test Werkzeug tests.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 from __future__ import with_statement
11
12 from werkzeug._compat import text_type
13
14
15 def strict_eq(x, y):
16 '''Equality test bypassing the implicit string conversion in Python 2'''
17 __tracebackhide__ = True
18 assert x == y
19 assert issubclass(type(x), type(y)) or issubclass(type(y), type(x))
20 if isinstance(x, dict) and isinstance(y, dict):
21 x = sorted(x.items())
22 y = sorted(y.items())
23 elif isinstance(x, set) and isinstance(y, set):
24 x = sorted(x)
25 y = sorted(y)
26 assert repr(x) == repr(y)
0 # -*- coding: utf-8 -*-
1 """
2 tests.conftest
3 ~~~~~~~~~~~~~~
4
5 :copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
6 :license: BSD, see LICENSE for more details.
7 """
8
9 from __future__ import with_statement
10
11 import os
12 import signal
13 import sys
14 import textwrap
15 import time
16
17 import requests
18 import pytest
19
20 from werkzeug import serving
21 from werkzeug.utils import cached_property
22 from werkzeug._compat import to_bytes
23
24
25 try:
26 __import__('pytest_xprocess')
27 except ImportError:
28 @pytest.fixture
29 def subprocess():
30 pytest.skip('pytest-xprocess not installed.')
31 else:
32 @pytest.fixture
33 def subprocess(xprocess):
34 return xprocess
35
36
37 def _patch_reloader_loop():
38 def f(x):
39 print('reloader loop finished')
40 return time.sleep(x)
41
42 import werkzeug._reloader
43 werkzeug._reloader.ReloaderLoop._sleep = staticmethod(f)
44
45
46 def _get_pid_middleware(f):
47 def inner(environ, start_response):
48 if environ['PATH_INFO'] == '/_getpid':
49 start_response('200 OK', [('Content-Type', 'text/plain')])
50 return [to_bytes(str(os.getpid()))]
51 return f(environ, start_response)
52 return inner
53
54
55 def _dev_server():
56 _patch_reloader_loop()
57 sys.path.insert(0, sys.argv[1])
58 import testsuite_app
59 app = _get_pid_middleware(testsuite_app.app)
60 serving.run_simple(hostname='localhost', application=app,
61 **testsuite_app.kwargs)
62
63 if __name__ == '__main__':
64 _dev_server()
65
66
67 class _ServerInfo(object):
68 xprocess = None
69 addr = None
70 url = None
71 port = None
72 last_pid = None
73
74 def __init__(self, xprocess, addr, url, port):
75 self.xprocess = xprocess
76 self.addr = addr
77 self.url = url
78 self.port = port
79
80 @cached_property
81 def logfile(self):
82 return self.xprocess.getinfo('dev_server').logpath.open()
83
84 def request_pid(self):
85 for i in range(20):
86 time.sleep(0.1 * i)
87 try:
88 self.last_pid = int(requests.get(self.url + '/_getpid',
89 verify=False).text)
90 return self.last_pid
91 except Exception as e: # urllib also raises socketerrors
92 print(self.url)
93 print(e)
94 return False
95
96 def wait_for_reloader(self):
97 old_pid = self.last_pid
98 for i in range(20):
99 time.sleep(0.1 * i)
100 new_pid = self.request_pid()
101 if not new_pid:
102 raise RuntimeError('Server is down.')
103 if self.request_pid() != old_pid:
104 return
105 raise RuntimeError('Server did not reload.')
106
107 def wait_for_reloader_loop(self):
108 for i in range(20):
109 time.sleep(0.1 * i)
110 line = self.logfile.readline()
111 if 'reloader loop finished' in line:
112 return
113
114
115 @pytest.fixture
116 def dev_server(tmpdir, subprocess, request, monkeypatch):
117 '''Run werkzeug.serving.run_simple in its own process.
118
119 :param application: String for the module that will be created. The module
120 must have a global ``app`` object, a ``kwargs`` dict is also available
121 whose values will be passed to ``run_simple``.
122 '''
123 def run_dev_server(application):
124 app_pkg = tmpdir.mkdir('testsuite_app')
125 appfile = app_pkg.join('__init__.py')
126 appfile.write('\n\n'.join((
127 'kwargs = dict(port=5001)',
128 textwrap.dedent(application)
129 )))
130
131 monkeypatch.delitem(sys.modules, 'testsuite_app', raising=False)
132 monkeypatch.syspath_prepend(str(tmpdir))
133 import testsuite_app
134 port = testsuite_app.kwargs['port']
135
136 if testsuite_app.kwargs.get('ssl_context', None):
137 url_base = 'https://localhost:{0}'.format(port)
138 else:
139 url_base = 'http://localhost:{0}'.format(port)
140
141 info = _ServerInfo(
142 subprocess,
143 'localhost:{0}'.format(port),
144 url_base,
145 port
146 )
147
148 def preparefunc(cwd):
149 args = [sys.executable, __file__, str(tmpdir)]
150 return info.request_pid, args
151
152 subprocess.ensure('dev_server', preparefunc, restart=True)
153
154 def teardown():
155 # Killing the process group that runs the server, not just the
156 # parent process attached. xprocess is confused about Werkzeug's
157 # reloader and won't help here.
158 pid = info.last_pid
159 os.killpg(os.getpgid(pid), signal.SIGTERM)
160 request.addfinalizer(teardown)
161
162 return info
163
164 return run_dev_server
0 # -*- coding: utf-8 -*-
1 """
2 tests.contrib
3 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5 Tests the contrib modules.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
0 # -*- coding: utf-8 -*-
1 """
2 tests.atom
3 ~~~~~~~~~~
4
5 Tests the cache system
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import datetime
11
12 from werkzeug.contrib.atom import format_iso8601
13
14
15 def test_format_iso8601():
16 # naive datetime should be treated as utc
17 dt = datetime.datetime(2014, 8, 31, 2, 5, 6)
18 assert format_iso8601(dt) == '2014-08-31T02:05:06Z'
19
20 # tz-aware datetime
21 dt = datetime.datetime(2014, 8, 31, 11, 5, 6, tzinfo=KST())
22 assert format_iso8601(dt) == '2014-08-31T11:05:06+09:00'
23
24
25 class KST(datetime.tzinfo):
26 """KST implementation for test_format_iso8601()."""
27
28 def utcoffset(self, dt):
29 return datetime.timedelta(hours=9)
30
31 def tzname(self, dt):
32 return 'KST'
33
34 def dst(self, dt):
35 return datetime.timedelta(0)
0 # -*- coding: utf-8 -*-
1 """
2 tests.cache
3 ~~~~~~~~~~~
4
5 Tests the cache system
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import pytest
11 import os
12
13 from werkzeug.contrib import cache
14
15 try:
16 import redis
17 except ImportError:
18 redis = None
19
20 try:
21 import pylibmc as memcache
22 except ImportError:
23 try:
24 from google.appengine.api import memcache
25 except ImportError:
26 try:
27 import memcache
28 except ImportError:
29 memcache = None
30
31
32 class CacheTests(object):
33 _can_use_fast_sleep = True
34
35 @pytest.fixture
36 def make_cache(self):
37 '''Return a cache class or factory.'''
38 raise NotImplementedError()
39
40 @pytest.fixture
41 def fast_sleep(self, monkeypatch):
42 if self._can_use_fast_sleep:
43 def sleep(delta):
44 orig_time = cache.time
45 monkeypatch.setattr(cache, 'time', lambda: orig_time() + delta)
46
47 return sleep
48 else:
49 import time
50 return time.sleep
51
52 @pytest.fixture
53 def c(self, make_cache):
54 '''Return a cache instance.'''
55 return make_cache()
56
57 def test_generic_get_dict(self, c):
58 assert c.set('a', 'a')
59 assert c.set('b', 'b')
60 d = c.get_dict('a', 'b')
61 assert 'a' in d
62 assert 'a' == d['a']
63 assert 'b' in d
64 assert 'b' == d['b']
65
66 def test_generic_set_get(self, c):
67 for i in range(3):
68 assert c.set(str(i), i * i)
69 for i in range(3):
70 result = c.get(str(i))
71 assert result == i * i, result
72
73 def test_generic_get_set(self, c):
74 assert c.set('foo', ['bar'])
75 assert c.get('foo') == ['bar']
76
77 def test_generic_get_many(self, c):
78 assert c.set('foo', ['bar'])
79 assert c.set('spam', 'eggs')
80 assert list(c.get_many('foo', 'spam')) == [['bar'], 'eggs']
81
82 def test_generic_set_many(self, c):
83 assert c.set_many({'foo': 'bar', 'spam': ['eggs']})
84 assert c.get('foo') == 'bar'
85 assert c.get('spam') == ['eggs']
86
87 def test_generic_expire(self, c, fast_sleep):
88 assert c.set('foo', 'bar', 1)
89 fast_sleep(2)
90 assert c.get('foo') is None
91
92 def test_generic_add(self, c):
93 # sanity check that add() works like set()
94 assert c.add('foo', 'bar')
95 assert c.get('foo') == 'bar'
96 assert not c.add('foo', 'qux')
97 assert c.get('foo') == 'bar'
98
99 def test_generic_delete(self, c):
100 assert c.add('foo', 'bar')
101 assert c.get('foo') == 'bar'
102 assert c.delete('foo')
103 assert c.get('foo') is None
104
105 def test_generic_delete_many(self, c):
106 assert c.add('foo', 'bar')
107 assert c.add('spam', 'eggs')
108 assert c.delete_many('foo', 'spam')
109 assert c.get('foo') is None
110 assert c.get('spam') is None
111
112 def test_generic_inc_dec(self, c):
113 assert c.set('foo', 1)
114 assert c.inc('foo') == c.get('foo') == 2
115 assert c.dec('foo') == c.get('foo') == 1
116 assert c.delete('foo')
117
118 def test_generic_true_false(self, c):
119 assert c.set('foo', True)
120 assert c.get('foo') == True
121 assert c.set('bar', False)
122 assert c.get('bar') == False
123
124 def test_purge(self):
125 c = cache.SimpleCache(threshold=2)
126 c.set('a', 'a')
127 c.set('b', 'b')
128 c.set('c', 'c')
129 c.set('d', 'd')
130 # Cache purges old items *before* it sets new ones.
131 assert len(c._cache) == 3
132
133
134 class TestSimpleCache(CacheTests):
135 @pytest.fixture
136 def make_cache(self):
137 return cache.SimpleCache
138
139
140 class TestFileSystemCache(CacheTests):
141 @pytest.fixture
142 def make_cache(self, tmpdir):
143 return lambda **kw: cache.FileSystemCache(cache_dir=str(tmpdir), **kw)
144
145 def test_filesystemcache_prune(self, make_cache):
146 THRESHOLD = 13
147 c = make_cache(threshold=THRESHOLD)
148 for i in range(2 * THRESHOLD):
149 assert c.set(str(i), i)
150 cache_files = os.listdir(c._path)
151 assert len(cache_files) <= THRESHOLD
152
153 def test_filesystemcache_clear(self, c):
154 assert c.set('foo', 'bar')
155 cache_files = os.listdir(c._path)
156 assert len(cache_files) == 1
157 assert c.clear()
158 cache_files = os.listdir(c._path)
159 assert len(cache_files) == 0
160
161
162 # Don't use pytest marker
163 # https://bitbucket.org/hpk42/pytest/issue/568
164 if redis is not None:
165 class TestRedisCache(CacheTests):
166 _can_use_fast_sleep = False
167
168 @pytest.fixture(params=[
169 ([], dict()),
170 ([redis.Redis()], dict()),
171 ([redis.StrictRedis()], dict())
172 ])
173 def make_cache(self, xprocess, request):
174 def preparefunc(cwd):
175 return 'server is now ready', ['redis-server']
176
177 xprocess.ensure('redis_server', preparefunc)
178 args, kwargs = request.param
179 c = cache.RedisCache(*args, key_prefix='werkzeug-test-case:',
180 **kwargs)
181 request.addfinalizer(c.clear)
182 return lambda: c
183
184 def test_compat(self, c):
185 assert c._client.set(c.key_prefix + 'foo', 'Awesome')
186 assert c.get('foo') == b'Awesome'
187 assert c._client.set(c.key_prefix + 'foo', '42')
188 assert c.get('foo') == 42
189
190
191 # Don't use pytest marker
192 # https://bitbucket.org/hpk42/pytest/issue/568
193 if memcache is not None:
194 class TestMemcachedCache(CacheTests):
195 _can_use_fast_sleep = False
196
197 @pytest.fixture
198 def make_cache(self, xprocess, request):
199 def preparefunc(cwd):
200 return '', ['memcached']
201
202 xprocess.ensure('memcached', preparefunc)
203 c = cache.MemcachedCache(key_prefix='werkzeug-test-case:')
204 request.addfinalizer(c.clear)
205 return lambda: c
206
207 def test_compat(self, c):
208 assert c._client.set(c.key_prefix + 'foo', 'bar')
209 assert c.get('foo') == 'bar'
210
211 def test_huge_timeouts(self, c):
212 # Timeouts greater than epoch are interpreted as POSIX timestamps
213 # (i.e. not relative to now, but relative to epoch)
214 import random
215 epoch = 2592000
216 timeout = epoch + random.random() * 100
217 c.set('foo', 'bar', timeout)
218 assert c.get('foo') == 'bar'
0 # -*- coding: utf-8 -*-
1 """
2 tests.fixers
3 ~~~~~~~~~~~~
4
5 Server / Browser fixers.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 from tests import strict_eq
11 from werkzeug.datastructures import ResponseCacheControl
12 from werkzeug.http import parse_cache_control_header
13
14 from werkzeug.test import create_environ, Client
15 from werkzeug.wrappers import Request, Response
16 from werkzeug.contrib import fixers
17 from werkzeug.utils import redirect
18
19
20 @Request.application
21 def path_check_app(request):
22 return Response('PATH_INFO: %s\nSCRIPT_NAME: %s' % (
23 request.environ.get('PATH_INFO', ''),
24 request.environ.get('SCRIPT_NAME', '')
25 ))
26
27
28 class TestServerFixer(object):
29
30 def test_cgi_root_fix(self):
31 app = fixers.CGIRootFix(path_check_app)
32 response = Response.from_app(app, dict(create_environ(),
33 SCRIPT_NAME='/foo',
34 PATH_INFO='/bar',
35 SERVER_SOFTWARE='lighttpd/1.4.27'
36 ))
37 assert response.get_data() == b'PATH_INFO: /foo/bar\nSCRIPT_NAME: '
38
39 def test_cgi_root_fix_custom_app_root(self):
40 app = fixers.CGIRootFix(path_check_app, app_root='/baz/poop/')
41 response = Response.from_app(app, dict(create_environ(),
42 SCRIPT_NAME='/foo',
43 PATH_INFO='/bar'
44 ))
45 assert response.get_data() == b'PATH_INFO: /foo/bar\nSCRIPT_NAME: baz/poop'
46
47 def test_path_info_from_request_uri_fix(self):
48 app = fixers.PathInfoFromRequestUriFix(path_check_app)
49 for key in 'REQUEST_URI', 'REQUEST_URL', 'UNENCODED_URL':
50 env = dict(create_environ(), SCRIPT_NAME='/test', PATH_INFO='/?????')
51 env[key] = '/test/foo%25bar?drop=this'
52 response = Response.from_app(app, env)
53 assert response.get_data() == b'PATH_INFO: /foo%bar\nSCRIPT_NAME: /test'
54
55 def test_proxy_fix(self):
56 @Request.application
57 def app(request):
58 return Response('%s|%s' % (
59 request.remote_addr,
60 # do not use request.host as this fixes too :)
61 request.environ['HTTP_HOST']
62 ))
63 app = fixers.ProxyFix(app, num_proxies=2)
64 environ = dict(create_environ(),
65 HTTP_X_FORWARDED_PROTO="https",
66 HTTP_X_FORWARDED_HOST='example.com',
67 HTTP_X_FORWARDED_FOR='1.2.3.4, 5.6.7.8',
68 REMOTE_ADDR='127.0.0.1',
69 HTTP_HOST='fake'
70 )
71
72 response = Response.from_app(app, environ)
73
74 assert response.get_data() == b'1.2.3.4|example.com'
75
76 # And we must check that if it is a redirection it is
77 # correctly done:
78
79 redirect_app = redirect('/foo/bar.hml')
80 response = Response.from_app(redirect_app, environ)
81
82 wsgi_headers = response.get_wsgi_headers(environ)
83 assert wsgi_headers['Location'] == 'https://example.com/foo/bar.hml'
84
85 def test_proxy_fix_weird_enum(self):
86 @fixers.ProxyFix
87 @Request.application
88 def app(request):
89 return Response(request.remote_addr)
90 environ = dict(create_environ(),
91 HTTP_X_FORWARDED_FOR=',',
92 REMOTE_ADDR='127.0.0.1',
93 )
94
95 response = Response.from_app(app, environ)
96 strict_eq(response.get_data(), b'127.0.0.1')
97
98 def test_header_rewriter_fix(self):
99 @Request.application
100 def application(request):
101 return Response("", headers=[
102 ('X-Foo', 'bar')
103 ])
104 application = fixers.HeaderRewriterFix(application, ('X-Foo',), (('X-Bar', '42'),))
105 response = Response.from_app(application, create_environ())
106 assert response.headers['Content-Type'] == 'text/plain; charset=utf-8'
107 assert 'X-Foo' not in response.headers
108 assert response.headers['X-Bar'] == '42'
109
110
111 class TestBrowserFixer(object):
112
113 def test_ie_fixes(self):
114 @fixers.InternetExplorerFix
115 @Request.application
116 def application(request):
117 response = Response('binary data here', mimetype='application/vnd.ms-excel')
118 response.headers['Vary'] = 'Cookie'
119 response.headers['Content-Disposition'] = 'attachment; filename=foo.xls'
120 return response
121
122 c = Client(application, Response)
123 response = c.get('/', headers=[
124 ('User-Agent', 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)')
125 ])
126
127 # IE gets no vary
128 assert response.get_data() == b'binary data here'
129 assert 'vary' not in response.headers
130 assert response.headers['content-disposition'] == 'attachment; filename=foo.xls'
131 assert response.headers['content-type'] == 'application/vnd.ms-excel'
132
133 # other browsers do
134 c = Client(application, Response)
135 response = c.get('/')
136 assert response.get_data() == b'binary data here'
137 assert 'vary' in response.headers
138
139 cc = ResponseCacheControl()
140 cc.no_cache = True
141
142 @fixers.InternetExplorerFix
143 @Request.application
144 def application(request):
145 response = Response('binary data here', mimetype='application/vnd.ms-excel')
146 response.headers['Pragma'] = ', '.join(pragma)
147 response.headers['Cache-Control'] = cc.to_header()
148 response.headers['Content-Disposition'] = 'attachment; filename=foo.xls'
149 return response
150
151
152 # IE has no pragma or cache control
153 pragma = ('no-cache',)
154 c = Client(application, Response)
155 response = c.get('/', headers=[
156 ('User-Agent', 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)')
157 ])
158 assert response.get_data() == b'binary data here'
159 assert 'pragma' not in response.headers
160 assert 'cache-control' not in response.headers
161 assert response.headers['content-disposition'] == 'attachment; filename=foo.xls'
162
163 # IE has simplified pragma
164 pragma = ('no-cache', 'x-foo')
165 cc.proxy_revalidate = True
166 response = c.get('/', headers=[
167 ('User-Agent', 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)')
168 ])
169 assert response.get_data() == b'binary data here'
170 assert response.headers['pragma'] == 'x-foo'
171 assert response.headers['cache-control'] == 'proxy-revalidate'
172 assert response.headers['content-disposition'] == 'attachment; filename=foo.xls'
173
174 # regular browsers get everything
175 response = c.get('/')
176 assert response.get_data() == b'binary data here'
177 assert response.headers['pragma'] == 'no-cache, x-foo'
178 cc = parse_cache_control_header(response.headers['cache-control'],
179 cls=ResponseCacheControl)
180 assert cc.no_cache
181 assert cc.proxy_revalidate
182 assert response.headers['content-disposition'] == 'attachment; filename=foo.xls'
0 # -*- coding: utf-8 -*-
1 """
2 tests.iterio
3 ~~~~~~~~~~~~
4
5 Tests the iterio object.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import pytest
11 from functools import partial
12
13 from tests import strict_eq
14 from werkzeug.contrib.iterio import IterIO, greenlet
15
16
17 class TestIterO(object):
18
19 def test_basic_native(self):
20 io = IterIO(["Hello", "World", "1", "2", "3"])
21 assert io.tell() == 0
22 assert io.read(2) == "He"
23 assert io.tell() == 2
24 assert io.read(3) == "llo"
25 assert io.tell() == 5
26 io.seek(0)
27 assert io.read(5) == "Hello"
28 assert io.tell() == 5
29 assert io._buf == "Hello"
30 assert io.read() == "World123"
31 assert io.tell() == 13
32 io.close()
33 assert io.closed
34
35 io = IterIO(["Hello\n", "World!"])
36 assert io.readline() == 'Hello\n'
37 assert io._buf == 'Hello\n'
38 assert io.read() == 'World!'
39 assert io._buf == 'Hello\nWorld!'
40 assert io.tell() == 12
41 io.seek(0)
42 assert io.readlines() == ['Hello\n', 'World!']
43
44 io = IterIO(['Line one\nLine ', 'two\nLine three'])
45 assert list(io) == ['Line one\n', 'Line two\n', 'Line three']
46 io = IterIO(iter('Line one\nLine two\nLine three'))
47 assert list(io) == ['Line one\n', 'Line two\n', 'Line three']
48 io = IterIO(['Line one\nL', 'ine', ' two', '\nLine three'])
49 assert list(io) == ['Line one\n', 'Line two\n', 'Line three']
50
51 io = IterIO(["foo\n", "bar"])
52 io.seek(-4, 2)
53 assert io.read(4) == '\nbar'
54
55 pytest.raises(IOError, io.seek, 2, 100)
56 io.close()
57 pytest.raises(ValueError, io.read)
58
59 def test_basic_bytes(self):
60 io = IterIO([b"Hello", b"World", b"1", b"2", b"3"])
61 assert io.tell() == 0
62 assert io.read(2) == b"He"
63 assert io.tell() == 2
64 assert io.read(3) == b"llo"
65 assert io.tell() == 5
66 io.seek(0)
67 assert io.read(5) == b"Hello"
68 assert io.tell() == 5
69 assert io._buf == b"Hello"
70 assert io.read() == b"World123"
71 assert io.tell() == 13
72 io.close()
73 assert io.closed
74
75 io = IterIO([b"Hello\n", b"World!"])
76 assert io.readline() == b'Hello\n'
77 assert io._buf == b'Hello\n'
78 assert io.read() == b'World!'
79 assert io._buf == b'Hello\nWorld!'
80 assert io.tell() == 12
81 io.seek(0)
82 assert io.readlines() == [b'Hello\n', b'World!']
83
84 io = IterIO([b"foo\n", b"bar"])
85 io.seek(-4, 2)
86 assert io.read(4) == b'\nbar'
87
88 pytest.raises(IOError, io.seek, 2, 100)
89 io.close()
90 pytest.raises(ValueError, io.read)
91
92 def test_basic_unicode(self):
93 io = IterIO([u"Hello", u"World", u"1", u"2", u"3"])
94 assert io.tell() == 0
95 assert io.read(2) == u"He"
96 assert io.tell() == 2
97 assert io.read(3) == u"llo"
98 assert io.tell() == 5
99 io.seek(0)
100 assert io.read(5) == u"Hello"
101 assert io.tell() == 5
102 assert io._buf == u"Hello"
103 assert io.read() == u"World123"
104 assert io.tell() == 13
105 io.close()
106 assert io.closed
107
108 io = IterIO([u"Hello\n", u"World!"])
109 assert io.readline() == u'Hello\n'
110 assert io._buf == u'Hello\n'
111 assert io.read() == u'World!'
112 assert io._buf == u'Hello\nWorld!'
113 assert io.tell() == 12
114 io.seek(0)
115 assert io.readlines() == [u'Hello\n', u'World!']
116
117 io = IterIO([u"foo\n", u"bar"])
118 io.seek(-4, 2)
119 assert io.read(4) == u'\nbar'
120
121 pytest.raises(IOError, io.seek, 2, 100)
122 io.close()
123 pytest.raises(ValueError, io.read)
124
125 def test_sentinel_cases(self):
126 io = IterIO([])
127 strict_eq(io.read(), '')
128 io = IterIO([], b'')
129 strict_eq(io.read(), b'')
130 io = IterIO([], u'')
131 strict_eq(io.read(), u'')
132
133 io = IterIO([])
134 strict_eq(io.read(), '')
135 io = IterIO([b''])
136 strict_eq(io.read(), b'')
137 io = IterIO([u''])
138 strict_eq(io.read(), u'')
139
140 io = IterIO([])
141 strict_eq(io.readline(), '')
142 io = IterIO([], b'')
143 strict_eq(io.readline(), b'')
144 io = IterIO([], u'')
145 strict_eq(io.readline(), u'')
146
147 io = IterIO([])
148 strict_eq(io.readline(), '')
149 io = IterIO([b''])
150 strict_eq(io.readline(), b'')
151 io = IterIO([u''])
152 strict_eq(io.readline(), u'')
153
154
155 @pytest.mark.skipif(greenlet is None, reason='Greenlet is not installed.')
156 class TestIterI(object):
157 def test_basic(self):
158 def producer(out):
159 out.write('1\n')
160 out.write('2\n')
161 out.flush()
162 out.write('3\n')
163 iterable = IterIO(producer)
164 assert next(iterable) == '1\n2\n'
165 assert next(iterable) == '3\n'
166 pytest.raises(StopIteration, next, iterable)
167
168 def test_sentinel_cases(self):
169 def producer_dummy_flush(out):
170 out.flush()
171 iterable = IterIO(producer_dummy_flush)
172 strict_eq(next(iterable), '')
173
174 def producer_empty(out):
175 pass
176 iterable = IterIO(producer_empty)
177 pytest.raises(StopIteration, next, iterable)
178
179 iterable = IterIO(producer_dummy_flush, b'')
180 strict_eq(next(iterable), b'')
181 iterable = IterIO(producer_dummy_flush, u'')
182 strict_eq(next(iterable), u'')
0 # -*- coding: utf-8 -*-
1 """
2 tests.securecookie
3 ~~~~~~~~~~~~~~~~~~
4
5 Tests the secure cookie.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10
11 from werkzeug.utils import parse_cookie
12 from werkzeug.wrappers import Request, Response
13 from werkzeug.contrib.securecookie import SecureCookie
14
15
16 def test_basic_support():
17 c = SecureCookie(secret_key=b'foo')
18 assert c.new
19 assert not c.modified
20 assert not c.should_save
21 c['x'] = 42
22 assert c.modified
23 assert c.should_save
24 s = c.serialize()
25
26 c2 = SecureCookie.unserialize(s, b'foo')
27 assert c is not c2
28 assert not c2.new
29 assert not c2.modified
30 assert not c2.should_save
31 assert c2 == c
32
33 c3 = SecureCookie.unserialize(s, b'wrong foo')
34 assert not c3.modified
35 assert not c3.new
36 assert c3 == {}
37
38 def test_wrapper_support():
39 req = Request.from_values()
40 resp = Response()
41 c = SecureCookie.load_cookie(req, secret_key=b'foo')
42 assert c.new
43 c['foo'] = 42
44 assert c.secret_key == b'foo'
45 c.save_cookie(resp)
46
47 req = Request.from_values(headers={
48 'Cookie': 'session="%s"' % parse_cookie(resp.headers['set-cookie'])['session']
49 })
50 c2 = SecureCookie.load_cookie(req, secret_key=b'foo')
51 assert not c2.new
52 assert c2 == c
0 # -*- coding: utf-8 -*-
1 """
2 tests.sessions
3 ~~~~~~~~~~~~~~
4
5 Added tests for the sessions.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import pytest
11 import os
12 import shutil
13 from tempfile import gettempdir
14
15 from werkzeug.contrib.sessions import FilesystemSessionStore
16
17
18 def test_default_tempdir():
19 store = FilesystemSessionStore()
20 assert store.path == gettempdir()
21
22 def test_basic_fs_sessions(tmpdir):
23 store = FilesystemSessionStore(str(tmpdir))
24 x = store.new()
25 assert x.new
26 assert not x.modified
27 x['foo'] = [1, 2, 3]
28 assert x.modified
29 store.save(x)
30
31 x2 = store.get(x.sid)
32 assert not x2.new
33 assert not x2.modified
34 assert x2 is not x
35 assert x2 == x
36 x2['test'] = 3
37 assert x2.modified
38 assert not x2.new
39 store.save(x2)
40
41 x = store.get(x.sid)
42 store.delete(x)
43 x2 = store.get(x.sid)
44 # the session is not new when it was used previously.
45 assert not x2.new
46
47 def test_non_urandom(tmpdir):
48 urandom = os.urandom
49 del os.urandom
50 try:
51 store = FilesystemSessionStore(str(tmpdir))
52 store.new()
53 finally:
54 os.urandom = urandom
55
56
57 def test_renewing_fs_session(tmpdir):
58 store = FilesystemSessionStore(str(tmpdir), renew_missing=True)
59 x = store.new()
60 store.save(x)
61 store.delete(x)
62 x2 = store.get(x.sid)
63 assert x2.new
64
65 def test_fs_session_lising(tmpdir):
66 store = FilesystemSessionStore(str(tmpdir), renew_missing=True)
67 sessions = set()
68 for x in range(10):
69 sess = store.new()
70 store.save(sess)
71 sessions.add(sess.sid)
72
73 listed_sessions = set(store.list())
74 assert sessions == listed_sessions
0 # -*- coding: utf-8 -*-
1 """
2 tests.contrib.wrappers
3 ~~~~~~~~~~~~~~~~~~~~~~
4
5 Added tests for the sessions.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10
11 from __future__ import with_statement
12
13 from werkzeug.contrib import wrappers
14 from werkzeug import routing
15 from werkzeug.wrappers import Request, Response
16
17
18 def test_reverse_slash_behavior():
19 class MyRequest(wrappers.ReverseSlashBehaviorRequestMixin, Request):
20 pass
21 req = MyRequest.from_values('/foo/bar', 'http://example.com/test')
22 assert req.url == 'http://example.com/test/foo/bar'
23 assert req.path == 'foo/bar'
24 assert req.script_root == '/test/'
25
26 # make sure the routing system works with the slashes in
27 # reverse order as well.
28 map = routing.Map([routing.Rule('/foo/bar', endpoint='foo')])
29 adapter = map.bind_to_environ(req.environ)
30 assert adapter.match() == ('foo', {})
31 adapter = map.bind(req.host, req.script_root)
32 assert adapter.match(req.path) == ('foo', {})
33
34 def test_dynamic_charset_request_mixin():
35 class MyRequest(wrappers.DynamicCharsetRequestMixin, Request):
36 pass
37 env = {'CONTENT_TYPE': 'text/html'}
38 req = MyRequest(env)
39 assert req.charset == 'latin1'
40
41 env = {'CONTENT_TYPE': 'text/html; charset=utf-8'}
42 req = MyRequest(env)
43 assert req.charset == 'utf-8'
44
45 env = {'CONTENT_TYPE': 'application/octet-stream'}
46 req = MyRequest(env)
47 assert req.charset == 'latin1'
48 assert req.url_charset == 'latin1'
49
50 MyRequest.url_charset = 'utf-8'
51 env = {'CONTENT_TYPE': 'application/octet-stream'}
52 req = MyRequest(env)
53 assert req.charset == 'latin1'
54 assert req.url_charset == 'utf-8'
55
56 def return_ascii(x):
57 return "ascii"
58 env = {'CONTENT_TYPE': 'text/plain; charset=x-weird-charset'}
59 req = MyRequest(env)
60 req.unknown_charset = return_ascii
61 assert req.charset == 'ascii'
62 assert req.url_charset == 'utf-8'
63
64 def test_dynamic_charset_response_mixin():
65 class MyResponse(wrappers.DynamicCharsetResponseMixin, Response):
66 default_charset = 'utf-7'
67 resp = MyResponse(mimetype='text/html')
68 assert resp.charset == 'utf-7'
69 resp.charset = 'utf-8'
70 assert resp.charset == 'utf-8'
71 assert resp.mimetype == 'text/html'
72 assert resp.mimetype_params == {'charset': 'utf-8'}
73 resp.mimetype_params['charset'] = 'iso-8859-15'
74 assert resp.charset == 'iso-8859-15'
75 resp.set_data(u'Hällo Wörld')
76 assert b''.join(resp.iter_encoded()) == \
77 u'Hällo Wörld'.encode('iso-8859-15')
78 del resp.headers['content-type']
79 try:
80 resp.charset = 'utf-8'
81 except TypeError as e:
82 pass
83 else:
84 assert False, 'expected type error on charset setting without ct'
0 --long text
1 --with boundary
2 --lookalikes--
0 #!/usr/bin/env python
1 """
2 Hacky helper application to collect form data.
3 """
4 from werkzeug.serving import run_simple
5 from werkzeug.wrappers import Request, Response
6
7
8 def copy_stream(request):
9 from os import mkdir
10 from time import time
11 folder = 'request-%d' % time()
12 mkdir(folder)
13 environ = request.environ
14 f = open(folder + '/request.txt', 'wb+')
15 f.write(environ['wsgi.input'].read(int(environ['CONTENT_LENGTH'])))
16 f.flush()
17 f.seek(0)
18 environ['wsgi.input'] = f
19 request.stat_folder = folder
20
21
22 def stats(request):
23 copy_stream(request)
24 f1 = request.files['file1']
25 f2 = request.files['file2']
26 text = request.form['text']
27 f1.save(request.stat_folder + '/file1.bin')
28 f2.save(request.stat_folder + '/file2.bin')
29 with open(request.stat_folder + '/text.txt', 'w') as f:
30 f.write(text.encode('utf-8'))
31 return Response('Done.')
32
33
34 def upload_file(request):
35 return Response('''
36 <h1>Upload File</h1>
37 <form action="" method="post" enctype="multipart/form-data">
38 <input type="file" name="file1"><br>
39 <input type="file" name="file2"><br>
40 <textarea name="text"></textarea><br>
41 <input type="submit" value="Send">
42 </form>
43 ''', mimetype='text/html')
44
45
46 def application(environ, start_responseonse):
47 request = Request(environ)
48 if request.method == 'POST':
49 response = stats(request)
50 else:
51 response = upload_file(request)
52 return response(environ, start_responseonse)
53
54
55 if __name__ == '__main__':
56 run_simple('localhost', 5000, application, use_debugger=True)
0 this is another text with ümläüts
0 # -*- coding: utf-8 -*-
1 """
2 tests.compat
3 ~~~~~~~~~~~~
4
5 Ensure that old stuff does not break on update.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import warnings
11
12 from werkzeug.wrappers import Response
13 from werkzeug.test import create_environ
14
15
16 def test_old_imports():
17 from werkzeug.utils import Headers, MultiDict, CombinedMultiDict, \
18 Headers, EnvironHeaders
19 from werkzeug.http import Accept, MIMEAccept, CharsetAccept, \
20 LanguageAccept, ETags, HeaderSet, WWWAuthenticate, \
21 Authorization
22
23 def test_exposed_werkzeug_mod():
24 import werkzeug
25 for key in werkzeug.__all__:
26 # deprecated, skip it
27 if key in ('templates', 'Template'):
28 continue
29 getattr(werkzeug, key)
0 # -*- coding: utf-8 -*-
1 """
2 tests.datastructures
3 ~~~~~~~~~~~~~~~~~~~~
4
5 Tests the functionality of the provided Werkzeug
6 datastructures.
7
8 Classes prefixed with an underscore are mixins and are not discovered by
9 the test runner.
10
11 TODO:
12
13 - FileMultiDict
14 - Immutable types undertested
15 - Split up dict tests
16
17 :copyright: (c) 2014 by Armin Ronacher.
18 :license: BSD, see LICENSE for more details.
19 """
20
21 from __future__ import with_statement
22
23 import pytest
24 from tests import strict_eq
25
26
27 import pickle
28 from contextlib import contextmanager
29 from copy import copy, deepcopy
30
31 from werkzeug import datastructures
32 from werkzeug._compat import iterkeys, itervalues, iteritems, iterlists, \
33 iterlistvalues, text_type, PY2
34 from werkzeug.exceptions import BadRequestKeyError
35
36
37 class TestNativeItermethods(object):
38 def test_basic(self):
39 @datastructures.native_itermethods(['keys', 'values', 'items'])
40 class StupidDict(object):
41 def keys(self, multi=1):
42 return iter(['a', 'b', 'c'] * multi)
43
44 def values(self, multi=1):
45 return iter([1, 2, 3] * multi)
46
47 def items(self, multi=1):
48 return iter(zip(iterkeys(self, multi=multi),
49 itervalues(self, multi=multi)))
50
51 d = StupidDict()
52 expected_keys = ['a', 'b', 'c']
53 expected_values = [1, 2, 3]
54 expected_items = list(zip(expected_keys, expected_values))
55
56 assert list(iterkeys(d)) == expected_keys
57 assert list(itervalues(d)) == expected_values
58 assert list(iteritems(d)) == expected_items
59
60 assert list(iterkeys(d, 2)) == expected_keys * 2
61 assert list(itervalues(d, 2)) == expected_values * 2
62 assert list(iteritems(d, 2)) == expected_items * 2
63
64
65 class _MutableMultiDictTests(object):
66 storage_class = None
67
68 def test_pickle(self):
69 cls = self.storage_class
70
71 def create_instance(module=None):
72 if module is None:
73 d = cls()
74 else:
75 old = cls.__module__
76 cls.__module__ = module
77 d = cls()
78 cls.__module__ = old
79 d.setlist(b'foo', [1, 2, 3, 4])
80 d.setlist(b'bar', b'foo bar baz'.split())
81 return d
82
83 for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
84 d = create_instance()
85 s = pickle.dumps(d, protocol)
86 ud = pickle.loads(s)
87 assert type(ud) == type(d)
88 assert ud == d
89 alternative = pickle.dumps(create_instance('werkzeug'), protocol)
90 assert pickle.loads(alternative) == d
91 ud[b'newkey'] = b'bla'
92 assert ud != d
93
94 def test_basic_interface(self):
95 md = self.storage_class()
96 assert isinstance(md, dict)
97
98 mapping = [('a', 1), ('b', 2), ('a', 2), ('d', 3),
99 ('a', 1), ('a', 3), ('d', 4), ('c', 3)]
100 md = self.storage_class(mapping)
101
102 # simple getitem gives the first value
103 assert md['a'] == 1
104 assert md['c'] == 3
105 with pytest.raises(KeyError):
106 md['e']
107 assert md.get('a') == 1
108
109 # list getitem
110 assert md.getlist('a') == [1, 2, 1, 3]
111 assert md.getlist('d') == [3, 4]
112 # do not raise if key not found
113 assert md.getlist('x') == []
114
115 # simple setitem overwrites all values
116 md['a'] = 42
117 assert md.getlist('a') == [42]
118
119 # list setitem
120 md.setlist('a', [1, 2, 3])
121 assert md['a'] == 1
122 assert md.getlist('a') == [1, 2, 3]
123
124 # verify that it does not change original lists
125 l1 = [1, 2, 3]
126 md.setlist('a', l1)
127 del l1[:]
128 assert md['a'] == 1
129
130 # setdefault, setlistdefault
131 assert md.setdefault('u', 23) == 23
132 assert md.getlist('u') == [23]
133 del md['u']
134
135 md.setlist('u', [-1, -2])
136
137 # delitem
138 del md['u']
139 with pytest.raises(KeyError):
140 md['u']
141 del md['d']
142 assert md.getlist('d') == []
143
144 # keys, values, items, lists
145 assert list(sorted(md.keys())) == ['a', 'b', 'c']
146 assert list(sorted(iterkeys(md))) == ['a', 'b', 'c']
147
148 assert list(sorted(itervalues(md))) == [1, 2, 3]
149 assert list(sorted(itervalues(md))) == [1, 2, 3]
150
151 assert list(sorted(md.items())) == [('a', 1), ('b', 2), ('c', 3)]
152 assert list(sorted(md.items(multi=True))) == \
153 [('a', 1), ('a', 2), ('a', 3), ('b', 2), ('c', 3)]
154 assert list(sorted(iteritems(md))) == [('a', 1), ('b', 2), ('c', 3)]
155 assert list(sorted(iteritems(md, multi=True))) == \
156 [('a', 1), ('a', 2), ('a', 3), ('b', 2), ('c', 3)]
157
158 assert list(sorted(md.lists())) == \
159 [('a', [1, 2, 3]), ('b', [2]), ('c', [3])]
160 assert list(sorted(iterlists(md))) == \
161 [('a', [1, 2, 3]), ('b', [2]), ('c', [3])]
162
163 # copy method
164 c = md.copy()
165 assert c['a'] == 1
166 assert c.getlist('a') == [1, 2, 3]
167
168 # copy method 2
169 c = copy(md)
170 assert c['a'] == 1
171 assert c.getlist('a') == [1, 2, 3]
172
173 # deepcopy method
174 c = md.deepcopy()
175 assert c['a'] == 1
176 assert c.getlist('a') == [1, 2, 3]
177
178 # deepcopy method 2
179 c = deepcopy(md)
180 assert c['a'] == 1
181 assert c.getlist('a') == [1, 2, 3]
182
183 # update with a multidict
184 od = self.storage_class([('a', 4), ('a', 5), ('y', 0)])
185 md.update(od)
186 assert md.getlist('a') == [1, 2, 3, 4, 5]
187 assert md.getlist('y') == [0]
188
189 # update with a regular dict
190 md = c
191 od = {'a': 4, 'y': 0}
192 md.update(od)
193 assert md.getlist('a') == [1, 2, 3, 4]
194 assert md.getlist('y') == [0]
195
196 # pop, poplist, popitem, popitemlist
197 assert md.pop('y') == 0
198 assert 'y' not in md
199 assert md.poplist('a') == [1, 2, 3, 4]
200 assert 'a' not in md
201 assert md.poplist('missing') == []
202
203 # remaining: b=2, c=3
204 popped = md.popitem()
205 assert popped in [('b', 2), ('c', 3)]
206 popped = md.popitemlist()
207 assert popped in [('b', [2]), ('c', [3])]
208
209 # type conversion
210 md = self.storage_class({'a': '4', 'b': ['2', '3']})
211 assert md.get('a', type=int) == 4
212 assert md.getlist('b', type=int) == [2, 3]
213
214 # repr
215 md = self.storage_class([('a', 1), ('a', 2), ('b', 3)])
216 assert "('a', 1)" in repr(md)
217 assert "('a', 2)" in repr(md)
218 assert "('b', 3)" in repr(md)
219
220 # add and getlist
221 md.add('c', '42')
222 md.add('c', '23')
223 assert md.getlist('c') == ['42', '23']
224 md.add('c', 'blah')
225 assert md.getlist('c', type=int) == [42, 23]
226
227 # setdefault
228 md = self.storage_class()
229 md.setdefault('x', []).append(42)
230 md.setdefault('x', []).append(23)
231 assert md['x'] == [42, 23]
232
233 # to dict
234 md = self.storage_class()
235 md['foo'] = 42
236 md.add('bar', 1)
237 md.add('bar', 2)
238 assert md.to_dict() == {'foo': 42, 'bar': 1}
239 assert md.to_dict(flat=False) == {'foo': [42], 'bar': [1, 2]}
240
241 # popitem from empty dict
242 with pytest.raises(KeyError):
243 self.storage_class().popitem()
244
245 with pytest.raises(KeyError):
246 self.storage_class().popitemlist()
247
248 # key errors are of a special type
249 with pytest.raises(BadRequestKeyError):
250 self.storage_class()[42]
251
252 # setlist works
253 md = self.storage_class()
254 md['foo'] = 42
255 md.setlist('foo', [1, 2])
256 assert md.getlist('foo') == [1, 2]
257
258
259 class _ImmutableDictTests(object):
260 storage_class = None
261
262 def test_follows_dict_interface(self):
263 cls = self.storage_class
264
265 data = {'foo': 1, 'bar': 2, 'baz': 3}
266 d = cls(data)
267
268 assert d['foo'] == 1
269 assert d['bar'] == 2
270 assert d['baz'] == 3
271 assert sorted(d.keys()) == ['bar', 'baz', 'foo']
272 assert 'foo' in d
273 assert 'foox' not in d
274 assert len(d) == 3
275
276 def test_copies_are_mutable(self):
277 cls = self.storage_class
278 immutable = cls({'a': 1})
279 with pytest.raises(TypeError):
280 immutable.pop('a')
281
282 mutable = immutable.copy()
283 mutable.pop('a')
284 assert 'a' in immutable
285 assert mutable is not immutable
286 assert copy(immutable) is immutable
287
288 def test_dict_is_hashable(self):
289 cls = self.storage_class
290 immutable = cls({'a': 1, 'b': 2})
291 immutable2 = cls({'a': 2, 'b': 2})
292 x = set([immutable])
293 assert immutable in x
294 assert immutable2 not in x
295 x.discard(immutable)
296 assert immutable not in x
297 assert immutable2 not in x
298 x.add(immutable2)
299 assert immutable not in x
300 assert immutable2 in x
301 x.add(immutable)
302 assert immutable in x
303 assert immutable2 in x
304
305
306 class TestImmutableTypeConversionDict(_ImmutableDictTests):
307 storage_class = datastructures.ImmutableTypeConversionDict
308
309
310 class TestImmutableMultiDict(_ImmutableDictTests):
311 storage_class = datastructures.ImmutableMultiDict
312
313 def test_multidict_is_hashable(self):
314 cls = self.storage_class
315 immutable = cls({'a': [1, 2], 'b': 2})
316 immutable2 = cls({'a': [1], 'b': 2})
317 x = set([immutable])
318 assert immutable in x
319 assert immutable2 not in x
320 x.discard(immutable)
321 assert immutable not in x
322 assert immutable2 not in x
323 x.add(immutable2)
324 assert immutable not in x
325 assert immutable2 in x
326 x.add(immutable)
327 assert immutable in x
328 assert immutable2 in x
329
330
331 class TestImmutableDict(_ImmutableDictTests):
332 storage_class = datastructures.ImmutableDict
333
334
335 class TestImmutableOrderedMultiDict(_ImmutableDictTests):
336 storage_class = datastructures.ImmutableOrderedMultiDict
337
338 def test_ordered_multidict_is_hashable(self):
339 a = self.storage_class([('a', 1), ('b', 1), ('a', 2)])
340 b = self.storage_class([('a', 1), ('a', 2), ('b', 1)])
341 assert hash(a) != hash(b)
342
343
344 class TestMultiDict(_MutableMultiDictTests):
345 storage_class = datastructures.MultiDict
346
347 def test_multidict_pop(self):
348 make_d = lambda: self.storage_class({'foo': [1, 2, 3, 4]})
349 d = make_d()
350 assert d.pop('foo') == 1
351 assert not d
352 d = make_d()
353 assert d.pop('foo', 32) == 1
354 assert not d
355 d = make_d()
356 assert d.pop('foos', 32) == 32
357 assert d
358
359 with pytest.raises(KeyError):
360 d.pop('foos')
361
362 def test_setlistdefault(self):
363 md = self.storage_class()
364 assert md.setlistdefault('u', [-1, -2]) == [-1, -2]
365 assert md.getlist('u') == [-1, -2]
366 assert md['u'] == -1
367
368 def test_iter_interfaces(self):
369 mapping = [('a', 1), ('b', 2), ('a', 2), ('d', 3),
370 ('a', 1), ('a', 3), ('d', 4), ('c', 3)]
371 md = self.storage_class(mapping)
372 assert list(zip(md.keys(), md.listvalues())) == list(md.lists())
373 assert list(zip(md, iterlistvalues(md))) == list(iterlists(md))
374 assert list(zip(iterkeys(md), iterlistvalues(md))) == \
375 list(iterlists(md))
376
377
378 class TestOrderedMultiDict(_MutableMultiDictTests):
379 storage_class = datastructures.OrderedMultiDict
380
381 def test_ordered_interface(self):
382 cls = self.storage_class
383
384 d = cls()
385 assert not d
386 d.add('foo', 'bar')
387 assert len(d) == 1
388 d.add('foo', 'baz')
389 assert len(d) == 1
390 assert list(iteritems(d)) == [('foo', 'bar')]
391 assert list(d) == ['foo']
392 assert list(iteritems(d, multi=True)) == \
393 [('foo', 'bar'), ('foo', 'baz')]
394 del d['foo']
395 assert not d
396 assert len(d) == 0
397 assert list(d) == []
398
399 d.update([('foo', 1), ('foo', 2), ('bar', 42)])
400 d.add('foo', 3)
401 assert d.getlist('foo') == [1, 2, 3]
402 assert d.getlist('bar') == [42]
403 assert list(iteritems(d)) == [('foo', 1), ('bar', 42)]
404
405 expected = ['foo', 'bar']
406
407 assert list(d.keys()) == expected
408 assert list(d) == expected
409 assert list(iterkeys(d)) == expected
410
411 assert list(iteritems(d, multi=True)) == \
412 [('foo', 1), ('foo', 2), ('bar', 42), ('foo', 3)]
413 assert len(d) == 2
414
415 assert d.pop('foo') == 1
416 assert d.pop('blafasel', None) is None
417 assert d.pop('blafasel', 42) == 42
418 assert len(d) == 1
419 assert d.poplist('bar') == [42]
420 assert not d
421
422 d.get('missingkey') is None
423
424 d.add('foo', 42)
425 d.add('foo', 23)
426 d.add('bar', 2)
427 d.add('foo', 42)
428 assert d == datastructures.MultiDict(d)
429 id = self.storage_class(d)
430 assert d == id
431 d.add('foo', 2)
432 assert d != id
433
434 d.update({'blah': [1, 2, 3]})
435 assert d['blah'] == 1
436 assert d.getlist('blah') == [1, 2, 3]
437
438 # setlist works
439 d = self.storage_class()
440 d['foo'] = 42
441 d.setlist('foo', [1, 2])
442 assert d.getlist('foo') == [1, 2]
443
444 with pytest.raises(BadRequestKeyError):
445 d.pop('missing')
446 with pytest.raises(BadRequestKeyError):
447 d['missing']
448
449 # popping
450 d = self.storage_class()
451 d.add('foo', 23)
452 d.add('foo', 42)
453 d.add('foo', 1)
454 assert d.popitem() == ('foo', 23)
455 with pytest.raises(BadRequestKeyError):
456 d.popitem()
457 assert not d
458
459 d.add('foo', 23)
460 d.add('foo', 42)
461 d.add('foo', 1)
462 assert d.popitemlist() == ('foo', [23, 42, 1])
463
464 with pytest.raises(BadRequestKeyError):
465 d.popitemlist()
466
467 def test_iterables(self):
468 a = datastructures.MultiDict((("key_a", "value_a"),))
469 b = datastructures.MultiDict((("key_b", "value_b"),))
470 ab = datastructures.CombinedMultiDict((a,b))
471
472 assert sorted(ab.lists()) == [('key_a', ['value_a']), ('key_b', ['value_b'])]
473 assert sorted(ab.listvalues()) == [['value_a'], ['value_b']]
474 assert sorted(ab.keys()) == ["key_a", "key_b"]
475
476 assert sorted(iterlists(ab)) == [('key_a', ['value_a']), ('key_b', ['value_b'])]
477 assert sorted(iterlistvalues(ab)) == [['value_a'], ['value_b']]
478 assert sorted(iterkeys(ab)) == ["key_a", "key_b"]
479
480
481 class TestCombinedMultiDict(object):
482 storage_class = datastructures.CombinedMultiDict
483
484 def test_basic_interface(self):
485 d1 = datastructures.MultiDict([('foo', '1')])
486 d2 = datastructures.MultiDict([('bar', '2'), ('bar', '3')])
487 d = self.storage_class([d1, d2])
488
489 # lookup
490 assert d['foo'] == '1'
491 assert d['bar'] == '2'
492 assert d.getlist('bar') == ['2', '3']
493
494 assert sorted(d.items()) == [('bar', '2'), ('foo', '1')]
495 assert sorted(d.items(multi=True)) == \
496 [('bar', '2'), ('bar', '3'), ('foo', '1')]
497 assert 'missingkey' not in d
498 assert 'foo' in d
499
500 # type lookup
501 assert d.get('foo', type=int) == 1
502 assert d.getlist('bar', type=int) == [2, 3]
503
504 # get key errors for missing stuff
505 with pytest.raises(KeyError):
506 d['missing']
507
508 # make sure that they are immutable
509 with pytest.raises(TypeError):
510 d['foo'] = 'blub'
511
512 # copies are immutable
513 d = d.copy()
514 with pytest.raises(TypeError):
515 d['foo'] = 'blub'
516
517 # make sure lists merges
518 md1 = datastructures.MultiDict((("foo", "bar"),))
519 md2 = datastructures.MultiDict((("foo", "blafasel"),))
520 x = self.storage_class((md1, md2))
521 assert list(iterlists(x)) == [('foo', ['bar', 'blafasel'])]
522
523 def test_length(self):
524 d1 = datastructures.MultiDict([('foo', '1')])
525 d2 = datastructures.MultiDict([('bar', '2')])
526 assert len(d1) == len(d2) == 1
527 d = self.storage_class([d1, d2])
528 assert len(d) == 2
529 d1.clear()
530 assert len(d1) == 0
531 assert len(d) == 1
532
533
534 class TestHeaders(object):
535 storage_class = datastructures.Headers
536
537 def test_basic_interface(self):
538 headers = self.storage_class()
539 headers.add('Content-Type', 'text/plain')
540 headers.add('X-Foo', 'bar')
541 assert 'x-Foo' in headers
542 assert 'Content-type' in headers
543
544 headers['Content-Type'] = 'foo/bar'
545 assert headers['Content-Type'] == 'foo/bar'
546 assert len(headers.getlist('Content-Type')) == 1
547
548 # list conversion
549 assert headers.to_wsgi_list() == [
550 ('Content-Type', 'foo/bar'),
551 ('X-Foo', 'bar')
552 ]
553 assert str(headers) == (
554 "Content-Type: foo/bar\r\n"
555 "X-Foo: bar\r\n"
556 "\r\n"
557 )
558 assert str(self.storage_class()) == "\r\n"
559
560 # extended add
561 headers.add('Content-Disposition', 'attachment', filename='foo')
562 assert headers['Content-Disposition'] == 'attachment; filename=foo'
563
564 headers.add('x', 'y', z='"')
565 assert headers['x'] == r'y; z="\""'
566
567 def test_defaults_and_conversion(self):
568 # defaults
569 headers = self.storage_class([
570 ('Content-Type', 'text/plain'),
571 ('X-Foo', 'bar'),
572 ('X-Bar', '1'),
573 ('X-Bar', '2')
574 ])
575 assert headers.getlist('x-bar') == ['1', '2']
576 assert headers.get('x-Bar') == '1'
577 assert headers.get('Content-Type') == 'text/plain'
578
579 assert headers.setdefault('X-Foo', 'nope') == 'bar'
580 assert headers.setdefault('X-Bar', 'nope') == '1'
581 assert headers.setdefault('X-Baz', 'quux') == 'quux'
582 assert headers.setdefault('X-Baz', 'nope') == 'quux'
583 headers.pop('X-Baz')
584
585 # type conversion
586 assert headers.get('x-bar', type=int) == 1
587 assert headers.getlist('x-bar', type=int) == [1, 2]
588
589 # list like operations
590 assert headers[0] == ('Content-Type', 'text/plain')
591 assert headers[:1] == self.storage_class([('Content-Type', 'text/plain')])
592 del headers[:2]
593 del headers[-1]
594 assert headers == self.storage_class([('X-Bar', '1')])
595
596 def test_copying(self):
597 a = self.storage_class([('foo', 'bar')])
598 b = a.copy()
599 a.add('foo', 'baz')
600 assert a.getlist('foo') == ['bar', 'baz']
601 assert b.getlist('foo') == ['bar']
602
603 def test_popping(self):
604 headers = self.storage_class([('a', 1)])
605 assert headers.pop('a') == 1
606 assert headers.pop('b', 2) == 2
607
608 with pytest.raises(KeyError):
609 headers.pop('c')
610
611 def test_set_arguments(self):
612 a = self.storage_class()
613 a.set('Content-Disposition', 'useless')
614 a.set('Content-Disposition', 'attachment', filename='foo')
615 assert a['Content-Disposition'] == 'attachment; filename=foo'
616
617 def test_reject_newlines(self):
618 h = self.storage_class()
619
620 for variation in 'foo\nbar', 'foo\r\nbar', 'foo\rbar':
621 with pytest.raises(ValueError):
622 h['foo'] = variation
623 with pytest.raises(ValueError):
624 h.add('foo', variation)
625 with pytest.raises(ValueError):
626 h.add('foo', 'test', option=variation)
627 with pytest.raises(ValueError):
628 h.set('foo', variation)
629 with pytest.raises(ValueError):
630 h.set('foo', 'test', option=variation)
631
632 def test_slicing(self):
633 # there's nothing wrong with these being native strings
634 # Headers doesn't care about the data types
635 h = self.storage_class()
636 h.set('X-Foo-Poo', 'bleh')
637 h.set('Content-Type', 'application/whocares')
638 h.set('X-Forwarded-For', '192.168.0.123')
639 h[:] = [(k, v) for k, v in h if k.startswith(u'X-')]
640 assert list(h) == [
641 ('X-Foo-Poo', 'bleh'),
642 ('X-Forwarded-For', '192.168.0.123')
643 ]
644
645 def test_bytes_operations(self):
646 h = self.storage_class()
647 h.set('X-Foo-Poo', 'bleh')
648 h.set('X-Whoops', b'\xff')
649
650 assert h.get('x-foo-poo', as_bytes=True) == b'bleh'
651 assert h.get('x-whoops', as_bytes=True) == b'\xff'
652
653 def test_to_wsgi_list(self):
654 h = self.storage_class()
655 h.set(u'Key', u'Value')
656 for key, value in h.to_wsgi_list():
657 if PY2:
658 strict_eq(key, b'Key')
659 strict_eq(value, b'Value')
660 else:
661 strict_eq(key, u'Key')
662 strict_eq(value, u'Value')
663
664
665 class TestEnvironHeaders(object):
666 storage_class = datastructures.EnvironHeaders
667
668 def test_basic_interface(self):
669 # this happens in multiple WSGI servers because they
670 # use a vary naive way to convert the headers;
671 broken_env = {
672 'HTTP_CONTENT_TYPE': 'text/html',
673 'CONTENT_TYPE': 'text/html',
674 'HTTP_CONTENT_LENGTH': '0',
675 'CONTENT_LENGTH': '0',
676 'HTTP_ACCEPT': '*',
677 'wsgi.version': (1, 0)
678 }
679 headers = self.storage_class(broken_env)
680 assert headers
681 assert len(headers) == 3
682 assert sorted(headers) == [
683 ('Accept', '*'),
684 ('Content-Length', '0'),
685 ('Content-Type', 'text/html')
686 ]
687 assert not self.storage_class({'wsgi.version': (1, 0)})
688 assert len(self.storage_class({'wsgi.version': (1, 0)})) == 0
689
690 def test_return_type_is_unicode(self):
691 # environ contains native strings; we return unicode
692 headers = self.storage_class({
693 'HTTP_FOO': '\xe2\x9c\x93',
694 'CONTENT_TYPE': 'text/plain',
695 })
696 assert headers['Foo'] == u"\xe2\x9c\x93"
697 assert isinstance(headers['Foo'], text_type)
698 assert isinstance(headers['Content-Type'], text_type)
699 iter_output = dict(iter(headers))
700 assert iter_output['Foo'] == u"\xe2\x9c\x93"
701 assert isinstance(iter_output['Foo'], text_type)
702 assert isinstance(iter_output['Content-Type'], text_type)
703
704 def test_bytes_operations(self):
705 foo_val = '\xff'
706 h = self.storage_class({
707 'HTTP_X_FOO': foo_val
708 })
709
710 assert h.get('x-foo', as_bytes=True) == b'\xff'
711 assert h.get('x-foo') == u'\xff'
712
713
714 class TestHeaderSet(object):
715 storage_class = datastructures.HeaderSet
716
717 def test_basic_interface(self):
718 hs = self.storage_class()
719 hs.add('foo')
720 hs.add('bar')
721 assert 'Bar' in hs
722 assert hs.find('foo') == 0
723 assert hs.find('BAR') == 1
724 assert hs.find('baz') < 0
725 hs.discard('missing')
726 hs.discard('foo')
727 assert hs.find('foo') < 0
728 assert hs.find('bar') == 0
729
730 with pytest.raises(IndexError):
731 hs.index('missing')
732
733 assert hs.index('bar') == 0
734 assert hs
735 hs.clear()
736 assert not hs
737
738
739 class TestImmutableList(object):
740 storage_class = datastructures.ImmutableList
741
742 def test_list_hashable(self):
743 t = (1, 2, 3, 4)
744 l = self.storage_class(t)
745 assert hash(t) == hash(l)
746 assert t != l
747
748
749 def make_call_asserter(func=None):
750 """Utility to assert a certain number of function calls.
751
752 :param func: Additional callback for each function call.
753
754 >>> assert_calls, func = make_call_asserter()
755 >>> with assert_calls(2):
756 func()
757 func()
758 """
759
760 calls = [0]
761
762 @contextmanager
763 def asserter(count, msg=None):
764 calls[0] = 0
765 yield
766 assert calls[0] == count
767
768 def wrapped(*args, **kwargs):
769 calls[0] += 1
770 if func is not None:
771 return func(*args, **kwargs)
772
773 return asserter, wrapped
774
775
776 class TestCallbackDict(object):
777 storage_class = datastructures.CallbackDict
778
779 def test_callback_dict_reads(self):
780 assert_calls, func = make_call_asserter()
781 initial = {'a': 'foo', 'b': 'bar'}
782 dct = self.storage_class(initial=initial, on_update=func)
783 with assert_calls(0, 'callback triggered by read-only method'):
784 # read-only methods
785 dct['a']
786 dct.get('a')
787 pytest.raises(KeyError, lambda: dct['x'])
788 'a' in dct
789 list(iter(dct))
790 dct.copy()
791 with assert_calls(0, 'callback triggered without modification'):
792 # methods that may write but don't
793 dct.pop('z', None)
794 dct.setdefault('a')
795
796 def test_callback_dict_writes(self):
797 assert_calls, func = make_call_asserter()
798 initial = {'a': 'foo', 'b': 'bar'}
799 dct = self.storage_class(initial=initial, on_update=func)
800 with assert_calls(8, 'callback not triggered by write method'):
801 # always-write methods
802 dct['z'] = 123
803 dct['z'] = 123 # must trigger again
804 del dct['z']
805 dct.pop('b', None)
806 dct.setdefault('x')
807 dct.popitem()
808 dct.update([])
809 dct.clear()
810 with assert_calls(0, 'callback triggered by failed del'):
811 pytest.raises(KeyError, lambda: dct.__delitem__('x'))
812 with assert_calls(0, 'callback triggered by failed pop'):
813 pytest.raises(KeyError, lambda: dct.pop('x'))
814
815
816 class TestCacheControl(object):
817 def test_repr(self):
818 cc = datastructures.RequestCacheControl(
819 [("max-age", "0"), ("private", "True")],
820 )
821 assert repr(cc) == "<RequestCacheControl max-age='0' private='True'>"
822
823
824 class TestAccept(object):
825 storage_class = datastructures.Accept
826
827 def test_accept_basic(self):
828 accept = self.storage_class([('tinker', 0), ('tailor', 0.333),
829 ('soldier', 0.667), ('sailor', 1)])
830 # check __getitem__ on indices
831 assert accept[3] == ('tinker', 0)
832 assert accept[2] == ('tailor', 0.333)
833 assert accept[1] == ('soldier', 0.667)
834 assert accept[0], ('sailor', 1)
835 # check __getitem__ on string
836 assert accept['tinker'] == 0
837 assert accept['tailor'] == 0.333
838 assert accept['soldier'] == 0.667
839 assert accept['sailor'] == 1
840 assert accept['spy'] == 0
841 # check quality method
842 assert accept.quality('tinker') == 0
843 assert accept.quality('tailor') == 0.333
844 assert accept.quality('soldier') == 0.667
845 assert accept.quality('sailor') == 1
846 assert accept.quality('spy') == 0
847 # check __contains__
848 assert 'sailor' in accept
849 assert 'spy' not in accept
850 # check index method
851 assert accept.index('tinker') == 3
852 assert accept.index('tailor') == 2
853 assert accept.index('soldier') == 1
854 assert accept.index('sailor') == 0
855 with pytest.raises(ValueError):
856 accept.index('spy')
857 # check find method
858 assert accept.find('tinker') == 3
859 assert accept.find('tailor') == 2
860 assert accept.find('soldier') == 1
861 assert accept.find('sailor') == 0
862 assert accept.find('spy') == -1
863 # check to_header method
864 assert accept.to_header() == \
865 'sailor,soldier;q=0.667,tailor;q=0.333,tinker;q=0'
866 # check best_match method
867 assert accept.best_match(['tinker', 'tailor', 'soldier', 'sailor'],
868 default=None) == 'sailor'
869 assert accept.best_match(['tinker', 'tailor', 'soldier'],
870 default=None) == 'soldier'
871 assert accept.best_match(['tinker', 'tailor'], default=None) == \
872 'tailor'
873 assert accept.best_match(['tinker'], default=None) is None
874 assert accept.best_match(['tinker'], default='x') == 'x'
875
876 def test_accept_wildcard(self):
877 accept = self.storage_class([('*', 0), ('asterisk', 1)])
878 assert '*' in accept
879 assert accept.best_match(['asterisk', 'star'], default=None) == \
880 'asterisk'
881 assert accept.best_match(['star'], default=None) is None
882
883 @pytest.mark.skipif(True, reason='Werkzeug doesn\'t respect specificity.')
884 def test_accept_wildcard_specificity(self):
885 accept = self.storage_class([('asterisk', 0), ('star', 0.5), ('*', 1)])
886 assert accept.best_match(['star', 'asterisk'], default=None) == 'star'
887 assert accept.best_match(['asterisk', 'star'], default=None) == 'star'
888 assert accept.best_match(['asterisk', 'times'], default=None) == \
889 'times'
890 assert accept.best_match(['asterisk'], default=None) is None
0 # -*- coding: utf-8 -*-
1 """
2 tests.debug
3 ~~~~~~~~~~~
4
5 Tests some debug utilities.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import sys
11 import os
12 import re
13 import io
14
15 import pytest
16
17 from werkzeug.debug.repr import debug_repr, DebugReprGenerator, \
18 dump, helper
19 from werkzeug.debug.console import HTMLStringO
20 from werkzeug.debug.tbtools import Traceback
21 from werkzeug._compat import PY2
22
23
24 class TestDebugRepr(object):
25
26 def test_basic_repr(self):
27 assert debug_repr([]) == u'[]'
28 assert debug_repr([1, 2]) == \
29 u'[<span class="number">1</span>, <span class="number">2</span>]'
30 assert debug_repr([1, 'test']) == \
31 u'[<span class="number">1</span>, <span class="string">\'test\'</span>]'
32 assert debug_repr([None]) == \
33 u'[<span class="object">None</span>]'
34
35 def test_sequence_repr(self):
36 assert debug_repr(list(range(20))) == (
37 u'[<span class="number">0</span>, <span class="number">1</span>, '
38 u'<span class="number">2</span>, <span class="number">3</span>, '
39 u'<span class="number">4</span>, <span class="number">5</span>, '
40 u'<span class="number">6</span>, <span class="number">7</span>, '
41 u'<span class="extended"><span class="number">8</span>, '
42 u'<span class="number">9</span>, <span class="number">10</span>, '
43 u'<span class="number">11</span>, <span class="number">12</span>, '
44 u'<span class="number">13</span>, <span class="number">14</span>, '
45 u'<span class="number">15</span>, <span class="number">16</span>, '
46 u'<span class="number">17</span>, <span class="number">18</span>, '
47 u'<span class="number">19</span></span>]'
48 )
49
50
51 def test_mapping_repr(self):
52 assert debug_repr({}) == u'{}'
53 assert debug_repr({'foo': 42}) == (
54 u'{<span class="pair"><span class="key"><span class="string">\'foo\''
55 u'</span></span>: <span class="value"><span class="number">42'
56 u'</span></span></span>}'
57 )
58 assert debug_repr(dict(zip(range(10), [None] * 10))) == (
59 u'{<span class="pair"><span class="key"><span class="number">0</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">1</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">2</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">3</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="extended"><span class="pair"><span class="key"><span class="number">4</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">5</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">6</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">7</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">8</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">9</span></span>: <span class="value"><span class="object">None</span></span></span></span>}'
60 )
61 assert debug_repr((1, 'zwei', u'drei')) == (
62 u'(<span class="number">1</span>, <span class="string">\''
63 u'zwei\'</span>, <span class="string">%s\'drei\'</span>)'
64 ) % ('u' if PY2 else '')
65
66 def test_custom_repr(self):
67 class Foo(object):
68 def __repr__(self):
69 return '<Foo 42>'
70 assert debug_repr(Foo()) == \
71 '<span class="object">&lt;Foo 42&gt;</span>'
72
73 def test_list_subclass_repr(self):
74 class MyList(list):
75 pass
76 assert debug_repr(MyList([1, 2])) == (
77 u'<span class="module">tests.test_debug.</span>MyList(['
78 u'<span class="number">1</span>, <span class="number">2</span>])'
79 )
80
81 def test_regex_repr(self):
82 assert debug_repr(re.compile(r'foo\d')) == \
83 u're.compile(<span class="string regex">r\'foo\\d\'</span>)'
84 # No ur'' in Py3
85 # http://bugs.python.org/issue15096
86 assert debug_repr(re.compile(u'foo\\d')) == (
87 u're.compile(<span class="string regex">%sr\'foo\\d\'</span>)' %
88 ('u' if PY2 else '')
89 )
90
91 def test_set_repr(self):
92 assert debug_repr(frozenset('x')) == \
93 u'frozenset([<span class="string">\'x\'</span>])'
94 assert debug_repr(set('x')) == \
95 u'set([<span class="string">\'x\'</span>])'
96
97 def test_recursive_repr(self):
98 a = [1]
99 a.append(a)
100 assert debug_repr(a) == u'[<span class="number">1</span>, [...]]'
101
102 def test_broken_repr(self):
103 class Foo(object):
104 def __repr__(self):
105 raise Exception('broken!')
106
107 assert debug_repr(Foo()) == (
108 u'<span class="brokenrepr">&lt;broken repr (Exception: '
109 u'broken!)&gt;</span>'
110 )
111
112
113 class Foo(object):
114 x = 42
115 y = 23
116
117
118 def __init__(self):
119 self.z = 15
120
121
122 class TestDebugHelpers(object):
123
124 def test_object_dumping(self):
125 drg = DebugReprGenerator()
126 out = drg.dump_object(Foo())
127 assert re.search('Details for tests.test_debug.Foo object at', out)
128 assert re.search('<th>x.*<span class="number">42</span>(?s)', out)
129 assert re.search('<th>y.*<span class="number">23</span>(?s)', out)
130 assert re.search('<th>z.*<span class="number">15</span>(?s)', out)
131
132 out = drg.dump_object({'x': 42, 'y': 23})
133 assert re.search('Contents of', out)
134 assert re.search('<th>x.*<span class="number">42</span>(?s)', out)
135 assert re.search('<th>y.*<span class="number">23</span>(?s)', out)
136
137 out = drg.dump_object({'x': 42, 'y': 23, 23: 11})
138 assert not re.search('Contents of', out)
139
140 out = drg.dump_locals({'x': 42, 'y': 23})
141 assert re.search('Local variables in frame', out)
142 assert re.search('<th>x.*<span class="number">42</span>(?s)', out)
143 assert re.search('<th>y.*<span class="number">23</span>(?s)', out)
144
145 def test_debug_dump(self):
146 old = sys.stdout
147 sys.stdout = HTMLStringO()
148 try:
149 dump([1, 2, 3])
150 x = sys.stdout.reset()
151 dump()
152 y = sys.stdout.reset()
153 finally:
154 sys.stdout = old
155
156 assert 'Details for list object at' in x
157 assert '<span class="number">1</span>' in x
158 assert 'Local variables in frame' in y
159 assert '<th>x' in y
160 assert '<th>old' in y
161
162 def test_debug_help(self):
163 old = sys.stdout
164 sys.stdout = HTMLStringO()
165 try:
166 helper([1, 2, 3])
167 x = sys.stdout.reset()
168 finally:
169 sys.stdout = old
170
171 assert 'Help on list object' in x
172 assert '__delitem__' in x
173
174
175 class TestTraceback(object):
176
177 def test_log(self):
178 try:
179 1/0
180 except ZeroDivisionError:
181 traceback = Traceback(*sys.exc_info())
182
183 buffer_ = io.BytesIO() if PY2 else io.StringIO()
184 traceback.log(buffer_)
185 assert buffer_.getvalue().strip() == traceback.plaintext.strip()
186
187 def test_sourcelines_encoding(self):
188 source = (u'# -*- coding: latin1 -*-\n\n'
189 u'def foo():\n'
190 u' """höhö"""\n'
191 u' 1 / 0\n'
192 u'foo()').encode('latin1')
193 code = compile(source, filename='lol.py', mode='exec')
194 try:
195 eval(code)
196 except ZeroDivisionError:
197 tb = sys.exc_info()[2]
198 traceback = Traceback(*sys.exc_info())
199
200 frames = traceback.frames
201 assert len(frames) == 3
202 assert frames[1].filename == 'lol.py'
203 assert frames[2].filename == 'lol.py'
204
205 class Loader(object):
206 def get_source(self, module):
207 return source
208
209 frames[1].loader = frames[2].loader = Loader()
210 assert frames[1].sourcelines == frames[2].sourcelines
211 assert [line.code for line in frames[1].get_annotated_lines()] == \
212 [line.code for line in frames[2].get_annotated_lines()]
213 assert u'höhö' in frames[1].sourcelines[3]
214
215 def test_filename_encoding(self, tmpdir, monkeypatch):
216 moduledir = tmpdir.mkdir('föö')
217 moduledir.join('bar.py').write('def foo():\n 1/0\n')
218 monkeypatch.syspath_prepend(str(moduledir))
219
220 import bar
221
222 try:
223 bar.foo()
224 except ZeroDivisionError:
225 traceback = Traceback(*sys.exc_info())
226
227 assert u'föö' in u'\n'.join(frame.render() for frame in traceback.frames)
0 # -*- coding: utf-8 -*-
1 """
2 tests.exceptions
3 ~~~~~~~~~~~~~~~~
4
5 The tests for the exception classes.
6
7 TODO:
8
9 - This is undertested. HTML is never checked
10
11 :copyright: (c) 2014 by Armin Ronacher.
12 :license: BSD, see LICENSE for more details.
13 """
14 import pytest
15
16 from werkzeug import exceptions
17 from werkzeug.wrappers import Response
18 from werkzeug._compat import text_type
19
20
21 def test_proxy_exception():
22 orig_resp = Response('Hello World')
23 with pytest.raises(exceptions.HTTPException) as excinfo:
24 exceptions.abort(orig_resp)
25 resp = excinfo.value.get_response({})
26 assert resp is orig_resp
27 assert resp.get_data() == b'Hello World'
28
29
30 @pytest.mark.parametrize('test', [
31 (exceptions.BadRequest, 400),
32 (exceptions.Unauthorized, 401),
33 (exceptions.Forbidden, 403),
34 (exceptions.NotFound, 404),
35 (exceptions.MethodNotAllowed, 405, ['GET', 'HEAD']),
36 (exceptions.NotAcceptable, 406),
37 (exceptions.RequestTimeout, 408),
38 (exceptions.Gone, 410),
39 (exceptions.LengthRequired, 411),
40 (exceptions.PreconditionFailed, 412),
41 (exceptions.RequestEntityTooLarge, 413),
42 (exceptions.RequestURITooLarge, 414),
43 (exceptions.UnsupportedMediaType, 415),
44 (exceptions.UnprocessableEntity, 422),
45 (exceptions.InternalServerError, 500),
46 (exceptions.NotImplemented, 501),
47 (exceptions.BadGateway, 502),
48 (exceptions.ServiceUnavailable, 503)
49 ])
50 def test_aborter_general(test):
51 exc_type = test[0]
52 args = test[1:]
53
54 with pytest.raises(exc_type) as exc_info:
55 exceptions.abort(*args)
56 assert type(exc_info.value) is exc_type
57
58
59 def test_aborter_custom():
60 myabort = exceptions.Aborter({1: exceptions.NotFound})
61 pytest.raises(LookupError, myabort, 404)
62 pytest.raises(exceptions.NotFound, myabort, 1)
63
64 myabort = exceptions.Aborter(extra={1: exceptions.NotFound})
65 pytest.raises(exceptions.NotFound, myabort, 404)
66 pytest.raises(exceptions.NotFound, myabort, 1)
67
68
69 def test_exception_repr():
70 exc = exceptions.NotFound()
71 assert text_type(exc) == '404: Not Found'
72 assert repr(exc) == "<NotFound '404: Not Found'>"
73
74 exc = exceptions.NotFound('Not There')
75 assert text_type(exc) == '404: Not Found'
76 assert repr(exc) == "<NotFound '404: Not Found'>"
77
78 def test_special_exceptions():
79 exc = exceptions.MethodNotAllowed(['GET', 'HEAD', 'POST'])
80 h = dict(exc.get_headers({}))
81 assert h['Allow'] == 'GET, HEAD, POST'
82 assert 'The method is not allowed' in exc.get_description()
0 # -*- coding: utf-8 -*-
1 """
2 tests.formparser
3 ~~~~~~~~~~~~~~~~
4
5 Tests the form parsing facilities.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 from __future__ import with_statement
11
12 import pytest
13
14 from os.path import join, dirname
15
16 from tests import strict_eq
17
18 from werkzeug import formparser
19 from werkzeug.test import create_environ, Client
20 from werkzeug.wrappers import Request, Response
21 from werkzeug.exceptions import RequestEntityTooLarge
22 from werkzeug.datastructures import MultiDict
23 from werkzeug.formparser import parse_form_data
24 from werkzeug._compat import BytesIO
25
26
27 @Request.application
28 def form_data_consumer(request):
29 result_object = request.args['object']
30 if result_object == 'text':
31 return Response(repr(request.form['text']))
32 f = request.files[result_object]
33 return Response(b'\n'.join((
34 repr(f.filename).encode('ascii'),
35 repr(f.name).encode('ascii'),
36 repr(f.content_type).encode('ascii'),
37 f.stream.read()
38 )))
39
40
41 def get_contents(filename):
42 with open(filename, 'rb') as f:
43 return f.read()
44
45
46 class TestFormParser(object):
47
48 def test_limiting(self):
49 data = b'foo=Hello+World&bar=baz'
50 req = Request.from_values(input_stream=BytesIO(data),
51 content_length=len(data),
52 content_type='application/x-www-form-urlencoded',
53 method='POST')
54 req.max_content_length = 400
55 strict_eq(req.form['foo'], u'Hello World')
56
57 req = Request.from_values(input_stream=BytesIO(data),
58 content_length=len(data),
59 content_type='application/x-www-form-urlencoded',
60 method='POST')
61 req.max_form_memory_size = 7
62 pytest.raises(RequestEntityTooLarge, lambda: req.form['foo'])
63
64 req = Request.from_values(input_stream=BytesIO(data),
65 content_length=len(data),
66 content_type='application/x-www-form-urlencoded',
67 method='POST')
68 req.max_form_memory_size = 400
69 strict_eq(req.form['foo'], u'Hello World')
70
71 data = (b'--foo\r\nContent-Disposition: form-field; name=foo\r\n\r\n'
72 b'Hello World\r\n'
73 b'--foo\r\nContent-Disposition: form-field; name=bar\r\n\r\n'
74 b'bar=baz\r\n--foo--')
75 req = Request.from_values(input_stream=BytesIO(data),
76 content_length=len(data),
77 content_type='multipart/form-data; boundary=foo',
78 method='POST')
79 req.max_content_length = 4
80 pytest.raises(RequestEntityTooLarge, lambda: req.form['foo'])
81
82 req = Request.from_values(input_stream=BytesIO(data),
83 content_length=len(data),
84 content_type='multipart/form-data; boundary=foo',
85 method='POST')
86 req.max_content_length = 400
87 strict_eq(req.form['foo'], u'Hello World')
88
89 req = Request.from_values(input_stream=BytesIO(data),
90 content_length=len(data),
91 content_type='multipart/form-data; boundary=foo',
92 method='POST')
93 req.max_form_memory_size = 7
94 pytest.raises(RequestEntityTooLarge, lambda: req.form['foo'])
95
96 req = Request.from_values(input_stream=BytesIO(data),
97 content_length=len(data),
98 content_type='multipart/form-data; boundary=foo',
99 method='POST')
100 req.max_form_memory_size = 400
101 strict_eq(req.form['foo'], u'Hello World')
102
103 def test_missing_multipart_boundary(self):
104 data = (b'--foo\r\nContent-Disposition: form-field; name=foo\r\n\r\n'
105 b'Hello World\r\n'
106 b'--foo\r\nContent-Disposition: form-field; name=bar\r\n\r\n'
107 b'bar=baz\r\n--foo--')
108 req = Request.from_values(input_stream=BytesIO(data),
109 content_length=len(data),
110 content_type='multipart/form-data',
111 method='POST')
112 assert req.form == {}
113
114 def test_parse_form_data_put_without_content(self):
115 # A PUT without a Content-Type header returns empty data
116
117 # Both rfc1945 and rfc2616 (1.0 and 1.1) say "Any HTTP/[1.0/1.1] message
118 # containing an entity-body SHOULD include a Content-Type header field
119 # defining the media type of that body." In the case where either
120 # headers are omitted, parse_form_data should still work.
121 env = create_environ('/foo', 'http://example.org/', method='PUT')
122 del env['CONTENT_TYPE']
123 del env['CONTENT_LENGTH']
124
125 stream, form, files = formparser.parse_form_data(env)
126 strict_eq(stream.read(), b'')
127 strict_eq(len(form), 0)
128 strict_eq(len(files), 0)
129
130 def test_parse_form_data_get_without_content(self):
131 env = create_environ('/foo', 'http://example.org/', method='GET')
132 del env['CONTENT_TYPE']
133 del env['CONTENT_LENGTH']
134
135 stream, form, files = formparser.parse_form_data(env)
136 strict_eq(stream.read(), b'')
137 strict_eq(len(form), 0)
138 strict_eq(len(files), 0)
139
140 def test_large_file(self):
141 data = b'x' * (1024 * 600)
142 req = Request.from_values(data={'foo': (BytesIO(data), 'test.txt')},
143 method='POST')
144 # make sure we have a real file here, because we expect to be
145 # on the disk. > 1024 * 500
146 assert hasattr(req.files['foo'].stream, u'fileno')
147 # close file to prevent fds from leaking
148 req.files['foo'].close()
149
150 def test_streaming_parse(self):
151 data = b'x' * (1024 * 600)
152 class StreamMPP(formparser.MultiPartParser):
153 def parse(self, file, boundary, content_length):
154 i = iter(self.parse_lines(file, boundary, content_length))
155 one = next(i)
156 two = next(i)
157 return self.cls(()), {'one': one, 'two': two}
158 class StreamFDP(formparser.FormDataParser):
159 def _sf_parse_multipart(self, stream, mimetype,
160 content_length, options):
161 form, files = StreamMPP(
162 self.stream_factory, self.charset, self.errors,
163 max_form_memory_size=self.max_form_memory_size,
164 cls=self.cls).parse(stream, options.get('boundary').encode('ascii'),
165 content_length)
166 return stream, form, files
167 parse_functions = {}
168 parse_functions.update(formparser.FormDataParser.parse_functions)
169 parse_functions['multipart/form-data'] = _sf_parse_multipart
170 class StreamReq(Request):
171 form_data_parser_class = StreamFDP
172 req = StreamReq.from_values(data={'foo': (BytesIO(data), 'test.txt')},
173 method='POST')
174 strict_eq('begin_file', req.files['one'][0])
175 strict_eq(('foo', 'test.txt'), req.files['one'][1][1:])
176 strict_eq('cont', req.files['two'][0])
177 strict_eq(data, req.files['two'][1])
178
179
180 class TestMultiPart(object):
181
182 def test_basic(self):
183 resources = join(dirname(__file__), 'multipart')
184 client = Client(form_data_consumer, Response)
185
186 repository = [
187 ('firefox3-2png1txt', '---------------------------186454651713519341951581030105', [
188 (u'anchor.png', 'file1', 'image/png', 'file1.png'),
189 (u'application_edit.png', 'file2', 'image/png', 'file2.png')
190 ], u'example text'),
191 ('firefox3-2pnglongtext', '---------------------------14904044739787191031754711748', [
192 (u'accept.png', 'file1', 'image/png', 'file1.png'),
193 (u'add.png', 'file2', 'image/png', 'file2.png')
194 ], u'--long text\r\n--with boundary\r\n--lookalikes--'),
195 ('opera8-2png1txt', '----------zEO9jQKmLc2Cq88c23Dx19', [
196 (u'arrow_branch.png', 'file1', 'image/png', 'file1.png'),
197 (u'award_star_bronze_1.png', 'file2', 'image/png', 'file2.png')
198 ], u'blafasel öäü'),
199 ('webkit3-2png1txt', '----WebKitFormBoundaryjdSFhcARk8fyGNy6', [
200 (u'gtk-apply.png', 'file1', 'image/png', 'file1.png'),
201 (u'gtk-no.png', 'file2', 'image/png', 'file2.png')
202 ], u'this is another text with ümläüts'),
203 ('ie6-2png1txt', '---------------------------7d91b03a20128', [
204 (u'file1.png', 'file1', 'image/x-png', 'file1.png'),
205 (u'file2.png', 'file2', 'image/x-png', 'file2.png')
206 ], u'ie6 sucks :-/')
207 ]
208
209 for name, boundary, files, text in repository:
210 folder = join(resources, name)
211 data = get_contents(join(folder, 'request.txt'))
212 for filename, field, content_type, fsname in files:
213 response = client.post('/?object=' + field, data=data, content_type=
214 'multipart/form-data; boundary="%s"' % boundary,
215 content_length=len(data))
216 lines = response.get_data().split(b'\n', 3)
217 strict_eq(lines[0], repr(filename).encode('ascii'))
218 strict_eq(lines[1], repr(field).encode('ascii'))
219 strict_eq(lines[2], repr(content_type).encode('ascii'))
220 strict_eq(lines[3], get_contents(join(folder, fsname)))
221 response = client.post('/?object=text', data=data, content_type=
222 'multipart/form-data; boundary="%s"' % boundary,
223 content_length=len(data))
224 strict_eq(response.get_data(), repr(text).encode('utf-8'))
225
226 def test_ie7_unc_path(self):
227 client = Client(form_data_consumer, Response)
228 data_file = join(dirname(__file__), 'multipart', 'ie7_full_path_request.txt')
229 data = get_contents(data_file)
230 boundary = '---------------------------7da36d1b4a0164'
231 response = client.post('/?object=cb_file_upload_multiple', data=data, content_type=
232 'multipart/form-data; boundary="%s"' % boundary, content_length=len(data))
233 lines = response.get_data().split(b'\n', 3)
234 strict_eq(lines[0],
235 repr(u'Sellersburg Town Council Meeting 02-22-2010doc.doc').encode('ascii'))
236
237 def test_end_of_file(self):
238 # This test looks innocent but it was actually timeing out in
239 # the Werkzeug 0.5 release version (#394)
240 data = (
241 b'--foo\r\n'
242 b'Content-Disposition: form-data; name="test"; filename="test.txt"\r\n'
243 b'Content-Type: text/plain\r\n\r\n'
244 b'file contents and no end'
245 )
246 data = Request.from_values(input_stream=BytesIO(data),
247 content_length=len(data),
248 content_type='multipart/form-data; boundary=foo',
249 method='POST')
250 assert not data.files
251 assert not data.form
252
253 def test_broken(self):
254 data = (
255 '--foo\r\n'
256 'Content-Disposition: form-data; name="test"; filename="test.txt"\r\n'
257 'Content-Transfer-Encoding: base64\r\n'
258 'Content-Type: text/plain\r\n\r\n'
259 'broken base 64'
260 '--foo--'
261 )
262 _, form, files = formparser.parse_form_data(create_environ(data=data,
263 method='POST', content_type='multipart/form-data; boundary=foo'))
264 assert not files
265 assert not form
266
267 pytest.raises(ValueError, formparser.parse_form_data,
268 create_environ(data=data, method='POST',
269 content_type='multipart/form-data; boundary=foo'),
270 silent=False)
271
272 def test_file_no_content_type(self):
273 data = (
274 b'--foo\r\n'
275 b'Content-Disposition: form-data; name="test"; filename="test.txt"\r\n\r\n'
276 b'file contents\r\n--foo--'
277 )
278 data = Request.from_values(input_stream=BytesIO(data),
279 content_length=len(data),
280 content_type='multipart/form-data; boundary=foo',
281 method='POST')
282 assert data.files['test'].filename == 'test.txt'
283 strict_eq(data.files['test'].read(), b'file contents')
284
285 def test_extra_newline(self):
286 # this test looks innocent but it was actually timeing out in
287 # the Werkzeug 0.5 release version (#394)
288 data = (
289 b'\r\n\r\n--foo\r\n'
290 b'Content-Disposition: form-data; name="foo"\r\n\r\n'
291 b'a string\r\n'
292 b'--foo--'
293 )
294 data = Request.from_values(input_stream=BytesIO(data),
295 content_length=len(data),
296 content_type='multipart/form-data; boundary=foo',
297 method='POST')
298 assert not data.files
299 strict_eq(data.form['foo'], u'a string')
300
301 def test_headers(self):
302 data = (b'--foo\r\n'
303 b'Content-Disposition: form-data; name="foo"; filename="foo.txt"\r\n'
304 b'X-Custom-Header: blah\r\n'
305 b'Content-Type: text/plain; charset=utf-8\r\n\r\n'
306 b'file contents, just the contents\r\n'
307 b'--foo--')
308 req = Request.from_values(input_stream=BytesIO(data),
309 content_length=len(data),
310 content_type='multipart/form-data; boundary=foo',
311 method='POST')
312 foo = req.files['foo']
313 strict_eq(foo.mimetype, 'text/plain')
314 strict_eq(foo.mimetype_params, {'charset': 'utf-8'})
315 strict_eq(foo.headers['content-type'], foo.content_type)
316 strict_eq(foo.content_type, 'text/plain; charset=utf-8')
317 strict_eq(foo.headers['x-custom-header'], 'blah')
318
319 def test_nonstandard_line_endings(self):
320 for nl in b'\n', b'\r', b'\r\n':
321 data = nl.join((
322 b'--foo',
323 b'Content-Disposition: form-data; name=foo',
324 b'',
325 b'this is just bar',
326 b'--foo',
327 b'Content-Disposition: form-data; name=bar',
328 b'',
329 b'blafasel',
330 b'--foo--'
331 ))
332 req = Request.from_values(input_stream=BytesIO(data),
333 content_length=len(data),
334 content_type='multipart/form-data; '
335 'boundary=foo', method='POST')
336 strict_eq(req.form['foo'], u'this is just bar')
337 strict_eq(req.form['bar'], u'blafasel')
338
339 def test_failures(self):
340 def parse_multipart(stream, boundary, content_length):
341 parser = formparser.MultiPartParser(content_length)
342 return parser.parse(stream, boundary, content_length)
343 pytest.raises(ValueError, parse_multipart, BytesIO(), b'broken ', 0)
344
345 data = b'--foo\r\n\r\nHello World\r\n--foo--'
346 pytest.raises(ValueError, parse_multipart, BytesIO(data), b'foo', len(data))
347
348 data = b'--foo\r\nContent-Disposition: form-field; name=foo\r\n' \
349 b'Content-Transfer-Encoding: base64\r\n\r\nHello World\r\n--foo--'
350 pytest.raises(ValueError, parse_multipart, BytesIO(data), b'foo', len(data))
351
352 data = b'--foo\r\nContent-Disposition: form-field; name=foo\r\n\r\nHello World\r\n'
353 pytest.raises(ValueError, parse_multipart, BytesIO(data), b'foo', len(data))
354
355 x = formparser.parse_multipart_headers(['foo: bar\r\n', ' x test\r\n'])
356 strict_eq(x['foo'], 'bar\n x test')
357 pytest.raises(ValueError, formparser.parse_multipart_headers,
358 ['foo: bar\r\n', ' x test'])
359
360 def test_bad_newline_bad_newline_assumption(self):
361 class ISORequest(Request):
362 charset = 'latin1'
363 contents = b'U2vlbmUgbORu'
364 data = b'--foo\r\nContent-Disposition: form-data; name="test"\r\n' \
365 b'Content-Transfer-Encoding: base64\r\n\r\n' + \
366 contents + b'\r\n--foo--'
367 req = ISORequest.from_values(input_stream=BytesIO(data),
368 content_length=len(data),
369 content_type='multipart/form-data; boundary=foo',
370 method='POST')
371 strict_eq(req.form['test'], u'Sk\xe5ne l\xe4n')
372
373 def test_empty_multipart(self):
374 environ = {}
375 data = b'--boundary--'
376 environ['REQUEST_METHOD'] = 'POST'
377 environ['CONTENT_TYPE'] = 'multipart/form-data; boundary=boundary'
378 environ['CONTENT_LENGTH'] = str(len(data))
379 environ['wsgi.input'] = BytesIO(data)
380 stream, form, files = parse_form_data(environ, silent=False)
381 rv = stream.read()
382 assert rv == b''
383 assert form == MultiDict()
384 assert files == MultiDict()
385
386
387 class TestInternalFunctions(object):
388
389 def test_line_parser(self):
390 assert formparser._line_parse('foo') == ('foo', False)
391 assert formparser._line_parse('foo\r\n') == ('foo', True)
392 assert formparser._line_parse('foo\r') == ('foo', True)
393 assert formparser._line_parse('foo\n') == ('foo', True)
394
395 def test_find_terminator(self):
396 lineiter = iter(b'\n\n\nfoo\nbar\nbaz'.splitlines(True))
397 find_terminator = formparser.MultiPartParser()._find_terminator
398 line = find_terminator(lineiter)
399 assert line == b'foo'
400 assert list(lineiter) == [b'bar\n', b'baz']
401 assert find_terminator([]) == b''
402 assert find_terminator([b'']) == b''
0 # -*- coding: utf-8 -*-
1 """
2 tests.http
3 ~~~~~~~~~~
4
5 HTTP parsing utilities.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import pytest
11
12 from datetime import datetime
13
14 from tests import strict_eq
15 from werkzeug._compat import itervalues, wsgi_encoding_dance
16
17 from werkzeug import http, datastructures
18 from werkzeug.test import create_environ
19
20
21 class TestHTTPUtility(object):
22
23 def test_accept(self):
24 a = http.parse_accept_header('en-us,ru;q=0.5')
25 assert list(itervalues(a)) == ['en-us', 'ru']
26 assert a.best == 'en-us'
27 assert a.find('ru') == 1
28 pytest.raises(ValueError, a.index, 'de')
29 assert a.to_header() == 'en-us,ru;q=0.5'
30
31 def test_mime_accept(self):
32 a = http.parse_accept_header('text/xml,application/xml,'
33 'application/xhtml+xml,'
34 'application/foo;quiet=no; bar=baz;q=0.6,'
35 'text/html;q=0.9,text/plain;q=0.8,'
36 'image/png,*/*;q=0.5',
37 datastructures.MIMEAccept)
38 pytest.raises(ValueError, lambda: a['missing'])
39 assert a['image/png'] == 1
40 assert a['text/plain'] == 0.8
41 assert a['foo/bar'] == 0.5
42 assert a['application/foo;quiet=no; bar=baz'] == 0.6
43 assert a[a.find('foo/bar')] == ('*/*', 0.5)
44
45 def test_accept_matches(self):
46 a = http.parse_accept_header('text/xml,application/xml,application/xhtml+xml,'
47 'text/html;q=0.9,text/plain;q=0.8,'
48 'image/png', datastructures.MIMEAccept)
49 assert a.best_match(['text/html', 'application/xhtml+xml']) == \
50 'application/xhtml+xml'
51 assert a.best_match(['text/html']) == 'text/html'
52 assert a.best_match(['foo/bar']) is None
53 assert a.best_match(['foo/bar', 'bar/foo'], default='foo/bar') == 'foo/bar'
54 assert a.best_match(['application/xml', 'text/xml']) == 'application/xml'
55
56 def test_charset_accept(self):
57 a = http.parse_accept_header('ISO-8859-1,utf-8;q=0.7,*;q=0.7',
58 datastructures.CharsetAccept)
59 assert a['iso-8859-1'] == a['iso8859-1']
60 assert a['iso-8859-1'] == 1
61 assert a['UTF8'] == 0.7
62 assert a['ebcdic'] == 0.7
63
64 def test_language_accept(self):
65 a = http.parse_accept_header('de-AT,de;q=0.8,en;q=0.5',
66 datastructures.LanguageAccept)
67 assert a.best == 'de-AT'
68 assert 'de_AT' in a
69 assert 'en' in a
70 assert a['de-at'] == 1
71 assert a['en'] == 0.5
72
73 def test_set_header(self):
74 hs = http.parse_set_header('foo, Bar, "Blah baz", Hehe')
75 assert 'blah baz' in hs
76 assert 'foobar' not in hs
77 assert 'foo' in hs
78 assert list(hs) == ['foo', 'Bar', 'Blah baz', 'Hehe']
79 hs.add('Foo')
80 assert hs.to_header() == 'foo, Bar, "Blah baz", Hehe'
81
82 def test_list_header(self):
83 hl = http.parse_list_header('foo baz, blah')
84 assert hl == ['foo baz', 'blah']
85
86 def test_dict_header(self):
87 d = http.parse_dict_header('foo="bar baz", blah=42')
88 assert d == {'foo': 'bar baz', 'blah': '42'}
89
90 def test_cache_control_header(self):
91 cc = http.parse_cache_control_header('max-age=0, no-cache')
92 assert cc.max_age == 0
93 assert cc.no_cache
94 cc = http.parse_cache_control_header('private, community="UCI"', None,
95 datastructures.ResponseCacheControl)
96 assert cc.private
97 assert cc['community'] == 'UCI'
98
99 c = datastructures.ResponseCacheControl()
100 assert c.no_cache is None
101 assert c.private is None
102 c.no_cache = True
103 assert c.no_cache == '*'
104 c.private = True
105 assert c.private == '*'
106 del c.private
107 assert c.private is None
108 assert c.to_header() == 'no-cache'
109
110 def test_authorization_header(self):
111 a = http.parse_authorization_header('Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==')
112 assert a.type == 'basic'
113 assert a.username == 'Aladdin'
114 assert a.password == 'open sesame'
115
116 a = http.parse_authorization_header('''Digest username="Mufasa",
117 realm="testrealm@host.invalid",
118 nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
119 uri="/dir/index.html",
120 qop=auth,
121 nc=00000001,
122 cnonce="0a4f113b",
123 response="6629fae49393a05397450978507c4ef1",
124 opaque="5ccc069c403ebaf9f0171e9517f40e41"''')
125 assert a.type == 'digest'
126 assert a.username == 'Mufasa'
127 assert a.realm == 'testrealm@host.invalid'
128 assert a.nonce == 'dcd98b7102dd2f0e8b11d0f600bfb0c093'
129 assert a.uri == '/dir/index.html'
130 assert 'auth' in a.qop
131 assert a.nc == '00000001'
132 assert a.cnonce == '0a4f113b'
133 assert a.response == '6629fae49393a05397450978507c4ef1'
134 assert a.opaque == '5ccc069c403ebaf9f0171e9517f40e41'
135
136 a = http.parse_authorization_header('''Digest username="Mufasa",
137 realm="testrealm@host.invalid",
138 nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
139 uri="/dir/index.html",
140 response="e257afa1414a3340d93d30955171dd0e",
141 opaque="5ccc069c403ebaf9f0171e9517f40e41"''')
142 assert a.type == 'digest'
143 assert a.username == 'Mufasa'
144 assert a.realm == 'testrealm@host.invalid'
145 assert a.nonce == 'dcd98b7102dd2f0e8b11d0f600bfb0c093'
146 assert a.uri == '/dir/index.html'
147 assert a.response == 'e257afa1414a3340d93d30955171dd0e'
148 assert a.opaque == '5ccc069c403ebaf9f0171e9517f40e41'
149
150 assert http.parse_authorization_header('') is None
151 assert http.parse_authorization_header(None) is None
152 assert http.parse_authorization_header('foo') is None
153
154 def test_www_authenticate_header(self):
155 wa = http.parse_www_authenticate_header('Basic realm="WallyWorld"')
156 assert wa.type == 'basic'
157 assert wa.realm == 'WallyWorld'
158 wa.realm = 'Foo Bar'
159 assert wa.to_header() == 'Basic realm="Foo Bar"'
160
161 wa = http.parse_www_authenticate_header('''Digest
162 realm="testrealm@host.com",
163 qop="auth,auth-int",
164 nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
165 opaque="5ccc069c403ebaf9f0171e9517f40e41"''')
166 assert wa.type == 'digest'
167 assert wa.realm == 'testrealm@host.com'
168 assert 'auth' in wa.qop
169 assert 'auth-int' in wa.qop
170 assert wa.nonce == 'dcd98b7102dd2f0e8b11d0f600bfb0c093'
171 assert wa.opaque == '5ccc069c403ebaf9f0171e9517f40e41'
172
173 wa = http.parse_www_authenticate_header('broken')
174 assert wa.type == 'broken'
175
176 assert not http.parse_www_authenticate_header('').type
177 assert not http.parse_www_authenticate_header('')
178
179 def test_etags(self):
180 assert http.quote_etag('foo') == '"foo"'
181 assert http.quote_etag('foo', True) == 'w/"foo"'
182 assert http.unquote_etag('"foo"') == ('foo', False)
183 assert http.unquote_etag('w/"foo"') == ('foo', True)
184 es = http.parse_etags('"foo", "bar", w/"baz", blar')
185 assert sorted(es) == ['bar', 'blar', 'foo']
186 assert 'foo' in es
187 assert 'baz' not in es
188 assert es.contains_weak('baz')
189 assert 'blar' in es
190 assert es.contains_raw('w/"baz"')
191 assert es.contains_raw('"foo"')
192 assert sorted(es.to_header().split(', ')) == ['"bar"', '"blar"', '"foo"', 'w/"baz"']
193
194 def test_etags_nonzero(self):
195 etags = http.parse_etags('w/"foo"')
196 assert bool(etags)
197 assert etags.contains_raw('w/"foo"')
198
199 def test_parse_date(self):
200 assert http.parse_date('Sun, 06 Nov 1994 08:49:37 GMT ') == datetime(1994, 11, 6, 8, 49, 37)
201 assert http.parse_date('Sunday, 06-Nov-94 08:49:37 GMT') == datetime(1994, 11, 6, 8, 49, 37)
202 assert http.parse_date(' Sun Nov 6 08:49:37 1994') == datetime(1994, 11, 6, 8, 49, 37)
203 assert http.parse_date('foo') is None
204
205 def test_parse_date_overflows(self):
206 assert http.parse_date(' Sun 02 Feb 1343 08:49:37 GMT') == datetime(1343, 2, 2, 8, 49, 37)
207 assert http.parse_date('Thu, 01 Jan 1970 00:00:00 GMT') == datetime(1970, 1, 1, 0, 0)
208 assert http.parse_date('Thu, 33 Jan 1970 00:00:00 GMT') is None
209
210 def test_remove_entity_headers(self):
211 now = http.http_date()
212 headers1 = [('Date', now), ('Content-Type', 'text/html'), ('Content-Length', '0')]
213 headers2 = datastructures.Headers(headers1)
214
215 http.remove_entity_headers(headers1)
216 assert headers1 == [('Date', now)]
217
218 http.remove_entity_headers(headers2)
219 assert headers2 == datastructures.Headers([(u'Date', now)])
220
221 def test_remove_hop_by_hop_headers(self):
222 headers1 = [('Connection', 'closed'), ('Foo', 'bar'),
223 ('Keep-Alive', 'wtf')]
224 headers2 = datastructures.Headers(headers1)
225
226 http.remove_hop_by_hop_headers(headers1)
227 assert headers1 == [('Foo', 'bar')]
228
229 http.remove_hop_by_hop_headers(headers2)
230 assert headers2 == datastructures.Headers([('Foo', 'bar')])
231
232 def test_parse_options_header(self):
233 assert http.parse_options_header(r'something; foo="other\"thing"') == \
234 ('something', {'foo': 'other"thing'})
235 assert http.parse_options_header(r'something; foo="other\"thing"; meh=42') == \
236 ('something', {'foo': 'other"thing', 'meh': '42'})
237 assert http.parse_options_header(r'something; foo="other\"thing"; meh=42; bleh') == \
238 ('something', {'foo': 'other"thing', 'meh': '42', 'bleh': None})
239 assert http.parse_options_header('something; foo="other;thing"; meh=42; bleh') == \
240 ('something', {'foo': 'other;thing', 'meh': '42', 'bleh': None})
241 assert http.parse_options_header('something; foo="otherthing"; meh=; bleh') == \
242 ('something', {'foo': 'otherthing', 'meh': None, 'bleh': None})
243
244
245
246 def test_dump_options_header(self):
247 assert http.dump_options_header('foo', {'bar': 42}) == \
248 'foo; bar=42'
249 assert http.dump_options_header('foo', {'bar': 42, 'fizz': None}) in \
250 ('foo; bar=42; fizz', 'foo; fizz; bar=42')
251
252 def test_dump_header(self):
253 assert http.dump_header([1, 2, 3]) == '1, 2, 3'
254 assert http.dump_header([1, 2, 3], allow_token=False) == '"1", "2", "3"'
255 assert http.dump_header({'foo': 'bar'}, allow_token=False) == 'foo="bar"'
256 assert http.dump_header({'foo': 'bar'}) == 'foo=bar'
257
258 def test_is_resource_modified(self):
259 env = create_environ()
260
261 # ignore POST
262 env['REQUEST_METHOD'] = 'POST'
263 assert not http.is_resource_modified(env, etag='testing')
264 env['REQUEST_METHOD'] = 'GET'
265
266 # etagify from data
267 pytest.raises(TypeError, http.is_resource_modified, env,
268 data='42', etag='23')
269 env['HTTP_IF_NONE_MATCH'] = http.generate_etag(b'awesome')
270 assert not http.is_resource_modified(env, data=b'awesome')
271
272 env['HTTP_IF_MODIFIED_SINCE'] = http.http_date(datetime(2008, 1, 1, 12, 30))
273 assert not http.is_resource_modified(env,
274 last_modified=datetime(2008, 1, 1, 12, 00))
275 assert http.is_resource_modified(env,
276 last_modified=datetime(2008, 1, 1, 13, 00))
277
278 def test_date_formatting(self):
279 assert http.cookie_date(0) == 'Thu, 01-Jan-1970 00:00:00 GMT'
280 assert http.cookie_date(datetime(1970, 1, 1)) == 'Thu, 01-Jan-1970 00:00:00 GMT'
281 assert http.http_date(0) == 'Thu, 01 Jan 1970 00:00:00 GMT'
282 assert http.http_date(datetime(1970, 1, 1)) == 'Thu, 01 Jan 1970 00:00:00 GMT'
283
284 def test_cookies(self):
285 strict_eq(
286 dict(http.parse_cookie('dismiss-top=6; CP=null*; PHPSESSID=0a539d42abc001cd'
287 'c762809248d4beed; a=42; b="\\\";"')),
288 {
289 'CP': u'null*',
290 'PHPSESSID': u'0a539d42abc001cdc762809248d4beed',
291 'a': u'42',
292 'dismiss-top': u'6',
293 'b': u'\";'
294 }
295 )
296 rv = http.dump_cookie('foo', 'bar baz blub', 360, httponly=True,
297 sync_expires=False)
298 assert type(rv) is str
299 assert set(rv.split('; ')) == set(['HttpOnly', 'Max-Age=360',
300 'Path=/', 'foo="bar baz blub"'])
301
302 strict_eq(dict(http.parse_cookie('fo234{=bar; blub=Blah')),
303 {'fo234{': u'bar', 'blub': u'Blah'})
304
305 def test_cookie_quoting(self):
306 val = http.dump_cookie("foo", "?foo")
307 strict_eq(val, 'foo="?foo"; Path=/')
308 strict_eq(dict(http.parse_cookie(val)), {'foo': u'?foo'})
309
310 strict_eq(dict(http.parse_cookie(r'foo="foo\054bar"')),
311 {'foo': u'foo,bar'})
312
313 def test_cookie_domain_resolving(self):
314 val = http.dump_cookie('foo', 'bar', domain=u'\N{SNOWMAN}.com')
315 strict_eq(val, 'foo=bar; Domain=xn--n3h.com; Path=/')
316
317 def test_cookie_unicode_dumping(self):
318 val = http.dump_cookie('foo', u'\N{SNOWMAN}')
319 h = datastructures.Headers()
320 h.add('Set-Cookie', val)
321 assert h['Set-Cookie'] == 'foo="\\342\\230\\203"; Path=/'
322
323 cookies = http.parse_cookie(h['Set-Cookie'])
324 assert cookies['foo'] == u'\N{SNOWMAN}'
325
326 def test_cookie_unicode_keys(self):
327 # Yes, this is technically against the spec but happens
328 val = http.dump_cookie(u'fö', u'fö')
329 assert val == wsgi_encoding_dance(u'fö="f\\303\\266"; Path=/', 'utf-8')
330 cookies = http.parse_cookie(val)
331 assert cookies[u'fö'] == u'fö'
332
333 def test_cookie_unicode_parsing(self):
334 # This is actually a correct test. This is what is being submitted
335 # by firefox if you set an unicode cookie and we get the cookie sent
336 # in on Python 3 under PEP 3333.
337 cookies = http.parse_cookie(u'fö=fö')
338 assert cookies[u'fö'] == u'fö'
339
340 def test_cookie_domain_encoding(self):
341 val = http.dump_cookie('foo', 'bar', domain=u'\N{SNOWMAN}.com')
342 strict_eq(val, 'foo=bar; Domain=xn--n3h.com; Path=/')
343
344 val = http.dump_cookie('foo', 'bar', domain=u'.\N{SNOWMAN}.com')
345 strict_eq(val, 'foo=bar; Domain=.xn--n3h.com; Path=/')
346
347 val = http.dump_cookie('foo', 'bar', domain=u'.foo.com')
348 strict_eq(val, 'foo=bar; Domain=.foo.com; Path=/')
349
350
351 class TestRange(object):
352
353 def test_if_range_parsing(self):
354 rv = http.parse_if_range_header('"Test"')
355 assert rv.etag == 'Test'
356 assert rv.date is None
357 assert rv.to_header() == '"Test"'
358
359 # weak information is dropped
360 rv = http.parse_if_range_header('w/"Test"')
361 assert rv.etag == 'Test'
362 assert rv.date is None
363 assert rv.to_header() == '"Test"'
364
365 # broken etags are supported too
366 rv = http.parse_if_range_header('bullshit')
367 assert rv.etag == 'bullshit'
368 assert rv.date is None
369 assert rv.to_header() == '"bullshit"'
370
371 rv = http.parse_if_range_header('Thu, 01 Jan 1970 00:00:00 GMT')
372 assert rv.etag is None
373 assert rv.date == datetime(1970, 1, 1)
374 assert rv.to_header() == 'Thu, 01 Jan 1970 00:00:00 GMT'
375
376 for x in '', None:
377 rv = http.parse_if_range_header(x)
378 assert rv.etag is None
379 assert rv.date is None
380 assert rv.to_header() == ''
381
382 def test_range_parsing(self):
383 rv = http.parse_range_header('bytes=52')
384 assert rv is None
385
386 rv = http.parse_range_header('bytes=52-')
387 assert rv.units == 'bytes'
388 assert rv.ranges == [(52, None)]
389 assert rv.to_header() == 'bytes=52-'
390
391 rv = http.parse_range_header('bytes=52-99')
392 assert rv.units == 'bytes'
393 assert rv.ranges == [(52, 100)]
394 assert rv.to_header() == 'bytes=52-99'
395
396 rv = http.parse_range_header('bytes=52-99,-1000')
397 assert rv.units == 'bytes'
398 assert rv.ranges == [(52, 100), (-1000, None)]
399 assert rv.to_header() == 'bytes=52-99,-1000'
400
401 rv = http.parse_range_header('bytes = 1 - 100')
402 assert rv.units == 'bytes'
403 assert rv.ranges == [(1, 101)]
404 assert rv.to_header() == 'bytes=1-100'
405
406 rv = http.parse_range_header('AWesomes=0-999')
407 assert rv.units == 'awesomes'
408 assert rv.ranges == [(0, 1000)]
409 assert rv.to_header() == 'awesomes=0-999'
410
411 def test_content_range_parsing(self):
412 rv = http.parse_content_range_header('bytes 0-98/*')
413 assert rv.units == 'bytes'
414 assert rv.start == 0
415 assert rv.stop == 99
416 assert rv.length is None
417 assert rv.to_header() == 'bytes 0-98/*'
418
419 rv = http.parse_content_range_header('bytes 0-98/*asdfsa')
420 assert rv is None
421
422 rv = http.parse_content_range_header('bytes 0-99/100')
423 assert rv.to_header() == 'bytes 0-99/100'
424 rv.start = None
425 rv.stop = None
426 assert rv.units == 'bytes'
427 assert rv.to_header() == 'bytes */100'
428
429 rv = http.parse_content_range_header('bytes */100')
430 assert rv.start is None
431 assert rv.stop is None
432 assert rv.length == 100
433 assert rv.units == 'bytes'
434
435
436 class TestRegression(object):
437
438 def test_best_match_works(self):
439 # was a bug in 0.6
440 rv = http.parse_accept_header('foo=,application/xml,application/xhtml+xml,'
441 'text/html;q=0.9,text/plain;q=0.8,'
442 'image/png,*/*;q=0.5',
443 datastructures.MIMEAccept).best_match(['foo/bar'])
444 assert rv == 'foo/bar'
0 # -*- coding: utf-8 -*-
1 """
2 tests.internal
3 ~~~~~~~~~~~~~~
4
5 Internal tests.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import pytest
11
12 from datetime import datetime
13 from warnings import filterwarnings, resetwarnings
14
15 from werkzeug.wrappers import Request, Response
16
17 from werkzeug import _internal as internal
18 from werkzeug.test import create_environ
19
20
21 def test_date_to_unix():
22 assert internal._date_to_unix(datetime(1970, 1, 1)) == 0
23 assert internal._date_to_unix(datetime(1970, 1, 1, 1, 0, 0)) == 3600
24 assert internal._date_to_unix(datetime(1970, 1, 1, 1, 1, 1)) == 3661
25 x = datetime(2010, 2, 15, 16, 15, 39)
26 assert internal._date_to_unix(x) == 1266250539
27
28 def test_easteregg():
29 req = Request.from_values('/?macgybarchakku')
30 resp = Response.force_type(internal._easteregg(None), req)
31 assert b'About Werkzeug' in resp.get_data()
32 assert b'the Swiss Army knife of Python web development' in resp.get_data()
33
34 def test_wrapper_internals():
35 req = Request.from_values(data={'foo': 'bar'}, method='POST')
36 req._load_form_data()
37 assert req.form.to_dict() == {'foo': 'bar'}
38
39 # second call does not break
40 req._load_form_data()
41 assert req.form.to_dict() == {'foo': 'bar'}
42
43 # check reprs
44 assert repr(req) == "<Request 'http://localhost/' [POST]>"
45 resp = Response()
46 assert repr(resp) == '<Response 0 bytes [200 OK]>'
47 resp.set_data('Hello World!')
48 assert repr(resp) == '<Response 12 bytes [200 OK]>'
49 resp.response = iter(['Test'])
50 assert repr(resp) == '<Response streamed [200 OK]>'
51
52 # unicode data does not set content length
53 response = Response([u'Hällo Wörld'])
54 headers = response.get_wsgi_headers(create_environ())
55 assert u'Content-Length' not in headers
56
57 response = Response([u'Hällo Wörld'.encode('utf-8')])
58 headers = response.get_wsgi_headers(create_environ())
59 assert u'Content-Length' in headers
60
61 # check for internal warnings
62 filterwarnings('error', category=Warning)
63 response = Response()
64 environ = create_environ()
65 response.response = 'What the...?'
66 pytest.raises(Warning, lambda: list(response.iter_encoded()))
67 pytest.raises(Warning, lambda: list(response.get_app_iter(environ)))
68 response.direct_passthrough = True
69 pytest.raises(Warning, lambda: list(response.iter_encoded()))
70 pytest.raises(Warning, lambda: list(response.get_app_iter(environ)))
71 resetwarnings()
0 # -*- coding: utf-8 -*-
1 """
2 tests.local
3 ~~~~~~~~~~~~~~~~~~~~~~~~
4
5 Local and local proxy tests.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import pytest
11
12 import time
13 from threading import Thread
14
15 from werkzeug import local
16
17
18 def test_basic_local():
19 l = local.Local()
20 l.foo = 0
21 values = []
22 def value_setter(idx):
23 time.sleep(0.01 * idx)
24 l.foo = idx
25 time.sleep(0.02)
26 values.append(l.foo)
27 threads = [Thread(target=value_setter, args=(x,))
28 for x in [1, 2, 3]]
29 for thread in threads:
30 thread.start()
31 time.sleep(0.2)
32 assert sorted(values) == [1, 2, 3]
33
34 def delfoo():
35 del l.foo
36 delfoo()
37 pytest.raises(AttributeError, lambda: l.foo)
38 pytest.raises(AttributeError, delfoo)
39
40 local.release_local(l)
41
42 def test_local_release():
43 l = local.Local()
44 l.foo = 42
45 local.release_local(l)
46 assert not hasattr(l, 'foo')
47
48 ls = local.LocalStack()
49 ls.push(42)
50 local.release_local(ls)
51 assert ls.top is None
52
53 def test_local_proxy():
54 foo = []
55 ls = local.LocalProxy(lambda: foo)
56 ls.append(42)
57 ls.append(23)
58 ls[1:] = [1, 2, 3]
59 assert foo == [42, 1, 2, 3]
60 assert repr(foo) == repr(ls)
61 assert foo[0] == 42
62 foo += [1]
63 assert list(foo) == [42, 1, 2, 3, 1]
64
65 def test_local_proxy_operations_math():
66 foo = 2
67 ls = local.LocalProxy(lambda: foo)
68 assert ls + 1 == 3
69 assert 1 + ls == 3
70 assert ls - 1 == 1
71 assert 1 - ls == -1
72 assert ls * 1 == 2
73 assert 1 * ls == 2
74 assert ls / 1 == 2
75 assert 1.0 / ls == 0.5
76 assert ls // 1.0 == 2.0
77 assert 1.0 // ls == 0.0
78 assert ls % 2 == 0
79 assert 2 % ls == 0
80
81 def test_local_proxy_operations_strings():
82 foo = "foo"
83 ls = local.LocalProxy(lambda: foo)
84 assert ls + "bar" == "foobar"
85 assert "bar" + ls == "barfoo"
86 assert ls * 2 == "foofoo"
87
88 foo = "foo %s"
89 assert ls % ("bar",) == "foo bar"
90
91 def test_local_stack():
92 ident = local.get_ident()
93
94 ls = local.LocalStack()
95 assert ident not in ls._local.__storage__
96 assert ls.top is None
97 ls.push(42)
98 assert ident in ls._local.__storage__
99 assert ls.top == 42
100 ls.push(23)
101 assert ls.top == 23
102 ls.pop()
103 assert ls.top == 42
104 ls.pop()
105 assert ls.top is None
106 assert ls.pop() is None
107 assert ls.pop() is None
108
109 proxy = ls()
110 ls.push([1, 2])
111 assert proxy == [1, 2]
112 ls.push((1, 2))
113 assert proxy == (1, 2)
114 ls.pop()
115 ls.pop()
116 assert repr(proxy) == '<LocalProxy unbound>'
117
118 assert ident not in ls._local.__storage__
119
120 def test_local_proxies_with_callables():
121 foo = 42
122 ls = local.LocalProxy(lambda: foo)
123 assert ls == 42
124 foo = [23]
125 ls.append(42)
126 assert ls == [23, 42]
127 assert foo == [23, 42]
128
129 def test_custom_idents():
130 ident = 0
131 l = local.Local()
132 stack = local.LocalStack()
133 mgr = local.LocalManager([l, stack], ident_func=lambda: ident)
134
135 l.foo = 42
136 stack.push({'foo': 42})
137 ident = 1
138 l.foo = 23
139 stack.push({'foo': 23})
140 ident = 0
141 assert l.foo == 42
142 assert stack.top['foo'] == 42
143 stack.pop()
144 assert stack.top is None
145 ident = 1
146 assert l.foo == 23
147 assert stack.top['foo'] == 23
148 stack.pop()
149 assert stack.top is None
0 # -*- coding: utf-8 -*-
1 """
2 tests.routing
3 ~~~~~~~~~~~~~
4
5 Routing tests.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import pytest
11
12 import uuid
13
14 from tests import strict_eq
15
16 from werkzeug import routing as r
17 from werkzeug.wrappers import Response
18 from werkzeug.datastructures import ImmutableDict, MultiDict
19 from werkzeug.test import create_environ
20
21
22 def test_basic_routing():
23 map = r.Map([
24 r.Rule('/', endpoint='index'),
25 r.Rule('/foo', endpoint='foo'),
26 r.Rule('/bar/', endpoint='bar')
27 ])
28 adapter = map.bind('example.org', '/')
29 assert adapter.match('/') == ('index', {})
30 assert adapter.match('/foo') == ('foo', {})
31 assert adapter.match('/bar/') == ('bar', {})
32 pytest.raises(r.RequestRedirect, lambda: adapter.match('/bar'))
33 pytest.raises(r.NotFound, lambda: adapter.match('/blub'))
34
35 adapter = map.bind('example.org', '/test')
36 with pytest.raises(r.RequestRedirect) as excinfo:
37 adapter.match('/bar')
38 assert excinfo.value.new_url == 'http://example.org/test/bar/'
39
40 adapter = map.bind('example.org', '/')
41 with pytest.raises(r.RequestRedirect) as excinfo:
42 adapter.match('/bar')
43 assert excinfo.value.new_url == 'http://example.org/bar/'
44
45 adapter = map.bind('example.org', '/')
46 with pytest.raises(r.RequestRedirect) as excinfo:
47 adapter.match('/bar', query_args={'aha': 'muhaha'})
48 assert excinfo.value.new_url == 'http://example.org/bar/?aha=muhaha'
49
50 adapter = map.bind('example.org', '/')
51 with pytest.raises(r.RequestRedirect) as excinfo:
52 adapter.match('/bar', query_args='aha=muhaha')
53 assert excinfo.value.new_url == 'http://example.org/bar/?aha=muhaha'
54
55 adapter = map.bind_to_environ(create_environ('/bar?foo=bar',
56 'http://example.org/'))
57 with pytest.raises(r.RequestRedirect) as excinfo:
58 adapter.match()
59 assert excinfo.value.new_url == 'http://example.org/bar/?foo=bar'
60
61 def test_environ_defaults():
62 environ = create_environ("/foo")
63 strict_eq(environ["PATH_INFO"], '/foo')
64 m = r.Map([r.Rule("/foo", endpoint="foo"), r.Rule("/bar", endpoint="bar")])
65 a = m.bind_to_environ(environ)
66 strict_eq(a.match("/foo"), ('foo', {}))
67 strict_eq(a.match(), ('foo', {}))
68 strict_eq(a.match("/bar"), ('bar', {}))
69 pytest.raises(r.NotFound, a.match, "/bars")
70
71 def test_environ_nonascii_pathinfo():
72 environ = create_environ(u'/лошадь')
73 m = r.Map([
74 r.Rule(u'/', endpoint='index'),
75 r.Rule(u'/лошадь', endpoint='horse')
76 ])
77 a = m.bind_to_environ(environ)
78 strict_eq(a.match(u'/'), ('index', {}))
79 strict_eq(a.match(u'/лошадь'), ('horse', {}))
80 pytest.raises(r.NotFound, a.match, u'/барсук')
81
82 def test_basic_building():
83 map = r.Map([
84 r.Rule('/', endpoint='index'),
85 r.Rule('/foo', endpoint='foo'),
86 r.Rule('/bar/<baz>', endpoint='bar'),
87 r.Rule('/bar/<int:bazi>', endpoint='bari'),
88 r.Rule('/bar/<float:bazf>', endpoint='barf'),
89 r.Rule('/bar/<path:bazp>', endpoint='barp'),
90 r.Rule('/hehe', endpoint='blah', subdomain='blah')
91 ])
92 adapter = map.bind('example.org', '/', subdomain='blah')
93
94 assert adapter.build('index', {}) == 'http://example.org/'
95 assert adapter.build('foo', {}) == 'http://example.org/foo'
96 assert adapter.build('bar', {'baz': 'blub'}) == 'http://example.org/bar/blub'
97 assert adapter.build('bari', {'bazi': 50}) == 'http://example.org/bar/50'
98 multivalues = MultiDict([('bazi', 50), ('bazi', None)])
99 assert adapter.build('bari', multivalues) == 'http://example.org/bar/50'
100 assert adapter.build('barf', {'bazf': 0.815}) == 'http://example.org/bar/0.815'
101 assert adapter.build('barp', {'bazp': 'la/di'}) == 'http://example.org/bar/la/di'
102 assert adapter.build('blah', {}) == '/hehe'
103 pytest.raises(r.BuildError, lambda: adapter.build('urks'))
104
105 adapter = map.bind('example.org', '/test', subdomain='blah')
106 assert adapter.build('index', {}) == 'http://example.org/test/'
107 assert adapter.build('foo', {}) == 'http://example.org/test/foo'
108 assert adapter.build('bar', {'baz': 'blub'}) == 'http://example.org/test/bar/blub'
109 assert adapter.build('bari', {'bazi': 50}) == 'http://example.org/test/bar/50'
110 assert adapter.build('barf', {'bazf': 0.815}) == 'http://example.org/test/bar/0.815'
111 assert adapter.build('barp', {'bazp': 'la/di'}) == 'http://example.org/test/bar/la/di'
112 assert adapter.build('blah', {}) == '/test/hehe'
113
114 adapter = map.bind('example.org')
115 assert adapter.build('foo', {}) == '/foo'
116 assert adapter.build('foo', {}, force_external=True) == 'http://example.org/foo'
117 adapter = map.bind('example.org', url_scheme='')
118 assert adapter.build('foo', {}) == '/foo'
119 assert adapter.build('foo', {}, force_external=True) == '//example.org/foo'
120
121 def test_defaults():
122 map = r.Map([
123 r.Rule('/foo/', defaults={'page': 1}, endpoint='foo'),
124 r.Rule('/foo/<int:page>', endpoint='foo')
125 ])
126 adapter = map.bind('example.org', '/')
127
128 assert adapter.match('/foo/') == ('foo', {'page': 1})
129 pytest.raises(r.RequestRedirect, lambda: adapter.match('/foo/1'))
130 assert adapter.match('/foo/2') == ('foo', {'page': 2})
131 assert adapter.build('foo', {}) == '/foo/'
132 assert adapter.build('foo', {'page': 1}) == '/foo/'
133 assert adapter.build('foo', {'page': 2}) == '/foo/2'
134
135 def test_greedy():
136 map = r.Map([
137 r.Rule('/foo', endpoint='foo'),
138 r.Rule('/<path:bar>', endpoint='bar'),
139 r.Rule('/<path:bar>/<path:blub>', endpoint='bar')
140 ])
141 adapter = map.bind('example.org', '/')
142
143 assert adapter.match('/foo') == ('foo', {})
144 assert adapter.match('/blub') == ('bar', {'bar': 'blub'})
145 assert adapter.match('/he/he') == ('bar', {'bar': 'he', 'blub': 'he'})
146
147 assert adapter.build('foo', {}) == '/foo'
148 assert adapter.build('bar', {'bar': 'blub'}) == '/blub'
149 assert adapter.build('bar', {'bar': 'blub', 'blub': 'bar'}) == '/blub/bar'
150
151 def test_path():
152 map = r.Map([
153 r.Rule('/', defaults={'name': 'FrontPage'}, endpoint='page'),
154 r.Rule('/Special', endpoint='special'),
155 r.Rule('/<int:year>', endpoint='year'),
156 r.Rule('/<path:name>', endpoint='page'),
157 r.Rule('/<path:name>/edit', endpoint='editpage'),
158 r.Rule('/<path:name>/silly/<path:name2>', endpoint='sillypage'),
159 r.Rule('/<path:name>/silly/<path:name2>/edit', endpoint='editsillypage'),
160 r.Rule('/Talk:<path:name>', endpoint='talk'),
161 r.Rule('/User:<username>', endpoint='user'),
162 r.Rule('/User:<username>/<path:name>', endpoint='userpage'),
163 r.Rule('/Files/<path:file>', endpoint='files'),
164 ])
165 adapter = map.bind('example.org', '/')
166
167 assert adapter.match('/') == ('page', {'name':'FrontPage'})
168 pytest.raises(r.RequestRedirect, lambda: adapter.match('/FrontPage'))
169 assert adapter.match('/Special') == ('special', {})
170 assert adapter.match('/2007') == ('year', {'year':2007})
171 assert adapter.match('/Some/Page') == ('page', {'name':'Some/Page'})
172 assert adapter.match('/Some/Page/edit') == ('editpage', {'name':'Some/Page'})
173 assert adapter.match('/Foo/silly/bar') == ('sillypage', {'name':'Foo', 'name2':'bar'})
174 assert adapter.match('/Foo/silly/bar/edit') == ('editsillypage', {'name':'Foo', 'name2':'bar'})
175 assert adapter.match('/Talk:Foo/Bar') == ('talk', {'name':'Foo/Bar'})
176 assert adapter.match('/User:thomas') == ('user', {'username':'thomas'})
177 assert adapter.match('/User:thomas/projects/werkzeug') == \
178 ('userpage', {'username':'thomas', 'name':'projects/werkzeug'})
179 assert adapter.match('/Files/downloads/werkzeug/0.2.zip') == \
180 ('files', {'file':'downloads/werkzeug/0.2.zip'})
181
182 def test_dispatch():
183 env = create_environ('/')
184 map = r.Map([
185 r.Rule('/', endpoint='root'),
186 r.Rule('/foo/', endpoint='foo')
187 ])
188 adapter = map.bind_to_environ(env)
189
190 raise_this = None
191 def view_func(endpoint, values):
192 if raise_this is not None:
193 raise raise_this
194 return Response(repr((endpoint, values)))
195 dispatch = lambda p, q=False: Response.force_type(adapter.dispatch(view_func, p,
196 catch_http_exceptions=q), env)
197
198 assert dispatch('/').data == b"('root', {})"
199 assert dispatch('/foo').status_code == 301
200 raise_this = r.NotFound()
201 pytest.raises(r.NotFound, lambda: dispatch('/bar'))
202 assert dispatch('/bar', True).status_code == 404
203
204 def test_http_host_before_server_name():
205 env = {
206 'HTTP_HOST': 'wiki.example.com',
207 'SERVER_NAME': 'web0.example.com',
208 'SERVER_PORT': '80',
209 'SCRIPT_NAME': '',
210 'PATH_INFO': '',
211 'REQUEST_METHOD': 'GET',
212 'wsgi.url_scheme': 'http'
213 }
214 map = r.Map([r.Rule('/', endpoint='index', subdomain='wiki')])
215 adapter = map.bind_to_environ(env, server_name='example.com')
216 assert adapter.match('/') == ('index', {})
217 assert adapter.build('index', force_external=True) == 'http://wiki.example.com/'
218 assert adapter.build('index') == '/'
219
220 env['HTTP_HOST'] = 'admin.example.com'
221 adapter = map.bind_to_environ(env, server_name='example.com')
222 assert adapter.build('index') == 'http://wiki.example.com/'
223
224 def test_adapter_url_parameter_sorting():
225 map = r.Map([r.Rule('/', endpoint='index')], sort_parameters=True,
226 sort_key=lambda x: x[1])
227 adapter = map.bind('localhost', '/')
228 assert adapter.build('index', {'x': 20, 'y': 10, 'z': 30},
229 force_external=True) == 'http://localhost/?y=10&x=20&z=30'
230
231 def test_request_direct_charset_bug():
232 map = r.Map([r.Rule(u'/öäü/')])
233 adapter = map.bind('localhost', '/')
234
235 with pytest.raises(r.RequestRedirect) as excinfo:
236 adapter.match(u'/öäü')
237 assert excinfo.value.new_url == 'http://localhost/%C3%B6%C3%A4%C3%BC/'
238
239 def test_request_redirect_default():
240 map = r.Map([r.Rule(u'/foo', defaults={'bar': 42}),
241 r.Rule(u'/foo/<int:bar>')])
242 adapter = map.bind('localhost', '/')
243
244 with pytest.raises(r.RequestRedirect) as excinfo:
245 adapter.match(u'/foo/42')
246 assert excinfo.value.new_url == 'http://localhost/foo'
247
248 def test_request_redirect_default_subdomain():
249 map = r.Map([r.Rule(u'/foo', defaults={'bar': 42}, subdomain='test'),
250 r.Rule(u'/foo/<int:bar>', subdomain='other')])
251 adapter = map.bind('localhost', '/', subdomain='other')
252
253 with pytest.raises(r.RequestRedirect) as excinfo:
254 adapter.match(u'/foo/42')
255 assert excinfo.value.new_url == 'http://test.localhost/foo'
256
257 def test_adapter_match_return_rule():
258 rule = r.Rule('/foo/', endpoint='foo')
259 map = r.Map([rule])
260 adapter = map.bind('localhost', '/')
261 assert adapter.match('/foo/', return_rule=True) == (rule, {})
262
263 def test_server_name_interpolation():
264 server_name = 'example.invalid'
265 map = r.Map([r.Rule('/', endpoint='index'),
266 r.Rule('/', endpoint='alt', subdomain='alt')])
267
268 env = create_environ('/', 'http://%s/' % server_name)
269 adapter = map.bind_to_environ(env, server_name=server_name)
270 assert adapter.match() == ('index', {})
271
272 env = create_environ('/', 'http://alt.%s/' % server_name)
273 adapter = map.bind_to_environ(env, server_name=server_name)
274 assert adapter.match() == ('alt', {})
275
276 env = create_environ('/', 'http://%s/' % server_name)
277 adapter = map.bind_to_environ(env, server_name='foo')
278 assert adapter.subdomain == '<invalid>'
279
280 def test_rule_emptying():
281 rule = r.Rule('/foo', {'meh': 'muh'}, 'x', ['POST'],
282 False, 'x', True, None)
283 rule2 = rule.empty()
284 assert rule.__dict__ == rule2.__dict__
285 rule.methods.add('GET')
286 assert rule.__dict__ != rule2.__dict__
287 rule.methods.discard('GET')
288 rule.defaults['meh'] = 'aha'
289 assert rule.__dict__ != rule2.__dict__
290
291 def test_rule_templates():
292 testcase = r.RuleTemplate(
293 [ r.Submount('/test/$app',
294 [ r.Rule('/foo/', endpoint='handle_foo')
295 , r.Rule('/bar/', endpoint='handle_bar')
296 , r.Rule('/baz/', endpoint='handle_baz')
297 ]),
298 r.EndpointPrefix('${app}',
299 [ r.Rule('/${app}-blah', endpoint='bar')
300 , r.Rule('/${app}-meh', endpoint='baz')
301 ]),
302 r.Subdomain('$app',
303 [ r.Rule('/blah', endpoint='x_bar')
304 , r.Rule('/meh', endpoint='x_baz')
305 ])
306 ])
307
308 url_map = r.Map(
309 [ testcase(app='test1')
310 , testcase(app='test2')
311 , testcase(app='test3')
312 , testcase(app='test4')
313 ])
314
315 out = sorted([(x.rule, x.subdomain, x.endpoint)
316 for x in url_map.iter_rules()])
317
318 assert out == ([
319 ('/blah', 'test1', 'x_bar'),
320 ('/blah', 'test2', 'x_bar'),
321 ('/blah', 'test3', 'x_bar'),
322 ('/blah', 'test4', 'x_bar'),
323 ('/meh', 'test1', 'x_baz'),
324 ('/meh', 'test2', 'x_baz'),
325 ('/meh', 'test3', 'x_baz'),
326 ('/meh', 'test4', 'x_baz'),
327 ('/test/test1/bar/', '', 'handle_bar'),
328 ('/test/test1/baz/', '', 'handle_baz'),
329 ('/test/test1/foo/', '', 'handle_foo'),
330 ('/test/test2/bar/', '', 'handle_bar'),
331 ('/test/test2/baz/', '', 'handle_baz'),
332 ('/test/test2/foo/', '', 'handle_foo'),
333 ('/test/test3/bar/', '', 'handle_bar'),
334 ('/test/test3/baz/', '', 'handle_baz'),
335 ('/test/test3/foo/', '', 'handle_foo'),
336 ('/test/test4/bar/', '', 'handle_bar'),
337 ('/test/test4/baz/', '', 'handle_baz'),
338 ('/test/test4/foo/', '', 'handle_foo'),
339 ('/test1-blah', '', 'test1bar'),
340 ('/test1-meh', '', 'test1baz'),
341 ('/test2-blah', '', 'test2bar'),
342 ('/test2-meh', '', 'test2baz'),
343 ('/test3-blah', '', 'test3bar'),
344 ('/test3-meh', '', 'test3baz'),
345 ('/test4-blah', '', 'test4bar'),
346 ('/test4-meh', '', 'test4baz')
347 ])
348
349 def test_non_string_parts():
350 m = r.Map([
351 r.Rule('/<foo>', endpoint='foo')
352 ])
353 a = m.bind('example.com')
354 assert a.build('foo', {'foo': 42}) == '/42'
355
356 def test_complex_routing_rules():
357 m = r.Map([
358 r.Rule('/', endpoint='index'),
359 r.Rule('/<int:blub>', endpoint='an_int'),
360 r.Rule('/<blub>', endpoint='a_string'),
361 r.Rule('/foo/', endpoint='nested'),
362 r.Rule('/foobar/', endpoint='nestedbar'),
363 r.Rule('/foo/<path:testing>/', endpoint='nested_show'),
364 r.Rule('/foo/<path:testing>/edit', endpoint='nested_edit'),
365 r.Rule('/users/', endpoint='users', defaults={'page': 1}),
366 r.Rule('/users/page/<int:page>', endpoint='users'),
367 r.Rule('/foox', endpoint='foox'),
368 r.Rule('/<path:bar>/<path:blub>', endpoint='barx_path_path')
369 ])
370 a = m.bind('example.com')
371
372 assert a.match('/') == ('index', {})
373 assert a.match('/42') == ('an_int', {'blub': 42})
374 assert a.match('/blub') == ('a_string', {'blub': 'blub'})
375 assert a.match('/foo/') == ('nested', {})
376 assert a.match('/foobar/') == ('nestedbar', {})
377 assert a.match('/foo/1/2/3/') == ('nested_show', {'testing': '1/2/3'})
378 assert a.match('/foo/1/2/3/edit') == ('nested_edit', {'testing': '1/2/3'})
379 assert a.match('/users/') == ('users', {'page': 1})
380 assert a.match('/users/page/2') == ('users', {'page': 2})
381 assert a.match('/foox') == ('foox', {})
382 assert a.match('/1/2/3') == ('barx_path_path', {'bar': '1', 'blub': '2/3'})
383
384 assert a.build('index') == '/'
385 assert a.build('an_int', {'blub': 42}) == '/42'
386 assert a.build('a_string', {'blub': 'test'}) == '/test'
387 assert a.build('nested') == '/foo/'
388 assert a.build('nestedbar') == '/foobar/'
389 assert a.build('nested_show', {'testing': '1/2/3'}) == '/foo/1/2/3/'
390 assert a.build('nested_edit', {'testing': '1/2/3'}) == '/foo/1/2/3/edit'
391 assert a.build('users', {'page': 1}) == '/users/'
392 assert a.build('users', {'page': 2}) == '/users/page/2'
393 assert a.build('foox') == '/foox'
394 assert a.build('barx_path_path', {'bar': '1', 'blub': '2/3'}) == '/1/2/3'
395
396 def test_default_converters():
397 class MyMap(r.Map):
398 default_converters = r.Map.default_converters.copy()
399 default_converters['foo'] = r.UnicodeConverter
400 assert isinstance(r.Map.default_converters, ImmutableDict)
401 m = MyMap([
402 r.Rule('/a/<foo:a>', endpoint='a'),
403 r.Rule('/b/<foo:b>', endpoint='b'),
404 r.Rule('/c/<c>', endpoint='c')
405 ], converters={'bar': r.UnicodeConverter})
406 a = m.bind('example.org', '/')
407 assert a.match('/a/1') == ('a', {'a': '1'})
408 assert a.match('/b/2') == ('b', {'b': '2'})
409 assert a.match('/c/3') == ('c', {'c': '3'})
410 assert 'foo' not in r.Map.default_converters
411
412 def test_uuid_converter():
413 m = r.Map([
414 r.Rule('/a/<uuid:a_uuid>', endpoint='a')
415 ])
416 a = m.bind('example.org', '/')
417 rooute, kwargs = a.match('/a/a8098c1a-f86e-11da-bd1a-00112444be1e')
418 assert type(kwargs['a_uuid']) == uuid.UUID
419
420 def test_converter_with_tuples():
421 '''
422 Regression test for https://github.com/mitsuhiko/werkzeug/issues/709
423 '''
424 class TwoValueConverter(r.BaseConverter):
425 def __init__(self, *args, **kwargs):
426 super(TwoValueConverter, self).__init__(*args, **kwargs)
427 self.regex = r'(\w\w+)/(\w\w+)'
428
429 def to_python(self, two_values):
430 one, two = two_values.split('/')
431 return one, two
432
433 def to_url(self, values):
434 return "%s/%s" % (values[0], values[1])
435
436 map = r.Map([
437 r.Rule('/<two:foo>/', endpoint='handler')
438 ], converters={'two': TwoValueConverter})
439 a = map.bind('example.org', '/')
440 route, kwargs = a.match('/qwert/yuiop/')
441 assert kwargs['foo'] == ('qwert', 'yuiop')
442
443 def test_build_append_unknown():
444 map = r.Map([
445 r.Rule('/bar/<float:bazf>', endpoint='barf')
446 ])
447 adapter = map.bind('example.org', '/', subdomain='blah')
448 assert adapter.build('barf', {'bazf': 0.815, 'bif' : 1.0}) == \
449 'http://example.org/bar/0.815?bif=1.0'
450 assert adapter.build('barf', {'bazf': 0.815, 'bif' : 1.0},
451 append_unknown=False) == 'http://example.org/bar/0.815'
452
453 def test_build_append_multiple():
454 map = r.Map([
455 r.Rule('/bar/<float:bazf>', endpoint='barf')
456 ])
457 adapter = map.bind('example.org', '/', subdomain='blah')
458 params = {'bazf': 0.815, 'bif': [1.0, 3.0], 'pof': 2.0}
459 a, b = adapter.build('barf', params).split('?')
460 assert a == 'http://example.org/bar/0.815'
461 assert set(b.split('&')) == set('pof=2.0&bif=1.0&bif=3.0'.split('&'))
462
463
464 def test_method_fallback():
465 map = r.Map([
466 r.Rule('/', endpoint='index', methods=['GET']),
467 r.Rule('/<name>', endpoint='hello_name', methods=['GET']),
468 r.Rule('/select', endpoint='hello_select', methods=['POST']),
469 r.Rule('/search_get', endpoint='search', methods=['GET']),
470 r.Rule('/search_post', endpoint='search', methods=['POST'])
471 ])
472 adapter = map.bind('example.com')
473 assert adapter.build('index') == '/'
474 assert adapter.build('index', method='GET') == '/'
475 assert adapter.build('hello_name', {'name': 'foo'}) == '/foo'
476 assert adapter.build('hello_select') == '/select'
477 assert adapter.build('hello_select', method='POST') == '/select'
478 assert adapter.build('search') == '/search_get'
479 assert adapter.build('search', method='GET') == '/search_get'
480 assert adapter.build('search', method='POST') == '/search_post'
481
482 def test_implicit_head():
483 url_map = r.Map([
484 r.Rule('/get', methods=['GET'], endpoint='a'),
485 r.Rule('/post', methods=['POST'], endpoint='b')
486 ])
487 adapter = url_map.bind('example.org')
488 assert adapter.match('/get', method='HEAD') == ('a', {})
489 pytest.raises(r.MethodNotAllowed, adapter.match,
490 '/post', method='HEAD')
491
492 def test_protocol_joining_bug():
493 m = r.Map([r.Rule('/<foo>', endpoint='x')])
494 a = m.bind('example.org')
495 assert a.build('x', {'foo': 'x:y'}) == '/x:y'
496 assert a.build('x', {'foo': 'x:y'}, force_external=True) == \
497 'http://example.org/x:y'
498
499 def test_allowed_methods_querying():
500 m = r.Map([r.Rule('/<foo>', methods=['GET', 'HEAD']),
501 r.Rule('/foo', methods=['POST'])])
502 a = m.bind('example.org')
503 assert sorted(a.allowed_methods('/foo')) == ['GET', 'HEAD', 'POST']
504
505 def test_external_building_with_port():
506 map = r.Map([
507 r.Rule('/', endpoint='index'),
508 ])
509 adapter = map.bind('example.org:5000', '/')
510 built_url = adapter.build('index', {}, force_external=True)
511 assert built_url == 'http://example.org:5000/', built_url
512
513 def test_external_building_with_port_bind_to_environ():
514 map = r.Map([
515 r.Rule('/', endpoint='index'),
516 ])
517 adapter = map.bind_to_environ(
518 create_environ('/', 'http://example.org:5000/'),
519 server_name="example.org:5000"
520 )
521 built_url = adapter.build('index', {}, force_external=True)
522 assert built_url == 'http://example.org:5000/', built_url
523
524 def test_external_building_with_port_bind_to_environ_wrong_servername():
525 map = r.Map([
526 r.Rule('/', endpoint='index'),
527 ])
528 environ = create_environ('/', 'http://example.org:5000/')
529 adapter = map.bind_to_environ(environ, server_name="example.org")
530 assert adapter.subdomain == '<invalid>'
531
532 def test_converter_parser():
533 args, kwargs = r.parse_converter_args(u'test, a=1, b=3.0')
534
535 assert args == ('test',)
536 assert kwargs == {'a': 1, 'b': 3.0 }
537
538 args, kwargs = r.parse_converter_args('')
539 assert not args and not kwargs
540
541 args, kwargs = r.parse_converter_args('a, b, c,')
542 assert args == ('a', 'b', 'c')
543 assert not kwargs
544
545 args, kwargs = r.parse_converter_args('True, False, None')
546 assert args == (True, False, None)
547
548 args, kwargs = r.parse_converter_args('"foo", u"bar"')
549 assert args == ('foo', 'bar')
550
551 def test_alias_redirects():
552 m = r.Map([
553 r.Rule('/', endpoint='index'),
554 r.Rule('/index.html', endpoint='index', alias=True),
555 r.Rule('/users/', defaults={'page': 1}, endpoint='users'),
556 r.Rule('/users/index.html', defaults={'page': 1}, alias=True,
557 endpoint='users'),
558 r.Rule('/users/page/<int:page>', endpoint='users'),
559 r.Rule('/users/page-<int:page>.html', alias=True, endpoint='users'),
560 ])
561 a = m.bind('example.com')
562
563 def ensure_redirect(path, new_url, args=None):
564 with pytest.raises(r.RequestRedirect) as excinfo:
565 a.match(path, query_args=args)
566 assert excinfo.value.new_url == 'http://example.com' + new_url
567
568 ensure_redirect('/index.html', '/')
569 ensure_redirect('/users/index.html', '/users/')
570 ensure_redirect('/users/page-2.html', '/users/page/2')
571 ensure_redirect('/users/page-1.html', '/users/')
572 ensure_redirect('/users/page-1.html', '/users/?foo=bar', {'foo': 'bar'})
573
574 assert a.build('index') == '/'
575 assert a.build('users', {'page': 1}) == '/users/'
576 assert a.build('users', {'page': 2}) == '/users/page/2'
577
578 @pytest.mark.parametrize('prefix', ('', '/aaa'))
579 def test_double_defaults(prefix):
580 m = r.Map([
581 r.Rule(prefix + '/', defaults={'foo': 1, 'bar': False}, endpoint='x'),
582 r.Rule(prefix + '/<int:foo>', defaults={'bar': False}, endpoint='x'),
583 r.Rule(prefix + '/bar/', defaults={'foo': 1, 'bar': True}, endpoint='x'),
584 r.Rule(prefix + '/bar/<int:foo>', defaults={'bar': True}, endpoint='x')
585 ])
586 a = m.bind('example.com')
587
588 assert a.match(prefix + '/') == ('x', {'foo': 1, 'bar': False})
589 assert a.match(prefix + '/2') == ('x', {'foo': 2, 'bar': False})
590 assert a.match(prefix + '/bar/') == ('x', {'foo': 1, 'bar': True})
591 assert a.match(prefix + '/bar/2') == ('x', {'foo': 2, 'bar': True})
592
593 assert a.build('x', {'foo': 1, 'bar': False}) == prefix + '/'
594 assert a.build('x', {'foo': 2, 'bar': False}) == prefix + '/2'
595 assert a.build('x', {'bar': False}) == prefix + '/'
596 assert a.build('x', {'foo': 1, 'bar': True}) == prefix + '/bar/'
597 assert a.build('x', {'foo': 2, 'bar': True}) == prefix + '/bar/2'
598 assert a.build('x', {'bar': True}) == prefix + '/bar/'
599
600 def test_host_matching():
601 m = r.Map([
602 r.Rule('/', endpoint='index', host='www.<domain>'),
603 r.Rule('/', endpoint='files', host='files.<domain>'),
604 r.Rule('/foo/', defaults={'page': 1}, host='www.<domain>', endpoint='x'),
605 r.Rule('/<int:page>', host='files.<domain>', endpoint='x')
606 ], host_matching=True)
607
608 a = m.bind('www.example.com')
609 assert a.match('/') == ('index', {'domain': 'example.com'})
610 assert a.match('/foo/') == ('x', {'domain': 'example.com', 'page': 1})
611
612 with pytest.raises(r.RequestRedirect) as excinfo:
613 a.match('/foo')
614 assert excinfo.value.new_url == 'http://www.example.com/foo/'
615
616 a = m.bind('files.example.com')
617 assert a.match('/') == ('files', {'domain': 'example.com'})
618 assert a.match('/2') == ('x', {'domain': 'example.com', 'page': 2})
619
620 with pytest.raises(r.RequestRedirect) as excinfo:
621 a.match('/1')
622 assert excinfo.value.new_url == 'http://www.example.com/foo/'
623
624 def test_host_matching_building():
625 m = r.Map([
626 r.Rule('/', endpoint='index', host='www.domain.com'),
627 r.Rule('/', endpoint='foo', host='my.domain.com')
628 ], host_matching=True)
629
630 www = m.bind('www.domain.com')
631 assert www.match('/') == ('index', {})
632 assert www.build('index') == '/'
633 assert www.build('foo') == 'http://my.domain.com/'
634
635 my = m.bind('my.domain.com')
636 assert my.match('/') == ('foo', {})
637 assert my.build('foo') == '/'
638 assert my.build('index') == 'http://www.domain.com/'
639
640 def test_server_name_casing():
641 m = r.Map([
642 r.Rule('/', endpoint='index', subdomain='foo')
643 ])
644
645 env = create_environ()
646 env['SERVER_NAME'] = env['HTTP_HOST'] = 'FOO.EXAMPLE.COM'
647 a = m.bind_to_environ(env, server_name='example.com')
648 assert a.match('/') == ('index', {})
649
650 env = create_environ()
651 env['SERVER_NAME'] = '127.0.0.1'
652 env['SERVER_PORT'] = '5000'
653 del env['HTTP_HOST']
654 a = m.bind_to_environ(env, server_name='example.com')
655 with pytest.raises(r.NotFound):
656 a.match()
657
658 def test_redirect_request_exception_code():
659 exc = r.RequestRedirect('http://www.google.com/')
660 exc.code = 307
661 env = create_environ()
662 strict_eq(exc.get_response(env).status_code, exc.code)
663
664 def test_redirect_path_quoting():
665 url_map = r.Map([
666 r.Rule('/<category>', defaults={'page': 1}, endpoint='category'),
667 r.Rule('/<category>/page/<int:page>', endpoint='category')
668 ])
669 adapter = url_map.bind('example.com')
670
671 with pytest.raises(r.RequestRedirect) as excinfo:
672 adapter.match('/foo bar/page/1')
673 response = excinfo.value.get_response({})
674 strict_eq(response.headers['location'],
675 u'http://example.com/foo%20bar')
676
677 def test_unicode_rules():
678 m = r.Map([
679 r.Rule(u'/войти/', endpoint='enter'),
680 r.Rule(u'/foo+bar/', endpoint='foobar')
681 ])
682 a = m.bind(u'☃.example.com')
683 with pytest.raises(r.RequestRedirect) as excinfo:
684 a.match(u'/войти')
685 strict_eq(excinfo.value.new_url,
686 'http://xn--n3h.example.com/%D0%B2%D0%BE%D0%B9%D1%82%D0%B8/')
687
688 endpoint, values = a.match(u'/войти/')
689 strict_eq(endpoint, 'enter')
690 strict_eq(values, {})
691
692 with pytest.raises(r.RequestRedirect) as excinfo:
693 a.match(u'/foo+bar')
694 strict_eq(excinfo.value.new_url, 'http://xn--n3h.example.com/foo+bar/')
695
696 endpoint, values = a.match(u'/foo+bar/')
697 strict_eq(endpoint, 'foobar')
698 strict_eq(values, {})
699
700 url = a.build('enter', {}, force_external=True)
701 strict_eq(url, 'http://xn--n3h.example.com/%D0%B2%D0%BE%D0%B9%D1%82%D0%B8/')
702
703 url = a.build('foobar', {}, force_external=True)
704 strict_eq(url, 'http://xn--n3h.example.com/foo+bar/')
705
706 def test_empty_path_info():
707 m = r.Map([
708 r.Rule("/", endpoint="index"),
709 ])
710
711 b = m.bind("example.com", script_name="/approot")
712 with pytest.raises(r.RequestRedirect) as excinfo:
713 b.match("")
714 assert excinfo.value.new_url == "http://example.com/approot/"
715
716 a = m.bind("example.com")
717 with pytest.raises(r.RequestRedirect) as excinfo:
718 a.match("")
719 assert excinfo.value.new_url == "http://example.com/"
720
721 def test_map_repr():
722 m = r.Map([
723 r.Rule(u'/wat', endpoint='enter'),
724 r.Rule(u'/woop', endpoint='foobar')
725 ])
726 rv = repr(m)
727 strict_eq(rv,
728 "Map([<Rule '/woop' -> foobar>, <Rule '/wat' -> enter>])")
729
730 def test_empty_subclass_rules_with_custom_kwargs():
731 class CustomRule(r.Rule):
732 def __init__(self, string=None, custom=None, *args, **kwargs):
733 self.custom = custom
734 super(CustomRule, self).__init__(string, *args, **kwargs)
735
736 rule1 = CustomRule(u'/foo', endpoint='bar')
737 try:
738 rule2 = rule1.empty()
739 assert rule1.rule == rule2.rule
740 except TypeError as e: # raised without fix in PR #675
741 raise e
0 # -*- coding: utf-8 -*-
1 """
2 tests.security
3 ~~~~~~~~~~~~~~
4
5 Tests the security helpers.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import os
11
12 from werkzeug.security import check_password_hash, generate_password_hash, \
13 safe_join, pbkdf2_hex, safe_str_cmp
14
15
16 def test_safe_str_cmp():
17 assert safe_str_cmp('a', 'a') is True
18 assert safe_str_cmp(b'a', u'a') is True
19 assert safe_str_cmp('a', 'b') is False
20 assert safe_str_cmp(b'aaa', 'aa') is False
21 assert safe_str_cmp(b'aaa', 'bbb') is False
22 assert safe_str_cmp(b'aaa', u'aaa') is True
23
24 def test_password_hashing():
25 hash0 = generate_password_hash('default')
26 assert check_password_hash(hash0, 'default')
27 assert hash0.startswith('pbkdf2:sha1:1000$')
28
29 hash1 = generate_password_hash('default', 'sha1')
30 hash2 = generate_password_hash(u'default', method='sha1')
31 assert hash1 != hash2
32 assert check_password_hash(hash1, 'default')
33 assert check_password_hash(hash2, 'default')
34 assert hash1.startswith('sha1$')
35 assert hash2.startswith('sha1$')
36
37 fakehash = generate_password_hash('default', method='plain')
38 assert fakehash == 'plain$$default'
39 assert check_password_hash(fakehash, 'default')
40
41 mhash = generate_password_hash(u'default', method='md5')
42 assert mhash.startswith('md5$')
43 assert check_password_hash(mhash, 'default')
44
45 legacy = 'md5$$c21f969b5f03d33d43e04f8f136e7682'
46 assert check_password_hash(legacy, 'default')
47
48 legacy = u'md5$$c21f969b5f03d33d43e04f8f136e7682'
49 assert check_password_hash(legacy, 'default')
50
51 def test_safe_join():
52 assert safe_join('foo', 'bar/baz') == os.path.join('foo', 'bar/baz')
53 assert safe_join('foo', '../bar/baz') is None
54 if os.name == 'nt':
55 assert safe_join('foo', 'foo\\bar') is None
56
57 def test_pbkdf2():
58 def check(data, salt, iterations, keylen, expected):
59 rv = pbkdf2_hex(data, salt, iterations, keylen)
60 assert rv == expected
61
62 # From RFC 6070
63 check('password', 'salt', 1, None,
64 '0c60c80f961f0e71f3a9b524af6012062fe037a6')
65 check('password', 'salt', 1, 20,
66 '0c60c80f961f0e71f3a9b524af6012062fe037a6')
67 check('password', 'salt', 2, 20,
68 'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957')
69 check('password', 'salt', 4096, 20,
70 '4b007901b765489abead49d926f721d065a429c1')
71 check('passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt',
72 4096, 25, '3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038')
73 check('pass\x00word', 'sa\x00lt', 4096, 16,
74 '56fa6aa75548099dcc37d7f03425e0c3')
75 # This one is from the RFC but it just takes for ages
76 ##check('password', 'salt', 16777216, 20,
77 ## 'eefe3d61cd4da4e4e9945b3d6ba2158c2634e984')
78
79 # From Crypt-PBKDF2
80 check('password', 'ATHENA.MIT.EDUraeburn', 1, 16,
81 'cdedb5281bb2f801565a1122b2563515')
82 check('password', 'ATHENA.MIT.EDUraeburn', 1, 32,
83 'cdedb5281bb2f801565a1122b25635150ad1f7a04bb9f3a333ecc0e2e1f70837')
84 check('password', 'ATHENA.MIT.EDUraeburn', 2, 16,
85 '01dbee7f4a9e243e988b62c73cda935d')
86 check('password', 'ATHENA.MIT.EDUraeburn', 2, 32,
87 '01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86')
88 check('password', 'ATHENA.MIT.EDUraeburn', 1200, 32,
89 '5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13')
90 check('X' * 64, 'pass phrase equals block size', 1200, 32,
91 '139c30c0966bc32ba55fdbf212530ac9c5ec59f1a452f5cc9ad940fea0598ed1')
92 check('X' * 65, 'pass phrase exceeds block size', 1200, 32,
93 '9ccad6d468770cd51b10e6a68721be611a8b4d282601db3b36be9246915ec82a')
0 # -*- coding: utf-8 -*-
1 """
2 tests.serving
3 ~~~~~~~~~~~~~
4
5 Added serving tests.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import os
11 import ssl
12 import subprocess
13 import textwrap
14
15
16 try:
17 import OpenSSL
18 except ImportError:
19 OpenSSL = None
20
21 try:
22 import watchdog
23 except ImportError:
24 watchdog = None
25
26 import requests
27 import requests.exceptions
28 import pytest
29
30 from werkzeug import __version__ as version, serving
31
32
33 def test_serving(dev_server):
34 server = dev_server('from werkzeug.testapp import test_app as app')
35 rv = requests.get('http://%s/?foo=bar&baz=blah' % server.addr).content
36 assert b'WSGI Information' in rv
37 assert b'foo=bar&amp;baz=blah' in rv
38 assert b'Werkzeug/' + version.encode('ascii') in rv
39
40
41 def test_broken_app(dev_server):
42 server = dev_server('''
43 def app(environ, start_response):
44 1 // 0
45 ''')
46
47 r = requests.get(server.url + '/?foo=bar&baz=blah')
48 assert r.status_code == 500
49 assert 'Internal Server Error' in r.text
50
51
52 @pytest.mark.skipif(not hasattr(ssl, 'SSLContext'),
53 reason='Missing PEP 466 (Python 2.7.9+) or Python 3.')
54 @pytest.mark.skipif(OpenSSL is None,
55 reason='OpenSSL is required for cert generation.')
56 def test_stdlib_ssl_contexts(dev_server, tmpdir):
57 certificate, private_key = \
58 serving.make_ssl_devcert(str(tmpdir.mkdir('certs')))
59
60 server = dev_server('''
61 def app(environ, start_response):
62 start_response('200 OK', [('Content-Type', 'text/html')])
63 return [b'hello']
64
65 import ssl
66 ctx = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
67 ctx.load_cert_chain("%s", "%s")
68 kwargs['ssl_context'] = ctx
69 ''' % (certificate, private_key))
70
71 assert server.addr is not None
72 r = requests.get(server.url, verify=False)
73 assert r.content == b'hello'
74
75
76 @pytest.mark.skipif(OpenSSL is None, reason='OpenSSL is not installed.')
77 def test_ssl_context_adhoc(dev_server):
78 server = dev_server('''
79 def app(environ, start_response):
80 start_response('200 OK', [('Content-Type', 'text/html')])
81 return [b'hello']
82
83 kwargs['ssl_context'] = 'adhoc'
84 ''')
85 r = requests.get(server.url, verify=False)
86 assert r.content == b'hello'
87
88
89 @pytest.mark.skipif(OpenSSL is None, reason='OpenSSL is not installed.')
90 def test_make_ssl_devcert(tmpdir):
91 certificate, private_key = \
92 serving.make_ssl_devcert(str(tmpdir))
93 assert os.path.isfile(certificate)
94 assert os.path.isfile(private_key)
95
96
97 @pytest.mark.skipif(watchdog is None, reason='Watchdog not installed.')
98 def test_reloader_broken_imports(tmpdir, dev_server):
99 # We explicitly assert that the server reloads on change, even though in
100 # this case the import could've just been retried. This is to assert
101 # correct behavior for apps that catch and cache import errors.
102 #
103 # Because this feature is achieved by recursively watching a large amount
104 # of directories, this only works for the watchdog reloader. The stat
105 # reloader is too inefficient to watch such a large amount of files.
106
107 real_app = tmpdir.join('real_app.py')
108 real_app.write("lol syntax error")
109
110 server = dev_server('''
111 trials = []
112 def app(environ, start_response):
113 assert not trials, 'should have reloaded'
114 trials.append(1)
115 import real_app
116 return real_app.real_app(environ, start_response)
117
118 kwargs['use_reloader'] = True
119 kwargs['reloader_interval'] = 0.1
120 kwargs['reloader_type'] = 'watchdog'
121 ''')
122 server.wait_for_reloader_loop()
123
124 r = requests.get(server.url)
125 assert r.status_code == 500
126
127 real_app.write(textwrap.dedent('''
128 def real_app(environ, start_response):
129 start_response('200 OK', [('Content-Type', 'text/html')])
130 return [b'hello']
131 '''))
132 server.wait_for_reloader()
133
134 r = requests.get(server.url)
135 assert r.status_code == 200
136 assert r.content == b'hello'
137
138
139 @pytest.mark.skipif(watchdog is None, reason='Watchdog not installed.')
140 def test_reloader_nested_broken_imports(tmpdir, dev_server):
141 real_app = tmpdir.mkdir('real_app')
142 real_app.join('__init__.py').write('from real_app.sub import real_app')
143 sub = real_app.mkdir('sub').join('__init__.py')
144 sub.write("lol syntax error")
145
146 server = dev_server('''
147 trials = []
148 def app(environ, start_response):
149 assert not trials, 'should have reloaded'
150 trials.append(1)
151 import real_app
152 return real_app.real_app(environ, start_response)
153
154 kwargs['use_reloader'] = True
155 kwargs['reloader_interval'] = 0.1
156 kwargs['reloader_type'] = 'watchdog'
157 ''')
158 server.wait_for_reloader_loop()
159
160 r = requests.get(server.url)
161 assert r.status_code == 500
162
163 sub.write(textwrap.dedent('''
164 def real_app(environ, start_response):
165 start_response('200 OK', [('Content-Type', 'text/html')])
166 return [b'hello']
167 '''))
168 server.wait_for_reloader()
169
170 r = requests.get(server.url)
171 assert r.status_code == 200
172 assert r.content == b'hello'
173
174
175 def test_monkeypached_sleep(tmpdir):
176 # removing the staticmethod wrapper in the definition of
177 # ReloaderLoop._sleep works most of the time, since `sleep` is a c
178 # function, and unlike python functions which are descriptors, doesn't
179 # become a method when attached to a class. however, if the user has called
180 # `eventlet.monkey_patch` before importing `_reloader`, `time.sleep` is a
181 # python function, and subsequently calling `ReloaderLoop._sleep` fails
182 # with a TypeError. This test checks that _sleep is attached correctly.
183 script = tmpdir.mkdir('app').join('test.py')
184 script.write(textwrap.dedent('''
185 import time
186
187 def sleep(secs):
188 pass
189
190 # simulate eventlet.monkey_patch by replacing the builtin sleep
191 # with a regular function before _reloader is imported
192 time.sleep = sleep
193
194 from werkzeug._reloader import ReloaderLoop
195 ReloaderLoop()._sleep(0)
196 '''))
197 subprocess.check_call(['python', str(script)])
0 # -*- coding: utf-8 -*-
1 """
2 tests.test
3 ~~~~~~~~~~
4
5 Tests the testing tools.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10
11 from __future__ import with_statement
12
13 import pytest
14
15 import sys
16 from io import BytesIO
17 from werkzeug._compat import iteritems, to_bytes, implements_iterator
18 from functools import partial
19
20 from tests import strict_eq
21
22 from werkzeug.wrappers import Request, Response, BaseResponse
23 from werkzeug.test import Client, EnvironBuilder, create_environ, \
24 ClientRedirectError, stream_encode_multipart, run_wsgi_app
25 from werkzeug.utils import redirect
26 from werkzeug.formparser import parse_form_data
27 from werkzeug.datastructures import MultiDict, FileStorage
28
29
30 def cookie_app(environ, start_response):
31 """A WSGI application which sets a cookie, and returns as a ersponse any
32 cookie which exists.
33 """
34 response = Response(environ.get('HTTP_COOKIE', 'No Cookie'),
35 mimetype='text/plain')
36 response.set_cookie('test', 'test')
37 return response(environ, start_response)
38
39
40 def redirect_loop_app(environ, start_response):
41 response = redirect('http://localhost/some/redirect/')
42 return response(environ, start_response)
43
44
45 def redirect_with_get_app(environ, start_response):
46 req = Request(environ)
47 if req.url not in ('http://localhost/',
48 'http://localhost/first/request',
49 'http://localhost/some/redirect/'):
50 assert False, 'redirect_demo_app() did not expect URL "%s"' % req.url
51 if '/some/redirect' not in req.url:
52 response = redirect('http://localhost/some/redirect/')
53 else:
54 response = Response('current url: %s' % req.url)
55 return response(environ, start_response)
56
57
58 def redirect_with_post_app(environ, start_response):
59 req = Request(environ)
60 if req.url == 'http://localhost/some/redirect/':
61 assert req.method == 'GET', 'request should be GET'
62 assert not req.form, 'request should not have data'
63 response = Response('current url: %s' % req.url)
64 else:
65 response = redirect('http://localhost/some/redirect/')
66 return response(environ, start_response)
67
68
69 def external_redirect_demo_app(environ, start_response):
70 response = redirect('http://example.com/')
71 return response(environ, start_response)
72
73
74 def external_subdomain_redirect_demo_app(environ, start_response):
75 if 'test.example.com' in environ['HTTP_HOST']:
76 response = Response('redirected successfully to subdomain')
77 else:
78 response = redirect('http://test.example.com/login')
79 return response(environ, start_response)
80
81
82 def multi_value_post_app(environ, start_response):
83 req = Request(environ)
84 assert req.form['field'] == 'val1', req.form['field']
85 assert req.form.getlist('field') == ['val1', 'val2'], req.form.getlist('field')
86 response = Response('ok')
87 return response(environ, start_response)
88
89
90
91 def test_cookie_forging():
92 c = Client(cookie_app)
93 c.set_cookie('localhost', 'foo', 'bar')
94 appiter, code, headers = c.open()
95 strict_eq(list(appiter), [b'foo=bar'])
96
97 def test_set_cookie_app():
98 c = Client(cookie_app)
99 appiter, code, headers = c.open()
100 assert 'Set-Cookie' in dict(headers)
101
102 def test_cookiejar_stores_cookie():
103 c = Client(cookie_app)
104 appiter, code, headers = c.open()
105 assert 'test' in c.cookie_jar._cookies['localhost.local']['/']
106
107 def test_no_initial_cookie():
108 c = Client(cookie_app)
109 appiter, code, headers = c.open()
110 strict_eq(b''.join(appiter), b'No Cookie')
111
112 def test_resent_cookie():
113 c = Client(cookie_app)
114 c.open()
115 appiter, code, headers = c.open()
116 strict_eq(b''.join(appiter), b'test=test')
117
118 def test_disable_cookies():
119 c = Client(cookie_app, use_cookies=False)
120 c.open()
121 appiter, code, headers = c.open()
122 strict_eq(b''.join(appiter), b'No Cookie')
123
124 def test_cookie_for_different_path():
125 c = Client(cookie_app)
126 c.open('/path1')
127 appiter, code, headers = c.open('/path2')
128 strict_eq(b''.join(appiter), b'test=test')
129
130 def test_environ_builder_basics():
131 b = EnvironBuilder()
132 assert b.content_type is None
133 b.method = 'POST'
134 assert b.content_type is None
135 b.form['test'] = 'normal value'
136 assert b.content_type == 'application/x-www-form-urlencoded'
137 b.files.add_file('test', BytesIO(b'test contents'), 'test.txt')
138 assert b.files['test'].content_type == 'text/plain'
139 assert b.content_type == 'multipart/form-data'
140
141 req = b.get_request()
142 b.close()
143
144 strict_eq(req.url, u'http://localhost/')
145 strict_eq(req.method, 'POST')
146 strict_eq(req.form['test'], u'normal value')
147 assert req.files['test'].content_type == 'text/plain'
148 strict_eq(req.files['test'].filename, u'test.txt')
149 strict_eq(req.files['test'].read(), b'test contents')
150
151 def test_environ_builder_headers():
152 b = EnvironBuilder(environ_base={'HTTP_USER_AGENT': 'Foo/0.1'},
153 environ_overrides={'wsgi.version': (1, 1)})
154 b.headers['X-Beat-My-Horse'] = 'very well sir'
155 env = b.get_environ()
156 strict_eq(env['HTTP_USER_AGENT'], 'Foo/0.1')
157 strict_eq(env['HTTP_X_BEAT_MY_HORSE'], 'very well sir')
158 strict_eq(env['wsgi.version'], (1, 1))
159
160 b.headers['User-Agent'] = 'Bar/1.0'
161 env = b.get_environ()
162 strict_eq(env['HTTP_USER_AGENT'], 'Bar/1.0')
163
164 def test_environ_builder_headers_content_type():
165 b = EnvironBuilder(headers={'Content-Type': 'text/plain'})
166 env = b.get_environ()
167 assert env['CONTENT_TYPE'] == 'text/plain'
168 b = EnvironBuilder(content_type='text/html',
169 headers={'Content-Type': 'text/plain'})
170 env = b.get_environ()
171 assert env['CONTENT_TYPE'] == 'text/html'
172
173 def test_environ_builder_paths():
174 b = EnvironBuilder(path='/foo', base_url='http://example.com/')
175 strict_eq(b.base_url, 'http://example.com/')
176 strict_eq(b.path, '/foo')
177 strict_eq(b.script_root, '')
178 strict_eq(b.host, 'example.com')
179
180 b = EnvironBuilder(path='/foo', base_url='http://example.com/bar')
181 strict_eq(b.base_url, 'http://example.com/bar/')
182 strict_eq(b.path, '/foo')
183 strict_eq(b.script_root, '/bar')
184 strict_eq(b.host, 'example.com')
185
186 b.host = 'localhost'
187 strict_eq(b.base_url, 'http://localhost/bar/')
188 b.base_url = 'http://localhost:8080/'
189 strict_eq(b.host, 'localhost:8080')
190 strict_eq(b.server_name, 'localhost')
191 strict_eq(b.server_port, 8080)
192
193 b.host = 'foo.invalid'
194 b.url_scheme = 'https'
195 b.script_root = '/test'
196 env = b.get_environ()
197 strict_eq(env['SERVER_NAME'], 'foo.invalid')
198 strict_eq(env['SERVER_PORT'], '443')
199 strict_eq(env['SCRIPT_NAME'], '/test')
200 strict_eq(env['PATH_INFO'], '/foo')
201 strict_eq(env['HTTP_HOST'], 'foo.invalid')
202 strict_eq(env['wsgi.url_scheme'], 'https')
203 strict_eq(b.base_url, 'https://foo.invalid/test/')
204
205 def test_environ_builder_content_type():
206 builder = EnvironBuilder()
207 assert builder.content_type is None
208 builder.method = 'POST'
209 assert builder.content_type is None
210 builder.method = 'PUT'
211 assert builder.content_type is None
212 builder.method = 'PATCH'
213 assert builder.content_type is None
214 builder.method = 'DELETE'
215 assert builder.content_type is None
216 builder.method = 'GET'
217 assert builder.content_type == None
218 builder.form['foo'] = 'bar'
219 assert builder.content_type == 'application/x-www-form-urlencoded'
220 builder.files.add_file('blafasel', BytesIO(b'foo'), 'test.txt')
221 assert builder.content_type == 'multipart/form-data'
222 req = builder.get_request()
223 strict_eq(req.form['foo'], u'bar')
224 strict_eq(req.files['blafasel'].read(), b'foo')
225
226 def test_environ_builder_stream_switch():
227 d = MultiDict(dict(foo=u'bar', blub=u'blah', hu=u'hum'))
228 for use_tempfile in False, True:
229 stream, length, boundary = stream_encode_multipart(
230 d, use_tempfile, threshold=150)
231 assert isinstance(stream, BytesIO) != use_tempfile
232
233 form = parse_form_data({'wsgi.input': stream, 'CONTENT_LENGTH': str(length),
234 'CONTENT_TYPE': 'multipart/form-data; boundary="%s"' %
235 boundary})[1]
236 strict_eq(form, d)
237 stream.close()
238
239 def test_environ_builder_unicode_file_mix():
240 for use_tempfile in False, True:
241 f = FileStorage(BytesIO(u'\N{SNOWMAN}'.encode('utf-8')),
242 'snowman.txt')
243 d = MultiDict(dict(f=f, s=u'\N{SNOWMAN}'))
244 stream, length, boundary = stream_encode_multipart(
245 d, use_tempfile, threshold=150)
246 assert isinstance(stream, BytesIO) != use_tempfile
247
248 _, form, files = parse_form_data({
249 'wsgi.input': stream,
250 'CONTENT_LENGTH': str(length),
251 'CONTENT_TYPE': 'multipart/form-data; boundary="%s"' %
252 boundary
253 })
254 strict_eq(form['s'], u'\N{SNOWMAN}')
255 strict_eq(files['f'].name, 'f')
256 strict_eq(files['f'].filename, u'snowman.txt')
257 strict_eq(files['f'].read(),
258 u'\N{SNOWMAN}'.encode('utf-8'))
259 stream.close()
260
261 def test_create_environ():
262 env = create_environ('/foo?bar=baz', 'http://example.org/')
263 expected = {
264 'wsgi.multiprocess': False,
265 'wsgi.version': (1, 0),
266 'wsgi.run_once': False,
267 'wsgi.errors': sys.stderr,
268 'wsgi.multithread': False,
269 'wsgi.url_scheme': 'http',
270 'SCRIPT_NAME': '',
271 'CONTENT_TYPE': '',
272 'CONTENT_LENGTH': '0',
273 'SERVER_NAME': 'example.org',
274 'REQUEST_METHOD': 'GET',
275 'HTTP_HOST': 'example.org',
276 'PATH_INFO': '/foo',
277 'SERVER_PORT': '80',
278 'SERVER_PROTOCOL': 'HTTP/1.1',
279 'QUERY_STRING': 'bar=baz'
280 }
281 for key, value in iteritems(expected):
282 assert env[key] == value
283 strict_eq(env['wsgi.input'].read(0), b'')
284 strict_eq(create_environ('/foo', 'http://example.com/')['SCRIPT_NAME'], '')
285
286 def test_file_closing():
287 closed = []
288 class SpecialInput(object):
289 def read(self, size):
290 return ''
291 def close(self):
292 closed.append(self)
293
294 env = create_environ(data={'foo': SpecialInput()})
295 strict_eq(len(closed), 1)
296 builder = EnvironBuilder()
297 builder.files.add_file('blah', SpecialInput())
298 builder.close()
299 strict_eq(len(closed), 2)
300
301 def test_follow_redirect():
302 env = create_environ('/', base_url='http://localhost')
303 c = Client(redirect_with_get_app)
304 appiter, code, headers = c.open(environ_overrides=env, follow_redirects=True)
305 strict_eq(code, '200 OK')
306 strict_eq(b''.join(appiter), b'current url: http://localhost/some/redirect/')
307
308 # Test that the :cls:`Client` is aware of user defined response wrappers
309 c = Client(redirect_with_get_app, response_wrapper=BaseResponse)
310 resp = c.get('/', follow_redirects=True)
311 strict_eq(resp.status_code, 200)
312 strict_eq(resp.data, b'current url: http://localhost/some/redirect/')
313
314 # test with URL other than '/' to make sure redirected URL's are correct
315 c = Client(redirect_with_get_app, response_wrapper=BaseResponse)
316 resp = c.get('/first/request', follow_redirects=True)
317 strict_eq(resp.status_code, 200)
318 strict_eq(resp.data, b'current url: http://localhost/some/redirect/')
319
320 def test_follow_redirect_with_post_307():
321 def redirect_with_post_307_app(environ, start_response):
322 req = Request(environ)
323 if req.url == 'http://localhost/some/redirect/':
324 assert req.method == 'POST', 'request should be POST'
325 assert not req.form, 'request should not have data'
326 response = Response('current url: %s' % req.url)
327 else:
328 response = redirect('http://localhost/some/redirect/', code=307)
329 return response(environ, start_response)
330
331 c = Client(redirect_with_post_307_app, response_wrapper=BaseResponse)
332 resp = c.post('/', follow_redirects=True, data='foo=blub+hehe&blah=42')
333 assert resp.status_code == 200
334 assert resp.data == b'current url: http://localhost/some/redirect/'
335
336 def test_follow_external_redirect():
337 env = create_environ('/', base_url='http://localhost')
338 c = Client(external_redirect_demo_app)
339 pytest.raises(RuntimeError, lambda:
340 c.get(environ_overrides=env, follow_redirects=True))
341
342 def test_follow_external_redirect_on_same_subdomain():
343 env = create_environ('/', base_url='http://example.com')
344 c = Client(external_subdomain_redirect_demo_app, allow_subdomain_redirects=True)
345 c.get(environ_overrides=env, follow_redirects=True)
346
347 # check that this does not work for real external domains
348 env = create_environ('/', base_url='http://localhost')
349 pytest.raises(RuntimeError, lambda:
350 c.get(environ_overrides=env, follow_redirects=True))
351
352 # check that subdomain redirects fail if no `allow_subdomain_redirects` is applied
353 c = Client(external_subdomain_redirect_demo_app)
354 pytest.raises(RuntimeError, lambda:
355 c.get(environ_overrides=env, follow_redirects=True))
356
357 def test_follow_redirect_loop():
358 c = Client(redirect_loop_app, response_wrapper=BaseResponse)
359 with pytest.raises(ClientRedirectError):
360 resp = c.get('/', follow_redirects=True)
361
362 def test_follow_redirect_with_post():
363 c = Client(redirect_with_post_app, response_wrapper=BaseResponse)
364 resp = c.post('/', follow_redirects=True, data='foo=blub+hehe&blah=42')
365 strict_eq(resp.status_code, 200)
366 strict_eq(resp.data, b'current url: http://localhost/some/redirect/')
367
368 def test_path_info_script_name_unquoting():
369 def test_app(environ, start_response):
370 start_response('200 OK', [('Content-Type', 'text/plain')])
371 return [environ['PATH_INFO'] + '\n' + environ['SCRIPT_NAME']]
372 c = Client(test_app, response_wrapper=BaseResponse)
373 resp = c.get('/foo%40bar')
374 strict_eq(resp.data, b'/foo@bar\n')
375 c = Client(test_app, response_wrapper=BaseResponse)
376 resp = c.get('/foo%40bar', 'http://localhost/bar%40baz')
377 strict_eq(resp.data, b'/foo@bar\n/bar@baz')
378
379 def test_multi_value_submit():
380 c = Client(multi_value_post_app, response_wrapper=BaseResponse)
381 data = {
382 'field': ['val1','val2']
383 }
384 resp = c.post('/', data=data)
385 strict_eq(resp.status_code, 200)
386 c = Client(multi_value_post_app, response_wrapper=BaseResponse)
387 data = MultiDict({
388 'field': ['val1', 'val2']
389 })
390 resp = c.post('/', data=data)
391 strict_eq(resp.status_code, 200)
392
393 def test_iri_support():
394 b = EnvironBuilder(u'/föö-bar', base_url=u'http://☃.net/')
395 strict_eq(b.path, '/f%C3%B6%C3%B6-bar')
396 strict_eq(b.base_url, 'http://xn--n3h.net/')
397
398 @pytest.mark.parametrize('buffered', (True, False))
399 @pytest.mark.parametrize('iterable', (True, False))
400 def test_run_wsgi_apps(buffered, iterable):
401 leaked_data = []
402
403 def simple_app(environ, start_response):
404 start_response('200 OK', [('Content-Type', 'text/html')])
405 return ['Hello World!']
406
407 def yielding_app(environ, start_response):
408 start_response('200 OK', [('Content-Type', 'text/html')])
409 yield 'Hello '
410 yield 'World!'
411
412 def late_start_response(environ, start_response):
413 yield 'Hello '
414 yield 'World'
415 start_response('200 OK', [('Content-Type', 'text/html')])
416 yield '!'
417
418 def depends_on_close(environ, start_response):
419 leaked_data.append('harhar')
420 start_response('200 OK', [('Content-Type', 'text/html')])
421 class Rv(object):
422 def __iter__(self):
423 yield 'Hello '
424 yield 'World'
425 yield '!'
426
427 def close(self):
428 assert leaked_data.pop() == 'harhar'
429
430 return Rv()
431
432
433 for app in (simple_app, yielding_app, late_start_response,
434 depends_on_close):
435 if iterable:
436 app = iterable_middleware(app)
437 app_iter, status, headers = run_wsgi_app(app, {}, buffered=buffered)
438 strict_eq(status, '200 OK')
439 strict_eq(list(headers), [('Content-Type', 'text/html')])
440 strict_eq(''.join(app_iter), 'Hello World!')
441
442 if hasattr(app_iter, 'close'):
443 app_iter.close()
444 assert not leaked_data
445
446 def test_run_wsgi_app_closing_iterator():
447 got_close = []
448 @implements_iterator
449 class CloseIter(object):
450 def __init__(self):
451 self.iterated = False
452 def __iter__(self):
453 return self
454 def close(self):
455 got_close.append(None)
456 def __next__(self):
457 if self.iterated:
458 raise StopIteration()
459 self.iterated = True
460 return 'bar'
461
462 def bar(environ, start_response):
463 start_response('200 OK', [('Content-Type', 'text/plain')])
464 return CloseIter()
465
466 app_iter, status, headers = run_wsgi_app(bar, {})
467 assert status == '200 OK'
468 assert list(headers) == [('Content-Type', 'text/plain')]
469 assert next(app_iter) == 'bar'
470 pytest.raises(StopIteration, partial(next, app_iter))
471 app_iter.close()
472
473 assert run_wsgi_app(bar, {}, True)[0] == ['bar']
474
475 assert len(got_close) == 2
476
477 def iterable_middleware(app):
478 '''Guarantee that the app returns an iterable'''
479 def inner(environ, start_response):
480 rv = app(environ, start_response)
481
482 class Iterable(object):
483 def __iter__(self):
484 return iter(rv)
485
486 if hasattr(rv, 'close'):
487 def close(self):
488 rv.close()
489
490 return Iterable()
491 return inner
492
493 def test_multiple_cookies():
494 @Request.application
495 def test_app(request):
496 response = Response(repr(sorted(request.cookies.items())))
497 response.set_cookie(u'test1', b'foo')
498 response.set_cookie(u'test2', b'bar')
499 return response
500 client = Client(test_app, Response)
501 resp = client.get('/')
502 strict_eq(resp.data, b'[]')
503 resp = client.get('/')
504 strict_eq(resp.data,
505 to_bytes(repr([('test1', u'foo'), ('test2', u'bar')]), 'ascii'))
506
507 def test_correct_open_invocation_on_redirect():
508 class MyClient(Client):
509 counter = 0
510 def open(self, *args, **kwargs):
511 self.counter += 1
512 env = kwargs.setdefault('environ_overrides', {})
513 env['werkzeug._foo'] = self.counter
514 return Client.open(self, *args, **kwargs)
515
516 @Request.application
517 def test_app(request):
518 return Response(str(request.environ['werkzeug._foo']))
519
520 c = MyClient(test_app, response_wrapper=Response)
521 strict_eq(c.get('/').data, b'1')
522 strict_eq(c.get('/').data, b'2')
523 strict_eq(c.get('/').data, b'3')
524
525 def test_correct_encoding():
526 req = Request.from_values(u'/\N{SNOWMAN}', u'http://example.com/foo')
527 strict_eq(req.script_root, u'/foo')
528 strict_eq(req.path, u'/\N{SNOWMAN}')
529
530 def test_full_url_requests_with_args():
531 base = 'http://example.com/'
532
533 @Request.application
534 def test_app(request):
535 return Response(request.args['x'])
536 client = Client(test_app, Response)
537 resp = client.get('/?x=42', base)
538 strict_eq(resp.data, b'42')
539 resp = client.get('http://www.example.com/?x=23', base)
540 strict_eq(resp.data, b'23')
541
542 def test_delete_requests_with_form():
543 @Request.application
544 def test_app(request):
545 return Response(request.form.get('x', None))
546
547 client = Client(test_app, Response)
548 resp = client.delete('/', data={'x': 42})
549 strict_eq(resp.data, b'42')
0 # -*- coding: utf-8 -*-
1 """
2 tests.urls
3 ~~~~~~~~~~
4
5 URL helper tests.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import pytest
11
12 from tests import strict_eq
13
14 from werkzeug.datastructures import OrderedMultiDict
15 from werkzeug import urls
16 from werkzeug._compat import text_type, NativeStringIO, BytesIO
17
18
19 def test_parsing():
20 url = urls.url_parse('http://anon:hunter2@[2001:db8:0:1]:80/a/b/c')
21 assert url.netloc == 'anon:hunter2@[2001:db8:0:1]:80'
22 assert url.username == 'anon'
23 assert url.password == 'hunter2'
24 assert url.port == 80
25 assert url.ascii_host == '2001:db8:0:1'
26
27 assert url.get_file_location() == (None, None) # no file scheme
28
29
30 @pytest.mark.parametrize('implicit_format', (True, False))
31 @pytest.mark.parametrize('localhost', ('127.0.0.1', '::1', 'localhost'))
32 def test_fileurl_parsing_windows(implicit_format, localhost, monkeypatch):
33 if implicit_format:
34 pathformat = None
35 monkeypatch.setattr('os.name', 'nt')
36 else:
37 pathformat = 'windows'
38 monkeypatch.delattr('os.name') # just to make sure it won't get used
39
40 url = urls.url_parse('file:///C:/Documents and Settings/Foobar/stuff.txt')
41 assert url.netloc == ''
42 assert url.scheme == 'file'
43 assert url.get_file_location(pathformat) == \
44 (None, r'C:\Documents and Settings\Foobar\stuff.txt')
45
46 url = urls.url_parse('file://///server.tld/file.txt')
47 assert url.get_file_location(pathformat) == ('server.tld', r'file.txt')
48
49 url = urls.url_parse('file://///server.tld')
50 assert url.get_file_location(pathformat) == ('server.tld', '')
51
52 url = urls.url_parse('file://///%s' % localhost)
53 assert url.get_file_location(pathformat) == (None, '')
54
55 url = urls.url_parse('file://///%s/file.txt' % localhost)
56 assert url.get_file_location(pathformat) == (None, r'file.txt')
57
58
59 def test_replace():
60 url = urls.url_parse('http://de.wikipedia.org/wiki/Troll')
61 strict_eq(url.replace(query='foo=bar'),
62 urls.url_parse('http://de.wikipedia.org/wiki/Troll?foo=bar'))
63 strict_eq(url.replace(scheme='https'),
64 urls.url_parse('https://de.wikipedia.org/wiki/Troll'))
65
66
67 def test_quoting():
68 strict_eq(urls.url_quote(u'\xf6\xe4\xfc'), '%C3%B6%C3%A4%C3%BC')
69 strict_eq(urls.url_unquote(urls.url_quote(u'#%="\xf6')), u'#%="\xf6')
70 strict_eq(urls.url_quote_plus('foo bar'), 'foo+bar')
71 strict_eq(urls.url_unquote_plus('foo+bar'), u'foo bar')
72 strict_eq(urls.url_quote_plus('foo+bar'), 'foo%2Bbar')
73 strict_eq(urls.url_unquote_plus('foo%2Bbar'), u'foo+bar')
74 strict_eq(urls.url_encode({b'a': None, b'b': b'foo bar'}), 'b=foo+bar')
75 strict_eq(urls.url_encode({u'a': None, u'b': u'foo bar'}), 'b=foo+bar')
76 strict_eq(urls.url_fix(u'http://de.wikipedia.org/wiki/Elf (Begriffsklärung)'),
77 'http://de.wikipedia.org/wiki/Elf%20(Begriffskl%C3%A4rung)')
78 strict_eq(urls.url_quote_plus(42), '42')
79 strict_eq(urls.url_quote(b'\xff'), '%FF')
80
81
82 def test_bytes_unquoting():
83 strict_eq(urls.url_unquote(urls.url_quote(
84 u'#%="\xf6', charset='latin1'), charset=None), b'#%="\xf6')
85
86
87 def test_url_decoding():
88 x = urls.url_decode(b'foo=42&bar=23&uni=H%C3%A4nsel')
89 strict_eq(x['foo'], u'42')
90 strict_eq(x['bar'], u'23')
91 strict_eq(x['uni'], u'Hänsel')
92
93 x = urls.url_decode(b'foo=42;bar=23;uni=H%C3%A4nsel', separator=b';')
94 strict_eq(x['foo'], u'42')
95 strict_eq(x['bar'], u'23')
96 strict_eq(x['uni'], u'Hänsel')
97
98 x = urls.url_decode(b'%C3%9Ch=H%C3%A4nsel', decode_keys=True)
99 strict_eq(x[u'Üh'], u'Hänsel')
100
101
102 def test_url_bytes_decoding():
103 x = urls.url_decode(b'foo=42&bar=23&uni=H%C3%A4nsel', charset=None)
104 strict_eq(x[b'foo'], b'42')
105 strict_eq(x[b'bar'], b'23')
106 strict_eq(x[b'uni'], u'Hänsel'.encode('utf-8'))
107
108
109 def test_streamed_url_decoding():
110 item1 = u'a' * 100000
111 item2 = u'b' * 400
112 string = ('a=%s&b=%s&c=%s' % (item1, item2, item2)).encode('ascii')
113 gen = urls.url_decode_stream(BytesIO(string), limit=len(string),
114 return_iterator=True)
115 strict_eq(next(gen), ('a', item1))
116 strict_eq(next(gen), ('b', item2))
117 strict_eq(next(gen), ('c', item2))
118 pytest.raises(StopIteration, lambda: next(gen))
119
120
121 def test_stream_decoding_string_fails():
122 pytest.raises(TypeError, urls.url_decode_stream, 'testing')
123
124
125 def test_url_encoding():
126 strict_eq(urls.url_encode({'foo': 'bar 45'}), 'foo=bar+45')
127 d = {'foo': 1, 'bar': 23, 'blah': u'Hänsel'}
128 strict_eq(urls.url_encode(d, sort=True), 'bar=23&blah=H%C3%A4nsel&foo=1')
129 strict_eq(urls.url_encode(d, sort=True, separator=u';'), 'bar=23;blah=H%C3%A4nsel;foo=1')
130
131
132 def test_sorted_url_encode():
133 strict_eq(urls.url_encode({u"a": 42, u"b": 23, 1: 1, 2: 2},
134 sort=True, key=lambda i: text_type(i[0])), '1=1&2=2&a=42&b=23')
135 strict_eq(urls.url_encode({u'A': 1, u'a': 2, u'B': 3, 'b': 4}, sort=True,
136 key=lambda x: x[0].lower() + x[0]), 'A=1&a=2&B=3&b=4')
137
138
139 def test_streamed_url_encoding():
140 out = NativeStringIO()
141 urls.url_encode_stream({'foo': 'bar 45'}, out)
142 strict_eq(out.getvalue(), 'foo=bar+45')
143
144 d = {'foo': 1, 'bar': 23, 'blah': u'Hänsel'}
145 out = NativeStringIO()
146 urls.url_encode_stream(d, out, sort=True)
147 strict_eq(out.getvalue(), 'bar=23&blah=H%C3%A4nsel&foo=1')
148 out = NativeStringIO()
149 urls.url_encode_stream(d, out, sort=True, separator=u';')
150 strict_eq(out.getvalue(), 'bar=23;blah=H%C3%A4nsel;foo=1')
151
152 gen = urls.url_encode_stream(d, sort=True)
153 strict_eq(next(gen), 'bar=23')
154 strict_eq(next(gen), 'blah=H%C3%A4nsel')
155 strict_eq(next(gen), 'foo=1')
156 pytest.raises(StopIteration, lambda: next(gen))
157
158
159 def test_url_fixing():
160 x = urls.url_fix(u'http://de.wikipedia.org/wiki/Elf (Begriffskl\xe4rung)')
161 assert x == 'http://de.wikipedia.org/wiki/Elf%20(Begriffskl%C3%A4rung)'
162
163 x = urls.url_fix("http://just.a.test/$-_.+!*'(),")
164 assert x == "http://just.a.test/$-_.+!*'(),"
165
166 x = urls.url_fix('http://höhöhö.at/höhöhö/hähähä')
167 assert x == r'http://xn--hhh-snabb.at/h%C3%B6h%C3%B6h%C3%B6/h%C3%A4h%C3%A4h%C3%A4'
168
169
170 def test_url_fixing_filepaths():
171 x = urls.url_fix(r'file://C:\Users\Administrator\My Documents\ÑÈáÇíí')
172 assert x == r'file:///C%3A/Users/Administrator/My%20Documents/%C3%91%C3%88%C3%A1%C3%87%C3%AD%C3%AD'
173
174 a = urls.url_fix(r'file:/C:/')
175 b = urls.url_fix(r'file://C:/')
176 c = urls.url_fix(r'file:///C:/')
177 assert a == b == c == r'file:///C%3A/'
178
179 x = urls.url_fix(r'file://host/sub/path')
180 assert x == r'file://host/sub/path'
181
182 x = urls.url_fix(r'file:///')
183 assert x == r'file:///'
184
185
186 def test_url_fixing_qs():
187 x = urls.url_fix(b'http://example.com/?foo=%2f%2f')
188 assert x == 'http://example.com/?foo=%2f%2f'
189
190 x = urls.url_fix('http://acronyms.thefreedictionary.com/Algebraic+Methods+of+Solving+the+Schr%C3%B6dinger+Equation')
191 assert x == 'http://acronyms.thefreedictionary.com/Algebraic+Methods+of+Solving+the+Schr%C3%B6dinger+Equation'
192
193
194 def test_iri_support():
195 strict_eq(urls.uri_to_iri('http://xn--n3h.net/'),
196 u'http://\u2603.net/')
197 strict_eq(
198 urls.uri_to_iri(b'http://%C3%BCser:p%C3%A4ssword@xn--n3h.net/p%C3%A5th'),
199 u'http://\xfcser:p\xe4ssword@\u2603.net/p\xe5th')
200 strict_eq(urls.iri_to_uri(u'http://☃.net/'), 'http://xn--n3h.net/')
201 strict_eq(
202 urls.iri_to_uri(u'http://üser:pässword@☃.net/påth'),
203 'http://%C3%BCser:p%C3%A4ssword@xn--n3h.net/p%C3%A5th')
204
205 strict_eq(urls.uri_to_iri('http://test.com/%3Fmeh?foo=%26%2F'),
206 u'http://test.com/%3Fmeh?foo=%26%2F')
207
208 # this should work as well, might break on 2.4 because of a broken
209 # idna codec
210 strict_eq(urls.uri_to_iri(b'/foo'), u'/foo')
211 strict_eq(urls.iri_to_uri(u'/foo'), '/foo')
212
213 strict_eq(urls.iri_to_uri(u'http://föö.com:8080/bam/baz'),
214 'http://xn--f-1gaa.com:8080/bam/baz')
215
216
217 def test_iri_safe_conversion():
218 strict_eq(urls.iri_to_uri(u'magnet:?foo=bar'),
219 'magnet:?foo=bar')
220 strict_eq(urls.iri_to_uri(u'itms-service://?foo=bar'),
221 'itms-service:?foo=bar')
222 strict_eq(urls.iri_to_uri(u'itms-service://?foo=bar',
223 safe_conversion=True),
224 'itms-service://?foo=bar')
225
226
227 def test_iri_safe_quoting():
228 uri = 'http://xn--f-1gaa.com/%2F%25?q=%C3%B6&x=%3D%25#%25'
229 iri = u'http://föö.com/%2F%25?q=ö&x=%3D%25#%25'
230 strict_eq(urls.uri_to_iri(uri), iri)
231 strict_eq(urls.iri_to_uri(urls.uri_to_iri(uri)), uri)
232
233
234 def test_ordered_multidict_encoding():
235 d = OrderedMultiDict()
236 d.add('foo', 1)
237 d.add('foo', 2)
238 d.add('foo', 3)
239 d.add('bar', 0)
240 d.add('foo', 4)
241 assert urls.url_encode(d) == 'foo=1&foo=2&foo=3&bar=0&foo=4'
242
243
244 def test_multidict_encoding():
245 d = OrderedMultiDict()
246 d.add('2013-10-10T23:26:05.657975+0000', '2013-10-10T23:26:05.657975+0000')
247 assert urls.url_encode(d) == '2013-10-10T23%3A26%3A05.657975%2B0000=2013-10-10T23%3A26%3A05.657975%2B0000'
248
249
250 def test_href():
251 x = urls.Href('http://www.example.com/')
252 strict_eq(x(u'foo'), 'http://www.example.com/foo')
253 strict_eq(x.foo(u'bar'), 'http://www.example.com/foo/bar')
254 strict_eq(x.foo(u'bar', x=42), 'http://www.example.com/foo/bar?x=42')
255 strict_eq(x.foo(u'bar', class_=42), 'http://www.example.com/foo/bar?class=42')
256 strict_eq(x.foo(u'bar', {u'class': 42}), 'http://www.example.com/foo/bar?class=42')
257 pytest.raises(AttributeError, lambda: x.__blah__)
258
259 x = urls.Href('blah')
260 strict_eq(x.foo(u'bar'), 'blah/foo/bar')
261
262 pytest.raises(TypeError, x.foo, {u"foo": 23}, x=42)
263
264 x = urls.Href('')
265 strict_eq(x('foo'), 'foo')
266
267
268 def test_href_url_join():
269 x = urls.Href(u'test')
270 assert x(u'foo:bar') == u'test/foo:bar'
271 assert x(u'http://example.com/') == u'test/http://example.com/'
272 assert x.a() == u'test/a'
273
274
275 def test_href_past_root():
276 base_href = urls.Href('http://www.blagga.com/1/2/3')
277 strict_eq(base_href('../foo'), 'http://www.blagga.com/1/2/foo')
278 strict_eq(base_href('../../foo'), 'http://www.blagga.com/1/foo')
279 strict_eq(base_href('../../../foo'), 'http://www.blagga.com/foo')
280 strict_eq(base_href('../../../../foo'), 'http://www.blagga.com/foo')
281 strict_eq(base_href('../../../../../foo'), 'http://www.blagga.com/foo')
282 strict_eq(base_href('../../../../../../foo'), 'http://www.blagga.com/foo')
283
284
285 def test_url_unquote_plus_unicode():
286 # was broken in 0.6
287 strict_eq(urls.url_unquote_plus(u'\x6d'), u'\x6d')
288 assert type(urls.url_unquote_plus(u'\x6d')) is text_type
289
290
291 def test_quoting_of_local_urls():
292 rv = urls.iri_to_uri(u'/foo\x8f')
293 strict_eq(rv, '/foo%C2%8F')
294 assert type(rv) is str
295
296
297 def test_url_attributes():
298 rv = urls.url_parse('http://foo%3a:bar%3a@[::1]:80/123?x=y#frag')
299 strict_eq(rv.scheme, 'http')
300 strict_eq(rv.auth, 'foo%3a:bar%3a')
301 strict_eq(rv.username, u'foo:')
302 strict_eq(rv.password, u'bar:')
303 strict_eq(rv.raw_username, 'foo%3a')
304 strict_eq(rv.raw_password, 'bar%3a')
305 strict_eq(rv.host, '::1')
306 assert rv.port == 80
307 strict_eq(rv.path, '/123')
308 strict_eq(rv.query, 'x=y')
309 strict_eq(rv.fragment, 'frag')
310
311 rv = urls.url_parse(u'http://\N{SNOWMAN}.com/')
312 strict_eq(rv.host, u'\N{SNOWMAN}.com')
313 strict_eq(rv.ascii_host, 'xn--n3h.com')
314
315
316 def test_url_attributes_bytes():
317 rv = urls.url_parse(b'http://foo%3a:bar%3a@[::1]:80/123?x=y#frag')
318 strict_eq(rv.scheme, b'http')
319 strict_eq(rv.auth, b'foo%3a:bar%3a')
320 strict_eq(rv.username, u'foo:')
321 strict_eq(rv.password, u'bar:')
322 strict_eq(rv.raw_username, b'foo%3a')
323 strict_eq(rv.raw_password, b'bar%3a')
324 strict_eq(rv.host, b'::1')
325 assert rv.port == 80
326 strict_eq(rv.path, b'/123')
327 strict_eq(rv.query, b'x=y')
328 strict_eq(rv.fragment, b'frag')
329
330
331 def test_url_joining():
332 strict_eq(urls.url_join('/foo', '/bar'), '/bar')
333 strict_eq(urls.url_join('http://example.com/foo', '/bar'),
334 'http://example.com/bar')
335 strict_eq(urls.url_join('file:///tmp/', 'test.html'),
336 'file:///tmp/test.html')
337 strict_eq(urls.url_join('file:///tmp/x', 'test.html'),
338 'file:///tmp/test.html')
339 strict_eq(urls.url_join('file:///tmp/x', '../../../x.html'),
340 'file:///x.html')
341
342
343 def test_partial_unencoded_decode():
344 ref = u'foo=정상처리'.encode('euc-kr')
345 x = urls.url_decode(ref, charset='euc-kr')
346 strict_eq(x['foo'], u'정상처리')
347
348
349 def test_iri_to_uri_idempotence_ascii_only():
350 uri = u'http://www.idempoten.ce'
351 uri = urls.iri_to_uri(uri)
352 assert urls.iri_to_uri(uri) == uri
353
354
355 def test_iri_to_uri_idempotence_non_ascii():
356 uri = u'http://\N{SNOWMAN}/\N{SNOWMAN}'
357 uri = urls.iri_to_uri(uri)
358 assert urls.iri_to_uri(uri) == uri
359
360
361 def test_uri_to_iri_idempotence_ascii_only():
362 uri = 'http://www.idempoten.ce'
363 uri = urls.uri_to_iri(uri)
364 assert urls.uri_to_iri(uri) == uri
365
366
367 def test_uri_to_iri_idempotence_non_ascii():
368 uri = 'http://xn--n3h/%E2%98%83'
369 uri = urls.uri_to_iri(uri)
370 assert urls.uri_to_iri(uri) == uri
371
372
373 def test_iri_to_uri_to_iri():
374 iri = u'http://föö.com/'
375 uri = urls.iri_to_uri(iri)
376 assert urls.uri_to_iri(uri) == iri
377
378
379 def test_uri_to_iri_to_uri():
380 uri = 'http://xn--f-rgao.com/%C3%9E'
381 iri = urls.uri_to_iri(uri)
382 assert urls.iri_to_uri(iri) == uri
383
384
385 def test_uri_iri_normalization():
386 uri = 'http://xn--f-rgao.com/%E2%98%90/fred?utf8=%E2%9C%93'
387 iri = u'http://föñ.com/\N{BALLOT BOX}/fred?utf8=\u2713'
388
389 tests = [
390 u'http://föñ.com/\N{BALLOT BOX}/fred?utf8=\u2713',
391 u'http://xn--f-rgao.com/\u2610/fred?utf8=\N{CHECK MARK}',
392 b'http://xn--f-rgao.com/%E2%98%90/fred?utf8=%E2%9C%93',
393 u'http://xn--f-rgao.com/%E2%98%90/fred?utf8=%E2%9C%93',
394 u'http://föñ.com/\u2610/fred?utf8=%E2%9C%93',
395 b'http://xn--f-rgao.com/\xe2\x98\x90/fred?utf8=\xe2\x9c\x93',
396 ]
397
398 for test in tests:
399 assert urls.uri_to_iri(test) == iri
400 assert urls.iri_to_uri(test) == uri
401 assert urls.uri_to_iri(urls.iri_to_uri(test)) == iri
402 assert urls.iri_to_uri(urls.uri_to_iri(test)) == uri
403 assert urls.uri_to_iri(urls.uri_to_iri(test)) == iri
404 assert urls.iri_to_uri(urls.iri_to_uri(test)) == uri
0 # -*- coding: utf-8 -*-
1 """
2 tests.utils
3 ~~~~~~~~~~~
4
5 General utilities.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 from __future__ import with_statement
11
12 import pytest
13
14 from datetime import datetime
15 from functools import partial
16
17 from werkzeug import utils
18 from werkzeug.datastructures import Headers
19 from werkzeug.http import parse_date, http_date
20 from werkzeug.wrappers import BaseResponse
21 from werkzeug.test import Client, run_wsgi_app
22 from werkzeug._compat import text_type, implements_iterator
23
24
25 def test_redirect():
26 resp = utils.redirect(u'/füübär')
27 assert b'/f%C3%BC%C3%BCb%C3%A4r' in resp.get_data()
28 assert resp.headers['Location'] == '/f%C3%BC%C3%BCb%C3%A4r'
29 assert resp.status_code == 302
30
31 resp = utils.redirect(u'http://☃.net/', 307)
32 assert b'http://xn--n3h.net/' in resp.get_data()
33 assert resp.headers['Location'] == 'http://xn--n3h.net/'
34 assert resp.status_code == 307
35
36 resp = utils.redirect('http://example.com/', 305)
37 assert resp.headers['Location'] == 'http://example.com/'
38 assert resp.status_code == 305
39
40 def test_redirect_no_unicode_header_keys():
41 # Make sure all headers are native keys. This was a bug at one point
42 # due to an incorrect conversion.
43 resp = utils.redirect('http://example.com/', 305)
44 for key, value in resp.headers.items():
45 assert type(key) == str
46 assert type(value) == text_type
47 assert resp.headers['Location'] == 'http://example.com/'
48 assert resp.status_code == 305
49
50 def test_redirect_xss():
51 location = 'http://example.com/?xss="><script>alert(1)</script>'
52 resp = utils.redirect(location)
53 assert b'<script>alert(1)</script>' not in resp.get_data()
54
55 location = 'http://example.com/?xss="onmouseover="alert(1)'
56 resp = utils.redirect(location)
57 assert b'href="http://example.com/?xss="onmouseover="alert(1)"' not in resp.get_data()
58
59
60 def test_redirect_with_custom_response_class():
61 class MyResponse(BaseResponse):
62 pass
63
64 location = "http://example.com/redirect"
65 resp = utils.redirect(location, Response=MyResponse)
66
67 assert isinstance(resp, MyResponse)
68 assert resp.headers['Location'] == location
69
70
71 def test_cached_property():
72 foo = []
73 class A(object):
74 def prop(self):
75 foo.append(42)
76 return 42
77 prop = utils.cached_property(prop)
78
79 a = A()
80 p = a.prop
81 q = a.prop
82 assert p == q == 42
83 assert foo == [42]
84
85 foo = []
86 class A(object):
87 def _prop(self):
88 foo.append(42)
89 return 42
90 prop = utils.cached_property(_prop, name='prop')
91 del _prop
92
93 a = A()
94 p = a.prop
95 q = a.prop
96 assert p == q == 42
97 assert foo == [42]
98
99 def test_environ_property():
100 class A(object):
101 environ = {'string': 'abc', 'number': '42'}
102
103 string = utils.environ_property('string')
104 missing = utils.environ_property('missing', 'spam')
105 read_only = utils.environ_property('number')
106 number = utils.environ_property('number', load_func=int)
107 broken_number = utils.environ_property('broken_number', load_func=int)
108 date = utils.environ_property('date', None, parse_date, http_date,
109 read_only=False)
110 foo = utils.environ_property('foo')
111
112 a = A()
113 assert a.string == 'abc'
114 assert a.missing == 'spam'
115 def test_assign():
116 a.read_only = 'something'
117 pytest.raises(AttributeError, test_assign)
118 assert a.number == 42
119 assert a.broken_number == None
120 assert a.date is None
121 a.date = datetime(2008, 1, 22, 10, 0, 0, 0)
122 assert a.environ['date'] == 'Tue, 22 Jan 2008 10:00:00 GMT'
123
124 def test_escape():
125 class Foo(str):
126 def __html__(self):
127 return text_type(self)
128 assert utils.escape(None) == ''
129 assert utils.escape(42) == '42'
130 assert utils.escape('<>') == '&lt;&gt;'
131 assert utils.escape('"foo"') == '&quot;foo&quot;'
132 assert utils.escape(Foo('<foo>')) == '<foo>'
133
134 def test_unescape():
135 assert utils.unescape('&lt;&auml;&gt;') == u'<ä>'
136
137 def test_import_string():
138 import cgi
139 from werkzeug.debug import DebuggedApplication
140 assert utils.import_string('cgi.escape') is cgi.escape
141 assert utils.import_string(u'cgi.escape') is cgi.escape
142 assert utils.import_string('cgi:escape') is cgi.escape
143 assert utils.import_string('XXXXXXXXXXXX', True) is None
144 assert utils.import_string('cgi.XXXXXXXXXXXX', True) is None
145 assert utils.import_string(u'werkzeug.debug.DebuggedApplication') is DebuggedApplication
146 pytest.raises(ImportError, utils.import_string, 'XXXXXXXXXXXXXXXX')
147 pytest.raises(ImportError, utils.import_string, 'cgi.XXXXXXXXXX')
148
149
150 def test_import_string_attribute_error(tmpdir, monkeypatch):
151 monkeypatch.syspath_prepend(str(tmpdir))
152 tmpdir.join('foo_test.py').write('from bar_test import value')
153 tmpdir.join('bar_test.py').write('raise AttributeError("screw you!")')
154 with pytest.raises(AttributeError) as foo_exc:
155 utils.import_string('foo_test')
156 assert 'screw you!' in str(foo_exc)
157
158 with pytest.raises(AttributeError) as bar_exc:
159 utils.import_string('bar_test')
160 assert 'screw you!' in str(bar_exc)
161
162
163 def test_find_modules():
164 assert list(utils.find_modules('werkzeug.debug')) == [
165 'werkzeug.debug.console', 'werkzeug.debug.repr',
166 'werkzeug.debug.tbtools'
167 ]
168
169 def test_html_builder():
170 html = utils.html
171 xhtml = utils.xhtml
172 assert html.p('Hello World') == '<p>Hello World</p>'
173 assert html.a('Test', href='#') == '<a href="#">Test</a>'
174 assert html.br() == '<br>'
175 assert xhtml.br() == '<br />'
176 assert html.img(src='foo') == '<img src="foo">'
177 assert xhtml.img(src='foo') == '<img src="foo" />'
178 assert html.html(html.head(
179 html.title('foo'),
180 html.script(type='text/javascript')
181 )) == (
182 '<html><head><title>foo</title><script type="text/javascript">'
183 '</script></head></html>'
184 )
185 assert html('<foo>') == '&lt;foo&gt;'
186 assert html.input(disabled=True) == '<input disabled>'
187 assert xhtml.input(disabled=True) == '<input disabled="disabled" />'
188 assert html.input(disabled='') == '<input>'
189 assert xhtml.input(disabled='') == '<input />'
190 assert html.input(disabled=None) == '<input>'
191 assert xhtml.input(disabled=None) == '<input />'
192 assert html.script('alert("Hello World");') == \
193 '<script>alert("Hello World");</script>'
194 assert xhtml.script('alert("Hello World");') == \
195 '<script>/*<![CDATA[*/alert("Hello World");/*]]>*/</script>'
196
197 def test_validate_arguments():
198 take_none = lambda: None
199 take_two = lambda a, b: None
200 take_two_one_default = lambda a, b=0: None
201
202 assert utils.validate_arguments(take_two, (1, 2,), {}) == ((1, 2), {})
203 assert utils.validate_arguments(take_two, (1,), {'b': 2}) == ((1, 2), {})
204 assert utils.validate_arguments(take_two_one_default, (1,), {}) == ((1, 0), {})
205 assert utils.validate_arguments(take_two_one_default, (1, 2), {}) == ((1, 2), {})
206
207 pytest.raises(utils.ArgumentValidationError,
208 utils.validate_arguments, take_two, (), {})
209
210 assert utils.validate_arguments(take_none, (1, 2,), {'c': 3}) == ((), {})
211 pytest.raises(utils.ArgumentValidationError,
212 utils.validate_arguments, take_none, (1,), {}, drop_extra=False)
213 pytest.raises(utils.ArgumentValidationError,
214 utils.validate_arguments, take_none, (), {'a': 1}, drop_extra=False)
215
216 def test_header_set_duplication_bug():
217 headers = Headers([
218 ('Content-Type', 'text/html'),
219 ('Foo', 'bar'),
220 ('Blub', 'blah')
221 ])
222 headers['blub'] = 'hehe'
223 headers['blafasel'] = 'humm'
224 assert headers == Headers([
225 ('Content-Type', 'text/html'),
226 ('Foo', 'bar'),
227 ('blub', 'hehe'),
228 ('blafasel', 'humm')
229 ])
230
231 def test_append_slash_redirect():
232 def app(env, sr):
233 return utils.append_slash_redirect(env)(env, sr)
234 client = Client(app, BaseResponse)
235 response = client.get('foo', base_url='http://example.org/app')
236 assert response.status_code == 301
237 assert response.headers['Location'] == 'http://example.org/app/foo/'
238
239 def test_cached_property_doc():
240 @utils.cached_property
241 def foo():
242 """testing"""
243 return 42
244 assert foo.__doc__ == 'testing'
245 assert foo.__name__ == 'foo'
246 assert foo.__module__ == __name__
247
248 def test_secure_filename():
249 assert utils.secure_filename('My cool movie.mov') == 'My_cool_movie.mov'
250 assert utils.secure_filename('../../../etc/passwd') == 'etc_passwd'
251 assert utils.secure_filename(u'i contain cool \xfcml\xe4uts.txt') == \
252 'i_contain_cool_umlauts.txt'
0 # -*- coding: utf-8 -*-
1 """
2 tests.wrappers
3 ~~~~~~~~~~~~~~
4
5 Tests for the response and request objects.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import pytest
11
12 import pickle
13 from io import BytesIO
14 from datetime import datetime
15 from werkzeug._compat import iteritems
16
17 from tests import strict_eq
18
19 from werkzeug import wrappers
20 from werkzeug.exceptions import SecurityError
21 from werkzeug.wsgi import LimitedStream
22 from werkzeug.datastructures import MultiDict, ImmutableOrderedMultiDict, \
23 ImmutableList, ImmutableTypeConversionDict, CharsetAccept, \
24 MIMEAccept, LanguageAccept, Accept, CombinedMultiDict
25 from werkzeug.test import Client, create_environ, run_wsgi_app
26 from werkzeug._compat import implements_iterator, text_type
27
28
29 class RequestTestResponse(wrappers.BaseResponse):
30 """Subclass of the normal response class we use to test response
31 and base classes. Has some methods to test if things in the
32 response match.
33 """
34
35 def __init__(self, response, status, headers):
36 wrappers.BaseResponse.__init__(self, response, status, headers)
37 self.body_data = pickle.loads(self.get_data())
38
39 def __getitem__(self, key):
40 return self.body_data[key]
41
42
43 def request_demo_app(environ, start_response):
44 request = wrappers.BaseRequest(environ)
45 assert 'werkzeug.request' in environ
46 start_response('200 OK', [('Content-Type', 'text/plain')])
47 return [pickle.dumps({
48 'args': request.args,
49 'args_as_list': list(request.args.lists()),
50 'form': request.form,
51 'form_as_list': list(request.form.lists()),
52 'environ': prepare_environ_pickle(request.environ),
53 'data': request.get_data()
54 })]
55
56
57 def prepare_environ_pickle(environ):
58 result = {}
59 for key, value in iteritems(environ):
60 try:
61 pickle.dumps((key, value))
62 except Exception:
63 continue
64 result[key] = value
65 return result
66
67
68
69 def assert_environ(environ, method):
70 strict_eq(environ['REQUEST_METHOD'], method)
71 strict_eq(environ['PATH_INFO'], '/')
72 strict_eq(environ['SCRIPT_NAME'], '')
73 strict_eq(environ['SERVER_NAME'], 'localhost')
74 strict_eq(environ['wsgi.version'], (1, 0))
75 strict_eq(environ['wsgi.url_scheme'], 'http')
76
77 def test_base_request():
78 client = Client(request_demo_app, RequestTestResponse)
79
80 # get requests
81 response = client.get('/?foo=bar&foo=hehe')
82 strict_eq(response['args'], MultiDict([('foo', u'bar'), ('foo', u'hehe')]))
83 strict_eq(response['args_as_list'], [('foo', [u'bar', u'hehe'])])
84 strict_eq(response['form'], MultiDict())
85 strict_eq(response['form_as_list'], [])
86 strict_eq(response['data'], b'')
87 assert_environ(response['environ'], 'GET')
88
89 # post requests with form data
90 response = client.post('/?blub=blah', data='foo=blub+hehe&blah=42',
91 content_type='application/x-www-form-urlencoded')
92 strict_eq(response['args'], MultiDict([('blub', u'blah')]))
93 strict_eq(response['args_as_list'], [('blub', [u'blah'])])
94 strict_eq(response['form'], MultiDict([('foo', u'blub hehe'), ('blah', u'42')]))
95 strict_eq(response['data'], b'')
96 # currently we do not guarantee that the values are ordered correctly
97 # for post data.
98 ## strict_eq(response['form_as_list'], [('foo', ['blub hehe']), ('blah', ['42'])])
99 assert_environ(response['environ'], 'POST')
100
101 # patch requests with form data
102 response = client.patch('/?blub=blah', data='foo=blub+hehe&blah=42',
103 content_type='application/x-www-form-urlencoded')
104 strict_eq(response['args'], MultiDict([('blub', u'blah')]))
105 strict_eq(response['args_as_list'], [('blub', [u'blah'])])
106 strict_eq(response['form'],
107 MultiDict([('foo', u'blub hehe'), ('blah', u'42')]))
108 strict_eq(response['data'], b'')
109 assert_environ(response['environ'], 'PATCH')
110
111 # post requests with json data
112 json = b'{"foo": "bar", "blub": "blah"}'
113 response = client.post('/?a=b', data=json, content_type='application/json')
114 strict_eq(response['data'], json)
115 strict_eq(response['args'], MultiDict([('a', u'b')]))
116 strict_eq(response['form'], MultiDict())
117
118 def test_query_string_is_bytes():
119 req = wrappers.Request.from_values(u'/?foo=%2f')
120 strict_eq(req.query_string, b'foo=%2f')
121
122 def test_request_repr():
123 req = wrappers.Request.from_values('/foobar')
124 assert "<Request 'http://localhost/foobar' [GET]>" == repr(req)
125 # test with non-ascii characters
126 req = wrappers.Request.from_values('/привет')
127 assert "<Request 'http://localhost/привет' [GET]>" == repr(req)
128 # test with unicode type for python 2
129 req = wrappers.Request.from_values(u'/привет')
130 assert "<Request 'http://localhost/привет' [GET]>" == repr(req)
131
132 def test_access_route():
133 req = wrappers.Request.from_values(headers={
134 'X-Forwarded-For': '192.168.1.2, 192.168.1.1'
135 })
136 req.environ['REMOTE_ADDR'] = '192.168.1.3'
137 assert req.access_route == ['192.168.1.2', '192.168.1.1']
138 strict_eq(req.remote_addr, '192.168.1.3')
139
140 req = wrappers.Request.from_values()
141 req.environ['REMOTE_ADDR'] = '192.168.1.3'
142 strict_eq(list(req.access_route), ['192.168.1.3'])
143
144 def test_url_request_descriptors():
145 req = wrappers.Request.from_values('/bar?foo=baz', 'http://example.com/test')
146 strict_eq(req.path, u'/bar')
147 strict_eq(req.full_path, u'/bar?foo=baz')
148 strict_eq(req.script_root, u'/test')
149 strict_eq(req.url, u'http://example.com/test/bar?foo=baz')
150 strict_eq(req.base_url, u'http://example.com/test/bar')
151 strict_eq(req.url_root, u'http://example.com/test/')
152 strict_eq(req.host_url, u'http://example.com/')
153 strict_eq(req.host, 'example.com')
154 strict_eq(req.scheme, 'http')
155
156 req = wrappers.Request.from_values('/bar?foo=baz', 'https://example.com/test')
157 strict_eq(req.scheme, 'https')
158
159 def test_url_request_descriptors_query_quoting():
160 next = 'http%3A%2F%2Fwww.example.com%2F%3Fnext%3D%2F'
161 req = wrappers.Request.from_values('/bar?next=' + next, 'http://example.com/')
162 assert req.path == u'/bar'
163 strict_eq(req.full_path, u'/bar?next=' + next)
164 strict_eq(req.url, u'http://example.com/bar?next=' + next)
165
166 def test_url_request_descriptors_hosts():
167 req = wrappers.Request.from_values('/bar?foo=baz', 'http://example.com/test')
168 req.trusted_hosts = ['example.com']
169 strict_eq(req.path, u'/bar')
170 strict_eq(req.full_path, u'/bar?foo=baz')
171 strict_eq(req.script_root, u'/test')
172 strict_eq(req.url, u'http://example.com/test/bar?foo=baz')
173 strict_eq(req.base_url, u'http://example.com/test/bar')
174 strict_eq(req.url_root, u'http://example.com/test/')
175 strict_eq(req.host_url, u'http://example.com/')
176 strict_eq(req.host, 'example.com')
177 strict_eq(req.scheme, 'http')
178
179 req = wrappers.Request.from_values('/bar?foo=baz', 'https://example.com/test')
180 strict_eq(req.scheme, 'https')
181
182 req = wrappers.Request.from_values('/bar?foo=baz', 'http://example.com/test')
183 req.trusted_hosts = ['example.org']
184 pytest.raises(SecurityError, lambda: req.url)
185 pytest.raises(SecurityError, lambda: req.base_url)
186 pytest.raises(SecurityError, lambda: req.url_root)
187 pytest.raises(SecurityError, lambda: req.host_url)
188 pytest.raises(SecurityError, lambda: req.host)
189
190 def test_authorization_mixin():
191 request = wrappers.Request.from_values(headers={
192 'Authorization': 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='
193 })
194 a = request.authorization
195 strict_eq(a.type, 'basic')
196 strict_eq(a.username, 'Aladdin')
197 strict_eq(a.password, 'open sesame')
198
199 def test_stream_only_mixing():
200 request = wrappers.PlainRequest.from_values(
201 data=b'foo=blub+hehe',
202 content_type='application/x-www-form-urlencoded'
203 )
204 assert list(request.files.items()) == []
205 assert list(request.form.items()) == []
206 pytest.raises(AttributeError, lambda: request.data)
207 strict_eq(request.stream.read(), b'foo=blub+hehe')
208
209 def test_base_response():
210 # unicode
211 response = wrappers.BaseResponse(u'öäü')
212 strict_eq(response.get_data(), u'öäü'.encode('utf-8'))
213
214 # writing
215 response = wrappers.Response('foo')
216 response.stream.write('bar')
217 strict_eq(response.get_data(), b'foobar')
218
219 # set cookie
220 response = wrappers.BaseResponse()
221 response.set_cookie('foo', 'bar', 60, 0, '/blub', 'example.org')
222 strict_eq(response.headers.to_wsgi_list(), [
223 ('Content-Type', 'text/plain; charset=utf-8'),
224 ('Set-Cookie', 'foo=bar; Domain=example.org; Expires=Thu, '
225 '01-Jan-1970 00:00:00 GMT; Max-Age=60; Path=/blub')
226 ])
227
228 # delete cookie
229 response = wrappers.BaseResponse()
230 response.delete_cookie('foo')
231 strict_eq(response.headers.to_wsgi_list(), [
232 ('Content-Type', 'text/plain; charset=utf-8'),
233 ('Set-Cookie', 'foo=; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/')
234 ])
235
236 # close call forwarding
237 closed = []
238 @implements_iterator
239 class Iterable(object):
240 def __next__(self):
241 raise StopIteration()
242 def __iter__(self):
243 return self
244 def close(self):
245 closed.append(True)
246 response = wrappers.BaseResponse(Iterable())
247 response.call_on_close(lambda: closed.append(True))
248 app_iter, status, headers = run_wsgi_app(response,
249 create_environ(),
250 buffered=True)
251 strict_eq(status, '200 OK')
252 strict_eq(''.join(app_iter), '')
253 strict_eq(len(closed), 2)
254
255 # with statement
256 del closed[:]
257 response = wrappers.BaseResponse(Iterable())
258 with response:
259 pass
260 assert len(closed) == 1
261
262 def test_response_status_codes():
263 response = wrappers.BaseResponse()
264 response.status_code = 404
265 strict_eq(response.status, '404 NOT FOUND')
266 response.status = '200 OK'
267 strict_eq(response.status_code, 200)
268 response.status = '999 WTF'
269 strict_eq(response.status_code, 999)
270 response.status_code = 588
271 strict_eq(response.status_code, 588)
272 strict_eq(response.status, '588 UNKNOWN')
273 response.status = 'wtf'
274 strict_eq(response.status_code, 0)
275 strict_eq(response.status, '0 wtf')
276
277 def test_type_forcing():
278 def wsgi_application(environ, start_response):
279 start_response('200 OK', [('Content-Type', 'text/html')])
280 return ['Hello World!']
281 base_response = wrappers.BaseResponse('Hello World!', content_type='text/html')
282
283 class SpecialResponse(wrappers.Response):
284 def foo(self):
285 return 42
286
287 # good enough for this simple application, but don't ever use that in
288 # real world examples!
289 fake_env = {}
290
291 for orig_resp in wsgi_application, base_response:
292 response = SpecialResponse.force_type(orig_resp, fake_env)
293 assert response.__class__ is SpecialResponse
294 strict_eq(response.foo(), 42)
295 strict_eq(response.get_data(), b'Hello World!')
296 assert response.content_type == 'text/html'
297
298 # without env, no arbitrary conversion
299 pytest.raises(TypeError, SpecialResponse.force_type, wsgi_application)
300
301 def test_accept_mixin():
302 request = wrappers.Request({
303 'HTTP_ACCEPT': 'text/xml,application/xml,application/xhtml+xml,'
304 'text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
305 'HTTP_ACCEPT_CHARSET': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
306 'HTTP_ACCEPT_ENCODING': 'gzip,deflate',
307 'HTTP_ACCEPT_LANGUAGE': 'en-us,en;q=0.5'
308 })
309 assert request.accept_mimetypes == MIMEAccept([
310 ('text/xml', 1), ('image/png', 1), ('application/xml', 1),
311 ('application/xhtml+xml', 1), ('text/html', 0.9),
312 ('text/plain', 0.8), ('*/*', 0.5)
313 ])
314 strict_eq(request.accept_charsets, CharsetAccept([
315 ('ISO-8859-1', 1), ('utf-8', 0.7), ('*', 0.7)
316 ]))
317 strict_eq(request.accept_encodings, Accept([
318 ('gzip', 1), ('deflate', 1)]))
319 strict_eq(request.accept_languages, LanguageAccept([
320 ('en-us', 1), ('en', 0.5)]))
321
322 request = wrappers.Request({'HTTP_ACCEPT': ''})
323 strict_eq(request.accept_mimetypes, MIMEAccept())
324
325 def test_etag_request_mixin():
326 request = wrappers.Request({
327 'HTTP_CACHE_CONTROL': 'no-store, no-cache',
328 'HTTP_IF_MATCH': 'w/"foo", bar, "baz"',
329 'HTTP_IF_NONE_MATCH': 'w/"foo", bar, "baz"',
330 'HTTP_IF_MODIFIED_SINCE': 'Tue, 22 Jan 2008 11:18:44 GMT',
331 'HTTP_IF_UNMODIFIED_SINCE': 'Tue, 22 Jan 2008 11:18:44 GMT'
332 })
333 assert request.cache_control.no_store
334 assert request.cache_control.no_cache
335
336 for etags in request.if_match, request.if_none_match:
337 assert etags('bar')
338 assert etags.contains_raw('w/"foo"')
339 assert etags.contains_weak('foo')
340 assert not etags.contains('foo')
341
342 assert request.if_modified_since == datetime(2008, 1, 22, 11, 18, 44)
343 assert request.if_unmodified_since == datetime(2008, 1, 22, 11, 18, 44)
344
345 def test_user_agent_mixin():
346 user_agents = [
347 ('Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.11) '
348 'Gecko/20071127 Firefox/2.0.0.11', 'firefox', 'macos', '2.0.0.11',
349 'en-US'),
350 ('Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; de-DE) Opera 8.54',
351 'opera', 'windows', '8.54', 'de-DE'),
352 ('Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420 '
353 '(KHTML, like Gecko) Version/3.0 Mobile/1A543a Safari/419.3',
354 'safari', 'iphone', '419.3', 'en'),
355 ('Bot Googlebot/2.1 ( http://www.googlebot.com/bot.html)',
356 'google', None, '2.1', None),
357 ('Mozilla/5.0 (X11; CrOS armv7l 3701.81.0) AppleWebKit/537.31 '
358 '(KHTML, like Gecko) Chrome/26.0.1410.57 Safari/537.31',
359 'chrome', 'chromeos', '26.0.1410.57', None),
360 ('Mozilla/5.0 (Windows NT 6.3; Trident/7.0; .NET4.0E; rv:11.0) like Gecko',
361 'msie', 'windows', '11.0', None),
362 ('Mozilla/5.0 (SymbianOS/9.3; Series60/3.2 NokiaE5-00/101.003; '
363 'Profile/MIDP-2.1 Configuration/CLDC-1.1 ) AppleWebKit/533.4 (KHTML, like Gecko) '
364 'NokiaBrowser/7.3.1.35 Mobile Safari/533.4 3gpp-gba',
365 'safari', 'symbian', '533.4', None)
366
367 ]
368 for ua, browser, platform, version, lang in user_agents:
369 request = wrappers.Request({'HTTP_USER_AGENT': ua})
370 strict_eq(request.user_agent.browser, browser)
371 strict_eq(request.user_agent.platform, platform)
372 strict_eq(request.user_agent.version, version)
373 strict_eq(request.user_agent.language, lang)
374 assert bool(request.user_agent)
375 strict_eq(request.user_agent.to_header(), ua)
376 strict_eq(str(request.user_agent), ua)
377
378 request = wrappers.Request({'HTTP_USER_AGENT': 'foo'})
379 assert not request.user_agent
380
381 def test_stream_wrapping():
382 class LowercasingStream(object):
383 def __init__(self, stream):
384 self._stream = stream
385 def read(self, size=-1):
386 return self._stream.read(size).lower()
387 def readline(self, size=-1):
388 return self._stream.readline(size).lower()
389
390 data = b'foo=Hello+World'
391 req = wrappers.Request.from_values('/', method='POST', data=data,
392 content_type='application/x-www-form-urlencoded')
393 req.stream = LowercasingStream(req.stream)
394 assert req.form['foo'] == 'hello world'
395
396 def test_data_descriptor_triggers_parsing():
397 data = b'foo=Hello+World'
398 req = wrappers.Request.from_values('/', method='POST', data=data,
399 content_type='application/x-www-form-urlencoded')
400
401 assert req.data == b''
402 assert req.form['foo'] == u'Hello World'
403
404 def test_get_data_method_parsing_caching_behavior():
405 data = b'foo=Hello+World'
406 req = wrappers.Request.from_values('/', method='POST', data=data,
407 content_type='application/x-www-form-urlencoded')
408
409 # get_data() caches, so form stays available
410 assert req.get_data() == data
411 assert req.form['foo'] == u'Hello World'
412 assert req.get_data() == data
413
414 # here we access the form data first, caching is bypassed
415 req = wrappers.Request.from_values('/', method='POST', data=data,
416 content_type='application/x-www-form-urlencoded')
417 assert req.form['foo'] == u'Hello World'
418 assert req.get_data() == b''
419
420 # Another case is uncached get data which trashes everything
421 req = wrappers.Request.from_values('/', method='POST', data=data,
422 content_type='application/x-www-form-urlencoded')
423 assert req.get_data(cache=False) == data
424 assert req.get_data(cache=False) == b''
425 assert req.form == {}
426
427 # Or we can implicitly start the form parser which is similar to
428 # the old .data behavior
429 req = wrappers.Request.from_values('/', method='POST', data=data,
430 content_type='application/x-www-form-urlencoded')
431 assert req.get_data(parse_form_data=True) == b''
432 assert req.form['foo'] == u'Hello World'
433
434 def test_etag_response_mixin():
435 response = wrappers.Response('Hello World')
436 assert response.get_etag() == (None, None)
437 response.add_etag()
438 assert response.get_etag() == ('b10a8db164e0754105b7a99be72e3fe5', False)
439 assert not response.cache_control
440 response.cache_control.must_revalidate = True
441 response.cache_control.max_age = 60
442 response.headers['Content-Length'] = len(response.get_data())
443 assert response.headers['Cache-Control'] in ('must-revalidate, max-age=60',
444 'max-age=60, must-revalidate')
445
446 assert 'date' not in response.headers
447 env = create_environ()
448 env.update({
449 'REQUEST_METHOD': 'GET',
450 'HTTP_IF_NONE_MATCH': response.get_etag()[0]
451 })
452 response.make_conditional(env)
453 assert 'date' in response.headers
454
455 # after the thing is invoked by the server as wsgi application
456 # (we're emulating this here), there must not be any entity
457 # headers left and the status code would have to be 304
458 resp = wrappers.Response.from_app(response, env)
459 assert resp.status_code == 304
460 assert not 'content-length' in resp.headers
461
462 # make sure date is not overriden
463 response = wrappers.Response('Hello World')
464 response.date = 1337
465 d = response.date
466 response.make_conditional(env)
467 assert response.date == d
468
469 # make sure content length is only set if missing
470 response = wrappers.Response('Hello World')
471 response.content_length = 999
472 response.make_conditional(env)
473 assert response.content_length == 999
474
475 def test_etag_response_mixin_freezing():
476 class WithFreeze(wrappers.ETagResponseMixin, wrappers.BaseResponse):
477 pass
478 class WithoutFreeze(wrappers.BaseResponse, wrappers.ETagResponseMixin):
479 pass
480
481 response = WithFreeze('Hello World')
482 response.freeze()
483 strict_eq(response.get_etag(),
484 (text_type(wrappers.generate_etag(b'Hello World')), False))
485 response = WithoutFreeze('Hello World')
486 response.freeze()
487 assert response.get_etag() == (None, None)
488 response = wrappers.Response('Hello World')
489 response.freeze()
490 assert response.get_etag() == (None, None)
491
492 def test_authenticate_mixin():
493 resp = wrappers.Response()
494 resp.www_authenticate.type = 'basic'
495 resp.www_authenticate.realm = 'Testing'
496 strict_eq(resp.headers['WWW-Authenticate'], u'Basic realm="Testing"')
497 resp.www_authenticate.realm = None
498 resp.www_authenticate.type = None
499 assert 'WWW-Authenticate' not in resp.headers
500
501 def test_authenticate_mixin_quoted_qop():
502 # Example taken from https://github.com/mitsuhiko/werkzeug/issues/633
503 resp = wrappers.Response()
504 resp.www_authenticate.set_digest('REALM','NONCE',qop=("auth","auth-int"))
505
506 actual = set((resp.headers['WWW-Authenticate'] + ',').split())
507 expected = set('Digest nonce="NONCE", realm="REALM", qop="auth, auth-int",'.split())
508 assert actual == expected
509
510 resp.www_authenticate.set_digest('REALM','NONCE',qop=("auth",))
511
512 actual = set((resp.headers['WWW-Authenticate'] + ',').split())
513 expected = set('Digest nonce="NONCE", realm="REALM", qop="auth",'.split())
514 assert actual == expected
515
516 def test_response_stream_mixin():
517 response = wrappers.Response()
518 response.stream.write('Hello ')
519 response.stream.write('World!')
520 assert response.response == ['Hello ', 'World!']
521 assert response.get_data() == b'Hello World!'
522
523 def test_common_response_descriptors_mixin():
524 response = wrappers.Response()
525 response.mimetype = 'text/html'
526 assert response.mimetype == 'text/html'
527 assert response.content_type == 'text/html; charset=utf-8'
528 assert response.mimetype_params == {'charset': 'utf-8'}
529 response.mimetype_params['x-foo'] = 'yep'
530 del response.mimetype_params['charset']
531 assert response.content_type == 'text/html; x-foo=yep'
532
533 now = datetime.utcnow().replace(microsecond=0)
534
535 assert response.content_length is None
536 response.content_length = '42'
537 assert response.content_length == 42
538
539 for attr in 'date', 'age', 'expires':
540 assert getattr(response, attr) is None
541 setattr(response, attr, now)
542 assert getattr(response, attr) == now
543
544 assert response.retry_after is None
545 response.retry_after = now
546 assert response.retry_after == now
547
548 assert not response.vary
549 response.vary.add('Cookie')
550 response.vary.add('Content-Language')
551 assert 'cookie' in response.vary
552 assert response.vary.to_header() == 'Cookie, Content-Language'
553 response.headers['Vary'] = 'Content-Encoding'
554 assert response.vary.as_set() == set(['content-encoding'])
555
556 response.allow.update(['GET', 'POST'])
557 assert response.headers['Allow'] == 'GET, POST'
558
559 response.content_language.add('en-US')
560 response.content_language.add('fr')
561 assert response.headers['Content-Language'] == 'en-US, fr'
562
563 def test_common_request_descriptors_mixin():
564 request = wrappers.Request.from_values(
565 content_type='text/html; charset=utf-8',
566 content_length='23',
567 headers={
568 'Referer': 'http://www.example.com/',
569 'Date': 'Sat, 28 Feb 2009 19:04:35 GMT',
570 'Max-Forwards': '10',
571 'Pragma': 'no-cache',
572 'Content-Encoding': 'gzip',
573 'Content-MD5': '9a3bc6dbc47a70db25b84c6e5867a072'
574 }
575 )
576
577 assert request.content_type == 'text/html; charset=utf-8'
578 assert request.mimetype == 'text/html'
579 assert request.mimetype_params == {'charset': 'utf-8'}
580 assert request.content_length == 23
581 assert request.referrer == 'http://www.example.com/'
582 assert request.date == datetime(2009, 2, 28, 19, 4, 35)
583 assert request.max_forwards == 10
584 assert 'no-cache' in request.pragma
585 assert request.content_encoding == 'gzip'
586 assert request.content_md5 == '9a3bc6dbc47a70db25b84c6e5867a072'
587
588 def test_shallow_mode():
589 request = wrappers.Request({'QUERY_STRING': 'foo=bar'}, shallow=True)
590 assert request.args['foo'] == 'bar'
591 pytest.raises(RuntimeError, lambda: request.form['foo'])
592
593 def test_form_parsing_failed():
594 data = (
595 b'--blah\r\n'
596 )
597 data = wrappers.Request.from_values(
598 input_stream=BytesIO(data),
599 content_length=len(data),
600 content_type='multipart/form-data; boundary=foo',
601 method='POST'
602 )
603 assert not data.files
604 assert not data.form
605
606 def test_file_closing():
607 data = (b'--foo\r\n'
608 b'Content-Disposition: form-data; name="foo"; filename="foo.txt"\r\n'
609 b'Content-Type: text/plain; charset=utf-8\r\n\r\n'
610 b'file contents, just the contents\r\n'
611 b'--foo--')
612 req = wrappers.Request.from_values(
613 input_stream=BytesIO(data),
614 content_length=len(data),
615 content_type='multipart/form-data; boundary=foo',
616 method='POST'
617 )
618 foo = req.files['foo']
619 assert foo.mimetype == 'text/plain'
620 assert foo.filename == 'foo.txt'
621
622 assert foo.closed == False
623 req.close()
624 assert foo.closed == True
625
626 def test_file_closing_with():
627 data = (b'--foo\r\n'
628 b'Content-Disposition: form-data; name="foo"; filename="foo.txt"\r\n'
629 b'Content-Type: text/plain; charset=utf-8\r\n\r\n'
630 b'file contents, just the contents\r\n'
631 b'--foo--')
632 req = wrappers.Request.from_values(
633 input_stream=BytesIO(data),
634 content_length=len(data),
635 content_type='multipart/form-data; boundary=foo',
636 method='POST'
637 )
638 with req:
639 foo = req.files['foo']
640 assert foo.mimetype == 'text/plain'
641 assert foo.filename == 'foo.txt'
642
643 assert foo.closed == True
644
645 def test_url_charset_reflection():
646 req = wrappers.Request.from_values()
647 req.charset = 'utf-7'
648 assert req.url_charset == 'utf-7'
649
650 def test_response_streamed():
651 r = wrappers.Response()
652 assert not r.is_streamed
653 r = wrappers.Response("Hello World")
654 assert not r.is_streamed
655 r = wrappers.Response(["foo", "bar"])
656 assert not r.is_streamed
657 def gen():
658 if 0:
659 yield None
660 r = wrappers.Response(gen())
661 assert r.is_streamed
662
663 def test_response_iter_wrapping():
664 def uppercasing(iterator):
665 for item in iterator:
666 yield item.upper()
667 def generator():
668 yield 'foo'
669 yield 'bar'
670 req = wrappers.Request.from_values()
671 resp = wrappers.Response(generator())
672 del resp.headers['Content-Length']
673 resp.response = uppercasing(resp.iter_encoded())
674 actual_resp = wrappers.Response.from_app(resp, req.environ, buffered=True)
675 assert actual_resp.get_data() == b'FOOBAR'
676
677 def test_response_freeze():
678 def generate():
679 yield "foo"
680 yield "bar"
681 resp = wrappers.Response(generate())
682 resp.freeze()
683 assert resp.response == [b'foo', b'bar']
684 assert resp.headers['content-length'] == '6'
685
686 def test_other_method_payload():
687 data = b'Hello World'
688 req = wrappers.Request.from_values(input_stream=BytesIO(data),
689 content_length=len(data),
690 content_type='text/plain',
691 method='WHAT_THE_FUCK')
692 assert req.get_data() == data
693 assert isinstance(req.stream, LimitedStream)
694
695 def test_urlfication():
696 resp = wrappers.Response()
697 resp.headers['Location'] = u'http://üser:pässword@☃.net/påth'
698 resp.headers['Content-Location'] = u'http://☃.net/'
699 headers = resp.get_wsgi_headers(create_environ())
700 assert headers['location'] == \
701 'http://%C3%BCser:p%C3%A4ssword@xn--n3h.net/p%C3%A5th'
702 assert headers['content-location'] == 'http://xn--n3h.net/'
703
704 def test_new_response_iterator_behavior():
705 req = wrappers.Request.from_values()
706 resp = wrappers.Response(u'Hello Wörld!')
707
708 def get_content_length(resp):
709 headers = resp.get_wsgi_headers(req.environ)
710 return headers.get('content-length', type=int)
711
712 def generate_items():
713 yield "Hello "
714 yield u"Wörld!"
715
716 # werkzeug encodes when set to `data` now, which happens
717 # if a string is passed to the response object.
718 assert resp.response == [u'Hello Wörld!'.encode('utf-8')]
719 assert resp.get_data() == u'Hello Wörld!'.encode('utf-8')
720 assert get_content_length(resp) == 13
721 assert not resp.is_streamed
722 assert resp.is_sequence
723
724 # try the same for manual assignment
725 resp.set_data(u'Wörd')
726 assert resp.response == [u'Wörd'.encode('utf-8')]
727 assert resp.get_data() == u'Wörd'.encode('utf-8')
728 assert get_content_length(resp) == 5
729 assert not resp.is_streamed
730 assert resp.is_sequence
731
732 # automatic generator sequence conversion
733 resp.response = generate_items()
734 assert resp.is_streamed
735 assert not resp.is_sequence
736 assert resp.get_data() == u'Hello Wörld!'.encode('utf-8')
737 assert resp.response == [b'Hello ', u'Wörld!'.encode('utf-8')]
738 assert not resp.is_streamed
739 assert resp.is_sequence
740
741 # automatic generator sequence conversion
742 resp.response = generate_items()
743 resp.implicit_sequence_conversion = False
744 assert resp.is_streamed
745 assert not resp.is_sequence
746 pytest.raises(RuntimeError, lambda: resp.get_data())
747 resp.make_sequence()
748 assert resp.get_data() == u'Hello Wörld!'.encode('utf-8')
749 assert resp.response == [b'Hello ', u'Wörld!'.encode('utf-8')]
750 assert not resp.is_streamed
751 assert resp.is_sequence
752
753 # stream makes it a list no matter how the conversion is set
754 for val in True, False:
755 resp.implicit_sequence_conversion = val
756 resp.response = ("foo", "bar")
757 assert resp.is_sequence
758 resp.stream.write('baz')
759 assert resp.response == ['foo', 'bar', 'baz']
760
761 def test_form_data_ordering():
762 class MyRequest(wrappers.Request):
763 parameter_storage_class = ImmutableOrderedMultiDict
764
765 req = MyRequest.from_values('/?foo=1&bar=0&foo=3')
766 assert list(req.args) == ['foo', 'bar']
767 assert list(req.args.items(multi=True)) == [
768 ('foo', '1'),
769 ('bar', '0'),
770 ('foo', '3')
771 ]
772 assert isinstance(req.args, ImmutableOrderedMultiDict)
773 assert isinstance(req.values, CombinedMultiDict)
774 assert req.values['foo'] == '1'
775 assert req.values.getlist('foo') == ['1', '3']
776
777 def test_storage_classes():
778 class MyRequest(wrappers.Request):
779 dict_storage_class = dict
780 list_storage_class = list
781 parameter_storage_class = dict
782 req = MyRequest.from_values('/?foo=baz', headers={
783 'Cookie': 'foo=bar'
784 })
785 assert type(req.cookies) is dict
786 assert req.cookies == {'foo': 'bar'}
787 assert type(req.access_route) is list
788
789 assert type(req.args) is dict
790 assert type(req.values) is CombinedMultiDict
791 assert req.values['foo'] == u'baz'
792
793 req = wrappers.Request.from_values(headers={
794 'Cookie': 'foo=bar'
795 })
796 assert type(req.cookies) is ImmutableTypeConversionDict
797 assert req.cookies == {'foo': 'bar'}
798 assert type(req.access_route) is ImmutableList
799
800 MyRequest.list_storage_class = tuple
801 req = MyRequest.from_values()
802 assert type(req.access_route) is tuple
803
804 def test_response_headers_passthrough():
805 headers = wrappers.Headers()
806 resp = wrappers.Response(headers=headers)
807 assert resp.headers is headers
808
809 def test_response_304_no_content_length():
810 resp = wrappers.Response('Test', status=304)
811 env = create_environ()
812 assert 'content-length' not in resp.get_wsgi_headers(env)
813
814 def test_ranges():
815 # basic range stuff
816 req = wrappers.Request.from_values()
817 assert req.range is None
818 req = wrappers.Request.from_values(headers={'Range': 'bytes=0-499'})
819 assert req.range.ranges == [(0, 500)]
820
821 resp = wrappers.Response()
822 resp.content_range = req.range.make_content_range(1000)
823 assert resp.content_range.units == 'bytes'
824 assert resp.content_range.start == 0
825 assert resp.content_range.stop == 500
826 assert resp.content_range.length == 1000
827 assert resp.headers['Content-Range'] == 'bytes 0-499/1000'
828
829 resp.content_range.unset()
830 assert 'Content-Range' not in resp.headers
831
832 resp.headers['Content-Range'] = 'bytes 0-499/1000'
833 assert resp.content_range.units == 'bytes'
834 assert resp.content_range.start == 0
835 assert resp.content_range.stop == 500
836 assert resp.content_range.length == 1000
837
838 def test_auto_content_length():
839 resp = wrappers.Response('Hello World!')
840 assert resp.content_length == 12
841
842 resp = wrappers.Response(['Hello World!'])
843 assert resp.content_length is None
844 assert resp.get_wsgi_headers({})['Content-Length'] == '12'
845
846 def test_stream_content_length():
847 resp = wrappers.Response()
848 resp.stream.writelines(['foo', 'bar', 'baz'])
849 assert resp.get_wsgi_headers({})['Content-Length'] == '9'
850
851 resp = wrappers.Response()
852 resp.make_conditional({'REQUEST_METHOD': 'GET'})
853 resp.stream.writelines(['foo', 'bar', 'baz'])
854 assert resp.get_wsgi_headers({})['Content-Length'] == '9'
855
856 resp = wrappers.Response('foo')
857 resp.stream.writelines(['bar', 'baz'])
858 assert resp.get_wsgi_headers({})['Content-Length'] == '9'
859
860 def test_disabled_auto_content_length():
861 class MyResponse(wrappers.Response):
862 automatically_set_content_length = False
863 resp = MyResponse('Hello World!')
864 assert resp.content_length is None
865
866 resp = MyResponse(['Hello World!'])
867 assert resp.content_length is None
868 assert 'Content-Length' not in resp.get_wsgi_headers({})
869
870 resp = MyResponse()
871 resp.make_conditional({
872 'REQUEST_METHOD': 'GET'
873 })
874 assert resp.content_length is None
875 assert 'Content-Length' not in resp.get_wsgi_headers({})
876
877 def test_location_header_autocorrect():
878 env = create_environ()
879 class MyResponse(wrappers.Response):
880 autocorrect_location_header = False
881 resp = MyResponse('Hello World!')
882 resp.headers['Location'] = '/test'
883 assert resp.get_wsgi_headers(env)['Location'] == '/test'
884
885 resp = wrappers.Response('Hello World!')
886 resp.headers['Location'] = '/test'
887 assert resp.get_wsgi_headers(env)['Location'] == 'http://localhost/test'
888
889 def test_modified_url_encoding():
890 class ModifiedRequest(wrappers.Request):
891 url_charset = 'euc-kr'
892
893 req = ModifiedRequest.from_values(u'/?foo=정상처리'.encode('euc-kr'))
894 strict_eq(req.args['foo'], u'정상처리')
895
896
897 def test_request_method_case_sensitivity():
898 req = wrappers.Request({'REQUEST_METHOD': 'get'})
899 assert req.method == 'GET'
0 # -*- coding: utf-8 -*-
1 """
2 tests.wsgi
3 ~~~~~~~~~~
4
5 Tests the WSGI utilities.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import pytest
11
12 from os import path
13 from contextlib import closing
14
15 from tests import strict_eq
16
17 from werkzeug.wrappers import BaseResponse
18 from werkzeug.exceptions import BadRequest, ClientDisconnected
19 from werkzeug.test import Client, create_environ, run_wsgi_app
20 from werkzeug import wsgi
21 from werkzeug._compat import StringIO, BytesIO, NativeStringIO, to_native, \
22 to_bytes
23
24
25 def test_shareddatamiddleware_get_file_loader():
26 app = wsgi.SharedDataMiddleware(None, {})
27 assert callable(app.get_file_loader('foo'))
28
29 def test_shared_data_middleware(tmpdir):
30 def null_application(environ, start_response):
31 start_response('404 NOT FOUND', [('Content-Type', 'text/plain')])
32 yield b'NOT FOUND'
33
34 test_dir = str(tmpdir)
35 with open(path.join(test_dir, to_native(u'äöü', 'utf-8')), 'w') as test_file:
36 test_file.write(u'FOUND')
37
38 app = wsgi.SharedDataMiddleware(null_application, {
39 '/': path.join(path.dirname(__file__), 'res'),
40 '/sources': path.join(path.dirname(__file__), 'res'),
41 '/pkg': ('werkzeug.debug', 'shared'),
42 '/foo': test_dir
43 })
44
45 for p in '/test.txt', '/sources/test.txt', '/foo/äöü':
46 app_iter, status, headers = run_wsgi_app(app, create_environ(p))
47 assert status == '200 OK'
48 with closing(app_iter) as app_iter:
49 data = b''.join(app_iter).strip()
50 assert data == b'FOUND'
51
52 app_iter, status, headers = run_wsgi_app(
53 app, create_environ('/pkg/debugger.js'))
54 with closing(app_iter) as app_iter:
55 contents = b''.join(app_iter)
56 assert b'$(function() {' in contents
57
58 app_iter, status, headers = run_wsgi_app(
59 app, create_environ('/missing'))
60 assert status == '404 NOT FOUND'
61 assert b''.join(app_iter).strip() == b'NOT FOUND'
62
63 def test_dispatchermiddleware():
64 def null_application(environ, start_response):
65 start_response('404 NOT FOUND', [('Content-Type', 'text/plain')])
66 yield b'NOT FOUND'
67
68 def dummy_application(environ, start_response):
69 start_response('200 OK', [('Content-Type', 'text/plain')])
70 yield to_bytes(environ['SCRIPT_NAME'])
71
72 app = wsgi.DispatcherMiddleware(null_application, {
73 '/test1': dummy_application,
74 '/test2/very': dummy_application,
75 })
76 tests = {
77 '/test1': ('/test1', '/test1/asfd', '/test1/very'),
78 '/test2/very': ('/test2/very', '/test2/very/long/path/after/script/name')
79 }
80 for name, urls in tests.items():
81 for p in urls:
82 environ = create_environ(p)
83 app_iter, status, headers = run_wsgi_app(app, environ)
84 assert status == '200 OK'
85 assert b''.join(app_iter).strip() == to_bytes(name)
86
87 app_iter, status, headers = run_wsgi_app(
88 app, create_environ('/missing'))
89 assert status == '404 NOT FOUND'
90 assert b''.join(app_iter).strip() == b'NOT FOUND'
91
92 def test_get_host():
93 env = {'HTTP_X_FORWARDED_HOST': 'example.org',
94 'SERVER_NAME': 'bullshit', 'HOST_NAME': 'ignore me dammit'}
95 assert wsgi.get_host(env) == 'example.org'
96 assert wsgi.get_host(create_environ('/', 'http://example.org')) == \
97 'example.org'
98
99 def test_get_host_multiple_forwarded():
100 env = {'HTTP_X_FORWARDED_HOST': 'example.com, example.org',
101 'SERVER_NAME': 'bullshit', 'HOST_NAME': 'ignore me dammit'}
102 assert wsgi.get_host(env) == 'example.com'
103 assert wsgi.get_host(create_environ('/', 'http://example.com')) == \
104 'example.com'
105
106 def test_get_host_validation():
107 env = {'HTTP_X_FORWARDED_HOST': 'example.org',
108 'SERVER_NAME': 'bullshit', 'HOST_NAME': 'ignore me dammit'}
109 assert wsgi.get_host(env, trusted_hosts=['.example.org']) == 'example.org'
110 pytest.raises(BadRequest, wsgi.get_host, env,
111 trusted_hosts=['example.com'])
112
113 def test_responder():
114 def foo(environ, start_response):
115 return BaseResponse(b'Test')
116 client = Client(wsgi.responder(foo), BaseResponse)
117 response = client.get('/')
118 assert response.status_code == 200
119 assert response.data == b'Test'
120
121 def test_pop_path_info():
122 original_env = {'SCRIPT_NAME': '/foo', 'PATH_INFO': '/a/b///c'}
123
124 # regular path info popping
125 def assert_tuple(script_name, path_info):
126 assert env.get('SCRIPT_NAME') == script_name
127 assert env.get('PATH_INFO') == path_info
128 env = original_env.copy()
129 pop = lambda: wsgi.pop_path_info(env)
130
131 assert_tuple('/foo', '/a/b///c')
132 assert pop() == 'a'
133 assert_tuple('/foo/a', '/b///c')
134 assert pop() == 'b'
135 assert_tuple('/foo/a/b', '///c')
136 assert pop() == 'c'
137 assert_tuple('/foo/a/b///c', '')
138 assert pop() is None
139
140 def test_peek_path_info():
141 env = {
142 'SCRIPT_NAME': '/foo',
143 'PATH_INFO': '/aaa/b///c'
144 }
145
146 assert wsgi.peek_path_info(env) == 'aaa'
147 assert wsgi.peek_path_info(env) == 'aaa'
148 assert wsgi.peek_path_info(env, charset=None) == b'aaa'
149 assert wsgi.peek_path_info(env, charset=None) == b'aaa'
150
151 def test_path_info_and_script_name_fetching():
152 env = create_environ(u'/\N{SNOWMAN}', u'http://example.com/\N{COMET}/')
153 assert wsgi.get_path_info(env) == u'/\N{SNOWMAN}'
154 assert wsgi.get_path_info(env, charset=None) == u'/\N{SNOWMAN}'.encode('utf-8')
155 assert wsgi.get_script_name(env) == u'/\N{COMET}'
156 assert wsgi.get_script_name(env, charset=None) == u'/\N{COMET}'.encode('utf-8')
157
158 def test_query_string_fetching():
159 env = create_environ(u'/?\N{SNOWMAN}=\N{COMET}')
160 qs = wsgi.get_query_string(env)
161 strict_eq(qs, '%E2%98%83=%E2%98%84')
162
163 def test_limited_stream():
164 class RaisingLimitedStream(wsgi.LimitedStream):
165 def on_exhausted(self):
166 raise BadRequest('input stream exhausted')
167
168 io = BytesIO(b'123456')
169 stream = RaisingLimitedStream(io, 3)
170 strict_eq(stream.read(), b'123')
171 pytest.raises(BadRequest, stream.read)
172
173 io = BytesIO(b'123456')
174 stream = RaisingLimitedStream(io, 3)
175 strict_eq(stream.tell(), 0)
176 strict_eq(stream.read(1), b'1')
177 strict_eq(stream.tell(), 1)
178 strict_eq(stream.read(1), b'2')
179 strict_eq(stream.tell(), 2)
180 strict_eq(stream.read(1), b'3')
181 strict_eq(stream.tell(), 3)
182 pytest.raises(BadRequest, stream.read)
183
184 io = BytesIO(b'123456\nabcdefg')
185 stream = wsgi.LimitedStream(io, 9)
186 strict_eq(stream.readline(), b'123456\n')
187 strict_eq(stream.readline(), b'ab')
188
189 io = BytesIO(b'123456\nabcdefg')
190 stream = wsgi.LimitedStream(io, 9)
191 strict_eq(stream.readlines(), [b'123456\n', b'ab'])
192
193 io = BytesIO(b'123456\nabcdefg')
194 stream = wsgi.LimitedStream(io, 9)
195 strict_eq(stream.readlines(2), [b'12'])
196 strict_eq(stream.readlines(2), [b'34'])
197 strict_eq(stream.readlines(), [b'56\n', b'ab'])
198
199 io = BytesIO(b'123456\nabcdefg')
200 stream = wsgi.LimitedStream(io, 9)
201 strict_eq(stream.readline(100), b'123456\n')
202
203 io = BytesIO(b'123456\nabcdefg')
204 stream = wsgi.LimitedStream(io, 9)
205 strict_eq(stream.readlines(100), [b'123456\n', b'ab'])
206
207 io = BytesIO(b'123456')
208 stream = wsgi.LimitedStream(io, 3)
209 strict_eq(stream.read(1), b'1')
210 strict_eq(stream.read(1), b'2')
211 strict_eq(stream.read(), b'3')
212 strict_eq(stream.read(), b'')
213
214 io = BytesIO(b'123456')
215 stream = wsgi.LimitedStream(io, 3)
216 strict_eq(stream.read(-1), b'123')
217
218 io = BytesIO(b'123456')
219 stream = wsgi.LimitedStream(io, 0)
220 strict_eq(stream.read(-1), b'')
221
222 io = StringIO(u'123456')
223 stream = wsgi.LimitedStream(io, 0)
224 strict_eq(stream.read(-1), u'')
225
226 io = StringIO(u'123\n456\n')
227 stream = wsgi.LimitedStream(io, 8)
228 strict_eq(list(stream), [u'123\n', u'456\n'])
229
230 def test_limited_stream_disconnection():
231 io = BytesIO(b'A bit of content')
232
233 # disconnect detection on out of bytes
234 stream = wsgi.LimitedStream(io, 255)
235 with pytest.raises(ClientDisconnected):
236 stream.read()
237
238 # disconnect detection because file close
239 io = BytesIO(b'x' * 255)
240 io.close()
241 stream = wsgi.LimitedStream(io, 255)
242 with pytest.raises(ClientDisconnected):
243 stream.read()
244
245 def test_path_info_extraction():
246 x = wsgi.extract_path_info('http://example.com/app', '/app/hello')
247 assert x == u'/hello'
248 x = wsgi.extract_path_info('http://example.com/app',
249 'https://example.com/app/hello')
250 assert x == u'/hello'
251 x = wsgi.extract_path_info('http://example.com/app/',
252 'https://example.com/app/hello')
253 assert x == u'/hello'
254 x = wsgi.extract_path_info('http://example.com/app/',
255 'https://example.com/app')
256 assert x == u'/'
257 x = wsgi.extract_path_info(u'http://☃.net/', u'/fööbär')
258 assert x == u'/fööbär'
259 x = wsgi.extract_path_info(u'http://☃.net/x', u'http://☃.net/x/fööbär')
260 assert x == u'/fööbär'
261
262 env = create_environ(u'/fööbär', u'http://☃.net/x/')
263 x = wsgi.extract_path_info(env, u'http://☃.net/x/fööbär')
264 assert x == u'/fööbär'
265
266 x = wsgi.extract_path_info('http://example.com/app/',
267 'https://example.com/a/hello')
268 assert x is None
269 x = wsgi.extract_path_info('http://example.com/app/',
270 'https://example.com/app/hello',
271 collapse_http_schemes=False)
272 assert x is None
273
274 def test_get_host_fallback():
275 assert wsgi.get_host({
276 'SERVER_NAME': 'foobar.example.com',
277 'wsgi.url_scheme': 'http',
278 'SERVER_PORT': '80'
279 }) == 'foobar.example.com'
280 assert wsgi.get_host({
281 'SERVER_NAME': 'foobar.example.com',
282 'wsgi.url_scheme': 'http',
283 'SERVER_PORT': '81'
284 }) == 'foobar.example.com:81'
285
286 def test_get_current_url_unicode():
287 env = create_environ()
288 env['QUERY_STRING'] = 'foo=bar&baz=blah&meh=\xcf'
289 rv = wsgi.get_current_url(env)
290 strict_eq(rv,
291 u'http://localhost/?foo=bar&baz=blah&meh=\ufffd')
292
293 def test_multi_part_line_breaks():
294 data = 'abcdef\r\nghijkl\r\nmnopqrstuvwxyz\r\nABCDEFGHIJK'
295 test_stream = NativeStringIO(data)
296 lines = list(wsgi.make_line_iter(test_stream, limit=len(data),
297 buffer_size=16))
298 assert lines == ['abcdef\r\n', 'ghijkl\r\n', 'mnopqrstuvwxyz\r\n',
299 'ABCDEFGHIJK']
300
301 data = 'abc\r\nThis line is broken by the buffer length.' \
302 '\r\nFoo bar baz'
303 test_stream = NativeStringIO(data)
304 lines = list(wsgi.make_line_iter(test_stream, limit=len(data),
305 buffer_size=24))
306 assert lines == ['abc\r\n', 'This line is broken by the buffer '
307 'length.\r\n', 'Foo bar baz']
308
309 def test_multi_part_line_breaks_bytes():
310 data = b'abcdef\r\nghijkl\r\nmnopqrstuvwxyz\r\nABCDEFGHIJK'
311 test_stream = BytesIO(data)
312 lines = list(wsgi.make_line_iter(test_stream, limit=len(data),
313 buffer_size=16))
314 assert lines == [b'abcdef\r\n', b'ghijkl\r\n', b'mnopqrstuvwxyz\r\n',
315 b'ABCDEFGHIJK']
316
317 data = b'abc\r\nThis line is broken by the buffer length.' \
318 b'\r\nFoo bar baz'
319 test_stream = BytesIO(data)
320 lines = list(wsgi.make_line_iter(test_stream, limit=len(data),
321 buffer_size=24))
322 assert lines == [b'abc\r\n', b'This line is broken by the buffer '
323 b'length.\r\n', b'Foo bar baz']
324
325 def test_multi_part_line_breaks_problematic():
326 data = 'abc\rdef\r\nghi'
327 for x in range(1, 10):
328 test_stream = NativeStringIO(data)
329 lines = list(wsgi.make_line_iter(test_stream, limit=len(data),
330 buffer_size=4))
331 assert lines == ['abc\r', 'def\r\n', 'ghi']
332
333 def test_iter_functions_support_iterators():
334 data = ['abcdef\r\nghi', 'jkl\r\nmnopqrstuvwxyz\r', '\nABCDEFGHIJK']
335 lines = list(wsgi.make_line_iter(data))
336 assert lines == ['abcdef\r\n', 'ghijkl\r\n', 'mnopqrstuvwxyz\r\n',
337 'ABCDEFGHIJK']
338
339 def test_make_chunk_iter():
340 data = [u'abcdefXghi', u'jklXmnopqrstuvwxyzX', u'ABCDEFGHIJK']
341 rv = list(wsgi.make_chunk_iter(data, 'X'))
342 assert rv == [u'abcdef', u'ghijkl', u'mnopqrstuvwxyz', u'ABCDEFGHIJK']
343
344 data = u'abcdefXghijklXmnopqrstuvwxyzXABCDEFGHIJK'
345 test_stream = StringIO(data)
346 rv = list(wsgi.make_chunk_iter(test_stream, 'X', limit=len(data),
347 buffer_size=4))
348 assert rv == [u'abcdef', u'ghijkl', u'mnopqrstuvwxyz', u'ABCDEFGHIJK']
349
350 def test_make_chunk_iter_bytes():
351 data = [b'abcdefXghi', b'jklXmnopqrstuvwxyzX', b'ABCDEFGHIJK']
352 rv = list(wsgi.make_chunk_iter(data, 'X'))
353 assert rv == [b'abcdef', b'ghijkl', b'mnopqrstuvwxyz', b'ABCDEFGHIJK']
354
355 data = b'abcdefXghijklXmnopqrstuvwxyzXABCDEFGHIJK'
356 test_stream = BytesIO(data)
357 rv = list(wsgi.make_chunk_iter(test_stream, 'X', limit=len(data),
358 buffer_size=4))
359 assert rv == [b'abcdef', b'ghijkl', b'mnopqrstuvwxyz', b'ABCDEFGHIJK']
360
361 def test_lines_longer_buffer_size():
362 data = '1234567890\n1234567890\n'
363 for bufsize in range(1, 15):
364 lines = list(wsgi.make_line_iter(NativeStringIO(data), limit=len(data),
365 buffer_size=4))
366 assert lines == ['1234567890\n', '1234567890\n']
1919 from werkzeug._compat import iteritems
2020
2121 # the version. Usually set automatically by a script.
22 __version__ = '0.9.6'
22 __version__ = '0.10.4'
2323
2424
2525 # This import magic raises concerns quite often which is why the implementation
8181 leave it as unicode.
8282 """
8383 try:
84 return str(s)
84 return to_native(s)
8585 except UnicodeError:
8686 return s
8787
336336 if b'.' in domain:
337337 return domain
338338 raise ValueError(
339 'Setting \'domain\' for a cookie on a server running localy (ex: '
340 'localhost) is not supportted by complying browsers. You should '
339 'Setting \'domain\' for a cookie on a server running locally (ex: '
340 'localhost) is not supported by complying browsers. You should '
341341 'have something like: \'127.0.0.1 localhost dev.localhost\' on '
342342 'your hosts file and then point your server to run on '
343343 '\'dev.localhost\' and also set \'domain\' for \'dev.localhost\''
0 import os
1 import sys
2 import time
3 import subprocess
4 import threading
5 from itertools import chain
6
7 from werkzeug._internal import _log
8 from werkzeug._compat import PY2, iteritems, text_type
9
10
11 def _iter_module_files():
12 """This iterates over all relevant Python files. It goes through all
13 loaded files from modules, all files in folders of already loaded modules
14 as well as all files reachable through a package.
15 """
16 # The list call is necessary on Python 3 in case the module
17 # dictionary modifies during iteration.
18 for module in list(sys.modules.values()):
19 if module is None:
20 continue
21 filename = getattr(module, '__file__', None)
22 if filename:
23 old = None
24 while not os.path.isfile(filename):
25 old = filename
26 filename = os.path.dirname(filename)
27 if filename == old:
28 break
29 else:
30 if filename[-4:] in ('.pyc', '.pyo'):
31 filename = filename[:-1]
32 yield filename
33
34
35 def _find_observable_paths(extra_files=None):
36 """Finds all paths that should be observed."""
37 rv = set(os.path.abspath(x) for x in sys.path)
38 for filename in extra_files or ():
39 rv.add(os.path.dirname(os.path.abspath(filename)))
40 for module in list(sys.modules.values()):
41 fn = getattr(module, '__file__', None)
42 if fn is None:
43 continue
44 fn = os.path.abspath(fn)
45 rv.add(os.path.dirname(fn))
46 return _find_common_roots(rv)
47
48
49 def _find_common_roots(paths):
50 """Out of some paths it finds the common roots that need monitoring."""
51 paths = [x.split(os.path.sep) for x in paths]
52 root = {}
53 for chunks in sorted(paths, key=len, reverse=True):
54 node = root
55 for chunk in chunks:
56 node = node.setdefault(chunk, {})
57 node.clear()
58
59 rv = set()
60 def _walk(node, path):
61 for prefix, child in iteritems(node):
62 _walk(child, path + (prefix,))
63 if not node:
64 rv.add('/'.join(path))
65 _walk(root, ())
66 return rv
67
68
69 class ReloaderLoop(object):
70 name = None
71
72 # monkeypatched by testsuite. wrapping with `staticmethod` is required in
73 # case time.sleep has been replaced by a non-c function (e.g. by
74 # `eventlet.monkey_patch`) before we get here
75 _sleep = staticmethod(time.sleep)
76
77 def __init__(self, extra_files=None, interval=1):
78 self.extra_files = set(os.path.abspath(x)
79 for x in extra_files or ())
80 self.interval = interval
81
82 def run(self):
83 pass
84
85 def restart_with_reloader(self):
86 """Spawn a new Python interpreter with the same arguments as this one,
87 but running the reloader thread.
88 """
89 while 1:
90 _log('info', ' * Restarting with %s' % self.name)
91 args = [sys.executable] + sys.argv
92 new_environ = os.environ.copy()
93 new_environ['WERKZEUG_RUN_MAIN'] = 'true'
94
95 # a weird bug on windows. sometimes unicode strings end up in the
96 # environment and subprocess.call does not like this, encode them
97 # to latin1 and continue.
98 if os.name == 'nt' and PY2:
99 for key, value in iteritems(new_environ):
100 if isinstance(value, text_type):
101 new_environ[key] = value.encode('iso-8859-1')
102
103 exit_code = subprocess.call(args, env=new_environ)
104 if exit_code != 3:
105 return exit_code
106
107 def trigger_reload(self, filename):
108 filename = os.path.abspath(filename)
109 _log('info', ' * Detected change in %r, reloading' % filename)
110 sys.exit(3)
111
112
113 class StatReloaderLoop(ReloaderLoop):
114 name = 'stat'
115
116 def run(self):
117 mtimes = {}
118 while 1:
119 for filename in chain(_iter_module_files(), self.extra_files):
120 try:
121 mtime = os.stat(filename).st_mtime
122 except OSError:
123 continue
124
125 old_time = mtimes.get(filename)
126 if old_time is None:
127 mtimes[filename] = mtime
128 continue
129 elif mtime > old_time:
130 self.trigger_reload(filename)
131 self._sleep(self.interval)
132
133
134 class WatchdogReloaderLoop(ReloaderLoop):
135
136 def __init__(self, *args, **kwargs):
137 ReloaderLoop.__init__(self, *args, **kwargs)
138 from watchdog.observers import Observer
139 from watchdog.events import FileSystemEventHandler
140 self.observable_paths = set()
141
142 def _check_modification(filename):
143 if filename in self.extra_files:
144 self.trigger_reload(filename)
145 dirname = os.path.dirname(filename)
146 if dirname.startswith(tuple(self.observable_paths)):
147 if filename.endswith(('.pyc', '.pyo')):
148 self.trigger_reload(filename[:-1])
149 elif filename.endswith('.py'):
150 self.trigger_reload(filename)
151
152 class _CustomHandler(FileSystemEventHandler):
153 def on_created(self, event):
154 _check_modification(event.src_path)
155 def on_modified(self, event):
156 _check_modification(event.src_path)
157
158 reloader_name = Observer.__name__.lower()
159 if reloader_name.endswith('observer'):
160 reloader_name = reloader_name[:-8]
161 reloader_name += ' reloader'
162
163 self.name = reloader_name
164
165 self.observer_class = Observer
166 self.event_handler = _CustomHandler()
167 self.should_reload = False
168
169 def trigger_reload(self, filename):
170 # This is called inside an event handler, which means we can't throw
171 # SystemExit here. https://github.com/gorakhargosh/watchdog/issues/294
172 self.should_reload = True
173 ReloaderLoop.trigger_reload(self, filename)
174
175 def run(self):
176 watches = {}
177 observer = self.observer_class()
178 observer.start()
179
180 while not self.should_reload:
181 to_delete = set(watches)
182 paths = _find_observable_paths(self.extra_files)
183 for path in paths:
184 if path not in watches:
185 try:
186 watches[path] = observer.schedule(
187 self.event_handler, path, recursive=True)
188 except OSError:
189 # "Path is not a directory". We could filter out
190 # those paths beforehand, but that would cause
191 # additional stat calls.
192 watches[path] = None
193 to_delete.discard(path)
194 for path in to_delete:
195 watch = watches.pop(path, None)
196 if watch is not None:
197 observer.unschedule(watch)
198 self.observable_paths = paths
199 self._sleep(self.interval)
200
201 sys.exit(3)
202
203
204 reloader_loops = {
205 'stat': StatReloaderLoop,
206 'watchdog': WatchdogReloaderLoop,
207 }
208
209 try:
210 __import__('watchdog.observers')
211 except ImportError:
212 reloader_loops['auto'] = reloader_loops['stat']
213 else:
214 reloader_loops['auto'] = reloader_loops['watchdog']
215
216
217 def run_with_reloader(main_func, extra_files=None, interval=1,
218 reloader_type='auto'):
219 """Run the given function in an independent python interpreter."""
220 import signal
221 reloader = reloader_loops[reloader_type](extra_files, interval)
222 signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
223 try:
224 if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
225 t = threading.Thread(target=main_func, args=())
226 t.setDaemon(True)
227 t.start()
228 reloader.run()
229 else:
230 sys.exit(reloader.restart_with_reloader())
231 except KeyboardInterrupt:
232 pass
4343
4444 def format_iso8601(obj):
4545 """Format a datetime object for iso8601"""
46 return obj.strftime('%Y-%m-%dT%H:%M:%SZ')
46 iso8601 = obj.isoformat()
47 if obj.tzinfo:
48 return iso8601
49 return iso8601 + 'Z'
4750
4851
4952 @implements_to_string
6063 :param updated: the time the feed was modified the last time. Must
6164 be a :class:`datetime.datetime` object. If not
6265 present the latest entry's `updated` is used.
66 Treated as UTC if naive datetime.
6367 :param feed_url: the URL to the feed. Should be the URL that was
6468 requested.
6569 :param author: the author of the feed. Must be either a string (the
238242 :param id: a globally unique id for the entry. Must be an URI. If
239243 not present the URL is used, but one of both is required.
240244 :param updated: the time the entry was modified the last time. Must
241 be a :class:`datetime.datetime` object. Required.
245 be a :class:`datetime.datetime` object. Treated as
246 UTC if naive datetime. Required.
242247 :param author: the author of the entry. Must be either a string (the
243248 name) or a dict with name (required) and uri or
244249 email (both optional). Can be a list of (may be
246251 multiple authors. Required if the feed does not have an
247252 author element.
248253 :param published: the time the entry was initially published. Must
249 be a :class:`datetime.datetime` object.
254 be a :class:`datetime.datetime` object. Treated as
255 UTC if naive datetime.
250256 :param rights: copyright information for the entry.
251257 :param rights_type: the type attribute for the rights element. One of
252258 ``'html'``, ``'text'`` or ``'xhtml'``. Default is
5757 """
5858 import os
5959 import re
60 import errno
6061 import tempfile
6162 from hashlib import md5
6263 from time import time
6364 try:
6465 import cPickle as pickle
65 except ImportError:
66 except ImportError: # pragma: no cover
6667 import pickle
6768
6869 from werkzeug._compat import iteritems, string_types, text_type, \
69 integer_types, to_bytes
70 integer_types, to_native
7071 from werkzeug.posixemulation import rename
7172
7273
8182 ... assert k*k == v
8283
8384 """
84 if hasattr(mappingorseq, "iteritems"):
85 return mappingorseq.iteritems()
86 elif hasattr(mappingorseq, "items"):
87 return mappingorseq.items()
85 if hasattr(mappingorseq, 'items'):
86 return iteritems(mappingorseq)
8887 return mappingorseq
8988
9089
9291 """Baseclass for the cache systems. All the cache systems implement this
9392 API or a superset of it.
9493
95 :param default_timeout: the default timeout that is used if no timeout is
96 specified on :meth:`set`.
94 :param default_timeout: the default timeout (in seconds) that is used if no
95 timeout is specified on :meth:`set`.
9796 """
9897
9998 def __init__(self, default_timeout=300):
10099 self.default_timeout = default_timeout
101100
102101 def get(self, key):
103 """Looks up key in the cache and returns the value for it.
104 If the key does not exist `None` is returned instead.
102 """Look up key in the cache and return the value for it.
105103
106104 :param key: the key to be looked up.
105 :returns: The value if it exists and is readable, else ``None``.
107106 """
108107 return None
109108
110109 def delete(self, key):
111 """Deletes `key` from the cache. If it does not exist in the cache
112 nothing happens.
110 """Delete `key` from the cache.
113111
114112 :param key: the key to delete.
115 """
116 pass
113 :returns: Whether the key existed and has been deleted.
114 :rtype: boolean
115 """
116 return True
117117
118118 def get_many(self, *keys):
119119 """Returns a list of values for the given keys.
120 For each key a item in the list is created. Example::
120 For each key a item in the list is created::
121121
122122 foo, bar = cache.get_many("foo", "bar")
123123
124 If a key can't be looked up `None` is returned for that key
125 instead.
124 Has the same error handling as :meth:`get`.
126125
127126 :param keys: The function accepts multiple keys as positional
128127 arguments.
130129 return map(self.get, keys)
131130
132131 def get_dict(self, *keys):
133 """Works like :meth:`get_many` but returns a dict::
132 """Like :meth:`get_many` but return a dict::
134133
135134 d = cache.get_dict("foo", "bar")
136135 foo = d["foo"]
142141 return dict(zip(keys, self.get_many(*keys)))
143142
144143 def set(self, key, value, timeout=None):
145 """Adds a new key/value to the cache (overwrites value, if key already
144 """Add a new key/value to the cache (overwrites value, if key already
146145 exists in the cache).
147146
148147 :param key: the key to set
149148 :param value: the value for the key
150149 :param timeout: the cache timeout for the key (if not specified,
151150 it uses the default timeout).
152 """
153 pass
151 :returns: ``True`` if key has been updated, ``False`` for backend
152 errors. Pickling errors, however, will raise a subclass of
153 ``pickle.PickleError``.
154 :rtype: boolean
155 """
156 return True
154157
155158 def add(self, key, value, timeout=None):
156159 """Works like :meth:`set` but does not overwrite the values of already
160163 :param value: the value for the key
161164 :param timeout: the cache timeout for the key or the default
162165 timeout if not specified.
163 """
164 pass
166 :returns: Same as :meth:`set`, but also ``False`` for already
167 existing keys.
168 :rtype: boolean
169 """
170 return True
165171
166172 def set_many(self, mapping, timeout=None):
167173 """Sets multiple keys and values from a mapping.
169175 :param mapping: a mapping with the keys/values to set.
170176 :param timeout: the cache timeout for the key (if not specified,
171177 it uses the default timeout).
172 """
178 :returns: Whether all given keys have been set.
179 :rtype: boolean
180 """
181 rv = True
173182 for key, value in _items(mapping):
174 self.set(key, value, timeout)
183 if not self.set(key, value, timeout):
184 rv = False
185 return rv
175186
176187 def delete_many(self, *keys):
177188 """Deletes multiple keys at once.
178189
179190 :param keys: The function accepts multiple keys as positional
180191 arguments.
181 """
182 for key in keys:
183 self.delete(key)
192 :returns: Whether all given keys have been deleted.
193 :rtype: boolean
194 """
195 return all(self.delete(key) for key in keys)
184196
185197 def clear(self):
186198 """Clears the cache. Keep in mind that not all caches support
187199 completely clearing the cache.
188 """
189 pass
200 :returns: Whether the cache has been cleared.
201 :rtype: boolean
202 """
203 return True
190204
191205 def inc(self, key, delta=1):
192206 """Increments the value of a key by `delta`. If the key does
196210
197211 :param key: the key to increment.
198212 :param delta: the delta to add.
199 """
200 self.set(key, (self.get(key) or 0) + delta)
213 :returns: The new value or ``None`` for backend errors.
214 """
215 value = (self.get(key) or 0) + delta
216 return value if self.set(key, value) else None
201217
202218 def dec(self, key, delta=1):
203219 """Decrements the value of a key by `delta`. If the key does
207223
208224 :param key: the key to increment.
209225 :param delta: the delta to subtract.
210 """
211 self.set(key, (self.get(key) or 0) - delta)
226 :returns: The new value or `None` for backend errors.
227 """
228 value = (self.get(key) or 0) - delta
229 return value if self.set(key, value) else None
212230
213231
214232 class NullCache(BaseCache):
240258 def _prune(self):
241259 if len(self._cache) > self._threshold:
242260 now = time()
261 toremove = []
243262 for idx, (key, (expires, _)) in enumerate(self._cache.items()):
244263 if expires <= now or idx % 3 == 0:
245 self._cache.pop(key, None)
264 toremove.append(key)
265 for key in toremove:
266 self._cache.pop(key, None)
246267
247268 def get(self, key):
248 expires, value = self._cache.get(key, (0, None))
249 if expires > time():
250 return pickle.loads(value)
269 try:
270 expires, value = self._cache[key]
271 if expires > time():
272 return pickle.loads(value)
273 except (KeyError, pickle.PickleError):
274 return None
251275
252276 def set(self, key, value, timeout=None):
253277 if timeout is None:
255279 self._prune()
256280 self._cache[key] = (time() + timeout, pickle.dumps(value,
257281 pickle.HIGHEST_PROTOCOL))
282 return True
258283
259284 def add(self, key, value, timeout=None):
260285 if timeout is None:
261286 timeout = self.default_timeout
262 if len(self._cache) > self._threshold:
263 self._prune()
287 self._prune()
264288 item = (time() + timeout, pickle.dumps(value,
265289 pickle.HIGHEST_PROTOCOL))
290 if key in self._cache:
291 return False
266292 self._cache.setdefault(key, item)
293 return True
267294
268295 def delete(self, key):
269 self._cache.pop(key, None)
270
271
272 _test_memcached_key = re.compile(br'[^\x00-\x21\xff]{1,250}$').match
296 return self._cache.pop(key, None) is not None
297
298
299 _test_memcached_key = re.compile(r'[^\x00-\x21\xff]{1,250}$').match
273300
274301 class MemcachedCache(BaseCache):
275302 """A cache that uses memcached as backend.
278305 :class:`memcache.Client` or a tuple/list of server addresses. In the
279306 event that a tuple/list is passed, Werkzeug tries to import the best
280307 available memcache library.
308
309 This cache looks into the following packages/modules to find bindings for
310 memcached:
311
312 - ``pylibmc``
313 - ``google.appengine.api.memcached``
314 - ``memcached``
281315
282316 Implementation notes: This cache backend works around some limitations in
283317 memcached to simplify the interface. For example unicode keys are encoded
310344 # client.
311345 self._client = servers
312346
313 self.key_prefix = to_bytes(key_prefix)
314
315 def get(self, key):
316 if isinstance(key, text_type):
317 key = key.encode('utf-8')
347 self.key_prefix = to_native(key_prefix)
348
349 def _normalize_key(self, key):
350 key = to_native(key, 'utf-8')
318351 if self.key_prefix:
319352 key = self.key_prefix + key
353 return key
354
355 def _normalize_timeout(self, timeout):
356 return int(time()) + timeout
357
358 def get(self, key):
359 key = self._normalize_key(key)
320360 # memcached doesn't support keys longer than that. Because often
321 # checks for so long keys can occour because it's tested from user
361 # checks for so long keys can occur because it's tested from user
322362 # submitted data etc we fail silently for getting.
323363 if _test_memcached_key(key):
324364 return self._client.get(key)
327367 key_mapping = {}
328368 have_encoded_keys = False
329369 for key in keys:
330 if isinstance(key, unicode):
331 encoded_key = key.encode('utf-8')
370 encoded_key = self._normalize_key(key)
371 if not isinstance(key, str):
332372 have_encoded_keys = True
333 else:
334 encoded_key = key
335 if self.key_prefix:
336 encoded_key = self.key_prefix + encoded_key
337373 if _test_memcached_key(key):
338374 key_mapping[encoded_key] = key
339375 d = rv = self._client.get_multi(key_mapping.keys())
350386 def add(self, key, value, timeout=None):
351387 if timeout is None:
352388 timeout = self.default_timeout
353 if isinstance(key, text_type):
354 key = key.encode('utf-8')
355 if self.key_prefix:
356 key = self.key_prefix + key
357 self._client.add(key, value, timeout)
389 key = self._normalize_key(key)
390 timeout = self._normalize_timeout(timeout)
391 return self._client.add(key, value, timeout)
358392
359393 def set(self, key, value, timeout=None):
360394 if timeout is None:
361395 timeout = self.default_timeout
362 if isinstance(key, text_type):
363 key = key.encode('utf-8')
364 if self.key_prefix:
365 key = self.key_prefix + key
366 self._client.set(key, value, timeout)
396 key = self._normalize_key(key)
397 timeout = self._normalize_timeout(timeout)
398 return self._client.set(key, value, timeout)
367399
368400 def get_many(self, *keys):
369401 d = self.get_dict(*keys)
374406 timeout = self.default_timeout
375407 new_mapping = {}
376408 for key, value in _items(mapping):
377 if isinstance(key, text_type):
378 key = key.encode('utf-8')
379 if self.key_prefix:
380 key = self.key_prefix + key
409 key = self._normalize_key(key)
381410 new_mapping[key] = value
382 self._client.set_multi(new_mapping, timeout)
411
412 timeout = self._normalize_timeout(timeout)
413 failed_keys = self._client.set_multi(new_mapping, timeout)
414 return not failed_keys
383415
384416 def delete(self, key):
385 if isinstance(key, unicode):
386 key = key.encode('utf-8')
387 if self.key_prefix:
388 key = self.key_prefix + key
417 key = self._normalize_key(key)
389418 if _test_memcached_key(key):
390 self._client.delete(key)
419 return self._client.delete(key)
391420
392421 def delete_many(self, *keys):
393422 new_keys = []
394423 for key in keys:
395 if isinstance(key, unicode):
396 key = key.encode('utf-8')
397 if self.key_prefix:
398 key = self.key_prefix + key
424 key = self._normalize_key(key)
399425 if _test_memcached_key(key):
400426 new_keys.append(key)
401 self._client.delete_multi(new_keys)
427 return self._client.delete_multi(new_keys)
402428
403429 def clear(self):
404 self._client.flush_all()
430 return self._client.flush_all()
405431
406432 def inc(self, key, delta=1):
407 if isinstance(key, unicode):
408 key = key.encode('utf-8')
409 if self.key_prefix:
410 key = self.key_prefix + key
411 self._client.incr(key, delta)
433 key = self._normalize_key(key)
434 return self._client.incr(key, delta)
412435
413436 def dec(self, key, delta=1):
414 if isinstance(key, unicode):
415 key = key.encode('utf-8')
416 if self.key_prefix:
417 key = self.key_prefix + key
418 self._client.decr(key, delta)
437 key = self._normalize_key(key)
438 return self._client.decr(key, delta)
419439
420440 def import_preferred_memcache_lib(self, servers):
421441 """Returns an initialized memcache client. Used by the constructor."""
464484
465485 .. versionchanged:: 0.8.3
466486 This cache backend now supports password authentication.
487
488 .. versionchanged:: 0.10
489 ``**kwargs`` is now passed to the redis object.
467490
468491 :param host: address of the Redis server or an object which API is
469492 compatible with the official Python Redis client (redis-py).
473496 :param default_timeout: the default timeout that is used if no timeout is
474497 specified on :meth:`~BaseCache.set`.
475498 :param key_prefix: A prefix that should be added to all keys.
499
500 Any additional keyword arguments will be passed to ``redis.Redis``.
476501 """
477502
478503 def __init__(self, host='localhost', port=6379, password=None,
479 db=0, default_timeout=300, key_prefix=None):
504 db=0, default_timeout=300, key_prefix=None, **kwargs):
480505 BaseCache.__init__(self, default_timeout)
481506 if isinstance(host, string_types):
482507 try:
483508 import redis
484509 except ImportError:
485510 raise RuntimeError('no redis module found')
486 self._client = redis.Redis(host=host, port=port, password=password, db=db)
511 if kwargs.get('decode_responses', None):
512 raise ValueError('decode_responses is not supported by '
513 'RedisCache.')
514 self._client = redis.Redis(host=host, port=port, password=password,
515 db=db, **kwargs)
487516 else:
488517 self._client = host
489518 self.key_prefix = key_prefix or ''
504533 if value is None:
505534 return None
506535 if value.startswith(b'!'):
507 return pickle.loads(value[1:])
536 try:
537 return pickle.loads(value[1:])
538 except pickle.PickleError:
539 return None
508540 try:
509541 return int(value)
510542 except ValueError:
523555 if timeout is None:
524556 timeout = self.default_timeout
525557 dump = self.dump_object(value)
526 self._client.setex(self.key_prefix + key, dump, timeout)
558 return self._client.setex(name=self.key_prefix + key,
559 value=dump, time=timeout)
527560
528561 def add(self, key, value, timeout=None):
529562 if timeout is None:
530563 timeout = self.default_timeout
531564 dump = self.dump_object(value)
532 added = self._client.setnx(self.key_prefix + key, dump)
533 if added:
534 self._client.expire(self.key_prefix + key, timeout)
565 return (
566 self._client.setnx(name=self.key_prefix + key, value=dump) and
567 self._client.expire(name=self.key_prefix + key, time=timeout)
568 )
535569
536570 def set_many(self, mapping, timeout=None):
537571 if timeout is None:
539573 pipe = self._client.pipeline()
540574 for key, value in _items(mapping):
541575 dump = self.dump_object(value)
542 pipe.setex(self.key_prefix + key, dump, timeout)
543 pipe.execute()
576 pipe.setex(name=self.key_prefix + key, value=dump, time=timeout)
577 return pipe.execute()
544578
545579 def delete(self, key):
546 self._client.delete(self.key_prefix + key)
580 return self._client.delete(self.key_prefix + key)
547581
548582 def delete_many(self, *keys):
549583 if not keys:
550584 return
551585 if self.key_prefix:
552586 keys = [self.key_prefix + key for key in keys]
553 self._client.delete(*keys)
587 return self._client.delete(*keys)
554588
555589 def clear(self):
590 status = False
556591 if self.key_prefix:
557592 keys = self._client.keys(self.key_prefix + '*')
558593 if keys:
559 self._client.delete(*keys)
594 status = self._client.delete(*keys)
560595 else:
561 self._client.flushdb()
596 status = self._client.flushdb()
597 return status
562598
563599 def inc(self, key, delta=1):
564 return self._client.incr(self.key_prefix + key, delta)
600 return self._client.incr(name=self.key_prefix + key, amount=delta)
565601
566602 def dec(self, key, delta=1):
567 return self._client.decr(self.key_prefix + key, delta)
603 return self._client.decr(name=self.key_prefix + key, amount=delta)
568604
569605
570606 class FileSystemCache(BaseCache):
589625 self._path = cache_dir
590626 self._threshold = threshold
591627 self._mode = mode
592 if not os.path.exists(self._path):
628
629 try:
593630 os.makedirs(self._path)
631 except OSError as ex:
632 if ex.errno != errno.EEXIST:
633 raise
594634
595635 def _list_dir(self):
596636 """return a list of (fully qualified) cache filenames
602642 entries = self._list_dir()
603643 if len(entries) > self._threshold:
604644 now = time()
605 for idx, fname in enumerate(entries):
606 remove = False
607 f = None
608 try:
609 try:
610 f = open(fname, 'rb')
645 try:
646 for idx, fname in enumerate(entries):
647 remove = False
648 with open(fname, 'rb') as f:
611649 expires = pickle.load(f)
612 remove = expires <= now or idx % 3 == 0
613 finally:
614 if f is not None:
615 f.close()
616 except Exception:
617 pass
618 if remove:
619 try:
650 remove = expires <= now or idx % 3 == 0
651
652 if remove:
620653 os.remove(fname)
621 except (IOError, OSError):
622 pass
654 except (IOError, OSError):
655 pass
623656
624657 def clear(self):
625658 for fname in self._list_dir():
626659 try:
627660 os.remove(fname)
628661 except (IOError, OSError):
629 pass
662 return False
663 return True
630664
631665 def _get_filename(self, key):
632666 if isinstance(key, text_type):
637671 def get(self, key):
638672 filename = self._get_filename(key)
639673 try:
640 f = open(filename, 'rb')
641 try:
674 with open(filename, 'rb') as f:
642675 if pickle.load(f) >= time():
643676 return pickle.load(f)
644 finally:
645 f.close()
646 os.remove(filename)
647 except Exception:
677 else:
678 os.remove(filename)
679 return None
680 except (IOError, OSError, pickle.PickleError):
648681 return None
649682
650683 def add(self, key, value, timeout=None):
651684 filename = self._get_filename(key)
652685 if not os.path.exists(filename):
653 self.set(key, value, timeout)
686 return self.set(key, value, timeout)
687 return False
654688
655689 def set(self, key, value, timeout=None):
656690 if timeout is None:
660694 try:
661695 fd, tmp = tempfile.mkstemp(suffix=self._fs_transaction_suffix,
662696 dir=self._path)
663 f = os.fdopen(fd, 'wb')
664 try:
697 with os.fdopen(fd, 'wb') as f:
665698 pickle.dump(int(time() + timeout), f, 1)
666699 pickle.dump(value, f, pickle.HIGHEST_PROTOCOL)
667 finally:
668 f.close()
669700 rename(tmp, filename)
670701 os.chmod(filename, self._mode)
671702 except (IOError, OSError):
672 pass
703 return False
704 else:
705 return True
673706
674707 def delete(self, key):
675708 try:
676709 os.remove(self._get_filename(key))
677710 except (IOError, OSError):
678 pass
711 return False
712 else:
713 return True
9393 class ProxyFix(object):
9494 """This middleware can be applied to add HTTP proxy support to an
9595 application that was not designed with HTTP proxies in mind. It
96 sets `REMOTE_ADDR`, `HTTP_HOST` from `X-Forwarded` headers.
96 sets `REMOTE_ADDR`, `HTTP_HOST` from `X-Forwarded` headers. While
97 Werkzeug-based applications already can use
98 :py:func:`werkzeug.wsgi.get_host` to retrieve the current host even if
99 behind proxy setups, this middleware can be used for applications which
100 access the WSGI environment directly.
97101
98102 If you have more than one proxy server in front of your app, set
99103 `num_proxies` accordingly.
304304 nl_pos = self._buf.find(_newline(self._buf), self.pos)
305305 buf = []
306306 try:
307 pos = self.pos
307 if self._buf is None:
308 pos = self.pos
309 else:
310 pos = len(self._buf)
308311 while nl_pos < 0:
309312 item = next(self._gen)
310313 local_pos = item.find(_newline(item))
2525 def render_template(name_parts, rules, converters):
2626 result = u''
2727 if name_parts:
28 for idx in xrange(0, len(name_parts) - 1):
28 for idx in range(0, len(name_parts) - 1):
2929 name = u'.'.join(name_parts[:idx + 1])
3030 result += u"if (typeof %s === 'undefined') %s = {}\n" % (name, name)
3131 result += '%s = ' % '.'.join(name_parts)
3232 result += """(function (server_name, script_name, subdomain, url_scheme) {
33 var converters = %(converters)s;
34 var rules = $rules;
33 var converters = [%(converters)s];
34 var rules = %(rules)s;
3535 function in_array(array, value) {
3636 if (array.indexOf != undefined) {
3737 return array.indexOf(value) != -1;
162162 + '/' + lstrip(rv.path, '/');
163163 }
164164 };
165 })""" % {'converters': u', '.join(converters)}
165 })""" % {'converters': u', '.join(converters),
166 'rules': rules}
167
166168 return result
167169
168170
130130 def __repr__(self):
131131 return '%s(%s)' % (
132132 self.__class__.__name__,
133 dict.__repr__(self),
133 list.__repr__(self),
134134 )
135135
136136
826826
827827 def _options_header_vkw(value, kw):
828828 return dump_options_header(value, dict((k.replace('_', '-'), v)
829 for k, v in kw.items()))
829 for k, v in kw.items()))
830830
831831
832832 def _unicodify_header_value(value):
13531353 rv.extend(d.getlist(key, type))
13541354 return rv
13551355
1356 def keys(self):
1356 def _keys_impl(self):
1357 """This function exists so __len__ can be implemented more efficiently,
1358 saving one list creation from an iterator.
1359
1360 Using this for Python 2's ``dict.keys`` behavior would be useless since
1361 `dict.keys` in Python 2 returns a list, while we have a set here.
1362 """
13571363 rv = set()
13581364 for d in self.dicts:
1359 rv.update(d.keys())
1360 return iter(rv)
1365 rv.update(iterkeys(d))
1366 return rv
1367
1368 def keys(self):
1369 return iter(self._keys_impl())
13611370
13621371 __iter__ = keys
13631372
14051414 return rv
14061415
14071416 def __len__(self):
1408 return len(self.keys())
1417 return len(self._keys_impl())
14091418
14101419 def __contains__(self, key):
14111420 for d in self.dicts:
16491658 for client_item, quality in self:
16501659 if quality <= best_quality:
16511660 break
1652 if self._value_matches(server_item, client_item):
1661 if (self._value_matches(server_item, client_item)
1662 and quality > 0):
16531663 best_quality = quality
16541664 result = server_item
16551665 return result
18301840 return self.to_header()
18311841
18321842 def __repr__(self):
1833 return '<%s %r>' % (
1843 return '<%s %s>' % (
18341844 self.__class__.__name__,
1835 self.to_header()
1845 " ".join(
1846 "%s=%r" % (k, v) for k, v in sorted(self.items())
1847 ),
18361848 )
18371849
18381850
23562368 """Provides simple access to `WWW-Authenticate` headers."""
23572369
23582370 #: list of keys that require quoting in the generated header
2359 _require_quoting = frozenset(['domain', 'nonce', 'opaque', 'realm'])
2371 _require_quoting = frozenset(['domain', 'nonce', 'opaque', 'realm', 'qop'])
23602372
23612373 def __init__(self, auth_type=None, values=None, on_update=None):
23622374 dict.__init__(self, values or ())
25982610
25992611 def __nonzero__(self):
26002612 return bool(self.filename)
2613 __bool__ = __nonzero__
26012614
26022615 def __getattr__(self, name):
26032616 return getattr(self.stream, name)
+0
-167
werkzeug/debug/shared/jquery.js less more
0 /*!
1 * jQuery JavaScript Library v1.4.4
2 * http://jquery.com/
3 *
4 * Copyright 2010, John Resig
5 * Dual licensed under the MIT or GPL Version 2 licenses.
6 * http://jquery.org/license
7 *
8 * Includes Sizzle.js
9 * http://sizzlejs.com/
10 * Copyright 2010, The Dojo Foundation
11 * Released under the MIT, BSD, and GPL Licenses.
12 *
13 * Date: Thu Nov 11 19:04:53 2010 -0500
14 */
15 (function(E,B){function ka(a,b,d){if(d===B&&a.nodeType===1){d=a.getAttribute("data-"+b);if(typeof d==="string"){try{d=d==="true"?true:d==="false"?false:d==="null"?null:!c.isNaN(d)?parseFloat(d):Ja.test(d)?c.parseJSON(d):d}catch(e){}c.data(a,b,d)}else d=B}return d}function U(){return false}function ca(){return true}function la(a,b,d){d[0].type=a;return c.event.handle.apply(b,d)}function Ka(a){var b,d,e,f,h,l,k,o,x,r,A,C=[];f=[];h=c.data(this,this.nodeType?"events":"__events__");if(typeof h==="function")h=
16 h.events;if(!(a.liveFired===this||!h||!h.live||a.button&&a.type==="click")){if(a.namespace)A=RegExp("(^|\\.)"+a.namespace.split(".").join("\\.(?:.*\\.)?")+"(\\.|$)");a.liveFired=this;var J=h.live.slice(0);for(k=0;k<J.length;k++){h=J[k];h.origType.replace(X,"")===a.type?f.push(h.selector):J.splice(k--,1)}f=c(a.target).closest(f,a.currentTarget);o=0;for(x=f.length;o<x;o++){r=f[o];for(k=0;k<J.length;k++){h=J[k];if(r.selector===h.selector&&(!A||A.test(h.namespace))){l=r.elem;e=null;if(h.preType==="mouseenter"||
17 h.preType==="mouseleave"){a.type=h.preType;e=c(a.relatedTarget).closest(h.selector)[0]}if(!e||e!==l)C.push({elem:l,handleObj:h,level:r.level})}}}o=0;for(x=C.length;o<x;o++){f=C[o];if(d&&f.level>d)break;a.currentTarget=f.elem;a.data=f.handleObj.data;a.handleObj=f.handleObj;A=f.handleObj.origHandler.apply(f.elem,arguments);if(A===false||a.isPropagationStopped()){d=f.level;if(A===false)b=false;if(a.isImmediatePropagationStopped())break}}return b}}function Y(a,b){return(a&&a!=="*"?a+".":"")+b.replace(La,
18 "`").replace(Ma,"&")}function ma(a,b,d){if(c.isFunction(b))return c.grep(a,function(f,h){return!!b.call(f,h,f)===d});else if(b.nodeType)return c.grep(a,function(f){return f===b===d});else if(typeof b==="string"){var e=c.grep(a,function(f){return f.nodeType===1});if(Na.test(b))return c.filter(b,e,!d);else b=c.filter(b,e)}return c.grep(a,function(f){return c.inArray(f,b)>=0===d})}function na(a,b){var d=0;b.each(function(){if(this.nodeName===(a[d]&&a[d].nodeName)){var e=c.data(a[d++]),f=c.data(this,
19 e);if(e=e&&e.events){delete f.handle;f.events={};for(var h in e)for(var l in e[h])c.event.add(this,h,e[h][l],e[h][l].data)}}})}function Oa(a,b){b.src?c.ajax({url:b.src,async:false,dataType:"script"}):c.globalEval(b.text||b.textContent||b.innerHTML||"");b.parentNode&&b.parentNode.removeChild(b)}function oa(a,b,d){var e=b==="width"?a.offsetWidth:a.offsetHeight;if(d==="border")return e;c.each(b==="width"?Pa:Qa,function(){d||(e-=parseFloat(c.css(a,"padding"+this))||0);if(d==="margin")e+=parseFloat(c.css(a,
20 "margin"+this))||0;else e-=parseFloat(c.css(a,"border"+this+"Width"))||0});return e}function da(a,b,d,e){if(c.isArray(b)&&b.length)c.each(b,function(f,h){d||Ra.test(a)?e(a,h):da(a+"["+(typeof h==="object"||c.isArray(h)?f:"")+"]",h,d,e)});else if(!d&&b!=null&&typeof b==="object")c.isEmptyObject(b)?e(a,""):c.each(b,function(f,h){da(a+"["+f+"]",h,d,e)});else e(a,b)}function S(a,b){var d={};c.each(pa.concat.apply([],pa.slice(0,b)),function(){d[this]=a});return d}function qa(a){if(!ea[a]){var b=c("<"+
21 a+">").appendTo("body"),d=b.css("display");b.remove();if(d==="none"||d==="")d="block";ea[a]=d}return ea[a]}function fa(a){return c.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:false}var t=E.document,c=function(){function a(){if(!b.isReady){try{t.documentElement.doScroll("left")}catch(j){setTimeout(a,1);return}b.ready()}}var b=function(j,s){return new b.fn.init(j,s)},d=E.jQuery,e=E.$,f,h=/^(?:[^<]*(<[\w\W]+>)[^>]*$|#([\w\-]+)$)/,l=/\S/,k=/^\s+/,o=/\s+$/,x=/\W/,r=/\d/,A=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,
22 C=/^[\],:{}\s]*$/,J=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,w=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,I=/(?:^|:|,)(?:\s*\[)+/g,L=/(webkit)[ \/]([\w.]+)/,g=/(opera)(?:.*version)?[ \/]([\w.]+)/,i=/(msie) ([\w.]+)/,n=/(mozilla)(?:.*? rv:([\w.]+))?/,m=navigator.userAgent,p=false,q=[],u,y=Object.prototype.toString,F=Object.prototype.hasOwnProperty,M=Array.prototype.push,N=Array.prototype.slice,O=String.prototype.trim,D=Array.prototype.indexOf,R={};b.fn=b.prototype={init:function(j,
23 s){var v,z,H;if(!j)return this;if(j.nodeType){this.context=this[0]=j;this.length=1;return this}if(j==="body"&&!s&&t.body){this.context=t;this[0]=t.body;this.selector="body";this.length=1;return this}if(typeof j==="string")if((v=h.exec(j))&&(v[1]||!s))if(v[1]){H=s?s.ownerDocument||s:t;if(z=A.exec(j))if(b.isPlainObject(s)){j=[t.createElement(z[1])];b.fn.attr.call(j,s,true)}else j=[H.createElement(z[1])];else{z=b.buildFragment([v[1]],[H]);j=(z.cacheable?z.fragment.cloneNode(true):z.fragment).childNodes}return b.merge(this,
24 j)}else{if((z=t.getElementById(v[2]))&&z.parentNode){if(z.id!==v[2])return f.find(j);this.length=1;this[0]=z}this.context=t;this.selector=j;return this}else if(!s&&!x.test(j)){this.selector=j;this.context=t;j=t.getElementsByTagName(j);return b.merge(this,j)}else return!s||s.jquery?(s||f).find(j):b(s).find(j);else if(b.isFunction(j))return f.ready(j);if(j.selector!==B){this.selector=j.selector;this.context=j.context}return b.makeArray(j,this)},selector:"",jquery:"1.4.4",length:0,size:function(){return this.length},
25 toArray:function(){return N.call(this,0)},get:function(j){return j==null?this.toArray():j<0?this.slice(j)[0]:this[j]},pushStack:function(j,s,v){var z=b();b.isArray(j)?M.apply(z,j):b.merge(z,j);z.prevObject=this;z.context=this.context;if(s==="find")z.selector=this.selector+(this.selector?" ":"")+v;else if(s)z.selector=this.selector+"."+s+"("+v+")";return z},each:function(j,s){return b.each(this,j,s)},ready:function(j){b.bindReady();if(b.isReady)j.call(t,b);else q&&q.push(j);return this},eq:function(j){return j===
26 -1?this.slice(j):this.slice(j,+j+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(N.apply(this,arguments),"slice",N.call(arguments).join(","))},map:function(j){return this.pushStack(b.map(this,function(s,v){return j.call(s,v,s)}))},end:function(){return this.prevObject||b(null)},push:M,sort:[].sort,splice:[].splice};b.fn.init.prototype=b.fn;b.extend=b.fn.extend=function(){var j,s,v,z,H,G=arguments[0]||{},K=1,Q=arguments.length,ga=false;
27 if(typeof G==="boolean"){ga=G;G=arguments[1]||{};K=2}if(typeof G!=="object"&&!b.isFunction(G))G={};if(Q===K){G=this;--K}for(;K<Q;K++)if((j=arguments[K])!=null)for(s in j){v=G[s];z=j[s];if(G!==z)if(ga&&z&&(b.isPlainObject(z)||(H=b.isArray(z)))){if(H){H=false;v=v&&b.isArray(v)?v:[]}else v=v&&b.isPlainObject(v)?v:{};G[s]=b.extend(ga,v,z)}else if(z!==B)G[s]=z}return G};b.extend({noConflict:function(j){E.$=e;if(j)E.jQuery=d;return b},isReady:false,readyWait:1,ready:function(j){j===true&&b.readyWait--;
28 if(!b.readyWait||j!==true&&!b.isReady){if(!t.body)return setTimeout(b.ready,1);b.isReady=true;if(!(j!==true&&--b.readyWait>0))if(q){var s=0,v=q;for(q=null;j=v[s++];)j.call(t,b);b.fn.trigger&&b(t).trigger("ready").unbind("ready")}}},bindReady:function(){if(!p){p=true;if(t.readyState==="complete")return setTimeout(b.ready,1);if(t.addEventListener){t.addEventListener("DOMContentLoaded",u,false);E.addEventListener("load",b.ready,false)}else if(t.attachEvent){t.attachEvent("onreadystatechange",u);E.attachEvent("onload",
29 b.ready);var j=false;try{j=E.frameElement==null}catch(s){}t.documentElement.doScroll&&j&&a()}}},isFunction:function(j){return b.type(j)==="function"},isArray:Array.isArray||function(j){return b.type(j)==="array"},isWindow:function(j){return j&&typeof j==="object"&&"setInterval"in j},isNaN:function(j){return j==null||!r.test(j)||isNaN(j)},type:function(j){return j==null?String(j):R[y.call(j)]||"object"},isPlainObject:function(j){if(!j||b.type(j)!=="object"||j.nodeType||b.isWindow(j))return false;if(j.constructor&&
30 !F.call(j,"constructor")&&!F.call(j.constructor.prototype,"isPrototypeOf"))return false;for(var s in j);return s===B||F.call(j,s)},isEmptyObject:function(j){for(var s in j)return false;return true},error:function(j){throw j;},parseJSON:function(j){if(typeof j!=="string"||!j)return null;j=b.trim(j);if(C.test(j.replace(J,"@").replace(w,"]").replace(I,"")))return E.JSON&&E.JSON.parse?E.JSON.parse(j):(new Function("return "+j))();else b.error("Invalid JSON: "+j)},noop:function(){},globalEval:function(j){if(j&&
31 l.test(j)){var s=t.getElementsByTagName("head")[0]||t.documentElement,v=t.createElement("script");v.type="text/javascript";if(b.support.scriptEval)v.appendChild(t.createTextNode(j));else v.text=j;s.insertBefore(v,s.firstChild);s.removeChild(v)}},nodeName:function(j,s){return j.nodeName&&j.nodeName.toUpperCase()===s.toUpperCase()},each:function(j,s,v){var z,H=0,G=j.length,K=G===B||b.isFunction(j);if(v)if(K)for(z in j){if(s.apply(j[z],v)===false)break}else for(;H<G;){if(s.apply(j[H++],v)===false)break}else if(K)for(z in j){if(s.call(j[z],
32 z,j[z])===false)break}else for(v=j[0];H<G&&s.call(v,H,v)!==false;v=j[++H]);return j},trim:O?function(j){return j==null?"":O.call(j)}:function(j){return j==null?"":j.toString().replace(k,"").replace(o,"")},makeArray:function(j,s){var v=s||[];if(j!=null){var z=b.type(j);j.length==null||z==="string"||z==="function"||z==="regexp"||b.isWindow(j)?M.call(v,j):b.merge(v,j)}return v},inArray:function(j,s){if(s.indexOf)return s.indexOf(j);for(var v=0,z=s.length;v<z;v++)if(s[v]===j)return v;return-1},merge:function(j,
33 s){var v=j.length,z=0;if(typeof s.length==="number")for(var H=s.length;z<H;z++)j[v++]=s[z];else for(;s[z]!==B;)j[v++]=s[z++];j.length=v;return j},grep:function(j,s,v){var z=[],H;v=!!v;for(var G=0,K=j.length;G<K;G++){H=!!s(j[G],G);v!==H&&z.push(j[G])}return z},map:function(j,s,v){for(var z=[],H,G=0,K=j.length;G<K;G++){H=s(j[G],G,v);if(H!=null)z[z.length]=H}return z.concat.apply([],z)},guid:1,proxy:function(j,s,v){if(arguments.length===2)if(typeof s==="string"){v=j;j=v[s];s=B}else if(s&&!b.isFunction(s)){v=
34 s;s=B}if(!s&&j)s=function(){return j.apply(v||this,arguments)};if(j)s.guid=j.guid=j.guid||s.guid||b.guid++;return s},access:function(j,s,v,z,H,G){var K=j.length;if(typeof s==="object"){for(var Q in s)b.access(j,Q,s[Q],z,H,v);return j}if(v!==B){z=!G&&z&&b.isFunction(v);for(Q=0;Q<K;Q++)H(j[Q],s,z?v.call(j[Q],Q,H(j[Q],s)):v,G);return j}return K?H(j[0],s):B},now:function(){return(new Date).getTime()},uaMatch:function(j){j=j.toLowerCase();j=L.exec(j)||g.exec(j)||i.exec(j)||j.indexOf("compatible")<0&&n.exec(j)||
35 [];return{browser:j[1]||"",version:j[2]||"0"}},browser:{}});b.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(j,s){R["[object "+s+"]"]=s.toLowerCase()});m=b.uaMatch(m);if(m.browser){b.browser[m.browser]=true;b.browser.version=m.version}if(b.browser.webkit)b.browser.safari=true;if(D)b.inArray=function(j,s){return D.call(s,j)};if(!/\s/.test("\u00a0")){k=/^[\s\xA0]+/;o=/[\s\xA0]+$/}f=b(t);if(t.addEventListener)u=function(){t.removeEventListener("DOMContentLoaded",u,
36 false);b.ready()};else if(t.attachEvent)u=function(){if(t.readyState==="complete"){t.detachEvent("onreadystatechange",u);b.ready()}};return E.jQuery=E.$=b}();(function(){c.support={};var a=t.documentElement,b=t.createElement("script"),d=t.createElement("div"),e="script"+c.now();d.style.display="none";d.innerHTML=" <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>";var f=d.getElementsByTagName("*"),h=d.getElementsByTagName("a")[0],l=t.createElement("select"),
37 k=l.appendChild(t.createElement("option"));if(!(!f||!f.length||!h)){c.support={leadingWhitespace:d.firstChild.nodeType===3,tbody:!d.getElementsByTagName("tbody").length,htmlSerialize:!!d.getElementsByTagName("link").length,style:/red/.test(h.getAttribute("style")),hrefNormalized:h.getAttribute("href")==="/a",opacity:/^0.55$/.test(h.style.opacity),cssFloat:!!h.style.cssFloat,checkOn:d.getElementsByTagName("input")[0].value==="on",optSelected:k.selected,deleteExpando:true,optDisabled:false,checkClone:false,
38 scriptEval:false,noCloneEvent:true,boxModel:null,inlineBlockNeedsLayout:false,shrinkWrapBlocks:false,reliableHiddenOffsets:true};l.disabled=true;c.support.optDisabled=!k.disabled;b.type="text/javascript";try{b.appendChild(t.createTextNode("window."+e+"=1;"))}catch(o){}a.insertBefore(b,a.firstChild);if(E[e]){c.support.scriptEval=true;delete E[e]}try{delete b.test}catch(x){c.support.deleteExpando=false}a.removeChild(b);if(d.attachEvent&&d.fireEvent){d.attachEvent("onclick",function r(){c.support.noCloneEvent=
39 false;d.detachEvent("onclick",r)});d.cloneNode(true).fireEvent("onclick")}d=t.createElement("div");d.innerHTML="<input type='radio' name='radiotest' checked='checked'/>";a=t.createDocumentFragment();a.appendChild(d.firstChild);c.support.checkClone=a.cloneNode(true).cloneNode(true).lastChild.checked;c(function(){var r=t.createElement("div");r.style.width=r.style.paddingLeft="1px";t.body.appendChild(r);c.boxModel=c.support.boxModel=r.offsetWidth===2;if("zoom"in r.style){r.style.display="inline";r.style.zoom=
40 1;c.support.inlineBlockNeedsLayout=r.offsetWidth===2;r.style.display="";r.innerHTML="<div style='width:4px;'></div>";c.support.shrinkWrapBlocks=r.offsetWidth!==2}r.innerHTML="<table><tr><td style='padding:0;display:none'></td><td>t</td></tr></table>";var A=r.getElementsByTagName("td");c.support.reliableHiddenOffsets=A[0].offsetHeight===0;A[0].style.display="";A[1].style.display="none";c.support.reliableHiddenOffsets=c.support.reliableHiddenOffsets&&A[0].offsetHeight===0;r.innerHTML="";t.body.removeChild(r).style.display=
41 "none"});a=function(r){var A=t.createElement("div");r="on"+r;var C=r in A;if(!C){A.setAttribute(r,"return;");C=typeof A[r]==="function"}return C};c.support.submitBubbles=a("submit");c.support.changeBubbles=a("change");a=b=d=f=h=null}})();var ra={},Ja=/^(?:\{.*\}|\[.*\])$/;c.extend({cache:{},uuid:0,expando:"jQuery"+c.now(),noData:{embed:true,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:true},data:function(a,b,d){if(c.acceptData(a)){a=a==E?ra:a;var e=a.nodeType,f=e?a[c.expando]:null,h=
42 c.cache;if(!(e&&!f&&typeof b==="string"&&d===B)){if(e)f||(a[c.expando]=f=++c.uuid);else h=a;if(typeof b==="object")if(e)h[f]=c.extend(h[f],b);else c.extend(h,b);else if(e&&!h[f])h[f]={};a=e?h[f]:h;if(d!==B)a[b]=d;return typeof b==="string"?a[b]:a}}},removeData:function(a,b){if(c.acceptData(a)){a=a==E?ra:a;var d=a.nodeType,e=d?a[c.expando]:a,f=c.cache,h=d?f[e]:e;if(b){if(h){delete h[b];d&&c.isEmptyObject(h)&&c.removeData(a)}}else if(d&&c.support.deleteExpando)delete a[c.expando];else if(a.removeAttribute)a.removeAttribute(c.expando);
43 else if(d)delete f[e];else for(var l in a)delete a[l]}},acceptData:function(a){if(a.nodeName){var b=c.noData[a.nodeName.toLowerCase()];if(b)return!(b===true||a.getAttribute("classid")!==b)}return true}});c.fn.extend({data:function(a,b){var d=null;if(typeof a==="undefined"){if(this.length){var e=this[0].attributes,f;d=c.data(this[0]);for(var h=0,l=e.length;h<l;h++){f=e[h].name;if(f.indexOf("data-")===0){f=f.substr(5);ka(this[0],f,d[f])}}}return d}else if(typeof a==="object")return this.each(function(){c.data(this,
44 a)});var k=a.split(".");k[1]=k[1]?"."+k[1]:"";if(b===B){d=this.triggerHandler("getData"+k[1]+"!",[k[0]]);if(d===B&&this.length){d=c.data(this[0],a);d=ka(this[0],a,d)}return d===B&&k[1]?this.data(k[0]):d}else return this.each(function(){var o=c(this),x=[k[0],b];o.triggerHandler("setData"+k[1]+"!",x);c.data(this,a,b);o.triggerHandler("changeData"+k[1]+"!",x)})},removeData:function(a){return this.each(function(){c.removeData(this,a)})}});c.extend({queue:function(a,b,d){if(a){b=(b||"fx")+"queue";var e=
45 c.data(a,b);if(!d)return e||[];if(!e||c.isArray(d))e=c.data(a,b,c.makeArray(d));else e.push(d);return e}},dequeue:function(a,b){b=b||"fx";var d=c.queue(a,b),e=d.shift();if(e==="inprogress")e=d.shift();if(e){b==="fx"&&d.unshift("inprogress");e.call(a,function(){c.dequeue(a,b)})}}});c.fn.extend({queue:function(a,b){if(typeof a!=="string"){b=a;a="fx"}if(b===B)return c.queue(this[0],a);return this.each(function(){var d=c.queue(this,a,b);a==="fx"&&d[0]!=="inprogress"&&c.dequeue(this,a)})},dequeue:function(a){return this.each(function(){c.dequeue(this,
46 a)})},delay:function(a,b){a=c.fx?c.fx.speeds[a]||a:a;b=b||"fx";return this.queue(b,function(){var d=this;setTimeout(function(){c.dequeue(d,b)},a)})},clearQueue:function(a){return this.queue(a||"fx",[])}});var sa=/[\n\t]/g,ha=/\s+/,Sa=/\r/g,Ta=/^(?:href|src|style)$/,Ua=/^(?:button|input)$/i,Va=/^(?:button|input|object|select|textarea)$/i,Wa=/^a(?:rea)?$/i,ta=/^(?:radio|checkbox)$/i;c.props={"for":"htmlFor","class":"className",readonly:"readOnly",maxlength:"maxLength",cellspacing:"cellSpacing",rowspan:"rowSpan",
47 colspan:"colSpan",tabindex:"tabIndex",usemap:"useMap",frameborder:"frameBorder"};c.fn.extend({attr:function(a,b){return c.access(this,a,b,true,c.attr)},removeAttr:function(a){return this.each(function(){c.attr(this,a,"");this.nodeType===1&&this.removeAttribute(a)})},addClass:function(a){if(c.isFunction(a))return this.each(function(x){var r=c(this);r.addClass(a.call(this,x,r.attr("class")))});if(a&&typeof a==="string")for(var b=(a||"").split(ha),d=0,e=this.length;d<e;d++){var f=this[d];if(f.nodeType===
48 1)if(f.className){for(var h=" "+f.className+" ",l=f.className,k=0,o=b.length;k<o;k++)if(h.indexOf(" "+b[k]+" ")<0)l+=" "+b[k];f.className=c.trim(l)}else f.className=a}return this},removeClass:function(a){if(c.isFunction(a))return this.each(function(o){var x=c(this);x.removeClass(a.call(this,o,x.attr("class")))});if(a&&typeof a==="string"||a===B)for(var b=(a||"").split(ha),d=0,e=this.length;d<e;d++){var f=this[d];if(f.nodeType===1&&f.className)if(a){for(var h=(" "+f.className+" ").replace(sa," "),
49 l=0,k=b.length;l<k;l++)h=h.replace(" "+b[l]+" "," ");f.className=c.trim(h)}else f.className=""}return this},toggleClass:function(a,b){var d=typeof a,e=typeof b==="boolean";if(c.isFunction(a))return this.each(function(f){var h=c(this);h.toggleClass(a.call(this,f,h.attr("class"),b),b)});return this.each(function(){if(d==="string")for(var f,h=0,l=c(this),k=b,o=a.split(ha);f=o[h++];){k=e?k:!l.hasClass(f);l[k?"addClass":"removeClass"](f)}else if(d==="undefined"||d==="boolean"){this.className&&c.data(this,
50 "__className__",this.className);this.className=this.className||a===false?"":c.data(this,"__className__")||""}})},hasClass:function(a){a=" "+a+" ";for(var b=0,d=this.length;b<d;b++)if((" "+this[b].className+" ").replace(sa," ").indexOf(a)>-1)return true;return false},val:function(a){if(!arguments.length){var b=this[0];if(b){if(c.nodeName(b,"option")){var d=b.attributes.value;return!d||d.specified?b.value:b.text}if(c.nodeName(b,"select")){var e=b.selectedIndex;d=[];var f=b.options;b=b.type==="select-one";
51 if(e<0)return null;var h=b?e:0;for(e=b?e+1:f.length;h<e;h++){var l=f[h];if(l.selected&&(c.support.optDisabled?!l.disabled:l.getAttribute("disabled")===null)&&(!l.parentNode.disabled||!c.nodeName(l.parentNode,"optgroup"))){a=c(l).val();if(b)return a;d.push(a)}}return d}if(ta.test(b.type)&&!c.support.checkOn)return b.getAttribute("value")===null?"on":b.value;return(b.value||"").replace(Sa,"")}return B}var k=c.isFunction(a);return this.each(function(o){var x=c(this),r=a;if(this.nodeType===1){if(k)r=
52 a.call(this,o,x.val());if(r==null)r="";else if(typeof r==="number")r+="";else if(c.isArray(r))r=c.map(r,function(C){return C==null?"":C+""});if(c.isArray(r)&&ta.test(this.type))this.checked=c.inArray(x.val(),r)>=0;else if(c.nodeName(this,"select")){var A=c.makeArray(r);c("option",this).each(function(){this.selected=c.inArray(c(this).val(),A)>=0});if(!A.length)this.selectedIndex=-1}else this.value=r}})}});c.extend({attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},
53 attr:function(a,b,d,e){if(!a||a.nodeType===3||a.nodeType===8)return B;if(e&&b in c.attrFn)return c(a)[b](d);e=a.nodeType!==1||!c.isXMLDoc(a);var f=d!==B;b=e&&c.props[b]||b;var h=Ta.test(b);if((b in a||a[b]!==B)&&e&&!h){if(f){b==="type"&&Ua.test(a.nodeName)&&a.parentNode&&c.error("type property can't be changed");if(d===null)a.nodeType===1&&a.removeAttribute(b);else a[b]=d}if(c.nodeName(a,"form")&&a.getAttributeNode(b))return a.getAttributeNode(b).nodeValue;if(b==="tabIndex")return(b=a.getAttributeNode("tabIndex"))&&
54 b.specified?b.value:Va.test(a.nodeName)||Wa.test(a.nodeName)&&a.href?0:B;return a[b]}if(!c.support.style&&e&&b==="style"){if(f)a.style.cssText=""+d;return a.style.cssText}f&&a.setAttribute(b,""+d);if(!a.attributes[b]&&a.hasAttribute&&!a.hasAttribute(b))return B;a=!c.support.hrefNormalized&&e&&h?a.getAttribute(b,2):a.getAttribute(b);return a===null?B:a}});var X=/\.(.*)$/,ia=/^(?:textarea|input|select)$/i,La=/\./g,Ma=/ /g,Xa=/[^\w\s.|`]/g,Ya=function(a){return a.replace(Xa,"\\$&")},ua={focusin:0,focusout:0};
55 c.event={add:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){if(c.isWindow(a)&&a!==E&&!a.frameElement)a=E;if(d===false)d=U;else if(!d)return;var f,h;if(d.handler){f=d;d=f.handler}if(!d.guid)d.guid=c.guid++;if(h=c.data(a)){var l=a.nodeType?"events":"__events__",k=h[l],o=h.handle;if(typeof k==="function"){o=k.handle;k=k.events}else if(!k){a.nodeType||(h[l]=h=function(){});h.events=k={}}if(!o)h.handle=o=function(){return typeof c!=="undefined"&&!c.event.triggered?c.event.handle.apply(o.elem,
56 arguments):B};o.elem=a;b=b.split(" ");for(var x=0,r;l=b[x++];){h=f?c.extend({},f):{handler:d,data:e};if(l.indexOf(".")>-1){r=l.split(".");l=r.shift();h.namespace=r.slice(0).sort().join(".")}else{r=[];h.namespace=""}h.type=l;if(!h.guid)h.guid=d.guid;var A=k[l],C=c.event.special[l]||{};if(!A){A=k[l]=[];if(!C.setup||C.setup.call(a,e,r,o)===false)if(a.addEventListener)a.addEventListener(l,o,false);else a.attachEvent&&a.attachEvent("on"+l,o)}if(C.add){C.add.call(a,h);if(!h.handler.guid)h.handler.guid=
57 d.guid}A.push(h);c.event.global[l]=true}a=null}}},global:{},remove:function(a,b,d,e){if(!(a.nodeType===3||a.nodeType===8)){if(d===false)d=U;var f,h,l=0,k,o,x,r,A,C,J=a.nodeType?"events":"__events__",w=c.data(a),I=w&&w[J];if(w&&I){if(typeof I==="function"){w=I;I=I.events}if(b&&b.type){d=b.handler;b=b.type}if(!b||typeof b==="string"&&b.charAt(0)==="."){b=b||"";for(f in I)c.event.remove(a,f+b)}else{for(b=b.split(" ");f=b[l++];){r=f;k=f.indexOf(".")<0;o=[];if(!k){o=f.split(".");f=o.shift();x=RegExp("(^|\\.)"+
58 c.map(o.slice(0).sort(),Ya).join("\\.(?:.*\\.)?")+"(\\.|$)")}if(A=I[f])if(d){r=c.event.special[f]||{};for(h=e||0;h<A.length;h++){C=A[h];if(d.guid===C.guid){if(k||x.test(C.namespace)){e==null&&A.splice(h--,1);r.remove&&r.remove.call(a,C)}if(e!=null)break}}if(A.length===0||e!=null&&A.length===1){if(!r.teardown||r.teardown.call(a,o)===false)c.removeEvent(a,f,w.handle);delete I[f]}}else for(h=0;h<A.length;h++){C=A[h];if(k||x.test(C.namespace)){c.event.remove(a,r,C.handler,h);A.splice(h--,1)}}}if(c.isEmptyObject(I)){if(b=
59 w.handle)b.elem=null;delete w.events;delete w.handle;if(typeof w==="function")c.removeData(a,J);else c.isEmptyObject(w)&&c.removeData(a)}}}}},trigger:function(a,b,d,e){var f=a.type||a;if(!e){a=typeof a==="object"?a[c.expando]?a:c.extend(c.Event(f),a):c.Event(f);if(f.indexOf("!")>=0){a.type=f=f.slice(0,-1);a.exclusive=true}if(!d){a.stopPropagation();c.event.global[f]&&c.each(c.cache,function(){this.events&&this.events[f]&&c.event.trigger(a,b,this.handle.elem)})}if(!d||d.nodeType===3||d.nodeType===
60 8)return B;a.result=B;a.target=d;b=c.makeArray(b);b.unshift(a)}a.currentTarget=d;(e=d.nodeType?c.data(d,"handle"):(c.data(d,"__events__")||{}).handle)&&e.apply(d,b);e=d.parentNode||d.ownerDocument;try{if(!(d&&d.nodeName&&c.noData[d.nodeName.toLowerCase()]))if(d["on"+f]&&d["on"+f].apply(d,b)===false){a.result=false;a.preventDefault()}}catch(h){}if(!a.isPropagationStopped()&&e)c.event.trigger(a,b,e,true);else if(!a.isDefaultPrevented()){var l;e=a.target;var k=f.replace(X,""),o=c.nodeName(e,"a")&&k===
61 "click",x=c.event.special[k]||{};if((!x._default||x._default.call(d,a)===false)&&!o&&!(e&&e.nodeName&&c.noData[e.nodeName.toLowerCase()])){try{if(e[k]){if(l=e["on"+k])e["on"+k]=null;c.event.triggered=true;e[k]()}}catch(r){}if(l)e["on"+k]=l;c.event.triggered=false}}},handle:function(a){var b,d,e,f;d=[];var h=c.makeArray(arguments);a=h[0]=c.event.fix(a||E.event);a.currentTarget=this;b=a.type.indexOf(".")<0&&!a.exclusive;if(!b){e=a.type.split(".");a.type=e.shift();d=e.slice(0).sort();e=RegExp("(^|\\.)"+
62 d.join("\\.(?:.*\\.)?")+"(\\.|$)")}a.namespace=a.namespace||d.join(".");f=c.data(this,this.nodeType?"events":"__events__");if(typeof f==="function")f=f.events;d=(f||{})[a.type];if(f&&d){d=d.slice(0);f=0;for(var l=d.length;f<l;f++){var k=d[f];if(b||e.test(k.namespace)){a.handler=k.handler;a.data=k.data;a.handleObj=k;k=k.handler.apply(this,h);if(k!==B){a.result=k;if(k===false){a.preventDefault();a.stopPropagation()}}if(a.isImmediatePropagationStopped())break}}}return a.result},props:"altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),
63 fix:function(a){if(a[c.expando])return a;var b=a;a=c.Event(b);for(var d=this.props.length,e;d;){e=this.props[--d];a[e]=b[e]}if(!a.target)a.target=a.srcElement||t;if(a.target.nodeType===3)a.target=a.target.parentNode;if(!a.relatedTarget&&a.fromElement)a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;if(a.pageX==null&&a.clientX!=null){b=t.documentElement;d=t.body;a.pageX=a.clientX+(b&&b.scrollLeft||d&&d.scrollLeft||0)-(b&&b.clientLeft||d&&d.clientLeft||0);a.pageY=a.clientY+(b&&b.scrollTop||
64 d&&d.scrollTop||0)-(b&&b.clientTop||d&&d.clientTop||0)}if(a.which==null&&(a.charCode!=null||a.keyCode!=null))a.which=a.charCode!=null?a.charCode:a.keyCode;if(!a.metaKey&&a.ctrlKey)a.metaKey=a.ctrlKey;if(!a.which&&a.button!==B)a.which=a.button&1?1:a.button&2?3:a.button&4?2:0;return a},guid:1E8,proxy:c.proxy,special:{ready:{setup:c.bindReady,teardown:c.noop},live:{add:function(a){c.event.add(this,Y(a.origType,a.selector),c.extend({},a,{handler:Ka,guid:a.handler.guid}))},remove:function(a){c.event.remove(this,
65 Y(a.origType,a.selector),a)}},beforeunload:{setup:function(a,b,d){if(c.isWindow(this))this.onbeforeunload=d},teardown:function(a,b){if(this.onbeforeunload===b)this.onbeforeunload=null}}}};c.removeEvent=t.removeEventListener?function(a,b,d){a.removeEventListener&&a.removeEventListener(b,d,false)}:function(a,b,d){a.detachEvent&&a.detachEvent("on"+b,d)};c.Event=function(a){if(!this.preventDefault)return new c.Event(a);if(a&&a.type){this.originalEvent=a;this.type=a.type}else this.type=a;this.timeStamp=
66 c.now();this[c.expando]=true};c.Event.prototype={preventDefault:function(){this.isDefaultPrevented=ca;var a=this.originalEvent;if(a)if(a.preventDefault)a.preventDefault();else a.returnValue=false},stopPropagation:function(){this.isPropagationStopped=ca;var a=this.originalEvent;if(a){a.stopPropagation&&a.stopPropagation();a.cancelBubble=true}},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=ca;this.stopPropagation()},isDefaultPrevented:U,isPropagationStopped:U,isImmediatePropagationStopped:U};
67 var va=function(a){var b=a.relatedTarget;try{for(;b&&b!==this;)b=b.parentNode;if(b!==this){a.type=a.data;c.event.handle.apply(this,arguments)}}catch(d){}},wa=function(a){a.type=a.data;c.event.handle.apply(this,arguments)};c.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){c.event.special[a]={setup:function(d){c.event.add(this,b,d&&d.selector?wa:va,a)},teardown:function(d){c.event.remove(this,b,d&&d.selector?wa:va)}}});if(!c.support.submitBubbles)c.event.special.submit={setup:function(){if(this.nodeName.toLowerCase()!==
68 "form"){c.event.add(this,"click.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="submit"||d==="image")&&c(b).closest("form").length){a.liveFired=B;return la("submit",this,arguments)}});c.event.add(this,"keypress.specialSubmit",function(a){var b=a.target,d=b.type;if((d==="text"||d==="password")&&c(b).closest("form").length&&a.keyCode===13){a.liveFired=B;return la("submit",this,arguments)}})}else return false},teardown:function(){c.event.remove(this,".specialSubmit")}};if(!c.support.changeBubbles){var V,
69 xa=function(a){var b=a.type,d=a.value;if(b==="radio"||b==="checkbox")d=a.checked;else if(b==="select-multiple")d=a.selectedIndex>-1?c.map(a.options,function(e){return e.selected}).join("-"):"";else if(a.nodeName.toLowerCase()==="select")d=a.selectedIndex;return d},Z=function(a,b){var d=a.target,e,f;if(!(!ia.test(d.nodeName)||d.readOnly)){e=c.data(d,"_change_data");f=xa(d);if(a.type!=="focusout"||d.type!=="radio")c.data(d,"_change_data",f);if(!(e===B||f===e))if(e!=null||f){a.type="change";a.liveFired=
70 B;return c.event.trigger(a,b,d)}}};c.event.special.change={filters:{focusout:Z,beforedeactivate:Z,click:function(a){var b=a.target,d=b.type;if(d==="radio"||d==="checkbox"||b.nodeName.toLowerCase()==="select")return Z.call(this,a)},keydown:function(a){var b=a.target,d=b.type;if(a.keyCode===13&&b.nodeName.toLowerCase()!=="textarea"||a.keyCode===32&&(d==="checkbox"||d==="radio")||d==="select-multiple")return Z.call(this,a)},beforeactivate:function(a){a=a.target;c.data(a,"_change_data",xa(a))}},setup:function(){if(this.type===
71 "file")return false;for(var a in V)c.event.add(this,a+".specialChange",V[a]);return ia.test(this.nodeName)},teardown:function(){c.event.remove(this,".specialChange");return ia.test(this.nodeName)}};V=c.event.special.change.filters;V.focus=V.beforeactivate}t.addEventListener&&c.each({focus:"focusin",blur:"focusout"},function(a,b){function d(e){e=c.event.fix(e);e.type=b;return c.event.trigger(e,null,e.target)}c.event.special[b]={setup:function(){ua[b]++===0&&t.addEventListener(a,d,true)},teardown:function(){--ua[b]===
72 0&&t.removeEventListener(a,d,true)}}});c.each(["bind","one"],function(a,b){c.fn[b]=function(d,e,f){if(typeof d==="object"){for(var h in d)this[b](h,e,d[h],f);return this}if(c.isFunction(e)||e===false){f=e;e=B}var l=b==="one"?c.proxy(f,function(o){c(this).unbind(o,l);return f.apply(this,arguments)}):f;if(d==="unload"&&b!=="one")this.one(d,e,f);else{h=0;for(var k=this.length;h<k;h++)c.event.add(this[h],d,l,e)}return this}});c.fn.extend({unbind:function(a,b){if(typeof a==="object"&&!a.preventDefault)for(var d in a)this.unbind(d,
73 a[d]);else{d=0;for(var e=this.length;d<e;d++)c.event.remove(this[d],a,b)}return this},delegate:function(a,b,d,e){return this.live(b,d,e,a)},undelegate:function(a,b,d){return arguments.length===0?this.unbind("live"):this.die(b,null,d,a)},trigger:function(a,b){return this.each(function(){c.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0]){var d=c.Event(a);d.preventDefault();d.stopPropagation();c.event.trigger(d,b,this[0]);return d.result}},toggle:function(a){for(var b=arguments,d=
74 1;d<b.length;)c.proxy(a,b[d++]);return this.click(c.proxy(a,function(e){var f=(c.data(this,"lastToggle"+a.guid)||0)%d;c.data(this,"lastToggle"+a.guid,f+1);e.preventDefault();return b[f].apply(this,arguments)||false}))},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}});var ya={focus:"focusin",blur:"focusout",mouseenter:"mouseover",mouseleave:"mouseout"};c.each(["live","die"],function(a,b){c.fn[b]=function(d,e,f,h){var l,k=0,o,x,r=h||this.selector;h=h?this:c(this.context);if(typeof d===
75 "object"&&!d.preventDefault){for(l in d)h[b](l,e,d[l],r);return this}if(c.isFunction(e)){f=e;e=B}for(d=(d||"").split(" ");(l=d[k++])!=null;){o=X.exec(l);x="";if(o){x=o[0];l=l.replace(X,"")}if(l==="hover")d.push("mouseenter"+x,"mouseleave"+x);else{o=l;if(l==="focus"||l==="blur"){d.push(ya[l]+x);l+=x}else l=(ya[l]||l)+x;if(b==="live"){x=0;for(var A=h.length;x<A;x++)c.event.add(h[x],"live."+Y(l,r),{data:e,selector:r,handler:f,origType:l,origHandler:f,preType:o})}else h.unbind("live."+Y(l,r),f)}}return this}});
76 c.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error".split(" "),function(a,b){c.fn[b]=function(d,e){if(e==null){e=d;d=null}return arguments.length>0?this.bind(b,d,e):this.trigger(b)};if(c.attrFn)c.attrFn[b]=true});E.attachEvent&&!E.addEventListener&&c(E).bind("unload",function(){for(var a in c.cache)if(c.cache[a].handle)try{c.event.remove(c.cache[a].handle.elem)}catch(b){}});
77 (function(){function a(g,i,n,m,p,q){p=0;for(var u=m.length;p<u;p++){var y=m[p];if(y){var F=false;for(y=y[g];y;){if(y.sizcache===n){F=m[y.sizset];break}if(y.nodeType===1&&!q){y.sizcache=n;y.sizset=p}if(y.nodeName.toLowerCase()===i){F=y;break}y=y[g]}m[p]=F}}}function b(g,i,n,m,p,q){p=0;for(var u=m.length;p<u;p++){var y=m[p];if(y){var F=false;for(y=y[g];y;){if(y.sizcache===n){F=m[y.sizset];break}if(y.nodeType===1){if(!q){y.sizcache=n;y.sizset=p}if(typeof i!=="string"){if(y===i){F=true;break}}else if(k.filter(i,
78 [y]).length>0){F=y;break}}y=y[g]}m[p]=F}}}var d=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,e=0,f=Object.prototype.toString,h=false,l=true;[0,0].sort(function(){l=false;return 0});var k=function(g,i,n,m){n=n||[];var p=i=i||t;if(i.nodeType!==1&&i.nodeType!==9)return[];if(!g||typeof g!=="string")return n;var q,u,y,F,M,N=true,O=k.isXML(i),D=[],R=g;do{d.exec("");if(q=d.exec(R)){R=q[3];D.push(q[1]);if(q[2]){F=q[3];
79 break}}}while(q);if(D.length>1&&x.exec(g))if(D.length===2&&o.relative[D[0]])u=L(D[0]+D[1],i);else for(u=o.relative[D[0]]?[i]:k(D.shift(),i);D.length;){g=D.shift();if(o.relative[g])g+=D.shift();u=L(g,u)}else{if(!m&&D.length>1&&i.nodeType===9&&!O&&o.match.ID.test(D[0])&&!o.match.ID.test(D[D.length-1])){q=k.find(D.shift(),i,O);i=q.expr?k.filter(q.expr,q.set)[0]:q.set[0]}if(i){q=m?{expr:D.pop(),set:C(m)}:k.find(D.pop(),D.length===1&&(D[0]==="~"||D[0]==="+")&&i.parentNode?i.parentNode:i,O);u=q.expr?k.filter(q.expr,
80 q.set):q.set;if(D.length>0)y=C(u);else N=false;for(;D.length;){q=M=D.pop();if(o.relative[M])q=D.pop();else M="";if(q==null)q=i;o.relative[M](y,q,O)}}else y=[]}y||(y=u);y||k.error(M||g);if(f.call(y)==="[object Array]")if(N)if(i&&i.nodeType===1)for(g=0;y[g]!=null;g++){if(y[g]&&(y[g]===true||y[g].nodeType===1&&k.contains(i,y[g])))n.push(u[g])}else for(g=0;y[g]!=null;g++)y[g]&&y[g].nodeType===1&&n.push(u[g]);else n.push.apply(n,y);else C(y,n);if(F){k(F,p,n,m);k.uniqueSort(n)}return n};k.uniqueSort=function(g){if(w){h=
81 l;g.sort(w);if(h)for(var i=1;i<g.length;i++)g[i]===g[i-1]&&g.splice(i--,1)}return g};k.matches=function(g,i){return k(g,null,null,i)};k.matchesSelector=function(g,i){return k(i,null,null,[g]).length>0};k.find=function(g,i,n){var m;if(!g)return[];for(var p=0,q=o.order.length;p<q;p++){var u,y=o.order[p];if(u=o.leftMatch[y].exec(g)){var F=u[1];u.splice(1,1);if(F.substr(F.length-1)!=="\\"){u[1]=(u[1]||"").replace(/\\/g,"");m=o.find[y](u,i,n);if(m!=null){g=g.replace(o.match[y],"");break}}}}m||(m=i.getElementsByTagName("*"));
82 return{set:m,expr:g}};k.filter=function(g,i,n,m){for(var p,q,u=g,y=[],F=i,M=i&&i[0]&&k.isXML(i[0]);g&&i.length;){for(var N in o.filter)if((p=o.leftMatch[N].exec(g))!=null&&p[2]){var O,D,R=o.filter[N];D=p[1];q=false;p.splice(1,1);if(D.substr(D.length-1)!=="\\"){if(F===y)y=[];if(o.preFilter[N])if(p=o.preFilter[N](p,F,n,y,m,M)){if(p===true)continue}else q=O=true;if(p)for(var j=0;(D=F[j])!=null;j++)if(D){O=R(D,p,j,F);var s=m^!!O;if(n&&O!=null)if(s)q=true;else F[j]=false;else if(s){y.push(D);q=true}}if(O!==
83 B){n||(F=y);g=g.replace(o.match[N],"");if(!q)return[];break}}}if(g===u)if(q==null)k.error(g);else break;u=g}return F};k.error=function(g){throw"Syntax error, unrecognized expression: "+g;};var o=k.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
84 POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(g){return g.getAttribute("href")}},relative:{"+":function(g,i){var n=typeof i==="string",m=n&&!/\W/.test(i);n=n&&!m;if(m)i=i.toLowerCase();m=0;for(var p=g.length,q;m<p;m++)if(q=g[m]){for(;(q=q.previousSibling)&&q.nodeType!==1;);g[m]=n||q&&q.nodeName.toLowerCase()===
85 i?q||false:q===i}n&&k.filter(i,g,true)},">":function(g,i){var n,m=typeof i==="string",p=0,q=g.length;if(m&&!/\W/.test(i))for(i=i.toLowerCase();p<q;p++){if(n=g[p]){n=n.parentNode;g[p]=n.nodeName.toLowerCase()===i?n:false}}else{for(;p<q;p++)if(n=g[p])g[p]=m?n.parentNode:n.parentNode===i;m&&k.filter(i,g,true)}},"":function(g,i,n){var m,p=e++,q=b;if(typeof i==="string"&&!/\W/.test(i)){m=i=i.toLowerCase();q=a}q("parentNode",i,p,g,m,n)},"~":function(g,i,n){var m,p=e++,q=b;if(typeof i==="string"&&!/\W/.test(i)){m=
86 i=i.toLowerCase();q=a}q("previousSibling",i,p,g,m,n)}},find:{ID:function(g,i,n){if(typeof i.getElementById!=="undefined"&&!n)return(g=i.getElementById(g[1]))&&g.parentNode?[g]:[]},NAME:function(g,i){if(typeof i.getElementsByName!=="undefined"){for(var n=[],m=i.getElementsByName(g[1]),p=0,q=m.length;p<q;p++)m[p].getAttribute("name")===g[1]&&n.push(m[p]);return n.length===0?null:n}},TAG:function(g,i){return i.getElementsByTagName(g[1])}},preFilter:{CLASS:function(g,i,n,m,p,q){g=" "+g[1].replace(/\\/g,
87 "")+" ";if(q)return g;q=0;for(var u;(u=i[q])!=null;q++)if(u)if(p^(u.className&&(" "+u.className+" ").replace(/[\t\n]/g," ").indexOf(g)>=0))n||m.push(u);else if(n)i[q]=false;return false},ID:function(g){return g[1].replace(/\\/g,"")},TAG:function(g){return g[1].toLowerCase()},CHILD:function(g){if(g[1]==="nth"){var i=/(-?)(\d*)n((?:\+|-)?\d*)/.exec(g[2]==="even"&&"2n"||g[2]==="odd"&&"2n+1"||!/\D/.test(g[2])&&"0n+"+g[2]||g[2]);g[2]=i[1]+(i[2]||1)-0;g[3]=i[3]-0}g[0]=e++;return g},ATTR:function(g,i,n,
88 m,p,q){i=g[1].replace(/\\/g,"");if(!q&&o.attrMap[i])g[1]=o.attrMap[i];if(g[2]==="~=")g[4]=" "+g[4]+" ";return g},PSEUDO:function(g,i,n,m,p){if(g[1]==="not")if((d.exec(g[3])||"").length>1||/^\w/.test(g[3]))g[3]=k(g[3],null,null,i);else{g=k.filter(g[3],i,n,true^p);n||m.push.apply(m,g);return false}else if(o.match.POS.test(g[0])||o.match.CHILD.test(g[0]))return true;return g},POS:function(g){g.unshift(true);return g}},filters:{enabled:function(g){return g.disabled===false&&g.type!=="hidden"},disabled:function(g){return g.disabled===
89 true},checked:function(g){return g.checked===true},selected:function(g){return g.selected===true},parent:function(g){return!!g.firstChild},empty:function(g){return!g.firstChild},has:function(g,i,n){return!!k(n[3],g).length},header:function(g){return/h\d/i.test(g.nodeName)},text:function(g){return"text"===g.type},radio:function(g){return"radio"===g.type},checkbox:function(g){return"checkbox"===g.type},file:function(g){return"file"===g.type},password:function(g){return"password"===g.type},submit:function(g){return"submit"===
90 g.type},image:function(g){return"image"===g.type},reset:function(g){return"reset"===g.type},button:function(g){return"button"===g.type||g.nodeName.toLowerCase()==="button"},input:function(g){return/input|select|textarea|button/i.test(g.nodeName)}},setFilters:{first:function(g,i){return i===0},last:function(g,i,n,m){return i===m.length-1},even:function(g,i){return i%2===0},odd:function(g,i){return i%2===1},lt:function(g,i,n){return i<n[3]-0},gt:function(g,i,n){return i>n[3]-0},nth:function(g,i,n){return n[3]-
91 0===i},eq:function(g,i,n){return n[3]-0===i}},filter:{PSEUDO:function(g,i,n,m){var p=i[1],q=o.filters[p];if(q)return q(g,n,i,m);else if(p==="contains")return(g.textContent||g.innerText||k.getText([g])||"").indexOf(i[3])>=0;else if(p==="not"){i=i[3];n=0;for(m=i.length;n<m;n++)if(i[n]===g)return false;return true}else k.error("Syntax error, unrecognized expression: "+p)},CHILD:function(g,i){var n=i[1],m=g;switch(n){case "only":case "first":for(;m=m.previousSibling;)if(m.nodeType===1)return false;if(n===
92 "first")return true;m=g;case "last":for(;m=m.nextSibling;)if(m.nodeType===1)return false;return true;case "nth":n=i[2];var p=i[3];if(n===1&&p===0)return true;var q=i[0],u=g.parentNode;if(u&&(u.sizcache!==q||!g.nodeIndex)){var y=0;for(m=u.firstChild;m;m=m.nextSibling)if(m.nodeType===1)m.nodeIndex=++y;u.sizcache=q}m=g.nodeIndex-p;return n===0?m===0:m%n===0&&m/n>=0}},ID:function(g,i){return g.nodeType===1&&g.getAttribute("id")===i},TAG:function(g,i){return i==="*"&&g.nodeType===1||g.nodeName.toLowerCase()===
93 i},CLASS:function(g,i){return(" "+(g.className||g.getAttribute("class"))+" ").indexOf(i)>-1},ATTR:function(g,i){var n=i[1];n=o.attrHandle[n]?o.attrHandle[n](g):g[n]!=null?g[n]:g.getAttribute(n);var m=n+"",p=i[2],q=i[4];return n==null?p==="!=":p==="="?m===q:p==="*="?m.indexOf(q)>=0:p==="~="?(" "+m+" ").indexOf(q)>=0:!q?m&&n!==false:p==="!="?m!==q:p==="^="?m.indexOf(q)===0:p==="$="?m.substr(m.length-q.length)===q:p==="|="?m===q||m.substr(0,q.length+1)===q+"-":false},POS:function(g,i,n,m){var p=o.setFilters[i[2]];
94 if(p)return p(g,n,i,m)}}},x=o.match.POS,r=function(g,i){return"\\"+(i-0+1)},A;for(A in o.match){o.match[A]=RegExp(o.match[A].source+/(?![^\[]*\])(?![^\(]*\))/.source);o.leftMatch[A]=RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[A].source.replace(/\\(\d+)/g,r))}var C=function(g,i){g=Array.prototype.slice.call(g,0);if(i){i.push.apply(i,g);return i}return g};try{Array.prototype.slice.call(t.documentElement.childNodes,0)}catch(J){C=function(g,i){var n=0,m=i||[];if(f.call(g)==="[object Array]")Array.prototype.push.apply(m,
95 g);else if(typeof g.length==="number")for(var p=g.length;n<p;n++)m.push(g[n]);else for(;g[n];n++)m.push(g[n]);return m}}var w,I;if(t.documentElement.compareDocumentPosition)w=function(g,i){if(g===i){h=true;return 0}if(!g.compareDocumentPosition||!i.compareDocumentPosition)return g.compareDocumentPosition?-1:1;return g.compareDocumentPosition(i)&4?-1:1};else{w=function(g,i){var n,m,p=[],q=[];n=g.parentNode;m=i.parentNode;var u=n;if(g===i){h=true;return 0}else if(n===m)return I(g,i);else if(n){if(!m)return 1}else return-1;
96 for(;u;){p.unshift(u);u=u.parentNode}for(u=m;u;){q.unshift(u);u=u.parentNode}n=p.length;m=q.length;for(u=0;u<n&&u<m;u++)if(p[u]!==q[u])return I(p[u],q[u]);return u===n?I(g,q[u],-1):I(p[u],i,1)};I=function(g,i,n){if(g===i)return n;for(g=g.nextSibling;g;){if(g===i)return-1;g=g.nextSibling}return 1}}k.getText=function(g){for(var i="",n,m=0;g[m];m++){n=g[m];if(n.nodeType===3||n.nodeType===4)i+=n.nodeValue;else if(n.nodeType!==8)i+=k.getText(n.childNodes)}return i};(function(){var g=t.createElement("div"),
97 i="script"+(new Date).getTime(),n=t.documentElement;g.innerHTML="<a name='"+i+"'/>";n.insertBefore(g,n.firstChild);if(t.getElementById(i)){o.find.ID=function(m,p,q){if(typeof p.getElementById!=="undefined"&&!q)return(p=p.getElementById(m[1]))?p.id===m[1]||typeof p.getAttributeNode!=="undefined"&&p.getAttributeNode("id").nodeValue===m[1]?[p]:B:[]};o.filter.ID=function(m,p){var q=typeof m.getAttributeNode!=="undefined"&&m.getAttributeNode("id");return m.nodeType===1&&q&&q.nodeValue===p}}n.removeChild(g);
98 n=g=null})();(function(){var g=t.createElement("div");g.appendChild(t.createComment(""));if(g.getElementsByTagName("*").length>0)o.find.TAG=function(i,n){var m=n.getElementsByTagName(i[1]);if(i[1]==="*"){for(var p=[],q=0;m[q];q++)m[q].nodeType===1&&p.push(m[q]);m=p}return m};g.innerHTML="<a href='#'></a>";if(g.firstChild&&typeof g.firstChild.getAttribute!=="undefined"&&g.firstChild.getAttribute("href")!=="#")o.attrHandle.href=function(i){return i.getAttribute("href",2)};g=null})();t.querySelectorAll&&
99 function(){var g=k,i=t.createElement("div");i.innerHTML="<p class='TEST'></p>";if(!(i.querySelectorAll&&i.querySelectorAll(".TEST").length===0)){k=function(m,p,q,u){p=p||t;m=m.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!u&&!k.isXML(p))if(p.nodeType===9)try{return C(p.querySelectorAll(m),q)}catch(y){}else if(p.nodeType===1&&p.nodeName.toLowerCase()!=="object"){var F=p.getAttribute("id"),M=F||"__sizzle__";F||p.setAttribute("id",M);try{return C(p.querySelectorAll("#"+M+" "+m),q)}catch(N){}finally{F||
100 p.removeAttribute("id")}}return g(m,p,q,u)};for(var n in g)k[n]=g[n];i=null}}();(function(){var g=t.documentElement,i=g.matchesSelector||g.mozMatchesSelector||g.webkitMatchesSelector||g.msMatchesSelector,n=false;try{i.call(t.documentElement,"[test!='']:sizzle")}catch(m){n=true}if(i)k.matchesSelector=function(p,q){q=q.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!k.isXML(p))try{if(n||!o.match.PSEUDO.test(q)&&!/!=/.test(q))return i.call(p,q)}catch(u){}return k(q,null,null,[p]).length>0}})();(function(){var g=
101 t.createElement("div");g.innerHTML="<div class='test e'></div><div class='test'></div>";if(!(!g.getElementsByClassName||g.getElementsByClassName("e").length===0)){g.lastChild.className="e";if(g.getElementsByClassName("e").length!==1){o.order.splice(1,0,"CLASS");o.find.CLASS=function(i,n,m){if(typeof n.getElementsByClassName!=="undefined"&&!m)return n.getElementsByClassName(i[1])};g=null}}})();k.contains=t.documentElement.contains?function(g,i){return g!==i&&(g.contains?g.contains(i):true)}:t.documentElement.compareDocumentPosition?
102 function(g,i){return!!(g.compareDocumentPosition(i)&16)}:function(){return false};k.isXML=function(g){return(g=(g?g.ownerDocument||g:0).documentElement)?g.nodeName!=="HTML":false};var L=function(g,i){for(var n,m=[],p="",q=i.nodeType?[i]:i;n=o.match.PSEUDO.exec(g);){p+=n[0];g=g.replace(o.match.PSEUDO,"")}g=o.relative[g]?g+"*":g;n=0;for(var u=q.length;n<u;n++)k(g,q[n],m);return k.filter(p,m)};c.find=k;c.expr=k.selectors;c.expr[":"]=c.expr.filters;c.unique=k.uniqueSort;c.text=k.getText;c.isXMLDoc=k.isXML;
103 c.contains=k.contains})();var Za=/Until$/,$a=/^(?:parents|prevUntil|prevAll)/,ab=/,/,Na=/^.[^:#\[\.,]*$/,bb=Array.prototype.slice,cb=c.expr.match.POS;c.fn.extend({find:function(a){for(var b=this.pushStack("","find",a),d=0,e=0,f=this.length;e<f;e++){d=b.length;c.find(a,this[e],b);if(e>0)for(var h=d;h<b.length;h++)for(var l=0;l<d;l++)if(b[l]===b[h]){b.splice(h--,1);break}}return b},has:function(a){var b=c(a);return this.filter(function(){for(var d=0,e=b.length;d<e;d++)if(c.contains(this,b[d]))return true})},
104 not:function(a){return this.pushStack(ma(this,a,false),"not",a)},filter:function(a){return this.pushStack(ma(this,a,true),"filter",a)},is:function(a){return!!a&&c.filter(a,this).length>0},closest:function(a,b){var d=[],e,f,h=this[0];if(c.isArray(a)){var l,k={},o=1;if(h&&a.length){e=0;for(f=a.length;e<f;e++){l=a[e];k[l]||(k[l]=c.expr.match.POS.test(l)?c(l,b||this.context):l)}for(;h&&h.ownerDocument&&h!==b;){for(l in k){e=k[l];if(e.jquery?e.index(h)>-1:c(h).is(e))d.push({selector:l,elem:h,level:o})}h=
105 h.parentNode;o++}}return d}l=cb.test(a)?c(a,b||this.context):null;e=0;for(f=this.length;e<f;e++)for(h=this[e];h;)if(l?l.index(h)>-1:c.find.matchesSelector(h,a)){d.push(h);break}else{h=h.parentNode;if(!h||!h.ownerDocument||h===b)break}d=d.length>1?c.unique(d):d;return this.pushStack(d,"closest",a)},index:function(a){if(!a||typeof a==="string")return c.inArray(this[0],a?c(a):this.parent().children());return c.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var d=typeof a==="string"?c(a,b||this.context):
106 c.makeArray(a),e=c.merge(this.get(),d);return this.pushStack(!d[0]||!d[0].parentNode||d[0].parentNode.nodeType===11||!e[0]||!e[0].parentNode||e[0].parentNode.nodeType===11?e:c.unique(e))},andSelf:function(){return this.add(this.prevObject)}});c.each({parent:function(a){return(a=a.parentNode)&&a.nodeType!==11?a:null},parents:function(a){return c.dir(a,"parentNode")},parentsUntil:function(a,b,d){return c.dir(a,"parentNode",d)},next:function(a){return c.nth(a,2,"nextSibling")},prev:function(a){return c.nth(a,
107 2,"previousSibling")},nextAll:function(a){return c.dir(a,"nextSibling")},prevAll:function(a){return c.dir(a,"previousSibling")},nextUntil:function(a,b,d){return c.dir(a,"nextSibling",d)},prevUntil:function(a,b,d){return c.dir(a,"previousSibling",d)},siblings:function(a){return c.sibling(a.parentNode.firstChild,a)},children:function(a){return c.sibling(a.firstChild)},contents:function(a){return c.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:c.makeArray(a.childNodes)}},function(a,
108 b){c.fn[a]=function(d,e){var f=c.map(this,b,d);Za.test(a)||(e=d);if(e&&typeof e==="string")f=c.filter(e,f);f=this.length>1?c.unique(f):f;if((this.length>1||ab.test(e))&&$a.test(a))f=f.reverse();return this.pushStack(f,a,bb.call(arguments).join(","))}});c.extend({filter:function(a,b,d){if(d)a=":not("+a+")";return b.length===1?c.find.matchesSelector(b[0],a)?[b[0]]:[]:c.find.matches(a,b)},dir:function(a,b,d){var e=[];for(a=a[b];a&&a.nodeType!==9&&(d===B||a.nodeType!==1||!c(a).is(d));){a.nodeType===1&&
109 e.push(a);a=a[b]}return e},nth:function(a,b,d){b=b||1;for(var e=0;a;a=a[d])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){for(var d=[];a;a=a.nextSibling)a.nodeType===1&&a!==b&&d.push(a);return d}});var za=/ jQuery\d+="(?:\d+|null)"/g,$=/^\s+/,Aa=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Ba=/<([\w:]+)/,db=/<tbody/i,eb=/<|&#?\w+;/,Ca=/<(?:script|object|embed|option|style)/i,Da=/checked\s*(?:[^=]|=\s*.checked.)/i,fb=/\=([^="'>\s]+\/)>/g,P={option:[1,
110 "<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]};P.optgroup=P.option;P.tbody=P.tfoot=P.colgroup=P.caption=P.thead;P.th=P.td;if(!c.support.htmlSerialize)P._default=[1,"div<div>","</div>"];c.fn.extend({text:function(a){if(c.isFunction(a))return this.each(function(b){var d=
111 c(this);d.text(a.call(this,b,d.text()))});if(typeof a!=="object"&&a!==B)return this.empty().append((this[0]&&this[0].ownerDocument||t).createTextNode(a));return c.text(this)},wrapAll:function(a){if(c.isFunction(a))return this.each(function(d){c(this).wrapAll(a.call(this,d))});if(this[0]){var b=c(a,this[0].ownerDocument).eq(0).clone(true);this[0].parentNode&&b.insertBefore(this[0]);b.map(function(){for(var d=this;d.firstChild&&d.firstChild.nodeType===1;)d=d.firstChild;return d}).append(this)}return this},
112 wrapInner:function(a){if(c.isFunction(a))return this.each(function(b){c(this).wrapInner(a.call(this,b))});return this.each(function(){var b=c(this),d=b.contents();d.length?d.wrapAll(a):b.append(a)})},wrap:function(a){return this.each(function(){c(this).wrapAll(a)})},unwrap:function(){return this.parent().each(function(){c.nodeName(this,"body")||c(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.appendChild(a)})},
113 prepend:function(){return this.domManip(arguments,true,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,this)});else if(arguments.length){var a=c(arguments[0]);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,false,function(b){this.parentNode.insertBefore(b,
114 this.nextSibling)});else if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,c(arguments[0]).toArray());return a}},remove:function(a,b){for(var d=0,e;(e=this[d])!=null;d++)if(!a||c.filter(a,[e]).length){if(!b&&e.nodeType===1){c.cleanData(e.getElementsByTagName("*"));c.cleanData([e])}e.parentNode&&e.parentNode.removeChild(e)}return this},empty:function(){for(var a=0,b;(b=this[a])!=null;a++)for(b.nodeType===1&&c.cleanData(b.getElementsByTagName("*"));b.firstChild;)b.removeChild(b.firstChild);
115 return this},clone:function(a){var b=this.map(function(){if(!c.support.noCloneEvent&&!c.isXMLDoc(this)){var d=this.outerHTML,e=this.ownerDocument;if(!d){d=e.createElement("div");d.appendChild(this.cloneNode(true));d=d.innerHTML}return c.clean([d.replace(za,"").replace(fb,'="$1">').replace($,"")],e)[0]}else return this.cloneNode(true)});if(a===true){na(this,b);na(this.find("*"),b.find("*"))}return b},html:function(a){if(a===B)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(za,""):null;
116 else if(typeof a==="string"&&!Ca.test(a)&&(c.support.leadingWhitespace||!$.test(a))&&!P[(Ba.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Aa,"<$1></$2>");try{for(var b=0,d=this.length;b<d;b++)if(this[b].nodeType===1){c.cleanData(this[b].getElementsByTagName("*"));this[b].innerHTML=a}}catch(e){this.empty().append(a)}}else c.isFunction(a)?this.each(function(f){var h=c(this);h.html(a.call(this,f,h.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(c.isFunction(a))return this.each(function(b){var d=
117 c(this),e=d.html();d.replaceWith(a.call(this,b,e))});if(typeof a!=="string")a=c(a).detach();return this.each(function(){var b=this.nextSibling,d=this.parentNode;c(this).remove();b?c(b).before(a):c(d).append(a)})}else return this.pushStack(c(c.isFunction(a)?a():a),"replaceWith",a)},detach:function(a){return this.remove(a,true)},domManip:function(a,b,d){var e,f,h,l=a[0],k=[];if(!c.support.checkClone&&arguments.length===3&&typeof l==="string"&&Da.test(l))return this.each(function(){c(this).domManip(a,
118 b,d,true)});if(c.isFunction(l))return this.each(function(x){var r=c(this);a[0]=l.call(this,x,b?r.html():B);r.domManip(a,b,d)});if(this[0]){e=l&&l.parentNode;e=c.support.parentNode&&e&&e.nodeType===11&&e.childNodes.length===this.length?{fragment:e}:c.buildFragment(a,this,k);h=e.fragment;if(f=h.childNodes.length===1?h=h.firstChild:h.firstChild){b=b&&c.nodeName(f,"tr");f=0;for(var o=this.length;f<o;f++)d.call(b?c.nodeName(this[f],"table")?this[f].getElementsByTagName("tbody")[0]||this[f].appendChild(this[f].ownerDocument.createElement("tbody")):
119 this[f]:this[f],f>0||e.cacheable||this.length>1?h.cloneNode(true):h)}k.length&&c.each(k,Oa)}return this}});c.buildFragment=function(a,b,d){var e,f,h;b=b&&b[0]?b[0].ownerDocument||b[0]:t;if(a.length===1&&typeof a[0]==="string"&&a[0].length<512&&b===t&&!Ca.test(a[0])&&(c.support.checkClone||!Da.test(a[0]))){f=true;if(h=c.fragments[a[0]])if(h!==1)e=h}if(!e){e=b.createDocumentFragment();c.clean(a,b,e,d)}if(f)c.fragments[a[0]]=h?e:1;return{fragment:e,cacheable:f}};c.fragments={};c.each({appendTo:"append",
120 prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){c.fn[a]=function(d){var e=[];d=c(d);var f=this.length===1&&this[0].parentNode;if(f&&f.nodeType===11&&f.childNodes.length===1&&d.length===1){d[b](this[0]);return this}else{f=0;for(var h=d.length;f<h;f++){var l=(f>0?this.clone(true):this).get();c(d[f])[b](l);e=e.concat(l)}return this.pushStack(e,a,d.selector)}}});c.extend({clean:function(a,b,d,e){b=b||t;if(typeof b.createElement==="undefined")b=b.ownerDocument||
121 b[0]&&b[0].ownerDocument||t;for(var f=[],h=0,l;(l=a[h])!=null;h++){if(typeof l==="number")l+="";if(l){if(typeof l==="string"&&!eb.test(l))l=b.createTextNode(l);else if(typeof l==="string"){l=l.replace(Aa,"<$1></$2>");var k=(Ba.exec(l)||["",""])[1].toLowerCase(),o=P[k]||P._default,x=o[0],r=b.createElement("div");for(r.innerHTML=o[1]+l+o[2];x--;)r=r.lastChild;if(!c.support.tbody){x=db.test(l);k=k==="table"&&!x?r.firstChild&&r.firstChild.childNodes:o[1]==="<table>"&&!x?r.childNodes:[];for(o=k.length-
122 1;o>=0;--o)c.nodeName(k[o],"tbody")&&!k[o].childNodes.length&&k[o].parentNode.removeChild(k[o])}!c.support.leadingWhitespace&&$.test(l)&&r.insertBefore(b.createTextNode($.exec(l)[0]),r.firstChild);l=r.childNodes}if(l.nodeType)f.push(l);else f=c.merge(f,l)}}if(d)for(h=0;f[h];h++)if(e&&c.nodeName(f[h],"script")&&(!f[h].type||f[h].type.toLowerCase()==="text/javascript"))e.push(f[h].parentNode?f[h].parentNode.removeChild(f[h]):f[h]);else{f[h].nodeType===1&&f.splice.apply(f,[h+1,0].concat(c.makeArray(f[h].getElementsByTagName("script"))));
123 d.appendChild(f[h])}return f},cleanData:function(a){for(var b,d,e=c.cache,f=c.event.special,h=c.support.deleteExpando,l=0,k;(k=a[l])!=null;l++)if(!(k.nodeName&&c.noData[k.nodeName.toLowerCase()]))if(d=k[c.expando]){if((b=e[d])&&b.events)for(var o in b.events)f[o]?c.event.remove(k,o):c.removeEvent(k,o,b.handle);if(h)delete k[c.expando];else k.removeAttribute&&k.removeAttribute(c.expando);delete e[d]}}});var Ea=/alpha\([^)]*\)/i,gb=/opacity=([^)]*)/,hb=/-([a-z])/ig,ib=/([A-Z])/g,Fa=/^-?\d+(?:px)?$/i,
124 jb=/^-?\d/,kb={position:"absolute",visibility:"hidden",display:"block"},Pa=["Left","Right"],Qa=["Top","Bottom"],W,Ga,aa,lb=function(a,b){return b.toUpperCase()};c.fn.css=function(a,b){if(arguments.length===2&&b===B)return this;return c.access(this,a,b,true,function(d,e,f){return f!==B?c.style(d,e,f):c.css(d,e)})};c.extend({cssHooks:{opacity:{get:function(a,b){if(b){var d=W(a,"opacity","opacity");return d===""?"1":d}else return a.style.opacity}}},cssNumber:{zIndex:true,fontWeight:true,opacity:true,
125 zoom:true,lineHeight:true},cssProps:{"float":c.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,b,d,e){if(!(!a||a.nodeType===3||a.nodeType===8||!a.style)){var f,h=c.camelCase(b),l=a.style,k=c.cssHooks[h];b=c.cssProps[h]||h;if(d!==B){if(!(typeof d==="number"&&isNaN(d)||d==null)){if(typeof d==="number"&&!c.cssNumber[h])d+="px";if(!k||!("set"in k)||(d=k.set(a,d))!==B)try{l[b]=d}catch(o){}}}else{if(k&&"get"in k&&(f=k.get(a,false,e))!==B)return f;return l[b]}}},css:function(a,b,d){var e,f=c.camelCase(b),
126 h=c.cssHooks[f];b=c.cssProps[f]||f;if(h&&"get"in h&&(e=h.get(a,true,d))!==B)return e;else if(W)return W(a,b,f)},swap:function(a,b,d){var e={},f;for(f in b){e[f]=a.style[f];a.style[f]=b[f]}d.call(a);for(f in b)a.style[f]=e[f]},camelCase:function(a){return a.replace(hb,lb)}});c.curCSS=c.css;c.each(["height","width"],function(a,b){c.cssHooks[b]={get:function(d,e,f){var h;if(e){if(d.offsetWidth!==0)h=oa(d,b,f);else c.swap(d,kb,function(){h=oa(d,b,f)});if(h<=0){h=W(d,b,b);if(h==="0px"&&aa)h=aa(d,b,b);
127 if(h!=null)return h===""||h==="auto"?"0px":h}if(h<0||h==null){h=d.style[b];return h===""||h==="auto"?"0px":h}return typeof h==="string"?h:h+"px"}},set:function(d,e){if(Fa.test(e)){e=parseFloat(e);if(e>=0)return e+"px"}else return e}}});if(!c.support.opacity)c.cssHooks.opacity={get:function(a,b){return gb.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var d=a.style;d.zoom=1;var e=c.isNaN(b)?"":"alpha(opacity="+b*100+")",f=
128 d.filter||"";d.filter=Ea.test(f)?f.replace(Ea,e):d.filter+" "+e}};if(t.defaultView&&t.defaultView.getComputedStyle)Ga=function(a,b,d){var e;d=d.replace(ib,"-$1").toLowerCase();if(!(b=a.ownerDocument.defaultView))return B;if(b=b.getComputedStyle(a,null)){e=b.getPropertyValue(d);if(e===""&&!c.contains(a.ownerDocument.documentElement,a))e=c.style(a,d)}return e};if(t.documentElement.currentStyle)aa=function(a,b){var d,e,f=a.currentStyle&&a.currentStyle[b],h=a.style;if(!Fa.test(f)&&jb.test(f)){d=h.left;
129 e=a.runtimeStyle.left;a.runtimeStyle.left=a.currentStyle.left;h.left=b==="fontSize"?"1em":f||0;f=h.pixelLeft+"px";h.left=d;a.runtimeStyle.left=e}return f===""?"auto":f};W=Ga||aa;if(c.expr&&c.expr.filters){c.expr.filters.hidden=function(a){var b=a.offsetHeight;return a.offsetWidth===0&&b===0||!c.support.reliableHiddenOffsets&&(a.style.display||c.css(a,"display"))==="none"};c.expr.filters.visible=function(a){return!c.expr.filters.hidden(a)}}var mb=c.now(),nb=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
130 ob=/^(?:select|textarea)/i,pb=/^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,qb=/^(?:GET|HEAD)$/,Ra=/\[\]$/,T=/\=\?(&|$)/,ja=/\?/,rb=/([?&])_=[^&]*/,sb=/^(\w+:)?\/\/([^\/?#]+)/,tb=/%20/g,ub=/#.*$/,Ha=c.fn.load;c.fn.extend({load:function(a,b,d){if(typeof a!=="string"&&Ha)return Ha.apply(this,arguments);else if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var f=a.slice(e,a.length);a=a.slice(0,e)}e="GET";if(b)if(c.isFunction(b)){d=b;b=null}else if(typeof b===
131 "object"){b=c.param(b,c.ajaxSettings.traditional);e="POST"}var h=this;c.ajax({url:a,type:e,dataType:"html",data:b,complete:function(l,k){if(k==="success"||k==="notmodified")h.html(f?c("<div>").append(l.responseText.replace(nb,"")).find(f):l.responseText);d&&h.each(d,[l.responseText,k,l])}});return this},serialize:function(){return c.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?c.makeArray(this.elements):this}).filter(function(){return this.name&&
132 !this.disabled&&(this.checked||ob.test(this.nodeName)||pb.test(this.type))}).map(function(a,b){var d=c(this).val();return d==null?null:c.isArray(d)?c.map(d,function(e){return{name:b.name,value:e}}):{name:b.name,value:d}}).get()}});c.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){c.fn[b]=function(d){return this.bind(b,d)}});c.extend({get:function(a,b,d,e){if(c.isFunction(b)){e=e||d;d=b;b=null}return c.ajax({type:"GET",url:a,data:b,success:d,dataType:e})},
133 getScript:function(a,b){return c.get(a,null,b,"script")},getJSON:function(a,b,d){return c.get(a,b,d,"json")},post:function(a,b,d,e){if(c.isFunction(b)){e=e||d;d=b;b={}}return c.ajax({type:"POST",url:a,data:b,success:d,dataType:e})},ajaxSetup:function(a){c.extend(c.ajaxSettings,a)},ajaxSettings:{url:location.href,global:true,type:"GET",contentType:"application/x-www-form-urlencoded",processData:true,async:true,xhr:function(){return new E.XMLHttpRequest},accepts:{xml:"application/xml, text/xml",html:"text/html",
134 script:"text/javascript, application/javascript",json:"application/json, text/javascript",text:"text/plain",_default:"*/*"}},ajax:function(a){var b=c.extend(true,{},c.ajaxSettings,a),d,e,f,h=b.type.toUpperCase(),l=qb.test(h);b.url=b.url.replace(ub,"");b.context=a&&a.context!=null?a.context:b;if(b.data&&b.processData&&typeof b.data!=="string")b.data=c.param(b.data,b.traditional);if(b.dataType==="jsonp"){if(h==="GET")T.test(b.url)||(b.url+=(ja.test(b.url)?"&":"?")+(b.jsonp||"callback")+"=?");else if(!b.data||
135 !T.test(b.data))b.data=(b.data?b.data+"&":"")+(b.jsonp||"callback")+"=?";b.dataType="json"}if(b.dataType==="json"&&(b.data&&T.test(b.data)||T.test(b.url))){d=b.jsonpCallback||"jsonp"+mb++;if(b.data)b.data=(b.data+"").replace(T,"="+d+"$1");b.url=b.url.replace(T,"="+d+"$1");b.dataType="script";var k=E[d];E[d]=function(m){if(c.isFunction(k))k(m);else{E[d]=B;try{delete E[d]}catch(p){}}f=m;c.handleSuccess(b,w,e,f);c.handleComplete(b,w,e,f);r&&r.removeChild(A)}}if(b.dataType==="script"&&b.cache===null)b.cache=
136 false;if(b.cache===false&&l){var o=c.now(),x=b.url.replace(rb,"$1_="+o);b.url=x+(x===b.url?(ja.test(b.url)?"&":"?")+"_="+o:"")}if(b.data&&l)b.url+=(ja.test(b.url)?"&":"?")+b.data;b.global&&c.active++===0&&c.event.trigger("ajaxStart");o=(o=sb.exec(b.url))&&(o[1]&&o[1].toLowerCase()!==location.protocol||o[2].toLowerCase()!==location.host);if(b.dataType==="script"&&h==="GET"&&o){var r=t.getElementsByTagName("head")[0]||t.documentElement,A=t.createElement("script");if(b.scriptCharset)A.charset=b.scriptCharset;
137 A.src=b.url;if(!d){var C=false;A.onload=A.onreadystatechange=function(){if(!C&&(!this.readyState||this.readyState==="loaded"||this.readyState==="complete")){C=true;c.handleSuccess(b,w,e,f);c.handleComplete(b,w,e,f);A.onload=A.onreadystatechange=null;r&&A.parentNode&&r.removeChild(A)}}}r.insertBefore(A,r.firstChild);return B}var J=false,w=b.xhr();if(w){b.username?w.open(h,b.url,b.async,b.username,b.password):w.open(h,b.url,b.async);try{if(b.data!=null&&!l||a&&a.contentType)w.setRequestHeader("Content-Type",
138 b.contentType);if(b.ifModified){c.lastModified[b.url]&&w.setRequestHeader("If-Modified-Since",c.lastModified[b.url]);c.etag[b.url]&&w.setRequestHeader("If-None-Match",c.etag[b.url])}o||w.setRequestHeader("X-Requested-With","XMLHttpRequest");w.setRequestHeader("Accept",b.dataType&&b.accepts[b.dataType]?b.accepts[b.dataType]+", */*; q=0.01":b.accepts._default)}catch(I){}if(b.beforeSend&&b.beforeSend.call(b.context,w,b)===false){b.global&&c.active--===1&&c.event.trigger("ajaxStop");w.abort();return false}b.global&&
139 c.triggerGlobal(b,"ajaxSend",[w,b]);var L=w.onreadystatechange=function(m){if(!w||w.readyState===0||m==="abort"){J||c.handleComplete(b,w,e,f);J=true;if(w)w.onreadystatechange=c.noop}else if(!J&&w&&(w.readyState===4||m==="timeout")){J=true;w.onreadystatechange=c.noop;e=m==="timeout"?"timeout":!c.httpSuccess(w)?"error":b.ifModified&&c.httpNotModified(w,b.url)?"notmodified":"success";var p;if(e==="success")try{f=c.httpData(w,b.dataType,b)}catch(q){e="parsererror";p=q}if(e==="success"||e==="notmodified")d||
140 c.handleSuccess(b,w,e,f);else c.handleError(b,w,e,p);d||c.handleComplete(b,w,e,f);m==="timeout"&&w.abort();if(b.async)w=null}};try{var g=w.abort;w.abort=function(){w&&Function.prototype.call.call(g,w);L("abort")}}catch(i){}b.async&&b.timeout>0&&setTimeout(function(){w&&!J&&L("timeout")},b.timeout);try{w.send(l||b.data==null?null:b.data)}catch(n){c.handleError(b,w,null,n);c.handleComplete(b,w,e,f)}b.async||L();return w}},param:function(a,b){var d=[],e=function(h,l){l=c.isFunction(l)?l():l;d[d.length]=
141 encodeURIComponent(h)+"="+encodeURIComponent(l)};if(b===B)b=c.ajaxSettings.traditional;if(c.isArray(a)||a.jquery)c.each(a,function(){e(this.name,this.value)});else for(var f in a)da(f,a[f],b,e);return d.join("&").replace(tb,"+")}});c.extend({active:0,lastModified:{},etag:{},handleError:function(a,b,d,e){a.error&&a.error.call(a.context,b,d,e);a.global&&c.triggerGlobal(a,"ajaxError",[b,a,e])},handleSuccess:function(a,b,d,e){a.success&&a.success.call(a.context,e,d,b);a.global&&c.triggerGlobal(a,"ajaxSuccess",
142 [b,a])},handleComplete:function(a,b,d){a.complete&&a.complete.call(a.context,b,d);a.global&&c.triggerGlobal(a,"ajaxComplete",[b,a]);a.global&&c.active--===1&&c.event.trigger("ajaxStop")},triggerGlobal:function(a,b,d){(a.context&&a.context.url==null?c(a.context):c.event).trigger(b,d)},httpSuccess:function(a){try{return!a.status&&location.protocol==="file:"||a.status>=200&&a.status<300||a.status===304||a.status===1223}catch(b){}return false},httpNotModified:function(a,b){var d=a.getResponseHeader("Last-Modified"),
143 e=a.getResponseHeader("Etag");if(d)c.lastModified[b]=d;if(e)c.etag[b]=e;return a.status===304},httpData:function(a,b,d){var e=a.getResponseHeader("content-type")||"",f=b==="xml"||!b&&e.indexOf("xml")>=0;a=f?a.responseXML:a.responseText;f&&a.documentElement.nodeName==="parsererror"&&c.error("parsererror");if(d&&d.dataFilter)a=d.dataFilter(a,b);if(typeof a==="string")if(b==="json"||!b&&e.indexOf("json")>=0)a=c.parseJSON(a);else if(b==="script"||!b&&e.indexOf("javascript")>=0)c.globalEval(a);return a}});
144 if(E.ActiveXObject)c.ajaxSettings.xhr=function(){if(E.location.protocol!=="file:")try{return new E.XMLHttpRequest}catch(a){}try{return new E.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}};c.support.ajax=!!c.ajaxSettings.xhr();var ea={},vb=/^(?:toggle|show|hide)$/,wb=/^([+\-]=)?([\d+.\-]+)(.*)$/,ba,pa=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]];c.fn.extend({show:function(a,b,d){if(a||a===0)return this.animate(S("show",
145 3),a,b,d);else{d=0;for(var e=this.length;d<e;d++){a=this[d];b=a.style.display;if(!c.data(a,"olddisplay")&&b==="none")b=a.style.display="";b===""&&c.css(a,"display")==="none"&&c.data(a,"olddisplay",qa(a.nodeName))}for(d=0;d<e;d++){a=this[d];b=a.style.display;if(b===""||b==="none")a.style.display=c.data(a,"olddisplay")||""}return this}},hide:function(a,b,d){if(a||a===0)return this.animate(S("hide",3),a,b,d);else{a=0;for(b=this.length;a<b;a++){d=c.css(this[a],"display");d!=="none"&&c.data(this[a],"olddisplay",
146 d)}for(a=0;a<b;a++)this[a].style.display="none";return this}},_toggle:c.fn.toggle,toggle:function(a,b,d){var e=typeof a==="boolean";if(c.isFunction(a)&&c.isFunction(b))this._toggle.apply(this,arguments);else a==null||e?this.each(function(){var f=e?a:c(this).is(":hidden");c(this)[f?"show":"hide"]()}):this.animate(S("toggle",3),a,b,d);return this},fadeTo:function(a,b,d,e){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,d,e)},animate:function(a,b,d,e){var f=c.speed(b,
147 d,e);if(c.isEmptyObject(a))return this.each(f.complete);return this[f.queue===false?"each":"queue"](function(){var h=c.extend({},f),l,k=this.nodeType===1,o=k&&c(this).is(":hidden"),x=this;for(l in a){var r=c.camelCase(l);if(l!==r){a[r]=a[l];delete a[l];l=r}if(a[l]==="hide"&&o||a[l]==="show"&&!o)return h.complete.call(this);if(k&&(l==="height"||l==="width")){h.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY];if(c.css(this,"display")==="inline"&&c.css(this,"float")==="none")if(c.support.inlineBlockNeedsLayout)if(qa(this.nodeName)===
148 "inline")this.style.display="inline-block";else{this.style.display="inline";this.style.zoom=1}else this.style.display="inline-block"}if(c.isArray(a[l])){(h.specialEasing=h.specialEasing||{})[l]=a[l][1];a[l]=a[l][0]}}if(h.overflow!=null)this.style.overflow="hidden";h.curAnim=c.extend({},a);c.each(a,function(A,C){var J=new c.fx(x,h,A);if(vb.test(C))J[C==="toggle"?o?"show":"hide":C](a);else{var w=wb.exec(C),I=J.cur()||0;if(w){var L=parseFloat(w[2]),g=w[3]||"px";if(g!=="px"){c.style(x,A,(L||1)+g);I=(L||
149 1)/J.cur()*I;c.style(x,A,I+g)}if(w[1])L=(w[1]==="-="?-1:1)*L+I;J.custom(I,L,g)}else J.custom(I,C,"")}});return true})},stop:function(a,b){var d=c.timers;a&&this.queue([]);this.each(function(){for(var e=d.length-1;e>=0;e--)if(d[e].elem===this){b&&d[e](true);d.splice(e,1)}});b||this.dequeue();return this}});c.each({slideDown:S("show",1),slideUp:S("hide",1),slideToggle:S("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){c.fn[a]=function(d,e,f){return this.animate(b,
150 d,e,f)}});c.extend({speed:function(a,b,d){var e=a&&typeof a==="object"?c.extend({},a):{complete:d||!d&&b||c.isFunction(a)&&a,duration:a,easing:d&&b||b&&!c.isFunction(b)&&b};e.duration=c.fx.off?0:typeof e.duration==="number"?e.duration:e.duration in c.fx.speeds?c.fx.speeds[e.duration]:c.fx.speeds._default;e.old=e.complete;e.complete=function(){e.queue!==false&&c(this).dequeue();c.isFunction(e.old)&&e.old.call(this)};return e},easing:{linear:function(a,b,d,e){return d+e*a},swing:function(a,b,d,e){return(-Math.cos(a*
151 Math.PI)/2+0.5)*e+d}},timers:[],fx:function(a,b,d){this.options=b;this.elem=a;this.prop=d;if(!b.orig)b.orig={}}});c.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this);(c.fx.step[this.prop]||c.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a=parseFloat(c.css(this.elem,this.prop));return a&&a>-1E4?a:0},custom:function(a,b,d){function e(l){return f.step(l)}
152 var f=this,h=c.fx;this.startTime=c.now();this.start=a;this.end=b;this.unit=d||this.unit||"px";this.now=this.start;this.pos=this.state=0;e.elem=this.elem;if(e()&&c.timers.push(e)&&!ba)ba=setInterval(h.tick,h.interval)},show:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.show=true;this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur());c(this.elem).show()},hide:function(){this.options.orig[this.prop]=c.style(this.elem,this.prop);this.options.hide=true;
153 this.custom(this.cur(),0)},step:function(a){var b=c.now(),d=true;if(a||b>=this.options.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();this.options.curAnim[this.prop]=true;for(var e in this.options.curAnim)if(this.options.curAnim[e]!==true)d=false;if(d){if(this.options.overflow!=null&&!c.support.shrinkWrapBlocks){var f=this.elem,h=this.options;c.each(["","X","Y"],function(k,o){f.style["overflow"+o]=h.overflow[k]})}this.options.hide&&c(this.elem).hide();if(this.options.hide||
154 this.options.show)for(var l in this.options.curAnim)c.style(this.elem,l,this.options.orig[l]);this.options.complete.call(this.elem)}return false}else{a=b-this.startTime;this.state=a/this.options.duration;b=this.options.easing||(c.easing.swing?"swing":"linear");this.pos=c.easing[this.options.specialEasing&&this.options.specialEasing[this.prop]||b](this.state,a,0,1,this.options.duration);this.now=this.start+(this.end-this.start)*this.pos;this.update()}return true}};c.extend(c.fx,{tick:function(){for(var a=
155 c.timers,b=0;b<a.length;b++)a[b]()||a.splice(b--,1);a.length||c.fx.stop()},interval:13,stop:function(){clearInterval(ba);ba=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){c.style(a.elem,"opacity",a.now)},_default:function(a){if(a.elem.style&&a.elem.style[a.prop]!=null)a.elem.style[a.prop]=(a.prop==="width"||a.prop==="height"?Math.max(0,a.now):a.now)+a.unit;else a.elem[a.prop]=a.now}}});if(c.expr&&c.expr.filters)c.expr.filters.animated=function(a){return c.grep(c.timers,function(b){return a===
156 b.elem}).length};var xb=/^t(?:able|d|h)$/i,Ia=/^(?:body|html)$/i;c.fn.offset="getBoundingClientRect"in t.documentElement?function(a){var b=this[0],d;if(a)return this.each(function(l){c.offset.setOffset(this,a,l)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);try{d=b.getBoundingClientRect()}catch(e){}var f=b.ownerDocument,h=f.documentElement;if(!d||!c.contains(h,b))return d||{top:0,left:0};b=f.body;f=fa(f);return{top:d.top+(f.pageYOffset||c.support.boxModel&&
157 h.scrollTop||b.scrollTop)-(h.clientTop||b.clientTop||0),left:d.left+(f.pageXOffset||c.support.boxModel&&h.scrollLeft||b.scrollLeft)-(h.clientLeft||b.clientLeft||0)}}:function(a){var b=this[0];if(a)return this.each(function(x){c.offset.setOffset(this,a,x)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return c.offset.bodyOffset(b);c.offset.initialize();var d,e=b.offsetParent,f=b.ownerDocument,h=f.documentElement,l=f.body;d=(f=f.defaultView)?f.getComputedStyle(b,null):b.currentStyle;
158 for(var k=b.offsetTop,o=b.offsetLeft;(b=b.parentNode)&&b!==l&&b!==h;){if(c.offset.supportsFixedPosition&&d.position==="fixed")break;d=f?f.getComputedStyle(b,null):b.currentStyle;k-=b.scrollTop;o-=b.scrollLeft;if(b===e){k+=b.offsetTop;o+=b.offsetLeft;if(c.offset.doesNotAddBorder&&!(c.offset.doesAddBorderForTableAndCells&&xb.test(b.nodeName))){k+=parseFloat(d.borderTopWidth)||0;o+=parseFloat(d.borderLeftWidth)||0}e=b.offsetParent}if(c.offset.subtractsBorderForOverflowNotVisible&&d.overflow!=="visible"){k+=
159 parseFloat(d.borderTopWidth)||0;o+=parseFloat(d.borderLeftWidth)||0}d=d}if(d.position==="relative"||d.position==="static"){k+=l.offsetTop;o+=l.offsetLeft}if(c.offset.supportsFixedPosition&&d.position==="fixed"){k+=Math.max(h.scrollTop,l.scrollTop);o+=Math.max(h.scrollLeft,l.scrollLeft)}return{top:k,left:o}};c.offset={initialize:function(){var a=t.body,b=t.createElement("div"),d,e,f,h=parseFloat(c.css(a,"marginTop"))||0;c.extend(b.style,{position:"absolute",top:0,left:0,margin:0,border:0,width:"1px",
160 height:"1px",visibility:"hidden"});b.innerHTML="<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>";a.insertBefore(b,a.firstChild);d=b.firstChild;e=d.firstChild;f=d.nextSibling.firstChild.firstChild;this.doesNotAddBorder=e.offsetTop!==5;this.doesAddBorderForTableAndCells=
161 f.offsetTop===5;e.style.position="fixed";e.style.top="20px";this.supportsFixedPosition=e.offsetTop===20||e.offsetTop===15;e.style.position=e.style.top="";d.style.overflow="hidden";d.style.position="relative";this.subtractsBorderForOverflowNotVisible=e.offsetTop===-5;this.doesNotIncludeMarginInBodyOffset=a.offsetTop!==h;a.removeChild(b);c.offset.initialize=c.noop},bodyOffset:function(a){var b=a.offsetTop,d=a.offsetLeft;c.offset.initialize();if(c.offset.doesNotIncludeMarginInBodyOffset){b+=parseFloat(c.css(a,
162 "marginTop"))||0;d+=parseFloat(c.css(a,"marginLeft"))||0}return{top:b,left:d}},setOffset:function(a,b,d){var e=c.css(a,"position");if(e==="static")a.style.position="relative";var f=c(a),h=f.offset(),l=c.css(a,"top"),k=c.css(a,"left"),o=e==="absolute"&&c.inArray("auto",[l,k])>-1;e={};var x={};if(o)x=f.position();l=o?x.top:parseInt(l,10)||0;k=o?x.left:parseInt(k,10)||0;if(c.isFunction(b))b=b.call(a,d,h);if(b.top!=null)e.top=b.top-h.top+l;if(b.left!=null)e.left=b.left-h.left+k;"using"in b?b.using.call(a,
163 e):f.css(e)}};c.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),d=this.offset(),e=Ia.test(b[0].nodeName)?{top:0,left:0}:b.offset();d.top-=parseFloat(c.css(a,"marginTop"))||0;d.left-=parseFloat(c.css(a,"marginLeft"))||0;e.top+=parseFloat(c.css(b[0],"borderTopWidth"))||0;e.left+=parseFloat(c.css(b[0],"borderLeftWidth"))||0;return{top:d.top-e.top,left:d.left-e.left}},offsetParent:function(){return this.map(function(){for(var a=this.offsetParent||t.body;a&&!Ia.test(a.nodeName)&&
164 c.css(a,"position")==="static";)a=a.offsetParent;return a})}});c.each(["Left","Top"],function(a,b){var d="scroll"+b;c.fn[d]=function(e){var f=this[0],h;if(!f)return null;if(e!==B)return this.each(function(){if(h=fa(this))h.scrollTo(!a?e:c(h).scrollLeft(),a?e:c(h).scrollTop());else this[d]=e});else return(h=fa(f))?"pageXOffset"in h?h[a?"pageYOffset":"pageXOffset"]:c.support.boxModel&&h.document.documentElement[d]||h.document.body[d]:f[d]}});c.each(["Height","Width"],function(a,b){var d=b.toLowerCase();
165 c.fn["inner"+b]=function(){return this[0]?parseFloat(c.css(this[0],d,"padding")):null};c.fn["outer"+b]=function(e){return this[0]?parseFloat(c.css(this[0],d,e?"margin":"border")):null};c.fn[d]=function(e){var f=this[0];if(!f)return e==null?null:this;if(c.isFunction(e))return this.each(function(l){var k=c(this);k[d](e.call(this,l,k[d]()))});if(c.isWindow(f))return f.document.compatMode==="CSS1Compat"&&f.document.documentElement["client"+b]||f.document.body["client"+b];else if(f.nodeType===9)return Math.max(f.documentElement["client"+
166 b],f.body["scroll"+b],f.documentElement["scroll"+b],f.body["offset"+b],f.documentElement["offset"+b]);else if(e===B){f=c.css(f,d);var h=parseFloat(f);return c.isNaN(h)?f:h}else return this.css(d,typeof e==="string"?e:e+"px")}})})(window);
1919
2020 from werkzeug.utils import cached_property, escape
2121 from werkzeug.debug.console import Console
22 from werkzeug._compat import range_type, PY2, text_type, string_types
23
24
25 _coding_re = re.compile(r'coding[:=]\s*([-\w.]+)')
26 _line_re = re.compile(r'^(.*?)$(?m)')
22 from werkzeug._compat import range_type, PY2, text_type, string_types, \
23 to_native, to_unicode
24
25
26 _coding_re = re.compile(br'coding[:=]\s*([-\w.]+)')
27 _line_re = re.compile(br'^(.*?)$(?m)')
2728 _funcdef_re = re.compile(r'^(\s*def\s)|(.*(?<!\w)lambda(:|\s))|^(\s*@)')
28 UTF8_COOKIE = '\xef\xbb\xbf'
29 UTF8_COOKIE = b'\xef\xbb\xbf'
2930
3031 system_exceptions = (SystemExit, KeyboardInterrupt)
3132 try:
268269 logfile = sys.stderr
269270 tb = self.plaintext.rstrip() + u'\n'
270271 if PY2:
271 tb.encode('utf-8', 'replace')
272 tb = tb.encode('utf-8', 'replace')
272273 logfile.write(tb)
273274
274275 def paste(self):
376377 # if it's a file on the file system resolve the real filename.
377378 if os.path.isfile(fn):
378379 fn = os.path.realpath(fn)
379 self.filename = fn
380 self.filename = to_unicode(fn, sys.getfilesystemencoding())
380381 self.module = self.globals.get('__name__')
381382 self.loader = self.globals.get('__loader__')
382383 self.code = tb.tb_frame.f_code
459460
460461 if source is None:
461462 try:
462 f = open(self.filename)
463 f = open(self.filename, mode='rb')
463464 except IOError:
464465 return []
465466 try:
478479 source = source[3:]
479480 else:
480481 for idx, match in enumerate(_line_re.finditer(source)):
481 match = _line_re.search(match.group())
482 match = _coding_re.search(match.group())
482483 if match is not None:
483484 charset = match.group(1)
484485 break
486487 break
487488
488489 # on broken cookies we fall back to utf-8 too
490 charset = to_native(charset)
489491 try:
490492 codecs.lookup(charset)
491493 except LookupError:
180180 it is silenced by default as far as Werkzeug is concerned.
181181
182182 Since disconnections cannot be reliably detected and are unspecified
183 by WSGI to a large extend this might or might not be raised if a client
183 by WSGI to a large extent this might or might not be raised if a client
184184 is gone.
185185
186186 .. versionadded:: 0.8
312312 """
313313 code = 410
314314 description = (
315 'The requested URL is no longer available on this server and '
316 'there is no forwarding address.</p><p>If you followed a link '
317 'from a foreign page, please contact the author of this page.'
315 'The requested URL is no longer available on this server and there '
316 'is no forwarding address. If you followed a link from a foreign '
317 'page, please contact the author of this page.'
318318 )
319319
320320
535535 )
536536
537537
538 class GatewayTimeout(HTTPException):
539 """*504* `Gateway Timeout`
540
541 Status code you should return if a connection to an upstream server
542 times out.
543 """
544 code = 504
545 description = (
546 'The connection to an upstream server timed out.'
547 )
548
549
550 class HTTPVersionNotSupported(HTTPException):
551 """*505* `HTTP Version Not Supported`
552
553 The server does not support the HTTP protocol version used in the request.
554 """
555 code = 505
556 description = (
557 'The server does not support the HTTP protocol version used in the '
558 'request.'
559 )
560
561
538562 default_exceptions = {}
539563 __all__ = ['HTTPException']
540564
541565 def _find_exceptions():
542566 for name, obj in iteritems(globals()):
543567 try:
544 if getattr(obj, 'code', None) is not None:
545 default_exceptions[obj.code] = obj
546 __all__.append(obj.__name__)
547 except TypeError: # pragma: no cover
568 is_http_exception = issubclass(obj, HTTPException)
569 except TypeError:
570 is_http_exception = False
571 if not is_http_exception or obj.code is None:
548572 continue
573 __all__.append(obj.__name__)
574 old_obj = default_exceptions.get(obj.code, None)
575 if old_obj is not None and issubclass(obj, old_obj):
576 continue
577 default_exceptions[obj.code] = obj
549578 _find_exceptions()
550579 del _find_exceptions
551580
3636 integer_types
3737
3838
39 # incorrect
4039 _cookie_charset = 'latin1'
41 _accept_re = re.compile(r'([^\s;,]+)(?:[^,]*?;\s*q=(\d*(?:\.\d+)?))?')
40 # for explanation of "media-range", etc. see Sections 5.3.{1,2} of RFC 7231
41 _accept_re = re.compile(
42 r'''( # media-range capturing-parenthesis
43 [^\s;,]+ # type/subtype
44 (?:[ \t]*;[ \t]* # ";"
45 (?: # parameter non-capturing-parenthesis
46 [^\s;,q][^\s;,]* # token that doesn't start with "q"
47 | # or
48 q[^\s;,=][^\s;,]* # token that is more than just "q"
49 )
50 )* # zero or more parameters
51 ) # end of media-range
52 (?:[ \t]*;[ \t]*q= # weight is a "q" parameter
53 (\d*(?:\.\d+)?) # qvalue capturing-parentheses
54 [^,]* # "extension" accept params: who cares?
55 )? # accept params are optional
56 ''', re.VERBOSE)
4257 _token_chars = frozenset("!#$%&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
4358 '^_`abcdefghijklmnopqrstuvwxyz|~')
4459 _etag_re = re.compile(r'([Ww]/)?(?:"(.*?)"|(.*?))(?:\s*,\s*|$)')
202202 scoped sessions) to the Werkzeug locals.
203203
204204 .. versionchanged:: 0.7
205 Yu can pass a different ident function to the local manager that
205 You can pass a different ident function to the local manager that
206206 will then be propagated to all the locals passed to the
207207 constructor.
208208 """
2121 import errno
2222 import time
2323 import random
24 from ._compat import to_unicode
2425
2526
2627 can_rename_open_file = False
3637 _MoveFileEx = ctypes.windll.kernel32.MoveFileExW
3738
3839 def _rename(src, dst):
39 if not isinstance(src, unicode):
40 src = unicode(src, sys.getfilesystemencoding())
41 if not isinstance(dst, unicode):
42 dst = unicode(dst, sys.getfilesystemencoding())
40 src = to_unicode(src, sys.getfilesystemencoding())
41 dst = to_unicode(dst, sys.getfilesystemencoding())
4342 if _rename_atomic(src, dst):
4443 return True
4544 retry = 0
9595 :license: BSD, see LICENSE for more details.
9696 """
9797 import re
98 import uuid
9899 import posixpath
100
99101 from pprint import pformat
102 from threading import Lock
100103
101104 from werkzeug.urls import url_encode, url_quote, url_join
102105 from werkzeug.utils import redirect, format_string
560563 self._trace = self._converters = self._regex = self._weights = None
561564
562565 def empty(self):
563 """Return an unbound copy of this rule. This can be useful if you
564 want to reuse an already bound URL for another map."""
566 """
567 Return an unbound copy of this rule.
568
569 This can be useful if want to reuse an already bound URL for another
570 map. See ``get_empty_kwargs`` to override what keyword arguments are
571 provided to the new copy.
572 """
573 return type(self)(self.rule, **self.get_empty_kwargs())
574
575 def get_empty_kwargs(self):
576 """
577 Provides kwargs for instantiating empty copy with empty()
578
579 Use this method to provide custom keyword arguments to the subclass of
580 ``Rule`` when calling ``some_rule.empty()``. Helpful when the subclass
581 has custom keyword arguments that are needed at instantiation.
582
583 Must return a ``dict`` that will be provided as kwargs to the new
584 instance of ``Rule``, following the initial ``self.rule`` value which
585 is always provided as the first, required positional argument.
586 """
565587 defaults = None
566588 if self.defaults:
567589 defaults = dict(self.defaults)
568 return Rule(self.rule, defaults, self.subdomain, self.methods,
569 self.build_only, self.endpoint, self.strict_slashes,
570 self.redirect_to, self.alias, self.host)
590 return dict(defaults=defaults, subdomain=self.subdomain,
591 methods=self.methods, build_only=self.build_only,
592 endpoint=self.endpoint, strict_slashes=self.strict_slashes,
593 redirect_to=self.redirect_to, alias=self.alias,
594 host=self.host)
571595
572596 def get_rules(self, map):
573597 yield self
601625
602626 .. versionadded:: 0.9
603627 """
604 if not converter_name in self.map.converters:
628 if converter_name not in self.map.converters:
605629 raise LookupError('the converter %r does not exist' % converter_name)
606630 return self.map.converters[converter_name](self.map, *args, **kwargs)
607631
966990 NumberConverter.__init__(self, map, 0, min, max)
967991
968992
993 class UUIDConverter(BaseConverter):
994 """This converter only accepts UUID strings::
995
996 Rule('/object/<uuid:identifier>')
997
998 .. versionadded:: 0.10
999
1000 :param map: the :class:`Map`.
1001 """
1002 regex = r'[A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-' \
1003 r'[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}'
1004
1005 def to_python(self, value):
1006 return uuid.UUID(value)
1007
1008 def to_url(self, value):
1009 return str(value)
1010
1011
9691012 #: the default converter mapping for the map.
9701013 DEFAULT_CONVERTERS = {
9711014 'default': UnicodeConverter,
9731016 'any': AnyConverter,
9741017 'path': PathConverter,
9751018 'int': IntegerConverter,
976 'float': FloatConverter
1019 'float': FloatConverter,
1020 'uuid': UUIDConverter,
9771021 }
9781022
9791023
10221066 self._rules = []
10231067 self._rules_by_endpoint = {}
10241068 self._remap = True
1069 self._remap_lock = Lock()
10251070
10261071 self.default_subdomain = default_subdomain
10271072 self.charset = charset
12051250 """Called before matching and building to keep the compiled rules
12061251 in the correct order after things changed.
12071252 """
1208 if self._remap:
1253 if not self._remap:
1254 return
1255
1256 with self._remap_lock:
1257 if not self._remap:
1258 return
1259
12091260 self._rules.sort(key=lambda x: x.match_compare_key())
12101261 for rules in itervalues(self._rules_by_endpoint):
12111262 rules.sort(key=lambda x: x.build_compare_key())
13771428 query_args = self.query_args
13781429 method = (method or self.default_method).upper()
13791430
1380 path = u'%s|/%s' % (self.map.host_matching and self.server_name or
1381 self.subdomain, path_info.lstrip('/'))
1431 path = u'%s|%s' % (
1432 self.map.host_matching and self.server_name or self.subdomain,
1433 path_info and '/%s' % path_info.lstrip('/')
1434 )
13821435
13831436 have_match_for = set()
13841437 for rule in self.map._rules:
14131466 else:
14141467 redirect_url = rule.redirect_to(self, **rv)
14151468 raise RequestRedirect(str(url_join('%s://%s%s%s' % (
1416 self.url_scheme,
1469 self.url_scheme or 'http',
14171470 self.subdomain and self.subdomain + '.' or '',
14181471 self.server_name,
14191472 self.script_name
15081561 if query_args:
15091562 suffix = '?' + self.encode_query_args(query_args)
15101563 return str('%s://%s/%s%s' % (
1511 self.url_scheme,
1564 self.url_scheme or 'http',
15121565 self.get_host(domain_part),
15131566 posixpath.join(self.script_name[:-1].lstrip('/'),
15141567 path_info.lstrip('/')),
15801633 >>> urls.build("index", {'q': 'My Searchstring'})
15811634 '/?q=My+Searchstring'
15821635
1636 When processing those additional values, lists are furthermore
1637 interpreted as multiple values (as per
1638 :py:class:`werkzeug.datastructures.MultiDict`):
1639
1640 >>> urls.build("index", {'q': ['a', 'b', 'c']})
1641 '/?q=a&q=b&q=c'
1642
15831643 If a rule does not exist when building a `BuildError` exception is
15841644 raised.
15851645
15951655 appended to the URL as query parameters.
15961656 :param method: the HTTP method for the rule if there are different
15971657 URLs for different methods on the same endpoint.
1598 :param force_external: enforce full canonical external URLs.
1658 :param force_external: enforce full canonical external URLs. If the URL
1659 scheme is not provided, this will generate
1660 a protocol-relative URL.
15991661 :param append_unknown: unknown parameters are appended to the generated
16001662 URL as query string argument. Disable this
16011663 if you want the builder to ignore those.
16031665 self.map.update()
16041666 if values:
16051667 if isinstance(values, MultiDict):
1606 valueiter = values.iteritems(multi=True)
1668 valueiter = iteritems(values, multi=True)
16071669 else:
16081670 valueiter = iteritems(values)
16091671 values = dict((k, v) for k, v in valueiter if v is not None)
16221684 (self.map.host_matching and host == self.server_name) or
16231685 (not self.map.host_matching and domain_part == self.subdomain)):
16241686 return str(url_join(self.script_name, './' + path.lstrip('/')))
1625 return str('%s://%s%s/%s' % (
1626 self.url_scheme,
1687 return str('%s//%s%s/%s' % (
1688 self.url_scheme + ':' if self.url_scheme else '',
16271689 host,
16281690 self.script_name[:-1],
16291691 path.lstrip('/')
193193
194194 def print_usage(actions):
195195 """Print the usage information. (Help screen)"""
196 actions = actions.items()
197 actions.sort()
196 actions = sorted(iteritems(actions))
198197 print('usage: %s <action> [<options>]' % basename(sys.argv[0]))
199198 print(' %s --help' % basename(sys.argv[0]))
200199 print()
309308 """Start a new development server."""
310309 from werkzeug.serving import run_simple
311310 app = app_factory()
312 run_simple(hostname, port, app, reloader, debugger, evalex,
313 extra_files, 1, threaded, processes,
311 run_simple(hostname, port, app,
312 use_reloader=reloader, use_debugger=debugger,
313 use_evalex=evalex, extra_files=extra_files,
314 reloader_interval=1, threaded=threaded, processes=processes,
314315 static_files=static_files, ssl_context=ssl_context)
315316 return action
4747
4848 def pbkdf2_hex(data, salt, iterations=DEFAULT_PBKDF2_ITERATIONS,
4949 keylen=None, hashfunc=None):
50 """Like :func:`pbkdf2_bin` but returns a hex encoded string.
50 """Like :func:`pbkdf2_bin`, but returns a hex-encoded string.
51
52 .. versionadded:: 0.9
53
54 :param data: the data to derive.
55 :param salt: the salt for the derivation.
56 :param iterations: the number of iterations.
57 :param keylen: the length of the resulting key. If not provided,
58 the digest size will be used.
59 :param hashfunc: the hash function to use. This can either be the
60 string name of a known hash function, or a function
61 from the hashlib module. Defaults to sha1.
62 """
63 rv = pbkdf2_bin(data, salt, iterations, keylen, hashfunc)
64 return to_native(codecs.encode(rv, 'hex_codec'))
65
66
67 _has_native_pbkdf2 = hasattr(hashlib, 'pbkdf2_hmac')
68
69
70 def pbkdf2_bin(data, salt, iterations=DEFAULT_PBKDF2_ITERATIONS,
71 keylen=None, hashfunc=None):
72 """Returns a binary digest for the PBKDF2 hash algorithm of `data`
73 with the given `salt`. It iterates `iterations` times and produces a
74 key of `keylen` bytes. By default, SHA-1 is used as hash function;
75 a different hashlib `hashfunc` can be provided.
5176
5277 .. versionadded:: 0.9
5378
6085 string name of a known hash function or a function
6186 from the hashlib module. Defaults to sha1.
6287 """
63 rv = pbkdf2_bin(data, salt, iterations, keylen, hashfunc)
64 return to_native(codecs.encode(rv, 'hex_codec'))
65
66
67 def pbkdf2_bin(data, salt, iterations=DEFAULT_PBKDF2_ITERATIONS,
68 keylen=None, hashfunc=None):
69 """Returns a binary digest for the PBKDF2 hash algorithm of `data`
70 with the given `salt`. It iterates `iterations` time and produces a
71 key of `keylen` bytes. By default SHA-1 is used as hash function,
72 a different hashlib `hashfunc` can be provided.
73
74 .. versionadded:: 0.9
75
76 :param data: the data to derive.
77 :param salt: the salt for the derivation.
78 :param iterations: the number of iterations.
79 :param keylen: the length of the resulting key. If not provided
80 the digest size will be used.
81 :param hashfunc: the hash function to use. This can either be the
82 string name of a known hash function or a function
83 from the hashlib module. Defaults to sha1.
84 """
8588 if isinstance(hashfunc, string_types):
8689 hashfunc = _hash_funcs[hashfunc]
8790 elif not hashfunc:
8891 hashfunc = hashlib.sha1
92 data = to_bytes(data)
8993 salt = to_bytes(salt)
90 mac = hmac.HMAC(to_bytes(data), None, hashfunc)
94
95 # If we're on Python with pbkdf2_hmac we can try to use it for
96 # compatible digests.
97 if _has_native_pbkdf2:
98 _test_hash = hashfunc()
99 if hasattr(_test_hash, 'name') and \
100 _test_hash.name in _hash_funcs:
101 return hashlib.pbkdf2_hmac(_test_hash.name,
102 data, salt, iterations,
103 keylen)
104
105 mac = hmac.HMAC(data, None, hashfunc)
91106 if not keylen:
92107 keylen = mac.digest_size
93108 def _pseudorandom(x, mac=mac):
108123 """This function compares strings in somewhat constant time. This
109124 requires that the length of at least one string is known in advance.
110125
111 Returns `True` if the two strings are equal or `False` if they are not.
126 Returns `True` if the two strings are equal, or `False` if they are not.
112127
113128 .. versionadded:: 0.7
114129 """
137152 def gen_salt(length):
138153 """Generate a random string of SALT_CHARS with specified ``length``."""
139154 if length <= 0:
140 raise ValueError('requested salt of length <= 0')
155 raise ValueError('Salt length must be positive')
141156 return ''.join(_sys_rng.choice(SALT_CHARS) for _ in range_type(length))
142157
143158
203218 pbkdf2:sha1:2000$salt$hash
204219 pbkdf2:sha1$salt$hash
205220
206 :param password: the password to hash
207 :param method: the hash method to use (one that hashlib supports), can
208 optionally be in the format ``pbpdf2:<method>[:iterations]``
221 :param password: the password to hash.
222 :param method: the hash method to use (one that hashlib supports). Can
223 optionally be in the format ``pbkdf2:<method>[:iterations]``
209224 to enable PBKDF2.
210 :param salt_length: the length of the salt in letters
225 :param salt_length: the length of the salt in letters.
211226 """
212227 salt = method != 'plain' and gen_salt(salt_length) or ''
213228 h, actual_method = _hash_internal(method, salt, password)
222237 Returns `True` if the password matched, `False` otherwise.
223238
224239 :param pwhash: a hashed string like returned by
225 :func:`generate_password_hash`
226 :param password: the plaintext password to compare against the hash
240 :func:`generate_password_hash`.
241 :param password: the plaintext password to compare against the hash.
227242 """
228243 if pwhash.count('$') < 2:
229244 return False
3939 import os
4040 import socket
4141 import sys
42 import time
42 import ssl
4343 import signal
44 import subprocess
45
46 try:
47 import thread
48 except ImportError:
49 import _thread as thread
44
45
46 def _get_openssl_crypto_module():
47 try:
48 from OpenSSL import crypto
49 except ImportError:
50 raise TypeError('Using ad-hoc certificates requires the pyOpenSSL '
51 'library.')
52 else:
53 return crypto
54
5055
5156 try:
5257 from SocketServer import ThreadingMixIn, ForkingMixIn
5762
5863 import werkzeug
5964 from werkzeug._internal import _log
60 from werkzeug._compat import iteritems, PY2, reraise, text_type, \
61 wsgi_encoding_dance
65 from werkzeug._compat import reraise, wsgi_encoding_dance
6266 from werkzeug.urls import url_parse, url_unquote
63 from werkzeug.exceptions import InternalServerError, BadRequest
67 from werkzeug.exceptions import InternalServerError
6468
6569
6670 class WSGIRequestHandler(BaseHTTPRequestHandler, object):
8791 'wsgi.multithread': self.server.multithread,
8892 'wsgi.multiprocess': self.server.multiprocess,
8993 'wsgi.run_once': False,
90 'werkzeug.server.shutdown':
91 shutdown_server,
94 'werkzeug.server.shutdown': shutdown_server,
9295 'SERVER_SOFTWARE': self.server_version,
9396 'REQUEST_METHOD': self.command,
9497 'SCRIPT_NAME': '',
269272
270273 def generate_adhoc_ssl_pair(cn=None):
271274 from random import random
272 from OpenSSL import crypto
275 crypto = _get_openssl_crypto_module()
273276
274277 # pretty damn sure that this is not actually accepted by anyone
275278 if cn is None:
276279 cn = '*'
277280
278281 cert = crypto.X509()
279 cert.set_serial_number(int(random() * sys.maxint))
282 cert.set_serial_number(int(random() * sys.maxsize))
280283 cert.gmtime_adj_notBefore(0)
281284 cert.gmtime_adj_notAfter(60 * 60 * 24 * 365)
282285
289292 issuer.O = 'Self-Signed'
290293
291294 pkey = crypto.PKey()
292 pkey.generate_key(crypto.TYPE_RSA, 768)
295 pkey.generate_key(crypto.TYPE_RSA, 1024)
293296 cert.set_pubkey(pkey)
294297 cert.sign(pkey, 'md5')
295298
322325 cert_file = base_path + '.crt'
323326 pkey_file = base_path + '.key'
324327
325 with open(cert_file, 'w') as f:
328 with open(cert_file, 'wb') as f:
326329 f.write(crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
327 with open(pkey_file, 'w') as f:
330 with open(pkey_file, 'wb') as f:
328331 f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
329332
330333 return cert_file, pkey_file
332335
333336 def generate_adhoc_ssl_context():
334337 """Generates an adhoc SSL context for the development server."""
335 from OpenSSL import SSL
338 crypto = _get_openssl_crypto_module()
339 import tempfile
340 import atexit
341
336342 cert, pkey = generate_adhoc_ssl_pair()
337 ctx = SSL.Context(SSL.SSLv23_METHOD)
338 ctx.use_privatekey(pkey)
339 ctx.use_certificate(cert)
343 cert_handle, cert_file = tempfile.mkstemp()
344 pkey_handle, pkey_file = tempfile.mkstemp()
345 atexit.register(os.remove, pkey_file)
346 atexit.register(os.remove, cert_file)
347
348 os.write(cert_handle, crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
349 os.write(pkey_handle, crypto.dump_privatekey(crypto.FILETYPE_PEM, pkey))
350 os.close(cert_handle)
351 os.close(pkey_handle)
352 ctx = load_ssl_context(cert_file, pkey_file)
340353 return ctx
341354
342355
343 def load_ssl_context(cert_file, pkey_file):
344 """Loads an SSL context from a certificate and private key file."""
345 from OpenSSL import SSL
346 ctx = SSL.Context(SSL.SSLv23_METHOD)
347 ctx.use_certificate_file(cert_file)
348 ctx.use_privatekey_file(pkey_file)
356 def load_ssl_context(cert_file, pkey_file=None, protocol=None):
357 """Loads SSL context from cert/private key files and optional protocol.
358 Many parameters are directly taken from the API of
359 :py:class:`ssl.SSLContext`.
360
361 :param cert_file: Path of the certificate to use.
362 :param pkey_file: Path of the private key to use. If not given, the key
363 will be obtained from the certificate file.
364 :param protocol: One of the ``PROTOCOL_*`` constants in the stdlib ``ssl``
365 module. Defaults to ``PROTOCOL_SSLv23``.
366 """
367 if protocol is None:
368 protocol = ssl.PROTOCOL_SSLv23
369 ctx = _SSLContext(protocol)
370 ctx.load_cert_chain(cert_file, pkey_file)
349371 return ctx
372
373
374 class _SSLContext(object):
375 '''A dummy class with a small subset of Python3's ``ssl.SSLContext``, only
376 intended to be used with and by Werkzeug.'''
377
378 def __init__(self, protocol):
379 self._protocol = protocol
380 self._certfile = None
381 self._keyfile = None
382 self._password = None
383
384 def load_cert_chain(self, certfile, keyfile=None, password=None):
385 self._certfile = certfile
386 self._keyfile = keyfile or certfile
387 self._password = password
388
389 def wrap_socket(self, sock, **kwargs):
390 return ssl.wrap_socket(sock, keyfile=self._keyfile,
391 certfile=self._certfile,
392 ssl_version=self._protocol, **kwargs)
350393
351394
352395 def is_ssl_error(error=None):
353396 """Checks if the given error (or the current one) is an SSL error."""
397 exc_types = (ssl.SSLError,)
398 try:
399 from OpenSSL.SSL import Error
400 exc_types += (Error,)
401 except ImportError:
402 pass
403
354404 if error is None:
355405 error = sys.exc_info()[1]
356 from OpenSSL import SSL
357 return isinstance(error, SSL.Error)
358
359
360 class _SSLConnectionFix(object):
361 """Wrapper around SSL connection to provide a working makefile()."""
362
363 def __init__(self, con):
364 self._con = con
365
366 def makefile(self, mode, bufsize):
367 return socket._fileobject(self._con, mode, bufsize)
368
369 def __getattr__(self, attrib):
370 return getattr(self._con, attrib)
371
372 def shutdown(self, arg=None):
373 try:
374 self._con.shutdown()
375 except Exception:
376 pass
406 return isinstance(error, exc_types)
377407
378408
379409 def select_ip_version(host, port):
382412 # and various operating systems. Probably this code also is
383413 # not supposed to work, but I can't come up with any other
384414 # ways to implement this.
385 ##try:
386 ## info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
387 ## socket.SOCK_STREAM, 0,
388 ## socket.AI_PASSIVE)
389 ## if info:
390 ## return info[0][0]
391 ##except socket.gaierror:
392 ## pass
415 # try:
416 # info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
417 # socket.SOCK_STREAM, 0,
418 # socket.AI_PASSIVE)
419 # if info:
420 # return info[0][0]
421 # except socket.gaierror:
422 # pass
393423 if ':' in host and hasattr(socket, 'AF_INET6'):
394424 return socket.AF_INET6
395425 return socket.AF_INET
412442 self.shutdown_signal = False
413443
414444 if ssl_context is not None:
415 try:
416 from OpenSSL import tsafe
417 except ImportError:
418 raise TypeError('SSL is not available if the OpenSSL '
419 'library is not installed.')
420445 if isinstance(ssl_context, tuple):
421446 ssl_context = load_ssl_context(*ssl_context)
422447 if ssl_context == 'adhoc':
423448 ssl_context = generate_adhoc_ssl_context()
424 self.socket = tsafe.Connection(ssl_context, self.socket)
449 self.socket = ssl_context.wrap_socket(self.socket,
450 server_side=True)
425451 self.ssl_context = ssl_context
426452 else:
427453 self.ssl_context = None
435461 HTTPServer.serve_forever(self)
436462 except KeyboardInterrupt:
437463 pass
464 finally:
465 self.server_close()
438466
439467 def handle_error(self, request, client_address):
440468 if self.passthrough_errors:
444472
445473 def get_request(self):
446474 con, info = self.socket.accept()
447 if self.ssl_context is not None:
448 con = _SSLConnectionFix(con)
449475 return con, info
450476
451477
485511 passthrough_errors, ssl_context)
486512
487513
488 def _iter_module_files():
489 # The list call is necessary on Python 3 in case the module
490 # dictionary modifies during iteration.
491 for module in list(sys.modules.values()):
492 filename = getattr(module, '__file__', None)
493 if filename:
494 old = None
495 while not os.path.isfile(filename):
496 old = filename
497 filename = os.path.dirname(filename)
498 if filename == old:
499 break
500 else:
501 if filename[-4:] in ('.pyc', '.pyo'):
502 filename = filename[:-1]
503 yield filename
504
505
506 def _reloader_stat_loop(extra_files=None, interval=1):
507 """When this function is run from the main thread, it will force other
508 threads to exit when any modules currently loaded change.
509
510 Copyright notice. This function is based on the autoreload.py from
511 the CherryPy trac which originated from WSGIKit which is now dead.
512
513 :param extra_files: a list of additional files it should watch.
514 def is_running_from_reloader():
515 """Checks if the application is running from within the Werkzeug
516 reloader subprocess.
517
518 .. versionadded:: 0.10
514519 """
515 from itertools import chain
516 mtimes = {}
517 while 1:
518 for filename in chain(_iter_module_files(), extra_files or ()):
519 try:
520 mtime = os.stat(filename).st_mtime
521 except OSError:
522 continue
523
524 old_time = mtimes.get(filename)
525 if old_time is None:
526 mtimes[filename] = mtime
527 continue
528 elif mtime > old_time:
529 _log('info', ' * Detected change in %r, reloading' % filename)
530 sys.exit(3)
531 time.sleep(interval)
532
533
534 def _reloader_inotify(extra_files=None, interval=None):
535 # Mutated by inotify loop when changes occur.
536 changed = [False]
537
538 # Setup inotify watches
539 from pyinotify import WatchManager, Notifier
540
541 # this API changed at one point, support both
542 try:
543 from pyinotify import EventsCodes as ec
544 ec.IN_ATTRIB
545 except (ImportError, AttributeError):
546 import pyinotify as ec
547
548 wm = WatchManager()
549 mask = ec.IN_DELETE_SELF | ec.IN_MOVE_SELF | ec.IN_MODIFY | ec.IN_ATTRIB
550
551 def signal_changed(event):
552 if changed[0]:
553 return
554 _log('info', ' * Detected change in %r, reloading' % event.path)
555 changed[:] = [True]
556
557 for fname in extra_files or ():
558 wm.add_watch(fname, mask, signal_changed)
559
560 # ... And now we wait...
561 notif = Notifier(wm)
562 try:
563 while not changed[0]:
564 # always reiterate through sys.modules, adding them
565 for fname in _iter_module_files():
566 wm.add_watch(fname, mask, signal_changed)
567 notif.process_events()
568 if notif.check_events(timeout=interval):
569 notif.read_events()
570 # TODO Set timeout to something small and check parent liveliness
571 finally:
572 notif.stop()
573 sys.exit(3)
574
575
576 # currently we always use the stat loop reloader for the simple reason
577 # that the inotify one does not respond to added files properly. Also
578 # it's quite buggy and the API is a mess.
579 reloader_loop = _reloader_stat_loop
580
581
582 def restart_with_reloader():
583 """Spawn a new Python interpreter with the same arguments as this one,
584 but running the reloader thread.
585 """
586 while 1:
587 _log('info', ' * Restarting with reloader')
588 args = [sys.executable] + sys.argv
589 new_environ = os.environ.copy()
590 new_environ['WERKZEUG_RUN_MAIN'] = 'true'
591
592 # a weird bug on windows. sometimes unicode strings end up in the
593 # environment and subprocess.call does not like this, encode them
594 # to latin1 and continue.
595 if os.name == 'nt' and PY2:
596 for key, value in iteritems(new_environ):
597 if isinstance(value, text_type):
598 new_environ[key] = value.encode('iso-8859-1')
599
600 exit_code = subprocess.call(args, env=new_environ)
601 if exit_code != 3:
602 return exit_code
603
604
605 def run_with_reloader(main_func, extra_files=None, interval=1):
606 """Run the given function in an independent python interpreter."""
607 import signal
608 signal.signal(signal.SIGTERM, lambda *args: sys.exit(0))
609 if os.environ.get('WERKZEUG_RUN_MAIN') == 'true':
610 thread.start_new_thread(main_func, ())
611 try:
612 reloader_loop(extra_files, interval)
613 except KeyboardInterrupt:
614 return
615 try:
616 sys.exit(restart_with_reloader())
617 except KeyboardInterrupt:
618 pass
520 return os.environ.get('WERKZEUG_RUN_MAIN') == 'true'
619521
620522
621523 def run_simple(hostname, port, application, use_reloader=False,
622524 use_debugger=False, use_evalex=True,
623 extra_files=None, reloader_interval=1, threaded=False,
624 processes=1, request_handler=None, static_files=None,
525 extra_files=None, reloader_interval=1,
526 reloader_type='auto', threaded=False, processes=1,
527 request_handler=None, static_files=None,
625528 passthrough_errors=False, ssl_context=None):
626 """Start an application using wsgiref and with an optional reloader. This
627 wraps `wsgiref` to fix the wrong default reporting of the multithreaded
628 WSGI variable and adds optional multithreading and fork support.
529 """Start a WSGI application. Optional features include a reloader,
530 multithreading and fork support.
629531
630532 This function has a command-line interface too::
631533
644546
645547 .. versionadded:: 0.9
646548 Added command-line interface.
549
550 .. versionadded:: 0.10
551 Improved the reloader and added support for changing the backend
552 through the `reloader_type` parameter. See :ref:`reloader`
553 for more information.
647554
648555 :param hostname: The host for the application. eg: ``'localhost'``
649556 :param port: The port for the server. eg: ``8080``
656563 additionally to the modules. For example configuration
657564 files.
658565 :param reloader_interval: the interval for the reloader in seconds.
566 :param reloader_type: the type of reloader to use. The default is
567 auto detection. Valid values are ``'stat'`` and
568 ``'watchdog'``. See :ref:`reloader` for more
569 information.
659570 :param threaded: should the process handle each request in a separate
660571 thread?
661572 :param processes: if greater than 1 then handle each request in a new process
672583 :param passthrough_errors: set this to `True` to disable the error catching.
673584 This means that the server will die on errors but
674585 it can be useful to hook debuggers in (pdb etc.)
675 :param ssl_context: an SSL context for the connection. Either an OpenSSL
676 context, a tuple in the form ``(cert_file, pkey_file)``,
677 the string ``'adhoc'`` if the server should
678 automatically create one, or `None` to disable SSL
679 (which is the default).
586 :param ssl_context: an SSL context for the connection. Either an
587 :class:`ssl.SSLContext`, a tuple in the form
588 ``(cert_file, pkey_file)``, the string ``'adhoc'`` if
589 the server should automatically create one, or ``None``
590 to disable SSL (which is the default).
680591 """
681592 if use_debugger:
682593 from werkzeug.debug import DebuggedApplication
694605 display_hostname = hostname != '*' and hostname or 'localhost'
695606 if ':' in display_hostname:
696607 display_hostname = '[%s]' % display_hostname
697 _log('info', ' * Running on %s://%s:%d/', ssl_context is None
698 and 'http' or 'https', display_hostname, port)
608 quit_msg = '(Press CTRL+C to quit)'
609 _log('info', ' * Running on %s://%s:%d/ %s', ssl_context is None
610 and 'http' or 'https', display_hostname, port, quit_msg)
699611 if use_reloader:
700612 # Create and destroy a socket so that any exceptions are raised before
701613 # we spawn a separate Python interpreter and lose this ability.
704616 test_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
705617 test_socket.bind((hostname, port))
706618 test_socket.close()
707 run_with_reloader(inner, extra_files, reloader_interval)
619
620 from ._reloader import run_with_reloader
621 run_with_reloader(inner, extra_files, reloader_interval,
622 reloader_type)
708623 else:
709624 inner()
625
626
627 def run_with_reloader(*args, **kwargs):
628 # People keep using undocumented APIs. Do not use this function
629 # please, we do not guarantee that it continues working.
630 from ._reloader import run_with_reloader
631 return run_with_reloader(*args, **kwargs)
632
710633
711634 def main():
712635 '''A simple command-line interface for :py:func:`run_simple`.'''
715638 import optparse
716639 from werkzeug.utils import import_string
717640
718 parser = optparse.OptionParser(usage='Usage: %prog [options] app_module:app_object')
641 parser = optparse.OptionParser(
642 usage='Usage: %prog [options] app_module:app_object')
719643 parser.add_option('-b', '--bind', dest='address',
720644 help='The hostname:port the app should listen on.')
721645 parser.add_option('-d', '--debug', dest='use_debugger',
378378 def _get_content_type(self):
379379 ct = self.headers.get('Content-Type')
380380 if ct is None and not self._input_stream:
381 if self.method in ('POST', 'PUT', 'PATCH'):
382 if self._files:
383 return 'multipart/form-data'
381 if self._files:
382 return 'multipart/form-data'
383 elif self._form:
384384 return 'application/x-www-form-urlencoded'
385385 return None
386386 return ct
679679 raise RuntimeError('%r does not support redirect to '
680680 'external targets' % self.__class__)
681681
682 status_code = int(response[1].split(None, 1)[0])
683 if status_code == 307:
684 method = environ['REQUEST_METHOD']
685 else:
686 method = 'GET'
687
682688 # For redirect handling we temporarily disable the response
683689 # wrapper. This is not threadsafe but not a real concern
684690 # since the test client must not be shared anyways.
687693 try:
688694 return self.open(path=script_root, base_url=base_url,
689695 query_string=qs, as_tuple=True,
690 buffered=buffered)
696 buffered=buffered, method=method)
691697 finally:
692698 self.response_wrapper = old_response_wrapper
693699
742748 or not follow_redirects:
743749 break
744750 new_location = response[2]['location']
751
752 method = 'GET'
753 if status_code == 307:
754 method = environ['REQUEST_METHOD']
755
745756 new_redirect_entry = (new_location, status_code)
746757 if new_redirect_entry in redirect_chain:
747758 raise ClientRedirectError('loop detected')
748759 redirect_chain.append(new_redirect_entry)
749760 environ, response = self.resolve_redirect(response, new_location,
750 environ, buffered=buffered)
761 environ,
762 buffered=buffered)
751763
752764 if self.response_wrapper is not None:
753765 response = self.response_wrapper(*response)
851863 response[:] = [status, headers]
852864 return buffer.append
853865
854 app_iter = app(environ, start_response)
866 app_rv = app(environ, start_response)
867 close_func = getattr(app_rv, 'close', None)
868 app_iter = iter(app_rv)
855869
856870 # when buffering we emit the close call early and convert the
857871 # application iterator into a regular list
858872 if buffered:
859 close_func = getattr(app_iter, 'close', None)
860873 try:
861874 app_iter = list(app_iter)
862875 finally:
863876 if close_func is not None:
864877 close_func()
865878
866 # otherwise we iterate the application iter until we have
867 # a response, chain the already received data with the already
868 # collected data and wrap it in a new `ClosingIterator` if
869 # we have a close callable.
879 # otherwise we iterate the application iter until we have a response, chain
880 # the already received data with the already collected data and wrap it in
881 # a new `ClosingIterator` if we need to restore a `close` callable from the
882 # original return value.
870883 else:
871884 while not response:
872885 buffer.append(next(app_iter))
873886 if buffer:
874 close_func = getattr(app_iter, 'close', None)
875887 app_iter = chain(buffer, app_iter)
876 if close_func is not None:
877 app_iter = ClosingIterator(app_iter, close_func)
888 if close_func is not None and app_iter is not app_rv:
889 app_iter = ClosingIterator(app_iter, close_func)
878890
879891 return app_iter, response[0], Headers(response[1])
+0
-267
werkzeug/testsuite/__init__.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite
3 ~~~~~~~~~~~~~~~~~~
4
5 Contains all test Werkzeug tests.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10
11 from __future__ import with_statement
12
13 import re
14 import sys
15 import unittest
16 import shutil
17 import tempfile
18 import atexit
19
20 from werkzeug.utils import find_modules
21 from werkzeug._compat import text_type, integer_types, reraise
22
23
24 def get_temporary_directory():
25 directory = tempfile.mkdtemp()
26 @atexit.register
27 def remove_directory():
28 try:
29 shutil.rmtree(directory)
30 except EnvironmentError:
31 pass
32 return directory
33
34
35 def iter_suites(package):
36 """Yields all testsuites."""
37 for module in find_modules(package, include_packages=True):
38 mod = __import__(module, fromlist=['*'])
39 if hasattr(mod, 'suite'):
40 yield mod.suite()
41
42
43 def find_all_tests(suite):
44 """Yields all the tests and their names from a given suite."""
45 suites = [suite]
46 while suites:
47 s = suites.pop()
48 try:
49 suites.extend(s)
50 except TypeError:
51 yield s, '%s.%s.%s' % (
52 s.__class__.__module__,
53 s.__class__.__name__,
54 s._testMethodName
55 )
56
57
58 class WerkzeugTestCase(unittest.TestCase):
59 """Baseclass for all the tests that Werkzeug uses. Use these
60 methods for testing instead of the camelcased ones in the
61 baseclass for consistency.
62 """
63
64 def setup(self):
65 pass
66
67 def teardown(self):
68 pass
69
70 def setUp(self):
71 self.setup()
72
73 def tearDown(self):
74 unittest.TestCase.tearDown(self)
75 self.teardown()
76
77 def assert_line_equal(self, x, y):
78 assert x == y, "lines not equal\n a = %r\n b = %r" % (x, y)
79
80 def assert_equal(self, x, y, msg=None):
81 return self.assertEqual(x, y, msg)
82
83 def assert_not_equal(self, x, y):
84 return self.assertNotEqual(x, y)
85
86 def assert_raises(self, exc_type, callable=None, *args, **kwargs):
87 catcher = _ExceptionCatcher(self, exc_type)
88 if callable is None:
89 return catcher
90 with catcher:
91 callable(*args, **kwargs)
92
93 if sys.version_info[:2] == (2, 6):
94 def assertIsNone(self, x):
95 assert x is None, "%r is not None" % (x,)
96
97 def assertIsNotNone(self, x):
98 assert x is not None, "%r is None" % (x, )
99
100 def assertIn(self, x, y):
101 assert x in y, "%r not in %r" % (x, y)
102
103 def assertNotIn(self, x, y):
104 assert x not in y, "%r in %r" % (x, y)
105
106 def assertIsInstance(self, x, y):
107 assert isinstance(x, y), "not isinstance(%r, %r)" % (x, y)
108
109 def assertIs(self, x, y):
110 assert x is y, "%r is not %r" % (x, y)
111
112 def assertIsNot(self, x, y):
113 assert x is not y, "%r is %r" % (x, y)
114
115 def assertSequenceEqual(self, x, y):
116 self.assertEqual(x, y)
117
118 def assertRaisesRegex(self, exc_type, regex, *args, **kwargs):
119 catcher = _ExceptionCatcher(self, exc_type)
120 if not args:
121 return catcher
122 elif callable(args[0]):
123 with catcher:
124 args[0](*args[1:], **kwargs)
125 if args[0] is not None:
126 assert re.search(args[0], catcher.exc_value[0])
127 else:
128 raise NotImplementedError()
129
130 elif sys.version_info[0] == 2:
131 def assertRaisesRegex(self, *args, **kwargs):
132 return self.assertRaisesRegexp(*args, **kwargs)
133
134 def assert_is_none(self, x):
135 self.assertIsNone(x)
136
137 def assert_is_not_none(self, x):
138 self.assertIsNotNone(x)
139
140 def assert_in(self, x, y):
141 self.assertIn(x, y)
142
143 def assert_is_instance(self, x, y):
144 self.assertIsInstance(x, y)
145
146 def assert_not_in(self, x, y):
147 self.assertNotIn(x, y)
148
149 def assert_is(self, x, y):
150 self.assertIs(x, y)
151
152 def assert_is_not(self, x, y):
153 self.assertIsNot(x, y)
154
155 def assert_true(self, x):
156 self.assertTrue(x)
157
158 def assert_false(self, x):
159 self.assertFalse(x)
160
161 def assert_raises_regex(self, *args, **kwargs):
162 return self.assertRaisesRegex(*args, **kwargs)
163
164 def assert_sequence_equal(self, x, y):
165 self.assertSequenceEqual(x, y)
166
167 def assert_strict_equal(self, x, y):
168 '''Stricter version of assert_equal that doesn't do implicit conversion
169 between unicode and strings'''
170 self.assert_equal(x, y)
171 assert issubclass(type(x), type(y)) or issubclass(type(y), type(x)), \
172 '%s != %s' % (type(x), type(y))
173 if isinstance(x, (bytes, text_type, integer_types)) or x is None:
174 return
175 elif isinstance(x, dict) or isinstance(y, dict):
176 x = sorted(x.items())
177 y = sorted(y.items())
178 elif isinstance(x, set) or isinstance(y, set):
179 x = sorted(x)
180 y = sorted(y)
181 rx, ry = repr(x), repr(y)
182 if rx != ry:
183 rx = rx[:200] + (rx[200:] and '...')
184 ry = ry[:200] + (ry[200:] and '...')
185 raise AssertionError(rx, ry)
186 assert repr(x) == repr(y), repr((x, y))[:200]
187
188
189 class _ExceptionCatcher(object):
190
191 def __init__(self, test_case, exc_type):
192 self.test_case = test_case
193 self.exc_type = exc_type
194 self.exc_value = None
195
196 def __enter__(self):
197 return self
198
199 def __exit__(self, exc_type, exc_value, tb):
200 exception_name = self.exc_type.__name__
201 if exc_type is None:
202 self.test_case.fail('Expected exception of type %r' %
203 exception_name)
204 elif not issubclass(exc_type, self.exc_type):
205 reraise(exc_type, exc_value, tb)
206 self.exc_value = exc_value
207 return True
208
209
210 class BetterLoader(unittest.TestLoader):
211 """A nicer loader that solves two problems. First of all we are setting
212 up tests from different sources and we're doing this programmatically
213 which breaks the default loading logic so this is required anyways.
214 Secondly this loader has a nicer interpolation for test names than the
215 default one so you can just do ``run-tests.py ViewTestCase`` and it
216 will work.
217 """
218
219 def getRootSuite(self):
220 return suite()
221
222 def loadTestsFromName(self, name, module=None):
223 root = self.getRootSuite()
224 if name == 'suite':
225 return root
226
227 all_tests = []
228 for testcase, testname in find_all_tests(root):
229 if testname == name or \
230 testname.endswith('.' + name) or \
231 ('.' + name + '.') in testname or \
232 testname.startswith(name + '.'):
233 all_tests.append(testcase)
234
235 if not all_tests:
236 raise LookupError('could not find test case for "%s"' % name)
237
238 if len(all_tests) == 1:
239 return all_tests[0]
240 rv = unittest.TestSuite()
241 for test in all_tests:
242 rv.addTest(test)
243 return rv
244
245
246 def suite():
247 """A testsuite that has all the Flask tests. You can use this
248 function to integrate the Flask tests into your own testsuite
249 in case you want to test that monkeypatches to Flask do not
250 break it.
251 """
252 suite = unittest.TestSuite()
253 for other_suite in iter_suites(__name__):
254 suite.addTest(other_suite)
255 return suite
256
257
258 def main():
259 """Runs the testsuite as command line application."""
260 try:
261 unittest.main(testLoader=BetterLoader(), defaultTest='suite')
262 except Exception:
263 import sys
264 import traceback
265 traceback.print_exc()
266 sys.exit(1)
+0
-40
werkzeug/testsuite/compat.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite.compat
3 ~~~~~~~~~~~~~~~~~~~~~~~~~
4
5 Ensure that old stuff does not break on update.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import unittest
11 import warnings
12 from werkzeug.testsuite import WerkzeugTestCase
13
14 from werkzeug.wrappers import Response
15 from werkzeug.test import create_environ
16
17
18 class CompatTestCase(WerkzeugTestCase):
19
20 def test_old_imports(self):
21 from werkzeug.utils import Headers, MultiDict, CombinedMultiDict, \
22 Headers, EnvironHeaders
23 from werkzeug.http import Accept, MIMEAccept, CharsetAccept, \
24 LanguageAccept, ETags, HeaderSet, WWWAuthenticate, \
25 Authorization
26
27 def test_exposed_werkzeug_mod(self):
28 import werkzeug
29 for key in werkzeug.__all__:
30 # deprecated, skip it
31 if key in ('templates', 'Template'):
32 continue
33 getattr(werkzeug, key)
34
35
36 def suite():
37 suite = unittest.TestSuite()
38 suite.addTest(unittest.makeSuite(CompatTestCase))
39 return suite
+0
-19
werkzeug/testsuite/contrib/__init__.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite.contrib
3 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5 Tests the contrib modules.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import unittest
11 from werkzeug.testsuite import iter_suites
12
13
14 def suite():
15 suite = unittest.TestSuite()
16 for other_suite in iter_suites(__name__):
17 suite.addTest(other_suite)
18 return suite
+0
-257
werkzeug/testsuite/contrib/cache.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite.cache
3 ~~~~~~~~~~~~~~~~~~~~~~~~
4
5 Tests the cache system
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import os
11 import time
12 import unittest
13 import tempfile
14 import shutil
15
16 from werkzeug.testsuite import WerkzeugTestCase
17 from werkzeug.contrib import cache
18
19 try:
20 import redis
21 try:
22 from redis.exceptions import ConnectionError as RedisConnectionError
23 cache.RedisCache(key_prefix='werkzeug-test-case:')._client.set('test','connection')
24 except RedisConnectionError:
25 redis = None
26 except ImportError:
27 redis = None
28 try:
29 import pylibmc as memcache
30 except ImportError:
31 try:
32 from google.appengine.api import memcache
33 except ImportError:
34 try:
35 import memcache
36 except ImportError:
37 memcache = None
38
39
40 class SimpleCacheTestCase(WerkzeugTestCase):
41
42 def test_get_dict(self):
43 c = cache.SimpleCache()
44 c.set('a', 'a')
45 c.set('b', 'b')
46 d = c.get_dict('a', 'b')
47 assert 'a' in d
48 assert 'a' == d['a']
49 assert 'b' in d
50 assert 'b' == d['b']
51
52 def test_set_many(self):
53 c = cache.SimpleCache()
54 c.set_many({0: 0, 1: 1, 2: 4})
55 assert c.get(2) == 4
56 c.set_many((i, i*i) for i in range(3))
57 assert c.get(2) == 4
58
59
60 class FileSystemCacheTestCase(WerkzeugTestCase):
61
62 def test_set_get(self):
63 tmp_dir = tempfile.mkdtemp()
64 try:
65 c = cache.FileSystemCache(cache_dir=tmp_dir)
66 for i in range(3):
67 c.set(str(i), i * i)
68 for i in range(3):
69 result = c.get(str(i))
70 assert result == i * i
71 finally:
72 shutil.rmtree(tmp_dir)
73
74 def test_filesystemcache_prune(self):
75 THRESHOLD = 13
76 tmp_dir = tempfile.mkdtemp()
77 c = cache.FileSystemCache(cache_dir=tmp_dir, threshold=THRESHOLD)
78 for i in range(2 * THRESHOLD):
79 c.set(str(i), i)
80 cache_files = os.listdir(tmp_dir)
81 shutil.rmtree(tmp_dir)
82 assert len(cache_files) <= THRESHOLD
83
84
85 def test_filesystemcache_clear(self):
86 tmp_dir = tempfile.mkdtemp()
87 c = cache.FileSystemCache(cache_dir=tmp_dir)
88 c.set('foo', 'bar')
89 cache_files = os.listdir(tmp_dir)
90 assert len(cache_files) == 1
91 c.clear()
92 cache_files = os.listdir(tmp_dir)
93 assert len(cache_files) == 0
94 shutil.rmtree(tmp_dir)
95
96
97 class RedisCacheTestCase(WerkzeugTestCase):
98
99 def make_cache(self):
100 return cache.RedisCache(key_prefix='werkzeug-test-case:')
101
102 def teardown(self):
103 self.make_cache().clear()
104
105 def test_compat(self):
106 c = self.make_cache()
107 c._client.set(c.key_prefix + 'foo', b'Awesome')
108 self.assert_equal(c.get('foo'), b'Awesome')
109 c._client.set(c.key_prefix + 'foo', b'42')
110 self.assert_equal(c.get('foo'), 42)
111
112 def test_get_set(self):
113 c = self.make_cache()
114 c.set('foo', ['bar'])
115 assert c.get('foo') == ['bar']
116
117 def test_get_many(self):
118 c = self.make_cache()
119 c.set('foo', ['bar'])
120 c.set('spam', 'eggs')
121 assert c.get_many('foo', 'spam') == [['bar'], 'eggs']
122
123 def test_set_many(self):
124 c = self.make_cache()
125 c.set_many({'foo': 'bar', 'spam': ['eggs']})
126 assert c.get('foo') == 'bar'
127 assert c.get('spam') == ['eggs']
128
129 def test_expire(self):
130 c = self.make_cache()
131 c.set('foo', 'bar', 1)
132 time.sleep(2)
133 assert c.get('foo') is None
134
135 def test_add(self):
136 c = self.make_cache()
137 # sanity check that add() works like set()
138 c.add('foo', 'bar')
139 assert c.get('foo') == 'bar'
140 c.add('foo', 'qux')
141 assert c.get('foo') == 'bar'
142
143 def test_delete(self):
144 c = self.make_cache()
145 c.add('foo', 'bar')
146 assert c.get('foo') == 'bar'
147 c.delete('foo')
148 assert c.get('foo') is None
149
150 def test_delete_many(self):
151 c = self.make_cache()
152 c.add('foo', 'bar')
153 c.add('spam', 'eggs')
154 c.delete_many('foo', 'spam')
155 assert c.get('foo') is None
156 assert c.get('spam') is None
157
158 def test_inc_dec(self):
159 c = self.make_cache()
160 c.set('foo', 1)
161 self.assert_equal(c.inc('foo'), 2)
162 self.assert_equal(c.dec('foo'), 1)
163 c.delete('foo')
164
165 def test_true_false(self):
166 c = self.make_cache()
167 c.set('foo', True)
168 assert c.get('foo') == True
169 c.set('bar', False)
170 assert c.get('bar') == False
171
172
173 class MemcachedCacheTestCase(WerkzeugTestCase):
174
175 def make_cache(self):
176 return cache.MemcachedCache(key_prefix='werkzeug-test-case:')
177
178 def teardown(self):
179 self.make_cache().clear()
180
181 def test_compat(self):
182 c = self.make_cache()
183 c._client.set(c.key_prefix + b'foo', 'bar')
184 self.assert_equal(c.get('foo'), 'bar')
185
186 def test_get_set(self):
187 c = self.make_cache()
188 c.set('foo', 'bar')
189 self.assert_equal(c.get('foo'), 'bar')
190
191 def test_get_many(self):
192 c = self.make_cache()
193 c.set('foo', 'bar')
194 c.set('spam', 'eggs')
195 self.assert_equal(c.get_many('foo', 'spam'), ['bar', 'eggs'])
196
197 def test_set_many(self):
198 c = self.make_cache()
199 c.set_many({'foo': 'bar', 'spam': 'eggs'})
200 self.assert_equal(c.get('foo'), 'bar')
201 self.assert_equal(c.get('spam'), 'eggs')
202
203 def test_expire(self):
204 c = self.make_cache()
205 c.set('foo', 'bar', 1)
206 time.sleep(2)
207 self.assert_is_none(c.get('foo'))
208
209 def test_add(self):
210 c = self.make_cache()
211 c.add('foo', 'bar')
212 self.assert_equal(c.get('foo'), 'bar')
213 c.add('foo', 'baz')
214 self.assert_equal(c.get('foo'), 'bar')
215
216 def test_delete(self):
217 c = self.make_cache()
218 c.add('foo', 'bar')
219 self.assert_equal(c.get('foo'), 'bar')
220 c.delete('foo')
221 self.assert_is_none(c.get('foo'))
222
223 def test_delete_many(self):
224 c = self.make_cache()
225 c.add('foo', 'bar')
226 c.add('spam', 'eggs')
227 c.delete_many('foo', 'spam')
228 self.assert_is_none(c.get('foo'))
229 self.assert_is_none(c.get('spam'))
230
231 def test_inc_dec(self):
232 c = self.make_cache()
233 c.set('foo', 1)
234 # XXX: Is this an intended difference?
235 c.inc('foo')
236 self.assert_equal(c.get('foo'), 2)
237 c.dec('foo')
238 self.assert_equal(c.get('foo'), 1)
239
240 def test_true_false(self):
241 c = self.make_cache()
242 c.set('foo', True)
243 self.assert_equal(c.get('foo'), True)
244 c.set('bar', False)
245 self.assert_equal(c.get('bar'), False)
246
247
248 def suite():
249 suite = unittest.TestSuite()
250 suite.addTest(unittest.makeSuite(SimpleCacheTestCase))
251 suite.addTest(unittest.makeSuite(FileSystemCacheTestCase))
252 if redis is not None:
253 suite.addTest(unittest.makeSuite(RedisCacheTestCase))
254 if memcache is not None:
255 suite.addTest(unittest.makeSuite(MemcachedCacheTestCase))
256 return suite
+0
-193
werkzeug/testsuite/contrib/fixers.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite.fixers
3 ~~~~~~~~~~~~~~~~~~~~~~~~~
4
5 Server / Browser fixers.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import unittest
11
12 from werkzeug.testsuite import WerkzeugTestCase
13 from werkzeug.datastructures import ResponseCacheControl
14 from werkzeug.http import parse_cache_control_header
15
16 from werkzeug.test import create_environ, Client
17 from werkzeug.wrappers import Request, Response
18 from werkzeug.contrib import fixers
19 from werkzeug.utils import redirect
20
21
22 @Request.application
23 def path_check_app(request):
24 return Response('PATH_INFO: %s\nSCRIPT_NAME: %s' % (
25 request.environ.get('PATH_INFO', ''),
26 request.environ.get('SCRIPT_NAME', '')
27 ))
28
29
30 class ServerFixerTestCase(WerkzeugTestCase):
31
32 def test_cgi_root_fix(self):
33 app = fixers.CGIRootFix(path_check_app)
34 response = Response.from_app(app, dict(create_environ(),
35 SCRIPT_NAME='/foo',
36 PATH_INFO='/bar',
37 SERVER_SOFTWARE='lighttpd/1.4.27'
38 ))
39 self.assert_equal(response.get_data(),
40 b'PATH_INFO: /foo/bar\nSCRIPT_NAME: ')
41
42 def test_cgi_root_fix_custom_app_root(self):
43 app = fixers.CGIRootFix(path_check_app, app_root='/baz/poop/')
44 response = Response.from_app(app, dict(create_environ(),
45 SCRIPT_NAME='/foo',
46 PATH_INFO='/bar'
47 ))
48 self.assert_equal(response.get_data(), b'PATH_INFO: /foo/bar\nSCRIPT_NAME: baz/poop')
49
50 def test_path_info_from_request_uri_fix(self):
51 app = fixers.PathInfoFromRequestUriFix(path_check_app)
52 for key in 'REQUEST_URI', 'REQUEST_URL', 'UNENCODED_URL':
53 env = dict(create_environ(), SCRIPT_NAME='/test', PATH_INFO='/?????')
54 env[key] = '/test/foo%25bar?drop=this'
55 response = Response.from_app(app, env)
56 self.assert_equal(response.get_data(), b'PATH_INFO: /foo%bar\nSCRIPT_NAME: /test')
57
58 def test_proxy_fix(self):
59 @Request.application
60 def app(request):
61 return Response('%s|%s' % (
62 request.remote_addr,
63 # do not use request.host as this fixes too :)
64 request.environ['HTTP_HOST']
65 ))
66 app = fixers.ProxyFix(app, num_proxies=2)
67 environ = dict(create_environ(),
68 HTTP_X_FORWARDED_PROTO="https",
69 HTTP_X_FORWARDED_HOST='example.com',
70 HTTP_X_FORWARDED_FOR='1.2.3.4, 5.6.7.8',
71 REMOTE_ADDR='127.0.0.1',
72 HTTP_HOST='fake'
73 )
74
75 response = Response.from_app(app, environ)
76
77 self.assert_equal(response.get_data(), b'1.2.3.4|example.com')
78
79 # And we must check that if it is a redirection it is
80 # correctly done:
81
82 redirect_app = redirect('/foo/bar.hml')
83 response = Response.from_app(redirect_app, environ)
84
85 wsgi_headers = response.get_wsgi_headers(environ)
86 assert wsgi_headers['Location'] == 'https://example.com/foo/bar.hml'
87
88 def test_proxy_fix_weird_enum(self):
89 @fixers.ProxyFix
90 @Request.application
91 def app(request):
92 return Response(request.remote_addr)
93 environ = dict(create_environ(),
94 HTTP_X_FORWARDED_FOR=',',
95 REMOTE_ADDR='127.0.0.1',
96 )
97
98 response = Response.from_app(app, environ)
99 self.assert_strict_equal(response.get_data(), b'127.0.0.1')
100
101 def test_header_rewriter_fix(self):
102 @Request.application
103 def application(request):
104 return Response("", headers=[
105 ('X-Foo', 'bar')
106 ])
107 application = fixers.HeaderRewriterFix(application, ('X-Foo',), (('X-Bar', '42'),))
108 response = Response.from_app(application, create_environ())
109 assert response.headers['Content-Type'] == 'text/plain; charset=utf-8'
110 assert 'X-Foo' not in response.headers
111 assert response.headers['X-Bar'] == '42'
112
113
114 class BrowserFixerTestCase(WerkzeugTestCase):
115
116 def test_ie_fixes(self):
117 @fixers.InternetExplorerFix
118 @Request.application
119 def application(request):
120 response = Response('binary data here', mimetype='application/vnd.ms-excel')
121 response.headers['Vary'] = 'Cookie'
122 response.headers['Content-Disposition'] = 'attachment; filename=foo.xls'
123 return response
124
125 c = Client(application, Response)
126 response = c.get('/', headers=[
127 ('User-Agent', 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)')
128 ])
129
130 # IE gets no vary
131 self.assert_equal(response.get_data(), b'binary data here')
132 assert 'vary' not in response.headers
133 assert response.headers['content-disposition'] == 'attachment; filename=foo.xls'
134 assert response.headers['content-type'] == 'application/vnd.ms-excel'
135
136 # other browsers do
137 c = Client(application, Response)
138 response = c.get('/')
139 self.assert_equal(response.get_data(), b'binary data here')
140 assert 'vary' in response.headers
141
142 cc = ResponseCacheControl()
143 cc.no_cache = True
144
145 @fixers.InternetExplorerFix
146 @Request.application
147 def application(request):
148 response = Response('binary data here', mimetype='application/vnd.ms-excel')
149 response.headers['Pragma'] = ', '.join(pragma)
150 response.headers['Cache-Control'] = cc.to_header()
151 response.headers['Content-Disposition'] = 'attachment; filename=foo.xls'
152 return response
153
154
155 # IE has no pragma or cache control
156 pragma = ('no-cache',)
157 c = Client(application, Response)
158 response = c.get('/', headers=[
159 ('User-Agent', 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)')
160 ])
161 self.assert_equal(response.get_data(), b'binary data here')
162 assert 'pragma' not in response.headers
163 assert 'cache-control' not in response.headers
164 assert response.headers['content-disposition'] == 'attachment; filename=foo.xls'
165
166 # IE has simplified pragma
167 pragma = ('no-cache', 'x-foo')
168 cc.proxy_revalidate = True
169 response = c.get('/', headers=[
170 ('User-Agent', 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)')
171 ])
172 self.assert_equal(response.get_data(), b'binary data here')
173 assert response.headers['pragma'] == 'x-foo'
174 assert response.headers['cache-control'] == 'proxy-revalidate'
175 assert response.headers['content-disposition'] == 'attachment; filename=foo.xls'
176
177 # regular browsers get everything
178 response = c.get('/')
179 self.assert_equal(response.get_data(), b'binary data here')
180 assert response.headers['pragma'] == 'no-cache, x-foo'
181 cc = parse_cache_control_header(response.headers['cache-control'],
182 cls=ResponseCacheControl)
183 assert cc.no_cache
184 assert cc.proxy_revalidate
185 assert response.headers['content-disposition'] == 'attachment; filename=foo.xls'
186
187
188 def suite():
189 suite = unittest.TestSuite()
190 suite.addTest(unittest.makeSuite(ServerFixerTestCase))
191 suite.addTest(unittest.makeSuite(BrowserFixerTestCase))
192 return suite
+0
-184
werkzeug/testsuite/contrib/iterio.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite.iterio
3 ~~~~~~~~~~~~~~~~~~~~~~~~~
4
5 Tests the iterio object.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import unittest
11 from functools import partial
12
13 from werkzeug.testsuite import WerkzeugTestCase
14 from werkzeug.contrib.iterio import IterIO, greenlet
15
16
17 class IterOTestSuite(WerkzeugTestCase):
18
19 def test_basic_native(self):
20 io = IterIO(["Hello", "World", "1", "2", "3"])
21 self.assert_equal(io.tell(), 0)
22 self.assert_equal(io.read(2), "He")
23 self.assert_equal(io.tell(), 2)
24 self.assert_equal(io.read(3), "llo")
25 self.assert_equal(io.tell(), 5)
26 io.seek(0)
27 self.assert_equal(io.read(5), "Hello")
28 self.assert_equal(io.tell(), 5)
29 self.assert_equal(io._buf, "Hello")
30 self.assert_equal(io.read(), "World123")
31 self.assert_equal(io.tell(), 13)
32 io.close()
33 assert io.closed
34
35 io = IterIO(["Hello\n", "World!"])
36 self.assert_equal(io.readline(), 'Hello\n')
37 self.assert_equal(io._buf, 'Hello\n')
38 self.assert_equal(io.read(), 'World!')
39 self.assert_equal(io._buf, 'Hello\nWorld!')
40 self.assert_equal(io.tell(), 12)
41 io.seek(0)
42 self.assert_equal(io.readlines(), ['Hello\n', 'World!'])
43
44 io = IterIO(["foo\n", "bar"])
45 io.seek(-4, 2)
46 self.assert_equal(io.read(4), '\nbar')
47
48 self.assert_raises(IOError, io.seek, 2, 100)
49 io.close()
50 self.assert_raises(ValueError, io.read)
51
52 def test_basic_bytes(self):
53 io = IterIO([b"Hello", b"World", b"1", b"2", b"3"])
54 self.assert_equal(io.tell(), 0)
55 self.assert_equal(io.read(2), b"He")
56 self.assert_equal(io.tell(), 2)
57 self.assert_equal(io.read(3), b"llo")
58 self.assert_equal(io.tell(), 5)
59 io.seek(0)
60 self.assert_equal(io.read(5), b"Hello")
61 self.assert_equal(io.tell(), 5)
62 self.assert_equal(io._buf, b"Hello")
63 self.assert_equal(io.read(), b"World123")
64 self.assert_equal(io.tell(), 13)
65 io.close()
66 assert io.closed
67
68 io = IterIO([b"Hello\n", b"World!"])
69 self.assert_equal(io.readline(), b'Hello\n')
70 self.assert_equal(io._buf, b'Hello\n')
71 self.assert_equal(io.read(), b'World!')
72 self.assert_equal(io._buf, b'Hello\nWorld!')
73 self.assert_equal(io.tell(), 12)
74 io.seek(0)
75 self.assert_equal(io.readlines(), [b'Hello\n', b'World!'])
76
77 io = IterIO([b"foo\n", b"bar"])
78 io.seek(-4, 2)
79 self.assert_equal(io.read(4), b'\nbar')
80
81 self.assert_raises(IOError, io.seek, 2, 100)
82 io.close()
83 self.assert_raises(ValueError, io.read)
84
85 def test_basic_unicode(self):
86 io = IterIO([u"Hello", u"World", u"1", u"2", u"3"])
87 self.assert_equal(io.tell(), 0)
88 self.assert_equal(io.read(2), u"He")
89 self.assert_equal(io.tell(), 2)
90 self.assert_equal(io.read(3), u"llo")
91 self.assert_equal(io.tell(), 5)
92 io.seek(0)
93 self.assert_equal(io.read(5), u"Hello")
94 self.assert_equal(io.tell(), 5)
95 self.assert_equal(io._buf, u"Hello")
96 self.assert_equal(io.read(), u"World123")
97 self.assert_equal(io.tell(), 13)
98 io.close()
99 assert io.closed
100
101 io = IterIO([u"Hello\n", u"World!"])
102 self.assert_equal(io.readline(), u'Hello\n')
103 self.assert_equal(io._buf, u'Hello\n')
104 self.assert_equal(io.read(), u'World!')
105 self.assert_equal(io._buf, u'Hello\nWorld!')
106 self.assert_equal(io.tell(), 12)
107 io.seek(0)
108 self.assert_equal(io.readlines(), [u'Hello\n', u'World!'])
109
110 io = IterIO([u"foo\n", u"bar"])
111 io.seek(-4, 2)
112 self.assert_equal(io.read(4), u'\nbar')
113
114 self.assert_raises(IOError, io.seek, 2, 100)
115 io.close()
116 self.assert_raises(ValueError, io.read)
117
118 def test_sentinel_cases(self):
119 io = IterIO([])
120 self.assert_strict_equal(io.read(), '')
121 io = IterIO([], b'')
122 self.assert_strict_equal(io.read(), b'')
123 io = IterIO([], u'')
124 self.assert_strict_equal(io.read(), u'')
125
126 io = IterIO([])
127 self.assert_strict_equal(io.read(), '')
128 io = IterIO([b''])
129 self.assert_strict_equal(io.read(), b'')
130 io = IterIO([u''])
131 self.assert_strict_equal(io.read(), u'')
132
133 io = IterIO([])
134 self.assert_strict_equal(io.readline(), '')
135 io = IterIO([], b'')
136 self.assert_strict_equal(io.readline(), b'')
137 io = IterIO([], u'')
138 self.assert_strict_equal(io.readline(), u'')
139
140 io = IterIO([])
141 self.assert_strict_equal(io.readline(), '')
142 io = IterIO([b''])
143 self.assert_strict_equal(io.readline(), b'')
144 io = IterIO([u''])
145 self.assert_strict_equal(io.readline(), u'')
146
147
148 class IterITestSuite(WerkzeugTestCase):
149
150 def test_basic(self):
151 def producer(out):
152 out.write('1\n')
153 out.write('2\n')
154 out.flush()
155 out.write('3\n')
156 iterable = IterIO(producer)
157 self.assert_equal(next(iterable), '1\n2\n')
158 self.assert_equal(next(iterable), '3\n')
159 self.assert_raises(StopIteration, next, iterable)
160
161 def test_sentinel_cases(self):
162 def producer_dummy_flush(out):
163 out.flush()
164 iterable = IterIO(producer_dummy_flush)
165 self.assert_strict_equal(next(iterable), '')
166
167 def producer_empty(out):
168 pass
169 iterable = IterIO(producer_empty)
170 self.assert_raises(StopIteration, next, iterable)
171
172 iterable = IterIO(producer_dummy_flush, b'')
173 self.assert_strict_equal(next(iterable), b'')
174 iterable = IterIO(producer_dummy_flush, u'')
175 self.assert_strict_equal(next(iterable), u'')
176
177
178 def suite():
179 suite = unittest.TestSuite()
180 suite.addTest(unittest.makeSuite(IterOTestSuite))
181 if greenlet is not None:
182 suite.addTest(unittest.makeSuite(IterITestSuite))
183 return suite
+0
-64
werkzeug/testsuite/contrib/securecookie.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite.securecookie
3 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5 Tests the secure cookie.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import unittest
11
12 from werkzeug.testsuite import WerkzeugTestCase
13
14 from werkzeug.utils import parse_cookie
15 from werkzeug.wrappers import Request, Response
16 from werkzeug.contrib.securecookie import SecureCookie
17
18
19 class SecureCookieTestCase(WerkzeugTestCase):
20
21 def test_basic_support(self):
22 c = SecureCookie(secret_key=b'foo')
23 assert c.new
24 assert not c.modified
25 assert not c.should_save
26 c['x'] = 42
27 assert c.modified
28 assert c.should_save
29 s = c.serialize()
30
31 c2 = SecureCookie.unserialize(s, b'foo')
32 assert c is not c2
33 assert not c2.new
34 assert not c2.modified
35 assert not c2.should_save
36 self.assert_equal(c2, c)
37
38 c3 = SecureCookie.unserialize(s, b'wrong foo')
39 assert not c3.modified
40 assert not c3.new
41 self.assert_equal(c3, {})
42
43 def test_wrapper_support(self):
44 req = Request.from_values()
45 resp = Response()
46 c = SecureCookie.load_cookie(req, secret_key=b'foo')
47 assert c.new
48 c['foo'] = 42
49 self.assert_equal(c.secret_key, b'foo')
50 c.save_cookie(resp)
51
52 req = Request.from_values(headers={
53 'Cookie': 'session="%s"' % parse_cookie(resp.headers['set-cookie'])['session']
54 })
55 c2 = SecureCookie.load_cookie(req, secret_key=b'foo')
56 assert not c2.new
57 self.assert_equal(c2, c)
58
59
60 def suite():
61 suite = unittest.TestSuite()
62 suite.addTest(unittest.makeSuite(SecureCookieTestCase))
63 return suite
+0
-91
werkzeug/testsuite/contrib/sessions.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite.sessions
3 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5 Added tests for the sessions.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import os
11 import unittest
12 import shutil
13 from tempfile import mkdtemp, gettempdir
14
15 from werkzeug.testsuite import WerkzeugTestCase
16 from werkzeug.contrib.sessions import FilesystemSessionStore
17
18
19
20 class SessionTestCase(WerkzeugTestCase):
21
22 def setup(self):
23 self.session_folder = mkdtemp()
24
25 def teardown(self):
26 shutil.rmtree(self.session_folder)
27
28 def test_default_tempdir(self):
29 store = FilesystemSessionStore()
30 assert store.path == gettempdir()
31
32 def test_basic_fs_sessions(self):
33 store = FilesystemSessionStore(self.session_folder)
34 x = store.new()
35 assert x.new
36 assert not x.modified
37 x['foo'] = [1, 2, 3]
38 assert x.modified
39 store.save(x)
40
41 x2 = store.get(x.sid)
42 assert not x2.new
43 assert not x2.modified
44 assert x2 is not x
45 assert x2 == x
46 x2['test'] = 3
47 assert x2.modified
48 assert not x2.new
49 store.save(x2)
50
51 x = store.get(x.sid)
52 store.delete(x)
53 x2 = store.get(x.sid)
54 # the session is not new when it was used previously.
55 assert not x2.new
56
57 def test_non_urandom(self):
58 urandom = os.urandom
59 del os.urandom
60 try:
61 store = FilesystemSessionStore(self.session_folder)
62 store.new()
63 finally:
64 os.urandom = urandom
65
66
67 def test_renewing_fs_session(self):
68 store = FilesystemSessionStore(self.session_folder, renew_missing=True)
69 x = store.new()
70 store.save(x)
71 store.delete(x)
72 x2 = store.get(x.sid)
73 assert x2.new
74
75 def test_fs_session_lising(self):
76 store = FilesystemSessionStore(self.session_folder, renew_missing=True)
77 sessions = set()
78 for x in range(10):
79 sess = store.new()
80 store.save(sess)
81 sessions.add(sess.sid)
82
83 listed_sessions = set(store.list())
84 assert sessions == listed_sessions
85
86
87 def suite():
88 suite = unittest.TestSuite()
89 suite.addTest(unittest.makeSuite(SessionTestCase))
90 return suite
+0
-97
werkzeug/testsuite/contrib/wrappers.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite.contrib.wrappers
3 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5 Added tests for the sessions.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10
11 from __future__ import with_statement
12
13 import unittest
14
15 from werkzeug.testsuite import WerkzeugTestCase
16
17 from werkzeug.contrib import wrappers
18 from werkzeug import routing
19 from werkzeug.wrappers import Request, Response
20
21
22 class WrappersTestCase(WerkzeugTestCase):
23
24 def test_reverse_slash_behavior(self):
25 class MyRequest(wrappers.ReverseSlashBehaviorRequestMixin, Request):
26 pass
27 req = MyRequest.from_values('/foo/bar', 'http://example.com/test')
28 assert req.url == 'http://example.com/test/foo/bar'
29 assert req.path == 'foo/bar'
30 assert req.script_root == '/test/'
31
32 # make sure the routing system works with the slashes in
33 # reverse order as well.
34 map = routing.Map([routing.Rule('/foo/bar', endpoint='foo')])
35 adapter = map.bind_to_environ(req.environ)
36 assert adapter.match() == ('foo', {})
37 adapter = map.bind(req.host, req.script_root)
38 assert adapter.match(req.path) == ('foo', {})
39
40 def test_dynamic_charset_request_mixin(self):
41 class MyRequest(wrappers.DynamicCharsetRequestMixin, Request):
42 pass
43 env = {'CONTENT_TYPE': 'text/html'}
44 req = MyRequest(env)
45 assert req.charset == 'latin1'
46
47 env = {'CONTENT_TYPE': 'text/html; charset=utf-8'}
48 req = MyRequest(env)
49 assert req.charset == 'utf-8'
50
51 env = {'CONTENT_TYPE': 'application/octet-stream'}
52 req = MyRequest(env)
53 assert req.charset == 'latin1'
54 assert req.url_charset == 'latin1'
55
56 MyRequest.url_charset = 'utf-8'
57 env = {'CONTENT_TYPE': 'application/octet-stream'}
58 req = MyRequest(env)
59 assert req.charset == 'latin1'
60 assert req.url_charset == 'utf-8'
61
62 def return_ascii(x):
63 return "ascii"
64 env = {'CONTENT_TYPE': 'text/plain; charset=x-weird-charset'}
65 req = MyRequest(env)
66 req.unknown_charset = return_ascii
67 assert req.charset == 'ascii'
68 assert req.url_charset == 'utf-8'
69
70 def test_dynamic_charset_response_mixin(self):
71 class MyResponse(wrappers.DynamicCharsetResponseMixin, Response):
72 default_charset = 'utf-7'
73 resp = MyResponse(mimetype='text/html')
74 assert resp.charset == 'utf-7'
75 resp.charset = 'utf-8'
76 assert resp.charset == 'utf-8'
77 assert resp.mimetype == 'text/html'
78 assert resp.mimetype_params == {'charset': 'utf-8'}
79 resp.mimetype_params['charset'] = 'iso-8859-15'
80 assert resp.charset == 'iso-8859-15'
81 resp.set_data(u'Hällo Wörld')
82 assert b''.join(resp.iter_encoded()) == \
83 u'Hällo Wörld'.encode('iso-8859-15')
84 del resp.headers['content-type']
85 try:
86 resp.charset = 'utf-8'
87 except TypeError as e:
88 pass
89 else:
90 assert False, 'expected type error on charset setting without ct'
91
92
93 def suite():
94 suite = unittest.TestSuite()
95 suite.addTest(unittest.makeSuite(WrappersTestCase))
96 return suite
+0
-810
werkzeug/testsuite/datastructures.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite.datastructures
3 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5 Tests the functionality of the provided Werkzeug
6 datastructures.
7
8 TODO:
9
10 - FileMultiDict
11 - Immutable types undertested
12 - Split up dict tests
13
14 :copyright: (c) 2014 by Armin Ronacher.
15 :license: BSD, see LICENSE for more details.
16 """
17
18 from __future__ import with_statement
19
20 import unittest
21 import pickle
22 from contextlib import contextmanager
23 from copy import copy, deepcopy
24
25 from werkzeug import datastructures
26 from werkzeug._compat import iterkeys, itervalues, iteritems, iterlists, \
27 iterlistvalues, text_type, PY2
28 from werkzeug.testsuite import WerkzeugTestCase
29 from werkzeug.exceptions import BadRequestKeyError
30
31
32 class NativeItermethodsTestCase(WerkzeugTestCase):
33 def test_basic(self):
34 @datastructures.native_itermethods(['keys', 'values', 'items'])
35 class StupidDict(object):
36 def keys(self, multi=1):
37 return iter(['a', 'b', 'c'] * multi)
38
39 def values(self, multi=1):
40 return iter([1, 2, 3] * multi)
41
42 def items(self, multi=1):
43 return iter(zip(iterkeys(self, multi=multi),
44 itervalues(self, multi=multi)))
45
46 d = StupidDict()
47 expected_keys = ['a', 'b', 'c']
48 expected_values = [1, 2, 3]
49 expected_items = list(zip(expected_keys, expected_values))
50
51 self.assert_equal(list(iterkeys(d)), expected_keys)
52 self.assert_equal(list(itervalues(d)), expected_values)
53 self.assert_equal(list(iteritems(d)), expected_items)
54
55 self.assert_equal(list(iterkeys(d, 2)), expected_keys * 2)
56 self.assert_equal(list(itervalues(d, 2)), expected_values * 2)
57 self.assert_equal(list(iteritems(d, 2)), expected_items * 2)
58
59
60 class MutableMultiDictBaseTestCase(WerkzeugTestCase):
61 storage_class = None
62
63 def test_pickle(self):
64 cls = self.storage_class
65
66 for protocol in range(pickle.HIGHEST_PROTOCOL + 1):
67 d = cls()
68 d.setlist(b'foo', [1, 2, 3, 4])
69 d.setlist(b'bar', b'foo bar baz'.split())
70 s = pickle.dumps(d, protocol)
71 ud = pickle.loads(s)
72 self.assert_equal(type(ud), type(d))
73 self.assert_equal(ud, d)
74 self.assert_equal(pickle.loads(
75 s.replace(b'werkzeug.datastructures', b'werkzeug')), d)
76 ud[b'newkey'] = b'bla'
77 self.assert_not_equal(ud, d)
78
79 def test_basic_interface(self):
80 md = self.storage_class()
81 assert isinstance(md, dict)
82
83 mapping = [('a', 1), ('b', 2), ('a', 2), ('d', 3),
84 ('a', 1), ('a', 3), ('d', 4), ('c', 3)]
85 md = self.storage_class(mapping)
86
87 # simple getitem gives the first value
88 self.assert_equal(md['a'], 1)
89 self.assert_equal(md['c'], 3)
90 with self.assert_raises(KeyError):
91 md['e']
92 self.assert_equal(md.get('a'), 1)
93
94 # list getitem
95 self.assert_equal(md.getlist('a'), [1, 2, 1, 3])
96 self.assert_equal(md.getlist('d'), [3, 4])
97 # do not raise if key not found
98 self.assert_equal(md.getlist('x'), [])
99
100 # simple setitem overwrites all values
101 md['a'] = 42
102 self.assert_equal(md.getlist('a'), [42])
103
104 # list setitem
105 md.setlist('a', [1, 2, 3])
106 self.assert_equal(md['a'], 1)
107 self.assert_equal(md.getlist('a'), [1, 2, 3])
108
109 # verify that it does not change original lists
110 l1 = [1, 2, 3]
111 md.setlist('a', l1)
112 del l1[:]
113 self.assert_equal(md['a'], 1)
114
115 # setdefault, setlistdefault
116 self.assert_equal(md.setdefault('u', 23), 23)
117 self.assert_equal(md.getlist('u'), [23])
118 del md['u']
119
120 md.setlist('u', [-1, -2])
121
122 # delitem
123 del md['u']
124 with self.assert_raises(KeyError):
125 md['u']
126 del md['d']
127 self.assert_equal(md.getlist('d'), [])
128
129 # keys, values, items, lists
130 self.assert_equal(list(sorted(md.keys())), ['a', 'b', 'c'])
131 self.assert_equal(list(sorted(iterkeys(md))), ['a', 'b', 'c'])
132
133 self.assert_equal(list(sorted(itervalues(md))), [1, 2, 3])
134 self.assert_equal(list(sorted(itervalues(md))), [1, 2, 3])
135
136 self.assert_equal(list(sorted(md.items())),
137 [('a', 1), ('b', 2), ('c', 3)])
138 self.assert_equal(list(sorted(md.items(multi=True))),
139 [('a', 1), ('a', 2), ('a', 3), ('b', 2), ('c', 3)])
140 self.assert_equal(list(sorted(iteritems(md))),
141 [('a', 1), ('b', 2), ('c', 3)])
142 self.assert_equal(list(sorted(iteritems(md, multi=True))),
143 [('a', 1), ('a', 2), ('a', 3), ('b', 2), ('c', 3)])
144
145 self.assert_equal(list(sorted(md.lists())),
146 [('a', [1, 2, 3]), ('b', [2]), ('c', [3])])
147 self.assert_equal(list(sorted(iterlists(md))),
148 [('a', [1, 2, 3]), ('b', [2]), ('c', [3])])
149
150 # copy method
151 c = md.copy()
152 self.assert_equal(c['a'], 1)
153 self.assert_equal(c.getlist('a'), [1, 2, 3])
154
155 # copy method 2
156 c = copy(md)
157 self.assert_equal(c['a'], 1)
158 self.assert_equal(c.getlist('a'), [1, 2, 3])
159
160 # deepcopy method
161 c = md.deepcopy()
162 self.assert_equal(c['a'], 1)
163 self.assert_equal(c.getlist('a'), [1, 2, 3])
164
165 # deepcopy method 2
166 c = deepcopy(md)
167 self.assert_equal(c['a'], 1)
168 self.assert_equal(c.getlist('a'), [1, 2, 3])
169
170 # update with a multidict
171 od = self.storage_class([('a', 4), ('a', 5), ('y', 0)])
172 md.update(od)
173 self.assert_equal(md.getlist('a'), [1, 2, 3, 4, 5])
174 self.assert_equal(md.getlist('y'), [0])
175
176 # update with a regular dict
177 md = c
178 od = {'a': 4, 'y': 0}
179 md.update(od)
180 self.assert_equal(md.getlist('a'), [1, 2, 3, 4])
181 self.assert_equal(md.getlist('y'), [0])
182
183 # pop, poplist, popitem, popitemlist
184 self.assert_equal(md.pop('y'), 0)
185 assert 'y' not in md
186 self.assert_equal(md.poplist('a'), [1, 2, 3, 4])
187 assert 'a' not in md
188 self.assert_equal(md.poplist('missing'), [])
189
190 # remaining: b=2, c=3
191 popped = md.popitem()
192 assert popped in [('b', 2), ('c', 3)]
193 popped = md.popitemlist()
194 assert popped in [('b', [2]), ('c', [3])]
195
196 # type conversion
197 md = self.storage_class({'a': '4', 'b': ['2', '3']})
198 self.assert_equal(md.get('a', type=int), 4)
199 self.assert_equal(md.getlist('b', type=int), [2, 3])
200
201 # repr
202 md = self.storage_class([('a', 1), ('a', 2), ('b', 3)])
203 assert "('a', 1)" in repr(md)
204 assert "('a', 2)" in repr(md)
205 assert "('b', 3)" in repr(md)
206
207 # add and getlist
208 md.add('c', '42')
209 md.add('c', '23')
210 self.assert_equal(md.getlist('c'), ['42', '23'])
211 md.add('c', 'blah')
212 self.assert_equal(md.getlist('c', type=int), [42, 23])
213
214 # setdefault
215 md = self.storage_class()
216 md.setdefault('x', []).append(42)
217 md.setdefault('x', []).append(23)
218 self.assert_equal(md['x'], [42, 23])
219
220 # to dict
221 md = self.storage_class()
222 md['foo'] = 42
223 md.add('bar', 1)
224 md.add('bar', 2)
225 self.assert_equal(md.to_dict(), {'foo': 42, 'bar': 1})
226 self.assert_equal(md.to_dict(flat=False), {'foo': [42], 'bar': [1, 2]})
227
228 # popitem from empty dict
229 with self.assert_raises(KeyError):
230 self.storage_class().popitem()
231
232 with self.assert_raises(KeyError):
233 self.storage_class().popitemlist()
234
235 # key errors are of a special type
236 with self.assert_raises(BadRequestKeyError):
237 self.storage_class()[42]
238
239 # setlist works
240 md = self.storage_class()
241 md['foo'] = 42
242 md.setlist('foo', [1, 2])
243 self.assert_equal(md.getlist('foo'), [1, 2])
244
245
246 class ImmutableDictBaseTestCase(WerkzeugTestCase):
247 storage_class = None
248
249 def test_follows_dict_interface(self):
250 cls = self.storage_class
251
252 data = {'foo': 1, 'bar': 2, 'baz': 3}
253 d = cls(data)
254
255 self.assert_equal(d['foo'], 1)
256 self.assert_equal(d['bar'], 2)
257 self.assert_equal(d['baz'], 3)
258 self.assert_equal(sorted(d.keys()), ['bar', 'baz', 'foo'])
259 self.assert_true('foo' in d)
260 self.assert_true('foox' not in d)
261 self.assert_equal(len(d), 3)
262
263 def test_copies_are_mutable(self):
264 cls = self.storage_class
265 immutable = cls({'a': 1})
266 with self.assert_raises(TypeError):
267 immutable.pop('a')
268
269 mutable = immutable.copy()
270 mutable.pop('a')
271 self.assert_true('a' in immutable)
272 self.assert_true(mutable is not immutable)
273 self.assert_true(copy(immutable) is immutable)
274
275 def test_dict_is_hashable(self):
276 cls = self.storage_class
277 immutable = cls({'a': 1, 'b': 2})
278 immutable2 = cls({'a': 2, 'b': 2})
279 x = set([immutable])
280 self.assert_true(immutable in x)
281 self.assert_true(immutable2 not in x)
282 x.discard(immutable)
283 self.assert_true(immutable not in x)
284 self.assert_true(immutable2 not in x)
285 x.add(immutable2)
286 self.assert_true(immutable not in x)
287 self.assert_true(immutable2 in x)
288 x.add(immutable)
289 self.assert_true(immutable in x)
290 self.assert_true(immutable2 in x)
291
292
293 class ImmutableTypeConversionDictTestCase(ImmutableDictBaseTestCase):
294 storage_class = datastructures.ImmutableTypeConversionDict
295
296
297 class ImmutableMultiDictTestCase(ImmutableDictBaseTestCase):
298 storage_class = datastructures.ImmutableMultiDict
299
300 def test_multidict_is_hashable(self):
301 cls = self.storage_class
302 immutable = cls({'a': [1, 2], 'b': 2})
303 immutable2 = cls({'a': [1], 'b': 2})
304 x = set([immutable])
305 self.assert_true(immutable in x)
306 self.assert_true(immutable2 not in x)
307 x.discard(immutable)
308 self.assert_true(immutable not in x)
309 self.assert_true(immutable2 not in x)
310 x.add(immutable2)
311 self.assert_true(immutable not in x)
312 self.assert_true(immutable2 in x)
313 x.add(immutable)
314 self.assert_true(immutable in x)
315 self.assert_true(immutable2 in x)
316
317
318 class ImmutableDictTestCase(ImmutableDictBaseTestCase):
319 storage_class = datastructures.ImmutableDict
320
321
322 class ImmutableOrderedMultiDictTestCase(ImmutableDictBaseTestCase):
323 storage_class = datastructures.ImmutableOrderedMultiDict
324
325 def test_ordered_multidict_is_hashable(self):
326 a = self.storage_class([('a', 1), ('b', 1), ('a', 2)])
327 b = self.storage_class([('a', 1), ('a', 2), ('b', 1)])
328 self.assert_not_equal(hash(a), hash(b))
329
330
331 class MultiDictTestCase(MutableMultiDictBaseTestCase):
332 storage_class = datastructures.MultiDict
333
334 def test_multidict_pop(self):
335 make_d = lambda: self.storage_class({'foo': [1, 2, 3, 4]})
336 d = make_d()
337 self.assert_equal(d.pop('foo'), 1)
338 assert not d
339 d = make_d()
340 self.assert_equal(d.pop('foo', 32), 1)
341 assert not d
342 d = make_d()
343 self.assert_equal(d.pop('foos', 32), 32)
344 assert d
345
346 with self.assert_raises(KeyError):
347 d.pop('foos')
348
349 def test_setlistdefault(self):
350 md = self.storage_class()
351 self.assert_equal(md.setlistdefault('u', [-1, -2]), [-1, -2])
352 self.assert_equal(md.getlist('u'), [-1, -2])
353 self.assert_equal(md['u'], -1)
354
355 def test_iter_interfaces(self):
356 mapping = [('a', 1), ('b', 2), ('a', 2), ('d', 3),
357 ('a', 1), ('a', 3), ('d', 4), ('c', 3)]
358 md = self.storage_class(mapping)
359 self.assert_equal(list(zip(md.keys(), md.listvalues())),
360 list(md.lists()))
361 self.assert_equal(list(zip(md, iterlistvalues(md))),
362 list(iterlists(md)))
363 self.assert_equal(list(zip(iterkeys(md), iterlistvalues(md))),
364 list(iterlists(md)))
365
366
367 class OrderedMultiDictTestCase(MutableMultiDictBaseTestCase):
368 storage_class = datastructures.OrderedMultiDict
369
370 def test_ordered_interface(self):
371 cls = self.storage_class
372
373 d = cls()
374 assert not d
375 d.add('foo', 'bar')
376 self.assert_equal(len(d), 1)
377 d.add('foo', 'baz')
378 self.assert_equal(len(d), 1)
379 self.assert_equal(list(iteritems(d)), [('foo', 'bar')])
380 self.assert_equal(list(d), ['foo'])
381 self.assert_equal(list(iteritems(d, multi=True)),
382 [('foo', 'bar'), ('foo', 'baz')])
383 del d['foo']
384 assert not d
385 self.assert_equal(len(d), 0)
386 self.assert_equal(list(d), [])
387
388 d.update([('foo', 1), ('foo', 2), ('bar', 42)])
389 d.add('foo', 3)
390 self.assert_equal(d.getlist('foo'), [1, 2, 3])
391 self.assert_equal(d.getlist('bar'), [42])
392 self.assert_equal(list(iteritems(d)), [('foo', 1), ('bar', 42)])
393
394 expected = ['foo', 'bar']
395
396 self.assert_sequence_equal(list(d.keys()), expected)
397 self.assert_sequence_equal(list(d), expected)
398 self.assert_sequence_equal(list(iterkeys(d)), expected)
399
400 self.assert_equal(list(iteritems(d, multi=True)),
401 [('foo', 1), ('foo', 2), ('bar', 42), ('foo', 3)])
402 self.assert_equal(len(d), 2)
403
404 self.assert_equal(d.pop('foo'), 1)
405 assert d.pop('blafasel', None) is None
406 self.assert_equal(d.pop('blafasel', 42), 42)
407 self.assert_equal(len(d), 1)
408 self.assert_equal(d.poplist('bar'), [42])
409 assert not d
410
411 d.get('missingkey') is None
412
413 d.add('foo', 42)
414 d.add('foo', 23)
415 d.add('bar', 2)
416 d.add('foo', 42)
417 self.assert_equal(d, datastructures.MultiDict(d))
418 id = self.storage_class(d)
419 self.assert_equal(d, id)
420 d.add('foo', 2)
421 assert d != id
422
423 d.update({'blah': [1, 2, 3]})
424 self.assert_equal(d['blah'], 1)
425 self.assert_equal(d.getlist('blah'), [1, 2, 3])
426
427 # setlist works
428 d = self.storage_class()
429 d['foo'] = 42
430 d.setlist('foo', [1, 2])
431 self.assert_equal(d.getlist('foo'), [1, 2])
432
433 with self.assert_raises(BadRequestKeyError):
434 d.pop('missing')
435 with self.assert_raises(BadRequestKeyError):
436 d['missing']
437
438 # popping
439 d = self.storage_class()
440 d.add('foo', 23)
441 d.add('foo', 42)
442 d.add('foo', 1)
443 self.assert_equal(d.popitem(), ('foo', 23))
444 with self.assert_raises(BadRequestKeyError):
445 d.popitem()
446 assert not d
447
448 d.add('foo', 23)
449 d.add('foo', 42)
450 d.add('foo', 1)
451 self.assert_equal(d.popitemlist(), ('foo', [23, 42, 1]))
452
453 with self.assert_raises(BadRequestKeyError):
454 d.popitemlist()
455
456 def test_iterables(self):
457 a = datastructures.MultiDict((("key_a", "value_a"),))
458 b = datastructures.MultiDict((("key_b", "value_b"),))
459 ab = datastructures.CombinedMultiDict((a,b))
460
461 self.assert_equal(sorted(ab.lists()), [('key_a', ['value_a']), ('key_b', ['value_b'])])
462 self.assert_equal(sorted(ab.listvalues()), [['value_a'], ['value_b']])
463 self.assert_equal(sorted(ab.keys()), ["key_a", "key_b"])
464
465 self.assert_equal(sorted(iterlists(ab)), [('key_a', ['value_a']), ('key_b', ['value_b'])])
466 self.assert_equal(sorted(iterlistvalues(ab)), [['value_a'], ['value_b']])
467 self.assert_equal(sorted(iterkeys(ab)), ["key_a", "key_b"])
468
469
470 class CombinedMultiDictTestCase(WerkzeugTestCase):
471 storage_class = datastructures.CombinedMultiDict
472
473 def test_basic_interface(self):
474 d1 = datastructures.MultiDict([('foo', '1')])
475 d2 = datastructures.MultiDict([('bar', '2'), ('bar', '3')])
476 d = self.storage_class([d1, d2])
477
478 # lookup
479 self.assert_equal(d['foo'], '1')
480 self.assert_equal(d['bar'], '2')
481 self.assert_equal(d.getlist('bar'), ['2', '3'])
482
483 self.assert_equal(sorted(d.items()),
484 [('bar', '2'), ('foo', '1')])
485 self.assert_equal(sorted(d.items(multi=True)),
486 [('bar', '2'), ('bar', '3'), ('foo', '1')])
487 assert 'missingkey' not in d
488 assert 'foo' in d
489
490 # type lookup
491 self.assert_equal(d.get('foo', type=int), 1)
492 self.assert_equal(d.getlist('bar', type=int), [2, 3])
493
494 # get key errors for missing stuff
495 with self.assert_raises(KeyError):
496 d['missing']
497
498 # make sure that they are immutable
499 with self.assert_raises(TypeError):
500 d['foo'] = 'blub'
501
502 # copies are immutable
503 d = d.copy()
504 with self.assert_raises(TypeError):
505 d['foo'] = 'blub'
506
507 # make sure lists merges
508 md1 = datastructures.MultiDict((("foo", "bar"),))
509 md2 = datastructures.MultiDict((("foo", "blafasel"),))
510 x = self.storage_class((md1, md2))
511 self.assert_equal(list(iterlists(x)), [('foo', ['bar', 'blafasel'])])
512
513
514 class HeadersTestCase(WerkzeugTestCase):
515 storage_class = datastructures.Headers
516
517 def test_basic_interface(self):
518 headers = self.storage_class()
519 headers.add('Content-Type', 'text/plain')
520 headers.add('X-Foo', 'bar')
521 assert 'x-Foo' in headers
522 assert 'Content-type' in headers
523
524 headers['Content-Type'] = 'foo/bar'
525 self.assert_equal(headers['Content-Type'], 'foo/bar')
526 self.assert_equal(len(headers.getlist('Content-Type')), 1)
527
528 # list conversion
529 self.assert_equal(headers.to_wsgi_list(), [
530 ('Content-Type', 'foo/bar'),
531 ('X-Foo', 'bar')
532 ])
533 self.assert_equal(str(headers), (
534 "Content-Type: foo/bar\r\n"
535 "X-Foo: bar\r\n"
536 "\r\n"))
537 self.assert_equal(str(self.storage_class()), "\r\n")
538
539 # extended add
540 headers.add('Content-Disposition', 'attachment', filename='foo')
541 self.assert_equal(headers['Content-Disposition'],
542 'attachment; filename=foo')
543
544 headers.add('x', 'y', z='"')
545 self.assert_equal(headers['x'], r'y; z="\""')
546
547 def test_defaults_and_conversion(self):
548 # defaults
549 headers = self.storage_class([
550 ('Content-Type', 'text/plain'),
551 ('X-Foo', 'bar'),
552 ('X-Bar', '1'),
553 ('X-Bar', '2')
554 ])
555 self.assert_equal(headers.getlist('x-bar'), ['1', '2'])
556 self.assert_equal(headers.get('x-Bar'), '1')
557 self.assert_equal(headers.get('Content-Type'), 'text/plain')
558
559 self.assert_equal(headers.setdefault('X-Foo', 'nope'), 'bar')
560 self.assert_equal(headers.setdefault('X-Bar', 'nope'), '1')
561 self.assert_equal(headers.setdefault('X-Baz', 'quux'), 'quux')
562 self.assert_equal(headers.setdefault('X-Baz', 'nope'), 'quux')
563 headers.pop('X-Baz')
564
565 # type conversion
566 self.assert_equal(headers.get('x-bar', type=int), 1)
567 self.assert_equal(headers.getlist('x-bar', type=int), [1, 2])
568
569 # list like operations
570 self.assert_equal(headers[0], ('Content-Type', 'text/plain'))
571 self.assert_equal(headers[:1], self.storage_class([('Content-Type', 'text/plain')]))
572 del headers[:2]
573 del headers[-1]
574 self.assert_equal(headers, self.storage_class([('X-Bar', '1')]))
575
576 def test_copying(self):
577 a = self.storage_class([('foo', 'bar')])
578 b = a.copy()
579 a.add('foo', 'baz')
580 self.assert_equal(a.getlist('foo'), ['bar', 'baz'])
581 self.assert_equal(b.getlist('foo'), ['bar'])
582
583 def test_popping(self):
584 headers = self.storage_class([('a', 1)])
585 self.assert_equal(headers.pop('a'), 1)
586 self.assert_equal(headers.pop('b', 2), 2)
587
588 with self.assert_raises(KeyError):
589 headers.pop('c')
590
591 def test_set_arguments(self):
592 a = self.storage_class()
593 a.set('Content-Disposition', 'useless')
594 a.set('Content-Disposition', 'attachment', filename='foo')
595 self.assert_equal(a['Content-Disposition'], 'attachment; filename=foo')
596
597 def test_reject_newlines(self):
598 h = self.storage_class()
599
600 for variation in 'foo\nbar', 'foo\r\nbar', 'foo\rbar':
601 with self.assert_raises(ValueError):
602 h['foo'] = variation
603 with self.assert_raises(ValueError):
604 h.add('foo', variation)
605 with self.assert_raises(ValueError):
606 h.add('foo', 'test', option=variation)
607 with self.assert_raises(ValueError):
608 h.set('foo', variation)
609 with self.assert_raises(ValueError):
610 h.set('foo', 'test', option=variation)
611
612 def test_slicing(self):
613 # there's nothing wrong with these being native strings
614 # Headers doesn't care about the data types
615 h = self.storage_class()
616 h.set('X-Foo-Poo', 'bleh')
617 h.set('Content-Type', 'application/whocares')
618 h.set('X-Forwarded-For', '192.168.0.123')
619 h[:] = [(k, v) for k, v in h if k.startswith(u'X-')]
620 self.assert_equal(list(h), [
621 ('X-Foo-Poo', 'bleh'),
622 ('X-Forwarded-For', '192.168.0.123')
623 ])
624
625 def test_bytes_operations(self):
626 h = self.storage_class()
627 h.set('X-Foo-Poo', 'bleh')
628 h.set('X-Whoops', b'\xff')
629
630 self.assert_equal(h.get('x-foo-poo', as_bytes=True), b'bleh')
631 self.assert_equal(h.get('x-whoops', as_bytes=True), b'\xff')
632
633 def test_to_wsgi_list(self):
634 h = self.storage_class()
635 h.set(u'Key', u'Value')
636 for key, value in h.to_wsgi_list():
637 if PY2:
638 self.assert_strict_equal(key, b'Key')
639 self.assert_strict_equal(value, b'Value')
640 else:
641 self.assert_strict_equal(key, u'Key')
642 self.assert_strict_equal(value, u'Value')
643
644
645
646 class EnvironHeadersTestCase(WerkzeugTestCase):
647 storage_class = datastructures.EnvironHeaders
648
649 def test_basic_interface(self):
650 # this happens in multiple WSGI servers because they
651 # use a vary naive way to convert the headers;
652 broken_env = {
653 'HTTP_CONTENT_TYPE': 'text/html',
654 'CONTENT_TYPE': 'text/html',
655 'HTTP_CONTENT_LENGTH': '0',
656 'CONTENT_LENGTH': '0',
657 'HTTP_ACCEPT': '*',
658 'wsgi.version': (1, 0)
659 }
660 headers = self.storage_class(broken_env)
661 assert headers
662 self.assert_equal(len(headers), 3)
663 self.assert_equal(sorted(headers), [
664 ('Accept', '*'),
665 ('Content-Length', '0'),
666 ('Content-Type', 'text/html')
667 ])
668 assert not self.storage_class({'wsgi.version': (1, 0)})
669 self.assert_equal(len(self.storage_class({'wsgi.version': (1, 0)})), 0)
670
671 def test_return_type_is_unicode(self):
672 # environ contains native strings; we return unicode
673 headers = self.storage_class({
674 'HTTP_FOO': '\xe2\x9c\x93',
675 'CONTENT_TYPE': 'text/plain',
676 })
677 self.assert_equal(headers['Foo'], u"\xe2\x9c\x93")
678 assert isinstance(headers['Foo'], text_type)
679 assert isinstance(headers['Content-Type'], text_type)
680 iter_output = dict(iter(headers))
681 self.assert_equal(iter_output['Foo'], u"\xe2\x9c\x93")
682 assert isinstance(iter_output['Foo'], text_type)
683 assert isinstance(iter_output['Content-Type'], text_type)
684
685 def test_bytes_operations(self):
686 foo_val = '\xff'
687 h = self.storage_class({
688 'HTTP_X_FOO': foo_val
689 })
690
691 self.assert_equal(h.get('x-foo', as_bytes=True), b'\xff')
692 self.assert_equal(h.get('x-foo'), u'\xff')
693
694
695 class HeaderSetTestCase(WerkzeugTestCase):
696 storage_class = datastructures.HeaderSet
697
698 def test_basic_interface(self):
699 hs = self.storage_class()
700 hs.add('foo')
701 hs.add('bar')
702 assert 'Bar' in hs
703 self.assert_equal(hs.find('foo'), 0)
704 self.assert_equal(hs.find('BAR'), 1)
705 assert hs.find('baz') < 0
706 hs.discard('missing')
707 hs.discard('foo')
708 assert hs.find('foo') < 0
709 self.assert_equal(hs.find('bar'), 0)
710
711 with self.assert_raises(IndexError):
712 hs.index('missing')
713
714 self.assert_equal(hs.index('bar'), 0)
715 assert hs
716 hs.clear()
717 assert not hs
718
719
720 class ImmutableListTestCase(WerkzeugTestCase):
721 storage_class = datastructures.ImmutableList
722
723 def test_list_hashable(self):
724 t = (1, 2, 3, 4)
725 l = self.storage_class(t)
726 self.assert_equal(hash(t), hash(l))
727 self.assert_not_equal(t, l)
728
729
730 def make_call_asserter(assert_equal_func, func=None):
731 """Utility to assert a certain number of function calls.
732
733 >>> assert_calls, func = make_call_asserter(self.assert_equal)
734 >>> with assert_calls(2):
735 func()
736 func()
737 """
738
739 calls = [0]
740
741 @contextmanager
742 def asserter(count, msg=None):
743 calls[0] = 0
744 yield
745 assert_equal_func(calls[0], count, msg)
746
747 def wrapped(*args, **kwargs):
748 calls[0] += 1
749 if func is not None:
750 return func(*args, **kwargs)
751
752 return asserter, wrapped
753
754
755 class CallbackDictTestCase(WerkzeugTestCase):
756 storage_class = datastructures.CallbackDict
757
758 def test_callback_dict_reads(self):
759 assert_calls, func = make_call_asserter(self.assert_equal)
760 initial = {'a': 'foo', 'b': 'bar'}
761 dct = self.storage_class(initial=initial, on_update=func)
762 with assert_calls(0, 'callback triggered by read-only method'):
763 # read-only methods
764 dct['a']
765 dct.get('a')
766 self.assert_raises(KeyError, lambda: dct['x'])
767 'a' in dct
768 list(iter(dct))
769 dct.copy()
770 with assert_calls(0, 'callback triggered without modification'):
771 # methods that may write but don't
772 dct.pop('z', None)
773 dct.setdefault('a')
774
775 def test_callback_dict_writes(self):
776 assert_calls, func = make_call_asserter(self.assert_equal)
777 initial = {'a': 'foo', 'b': 'bar'}
778 dct = self.storage_class(initial=initial, on_update=func)
779 with assert_calls(8, 'callback not triggered by write method'):
780 # always-write methods
781 dct['z'] = 123
782 dct['z'] = 123 # must trigger again
783 del dct['z']
784 dct.pop('b', None)
785 dct.setdefault('x')
786 dct.popitem()
787 dct.update([])
788 dct.clear()
789 with assert_calls(0, 'callback triggered by failed del'):
790 self.assert_raises(KeyError, lambda: dct.__delitem__('x'))
791 with assert_calls(0, 'callback triggered by failed pop'):
792 self.assert_raises(KeyError, lambda: dct.pop('x'))
793
794
795 def suite():
796 suite = unittest.TestSuite()
797 suite.addTest(unittest.makeSuite(MultiDictTestCase))
798 suite.addTest(unittest.makeSuite(OrderedMultiDictTestCase))
799 suite.addTest(unittest.makeSuite(CombinedMultiDictTestCase))
800 suite.addTest(unittest.makeSuite(ImmutableTypeConversionDictTestCase))
801 suite.addTest(unittest.makeSuite(ImmutableMultiDictTestCase))
802 suite.addTest(unittest.makeSuite(ImmutableDictTestCase))
803 suite.addTest(unittest.makeSuite(ImmutableOrderedMultiDictTestCase))
804 suite.addTest(unittest.makeSuite(HeadersTestCase))
805 suite.addTest(unittest.makeSuite(EnvironHeadersTestCase))
806 suite.addTest(unittest.makeSuite(HeaderSetTestCase))
807 suite.addTest(unittest.makeSuite(NativeItermethodsTestCase))
808 suite.addTest(unittest.makeSuite(CallbackDictTestCase))
809 return suite
+0
-172
werkzeug/testsuite/debug.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite.debug
3 ~~~~~~~~~~~~~~~~~~~~~~~~
4
5 Tests some debug utilities.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import unittest
11 import sys
12 import re
13
14 from werkzeug.testsuite import WerkzeugTestCase
15 from werkzeug.debug.repr import debug_repr, DebugReprGenerator, \
16 dump, helper
17 from werkzeug.debug.console import HTMLStringO
18 from werkzeug._compat import PY2
19
20
21 class DebugReprTestCase(WerkzeugTestCase):
22
23 def test_basic_repr(self):
24 self.assert_equal(debug_repr([]), u'[]')
25 self.assert_equal(debug_repr([1, 2]),
26 u'[<span class="number">1</span>, <span class="number">2</span>]')
27 self.assert_equal(debug_repr([1, 'test']),
28 u'[<span class="number">1</span>, <span class="string">\'test\'</span>]')
29 self.assert_equal(debug_repr([None]),
30 u'[<span class="object">None</span>]')
31
32 def test_sequence_repr(self):
33 self.assert_equal(debug_repr(list(range(20))), (
34 u'[<span class="number">0</span>, <span class="number">1</span>, '
35 u'<span class="number">2</span>, <span class="number">3</span>, '
36 u'<span class="number">4</span>, <span class="number">5</span>, '
37 u'<span class="number">6</span>, <span class="number">7</span>, '
38 u'<span class="extended"><span class="number">8</span>, '
39 u'<span class="number">9</span>, <span class="number">10</span>, '
40 u'<span class="number">11</span>, <span class="number">12</span>, '
41 u'<span class="number">13</span>, <span class="number">14</span>, '
42 u'<span class="number">15</span>, <span class="number">16</span>, '
43 u'<span class="number">17</span>, <span class="number">18</span>, '
44 u'<span class="number">19</span></span>]'
45 ))
46
47 def test_mapping_repr(self):
48 self.assert_equal(debug_repr({}), u'{}')
49 self.assert_equal(debug_repr({'foo': 42}),
50 u'{<span class="pair"><span class="key"><span class="string">\'foo\''
51 u'</span></span>: <span class="value"><span class="number">42'
52 u'</span></span></span>}')
53 self.assert_equal(debug_repr(dict(zip(range(10), [None] * 10))),
54 u'{<span class="pair"><span class="key"><span class="number">0</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">1</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">2</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">3</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="extended"><span class="pair"><span class="key"><span class="number">4</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">5</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">6</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">7</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">8</span></span>: <span class="value"><span class="object">None</span></span></span>, <span class="pair"><span class="key"><span class="number">9</span></span>: <span class="value"><span class="object">None</span></span></span></span>}')
55 self.assert_equal(
56 debug_repr((1, 'zwei', u'drei')),
57 u'(<span class="number">1</span>, <span class="string">\''
58 u'zwei\'</span>, <span class="string">%s\'drei\'</span>)' % ('u' if PY2 else ''))
59
60 def test_custom_repr(self):
61 class Foo(object):
62 def __repr__(self):
63 return '<Foo 42>'
64 self.assert_equal(debug_repr(Foo()),
65 '<span class="object">&lt;Foo 42&gt;</span>')
66
67 def test_list_subclass_repr(self):
68 class MyList(list):
69 pass
70 self.assert_equal(
71 debug_repr(MyList([1, 2])),
72 u'<span class="module">werkzeug.testsuite.debug.</span>MyList(['
73 u'<span class="number">1</span>, <span class="number">2</span>])')
74
75 def test_regex_repr(self):
76 self.assert_equal(debug_repr(re.compile(r'foo\d')),
77 u're.compile(<span class="string regex">r\'foo\\d\'</span>)')
78 #XXX: no raw string here cause of a syntax bug in py3.3
79 self.assert_equal(debug_repr(re.compile(u'foo\\d')),
80 u're.compile(<span class="string regex">%sr\'foo\\d\'</span>)' %
81 ('u' if PY2 else ''))
82
83 def test_set_repr(self):
84 self.assert_equal(debug_repr(frozenset('x')),
85 u'frozenset([<span class="string">\'x\'</span>])')
86 self.assert_equal(debug_repr(set('x')),
87 u'set([<span class="string">\'x\'</span>])')
88
89 def test_recursive_repr(self):
90 a = [1]
91 a.append(a)
92 self.assert_equal(debug_repr(a),
93 u'[<span class="number">1</span>, [...]]')
94
95 def test_broken_repr(self):
96 class Foo(object):
97 def __repr__(self):
98 raise Exception('broken!')
99
100 self.assert_equal(
101 debug_repr(Foo()),
102 u'<span class="brokenrepr">&lt;broken repr (Exception: '
103 u'broken!)&gt;</span>')
104
105
106 class Foo(object):
107 x = 42
108 y = 23
109
110 def __init__(self):
111 self.z = 15
112
113
114 class DebugHelpersTestCase(WerkzeugTestCase):
115
116 def test_object_dumping(self):
117 drg = DebugReprGenerator()
118 out = drg.dump_object(Foo())
119 assert re.search('Details for werkzeug.testsuite.debug.Foo object at', out)
120 assert re.search('<th>x.*<span class="number">42</span>(?s)', out)
121 assert re.search('<th>y.*<span class="number">23</span>(?s)', out)
122 assert re.search('<th>z.*<span class="number">15</span>(?s)', out)
123
124 out = drg.dump_object({'x': 42, 'y': 23})
125 assert re.search('Contents of', out)
126 assert re.search('<th>x.*<span class="number">42</span>(?s)', out)
127 assert re.search('<th>y.*<span class="number">23</span>(?s)', out)
128
129 out = drg.dump_object({'x': 42, 'y': 23, 23: 11})
130 assert not re.search('Contents of', out)
131
132 out = drg.dump_locals({'x': 42, 'y': 23})
133 assert re.search('Local variables in frame', out)
134 assert re.search('<th>x.*<span class="number">42</span>(?s)', out)
135 assert re.search('<th>y.*<span class="number">23</span>(?s)', out)
136
137 def test_debug_dump(self):
138 old = sys.stdout
139 sys.stdout = HTMLStringO()
140 try:
141 dump([1, 2, 3])
142 x = sys.stdout.reset()
143 dump()
144 y = sys.stdout.reset()
145 finally:
146 sys.stdout = old
147
148 self.assert_in('Details for list object at', x)
149 self.assert_in('<span class="number">1</span>', x)
150 self.assert_in('Local variables in frame', y)
151 self.assert_in('<th>x', y)
152 self.assert_in('<th>old', y)
153
154 def test_debug_help(self):
155 old = sys.stdout
156 sys.stdout = HTMLStringO()
157 try:
158 helper([1, 2, 3])
159 x = sys.stdout.reset()
160 finally:
161 sys.stdout = old
162
163 self.assert_in('Help on list object', x)
164 self.assert_in('__delitem__', x)
165
166
167 def suite():
168 suite = unittest.TestSuite()
169 suite.addTest(unittest.makeSuite(DebugReprTestCase))
170 suite.addTest(unittest.makeSuite(DebugHelpersTestCase))
171 return suite
+0
-85
werkzeug/testsuite/exceptions.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite.exceptions
3 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5 The tests for the exception classes.
6
7 TODO:
8
9 - This is undertested. HTML is never checked
10
11 :copyright: (c) 2014 by Armin Ronacher.
12 :license: BSD, see LICENSE for more details.
13 """
14 import unittest
15
16 from werkzeug.testsuite import WerkzeugTestCase
17
18 from werkzeug import exceptions
19 from werkzeug.wrappers import Response
20 from werkzeug._compat import text_type
21
22
23 class ExceptionsTestCase(WerkzeugTestCase):
24
25 def test_proxy_exception(self):
26 orig_resp = Response('Hello World')
27 try:
28 exceptions.abort(orig_resp)
29 except exceptions.HTTPException as e:
30 resp = e.get_response({})
31 else:
32 self.fail('exception not raised')
33 self.assert_true(resp is orig_resp)
34 self.assert_equal(resp.get_data(), b'Hello World')
35
36 def test_aborter(self):
37 abort = exceptions.abort
38 self.assert_raises(exceptions.BadRequest, abort, 400)
39 self.assert_raises(exceptions.Unauthorized, abort, 401)
40 self.assert_raises(exceptions.Forbidden, abort, 403)
41 self.assert_raises(exceptions.NotFound, abort, 404)
42 self.assert_raises(exceptions.MethodNotAllowed, abort, 405, ['GET', 'HEAD'])
43 self.assert_raises(exceptions.NotAcceptable, abort, 406)
44 self.assert_raises(exceptions.RequestTimeout, abort, 408)
45 self.assert_raises(exceptions.Gone, abort, 410)
46 self.assert_raises(exceptions.LengthRequired, abort, 411)
47 self.assert_raises(exceptions.PreconditionFailed, abort, 412)
48 self.assert_raises(exceptions.RequestEntityTooLarge, abort, 413)
49 self.assert_raises(exceptions.RequestURITooLarge, abort, 414)
50 self.assert_raises(exceptions.UnsupportedMediaType, abort, 415)
51 self.assert_raises(exceptions.UnprocessableEntity, abort, 422)
52 self.assert_raises(exceptions.InternalServerError, abort, 500)
53 self.assert_raises(exceptions.NotImplemented, abort, 501)
54 self.assert_raises(exceptions.BadGateway, abort, 502)
55 self.assert_raises(exceptions.ServiceUnavailable, abort, 503)
56
57 myabort = exceptions.Aborter({1: exceptions.NotFound})
58 self.assert_raises(LookupError, myabort, 404)
59 self.assert_raises(exceptions.NotFound, myabort, 1)
60
61 myabort = exceptions.Aborter(extra={1: exceptions.NotFound})
62 self.assert_raises(exceptions.NotFound, myabort, 404)
63 self.assert_raises(exceptions.NotFound, myabort, 1)
64
65 def test_exception_repr(self):
66 exc = exceptions.NotFound()
67 self.assert_equal(text_type(exc), '404: Not Found')
68 self.assert_equal(repr(exc), "<NotFound '404: Not Found'>")
69
70 exc = exceptions.NotFound('Not There')
71 self.assert_equal(text_type(exc), '404: Not Found')
72 self.assert_equal(repr(exc), "<NotFound '404: Not Found'>")
73
74 def test_special_exceptions(self):
75 exc = exceptions.MethodNotAllowed(['GET', 'HEAD', 'POST'])
76 h = dict(exc.get_headers({}))
77 self.assert_equal(h['Allow'], 'GET, HEAD, POST')
78 self.assert_true('The method is not allowed' in exc.get_description())
79
80
81 def suite():
82 suite = unittest.TestSuite()
83 suite.addTest(unittest.makeSuite(ExceptionsTestCase))
84 return suite
+0
-411
werkzeug/testsuite/formparser.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite.formparser
3 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5 Tests the form parsing facilities.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10
11 from __future__ import with_statement
12
13 import unittest
14 from os.path import join, dirname
15
16 from werkzeug.testsuite import WerkzeugTestCase
17
18 from werkzeug import formparser
19 from werkzeug.test import create_environ, Client
20 from werkzeug.wrappers import Request, Response
21 from werkzeug.exceptions import RequestEntityTooLarge
22 from werkzeug.datastructures import MultiDict
23 from werkzeug.formparser import parse_form_data
24 from werkzeug._compat import BytesIO
25
26
27 @Request.application
28 def form_data_consumer(request):
29 result_object = request.args['object']
30 if result_object == 'text':
31 return Response(repr(request.form['text']))
32 f = request.files[result_object]
33 return Response(b'\n'.join((
34 repr(f.filename).encode('ascii'),
35 repr(f.name).encode('ascii'),
36 repr(f.content_type).encode('ascii'),
37 f.stream.read()
38 )))
39
40
41 def get_contents(filename):
42 with open(filename, 'rb') as f:
43 return f.read()
44
45
46 class FormParserTestCase(WerkzeugTestCase):
47
48 def test_limiting(self):
49 data = b'foo=Hello+World&bar=baz'
50 req = Request.from_values(input_stream=BytesIO(data),
51 content_length=len(data),
52 content_type='application/x-www-form-urlencoded',
53 method='POST')
54 req.max_content_length = 400
55 self.assert_strict_equal(req.form['foo'], u'Hello World')
56
57 req = Request.from_values(input_stream=BytesIO(data),
58 content_length=len(data),
59 content_type='application/x-www-form-urlencoded',
60 method='POST')
61 req.max_form_memory_size = 7
62 self.assert_raises(RequestEntityTooLarge, lambda: req.form['foo'])
63
64 req = Request.from_values(input_stream=BytesIO(data),
65 content_length=len(data),
66 content_type='application/x-www-form-urlencoded',
67 method='POST')
68 req.max_form_memory_size = 400
69 self.assert_strict_equal(req.form['foo'], u'Hello World')
70
71 data = (b'--foo\r\nContent-Disposition: form-field; name=foo\r\n\r\n'
72 b'Hello World\r\n'
73 b'--foo\r\nContent-Disposition: form-field; name=bar\r\n\r\n'
74 b'bar=baz\r\n--foo--')
75 req = Request.from_values(input_stream=BytesIO(data),
76 content_length=len(data),
77 content_type='multipart/form-data; boundary=foo',
78 method='POST')
79 req.max_content_length = 4
80 self.assert_raises(RequestEntityTooLarge, lambda: req.form['foo'])
81
82 req = Request.from_values(input_stream=BytesIO(data),
83 content_length=len(data),
84 content_type='multipart/form-data; boundary=foo',
85 method='POST')
86 req.max_content_length = 400
87 self.assert_strict_equal(req.form['foo'], u'Hello World')
88
89 req = Request.from_values(input_stream=BytesIO(data),
90 content_length=len(data),
91 content_type='multipart/form-data; boundary=foo',
92 method='POST')
93 req.max_form_memory_size = 7
94 self.assert_raises(RequestEntityTooLarge, lambda: req.form['foo'])
95
96 req = Request.from_values(input_stream=BytesIO(data),
97 content_length=len(data),
98 content_type='multipart/form-data; boundary=foo',
99 method='POST')
100 req.max_form_memory_size = 400
101 self.assert_strict_equal(req.form['foo'], u'Hello World')
102
103 def test_missing_multipart_boundary(self):
104 data = (b'--foo\r\nContent-Disposition: form-field; name=foo\r\n\r\n'
105 b'Hello World\r\n'
106 b'--foo\r\nContent-Disposition: form-field; name=bar\r\n\r\n'
107 b'bar=baz\r\n--foo--')
108 req = Request.from_values(input_stream=BytesIO(data),
109 content_length=len(data),
110 content_type='multipart/form-data',
111 method='POST')
112 self.assert_equal(req.form, {})
113
114 def test_parse_form_data_put_without_content(self):
115 # A PUT without a Content-Type header returns empty data
116
117 # Both rfc1945 and rfc2616 (1.0 and 1.1) say "Any HTTP/[1.0/1.1] message
118 # containing an entity-body SHOULD include a Content-Type header field
119 # defining the media type of that body." In the case where either
120 # headers are omitted, parse_form_data should still work.
121 env = create_environ('/foo', 'http://example.org/', method='PUT')
122 del env['CONTENT_TYPE']
123 del env['CONTENT_LENGTH']
124
125 stream, form, files = formparser.parse_form_data(env)
126 self.assert_strict_equal(stream.read(), b'')
127 self.assert_strict_equal(len(form), 0)
128 self.assert_strict_equal(len(files), 0)
129
130 def test_parse_form_data_get_without_content(self):
131 env = create_environ('/foo', 'http://example.org/', method='GET')
132 del env['CONTENT_TYPE']
133 del env['CONTENT_LENGTH']
134
135 stream, form, files = formparser.parse_form_data(env)
136 self.assert_strict_equal(stream.read(), b'')
137 self.assert_strict_equal(len(form), 0)
138 self.assert_strict_equal(len(files), 0)
139
140 def test_large_file(self):
141 data = b'x' * (1024 * 600)
142 req = Request.from_values(data={'foo': (BytesIO(data), 'test.txt')},
143 method='POST')
144 # make sure we have a real file here, because we expect to be
145 # on the disk. > 1024 * 500
146 self.assert_true(hasattr(req.files['foo'].stream, u'fileno'))
147 # close file to prevent fds from leaking
148 req.files['foo'].close()
149
150 def test_streaming_parse(self):
151 data = b'x' * (1024 * 600)
152 class StreamMPP(formparser.MultiPartParser):
153 def parse(self, file, boundary, content_length):
154 i = iter(self.parse_lines(file, boundary, content_length))
155 one = next(i)
156 two = next(i)
157 return self.cls(()), {'one': one, 'two': two}
158 class StreamFDP(formparser.FormDataParser):
159 def _sf_parse_multipart(self, stream, mimetype,
160 content_length, options):
161 form, files = StreamMPP(
162 self.stream_factory, self.charset, self.errors,
163 max_form_memory_size=self.max_form_memory_size,
164 cls=self.cls).parse(stream, options.get('boundary').encode('ascii'),
165 content_length)
166 return stream, form, files
167 parse_functions = {}
168 parse_functions.update(formparser.FormDataParser.parse_functions)
169 parse_functions['multipart/form-data'] = _sf_parse_multipart
170 class StreamReq(Request):
171 form_data_parser_class = StreamFDP
172 req = StreamReq.from_values(data={'foo': (BytesIO(data), 'test.txt')},
173 method='POST')
174 self.assert_strict_equal('begin_file', req.files['one'][0])
175 self.assert_strict_equal(('foo', 'test.txt'), req.files['one'][1][1:])
176 self.assert_strict_equal('cont', req.files['two'][0])
177 self.assert_strict_equal(data, req.files['two'][1])
178
179
180 class MultiPartTestCase(WerkzeugTestCase):
181
182 def test_basic(self):
183 resources = join(dirname(__file__), 'multipart')
184 client = Client(form_data_consumer, Response)
185
186 repository = [
187 ('firefox3-2png1txt', '---------------------------186454651713519341951581030105', [
188 (u'anchor.png', 'file1', 'image/png', 'file1.png'),
189 (u'application_edit.png', 'file2', 'image/png', 'file2.png')
190 ], u'example text'),
191 ('firefox3-2pnglongtext', '---------------------------14904044739787191031754711748', [
192 (u'accept.png', 'file1', 'image/png', 'file1.png'),
193 (u'add.png', 'file2', 'image/png', 'file2.png')
194 ], u'--long text\r\n--with boundary\r\n--lookalikes--'),
195 ('opera8-2png1txt', '----------zEO9jQKmLc2Cq88c23Dx19', [
196 (u'arrow_branch.png', 'file1', 'image/png', 'file1.png'),
197 (u'award_star_bronze_1.png', 'file2', 'image/png', 'file2.png')
198 ], u'blafasel öäü'),
199 ('webkit3-2png1txt', '----WebKitFormBoundaryjdSFhcARk8fyGNy6', [
200 (u'gtk-apply.png', 'file1', 'image/png', 'file1.png'),
201 (u'gtk-no.png', 'file2', 'image/png', 'file2.png')
202 ], u'this is another text with ümläüts'),
203 ('ie6-2png1txt', '---------------------------7d91b03a20128', [
204 (u'file1.png', 'file1', 'image/x-png', 'file1.png'),
205 (u'file2.png', 'file2', 'image/x-png', 'file2.png')
206 ], u'ie6 sucks :-/')
207 ]
208
209 for name, boundary, files, text in repository:
210 folder = join(resources, name)
211 data = get_contents(join(folder, 'request.txt'))
212 for filename, field, content_type, fsname in files:
213 response = client.post('/?object=' + field, data=data, content_type=
214 'multipart/form-data; boundary="%s"' % boundary,
215 content_length=len(data))
216 lines = response.get_data().split(b'\n', 3)
217 self.assert_strict_equal(lines[0], repr(filename).encode('ascii'))
218 self.assert_strict_equal(lines[1], repr(field).encode('ascii'))
219 self.assert_strict_equal(lines[2], repr(content_type).encode('ascii'))
220 self.assert_strict_equal(lines[3], get_contents(join(folder, fsname)))
221 response = client.post('/?object=text', data=data, content_type=
222 'multipart/form-data; boundary="%s"' % boundary,
223 content_length=len(data))
224 self.assert_strict_equal(response.get_data(), repr(text).encode('utf-8'))
225
226 def test_ie7_unc_path(self):
227 client = Client(form_data_consumer, Response)
228 data_file = join(dirname(__file__), 'multipart', 'ie7_full_path_request.txt')
229 data = get_contents(data_file)
230 boundary = '---------------------------7da36d1b4a0164'
231 response = client.post('/?object=cb_file_upload_multiple', data=data, content_type=
232 'multipart/form-data; boundary="%s"' % boundary, content_length=len(data))
233 lines = response.get_data().split(b'\n', 3)
234 self.assert_strict_equal(lines[0],
235 repr(u'Sellersburg Town Council Meeting 02-22-2010doc.doc').encode('ascii'))
236
237 def test_end_of_file(self):
238 # This test looks innocent but it was actually timeing out in
239 # the Werkzeug 0.5 release version (#394)
240 data = (
241 b'--foo\r\n'
242 b'Content-Disposition: form-data; name="test"; filename="test.txt"\r\n'
243 b'Content-Type: text/plain\r\n\r\n'
244 b'file contents and no end'
245 )
246 data = Request.from_values(input_stream=BytesIO(data),
247 content_length=len(data),
248 content_type='multipart/form-data; boundary=foo',
249 method='POST')
250 self.assert_true(not data.files)
251 self.assert_true(not data.form)
252
253 def test_broken(self):
254 data = (
255 '--foo\r\n'
256 'Content-Disposition: form-data; name="test"; filename="test.txt"\r\n'
257 'Content-Transfer-Encoding: base64\r\n'
258 'Content-Type: text/plain\r\n\r\n'
259 'broken base 64'
260 '--foo--'
261 )
262 _, form, files = formparser.parse_form_data(create_environ(data=data,
263 method='POST', content_type='multipart/form-data; boundary=foo'))
264 self.assert_true(not files)
265 self.assert_true(not form)
266
267 self.assert_raises(ValueError, formparser.parse_form_data,
268 create_environ(data=data, method='POST',
269 content_type='multipart/form-data; boundary=foo'),
270 silent=False)
271
272 def test_file_no_content_type(self):
273 data = (
274 b'--foo\r\n'
275 b'Content-Disposition: form-data; name="test"; filename="test.txt"\r\n\r\n'
276 b'file contents\r\n--foo--'
277 )
278 data = Request.from_values(input_stream=BytesIO(data),
279 content_length=len(data),
280 content_type='multipart/form-data; boundary=foo',
281 method='POST')
282 self.assert_equal(data.files['test'].filename, 'test.txt')
283 self.assert_strict_equal(data.files['test'].read(), b'file contents')
284
285 def test_extra_newline(self):
286 # this test looks innocent but it was actually timeing out in
287 # the Werkzeug 0.5 release version (#394)
288 data = (
289 b'\r\n\r\n--foo\r\n'
290 b'Content-Disposition: form-data; name="foo"\r\n\r\n'
291 b'a string\r\n'
292 b'--foo--'
293 )
294 data = Request.from_values(input_stream=BytesIO(data),
295 content_length=len(data),
296 content_type='multipart/form-data; boundary=foo',
297 method='POST')
298 self.assert_true(not data.files)
299 self.assert_strict_equal(data.form['foo'], u'a string')
300
301 def test_headers(self):
302 data = (b'--foo\r\n'
303 b'Content-Disposition: form-data; name="foo"; filename="foo.txt"\r\n'
304 b'X-Custom-Header: blah\r\n'
305 b'Content-Type: text/plain; charset=utf-8\r\n\r\n'
306 b'file contents, just the contents\r\n'
307 b'--foo--')
308 req = Request.from_values(input_stream=BytesIO(data),
309 content_length=len(data),
310 content_type='multipart/form-data; boundary=foo',
311 method='POST')
312 foo = req.files['foo']
313 self.assert_strict_equal(foo.mimetype, 'text/plain')
314 self.assert_strict_equal(foo.mimetype_params, {'charset': 'utf-8'})
315 self.assert_strict_equal(foo.headers['content-type'], foo.content_type)
316 self.assert_strict_equal(foo.content_type, 'text/plain; charset=utf-8')
317 self.assert_strict_equal(foo.headers['x-custom-header'], 'blah')
318
319 def test_nonstandard_line_endings(self):
320 for nl in b'\n', b'\r', b'\r\n':
321 data = nl.join((
322 b'--foo',
323 b'Content-Disposition: form-data; name=foo',
324 b'',
325 b'this is just bar',
326 b'--foo',
327 b'Content-Disposition: form-data; name=bar',
328 b'',
329 b'blafasel',
330 b'--foo--'
331 ))
332 req = Request.from_values(input_stream=BytesIO(data),
333 content_length=len(data),
334 content_type='multipart/form-data; '
335 'boundary=foo', method='POST')
336 self.assert_strict_equal(req.form['foo'], u'this is just bar')
337 self.assert_strict_equal(req.form['bar'], u'blafasel')
338
339 def test_failures(self):
340 def parse_multipart(stream, boundary, content_length):
341 parser = formparser.MultiPartParser(content_length)
342 return parser.parse(stream, boundary, content_length)
343 self.assert_raises(ValueError, parse_multipart, BytesIO(), b'broken ', 0)
344
345 data = b'--foo\r\n\r\nHello World\r\n--foo--'
346 self.assert_raises(ValueError, parse_multipart, BytesIO(data), b'foo', len(data))
347
348 data = b'--foo\r\nContent-Disposition: form-field; name=foo\r\n' \
349 b'Content-Transfer-Encoding: base64\r\n\r\nHello World\r\n--foo--'
350 self.assert_raises(ValueError, parse_multipart, BytesIO(data), b'foo', len(data))
351
352 data = b'--foo\r\nContent-Disposition: form-field; name=foo\r\n\r\nHello World\r\n'
353 self.assert_raises(ValueError, parse_multipart, BytesIO(data), b'foo', len(data))
354
355 x = formparser.parse_multipart_headers(['foo: bar\r\n', ' x test\r\n'])
356 self.assert_strict_equal(x['foo'], 'bar\n x test')
357 self.assert_raises(ValueError, formparser.parse_multipart_headers,
358 ['foo: bar\r\n', ' x test'])
359
360 def test_bad_newline_bad_newline_assumption(self):
361 class ISORequest(Request):
362 charset = 'latin1'
363 contents = b'U2vlbmUgbORu'
364 data = b'--foo\r\nContent-Disposition: form-data; name="test"\r\n' \
365 b'Content-Transfer-Encoding: base64\r\n\r\n' + \
366 contents + b'\r\n--foo--'
367 req = ISORequest.from_values(input_stream=BytesIO(data),
368 content_length=len(data),
369 content_type='multipart/form-data; boundary=foo',
370 method='POST')
371 self.assert_strict_equal(req.form['test'], u'Sk\xe5ne l\xe4n')
372
373 def test_empty_multipart(self):
374 environ = {}
375 data = b'--boundary--'
376 environ['REQUEST_METHOD'] = 'POST'
377 environ['CONTENT_TYPE'] = 'multipart/form-data; boundary=boundary'
378 environ['CONTENT_LENGTH'] = str(len(data))
379 environ['wsgi.input'] = BytesIO(data)
380 stream, form, files = parse_form_data(environ, silent=False)
381 rv = stream.read()
382 self.assert_equal(rv, b'')
383 self.assert_equal(form, MultiDict())
384 self.assert_equal(files, MultiDict())
385
386
387 class InternalFunctionsTestCase(WerkzeugTestCase):
388
389 def test_line_parser(self):
390 assert formparser._line_parse('foo') == ('foo', False)
391 assert formparser._line_parse('foo\r\n') == ('foo', True)
392 assert formparser._line_parse('foo\r') == ('foo', True)
393 assert formparser._line_parse('foo\n') == ('foo', True)
394
395 def test_find_terminator(self):
396 lineiter = iter(b'\n\n\nfoo\nbar\nbaz'.splitlines(True))
397 find_terminator = formparser.MultiPartParser()._find_terminator
398 line = find_terminator(lineiter)
399 self.assert_equal(line, b'foo')
400 self.assert_equal(list(lineiter), [b'bar\n', b'baz'])
401 self.assert_equal(find_terminator([]), b'')
402 self.assert_equal(find_terminator([b'']), b'')
403
404
405 def suite():
406 suite = unittest.TestSuite()
407 suite.addTest(unittest.makeSuite(FormParserTestCase))
408 suite.addTest(unittest.makeSuite(MultiPartTestCase))
409 suite.addTest(unittest.makeSuite(InternalFunctionsTestCase))
410 return suite
+0
-449
werkzeug/testsuite/http.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite.http
3 ~~~~~~~~~~~~~~~~~~~~~~~
4
5 HTTP parsing utilities.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import unittest
11 from datetime import datetime
12
13 from werkzeug.testsuite import WerkzeugTestCase
14 from werkzeug._compat import itervalues, wsgi_encoding_dance
15
16 from werkzeug import http, datastructures
17 from werkzeug.test import create_environ
18
19
20 class HTTPUtilityTestCase(WerkzeugTestCase):
21
22 def test_accept(self):
23 a = http.parse_accept_header('en-us,ru;q=0.5')
24 self.assert_equal(list(itervalues(a)), ['en-us', 'ru'])
25 self.assert_equal(a.best, 'en-us')
26 self.assert_equal(a.find('ru'), 1)
27 self.assert_raises(ValueError, a.index, 'de')
28 self.assert_equal(a.to_header(), 'en-us,ru;q=0.5')
29
30 def test_mime_accept(self):
31 a = http.parse_accept_header('text/xml,application/xml,'
32 'application/xhtml+xml,'
33 'text/html;q=0.9,text/plain;q=0.8,'
34 'image/png,*/*;q=0.5',
35 datastructures.MIMEAccept)
36 self.assert_raises(ValueError, lambda: a['missing'])
37 self.assert_equal(a['image/png'], 1)
38 self.assert_equal(a['text/plain'], 0.8)
39 self.assert_equal(a['foo/bar'], 0.5)
40 self.assert_equal(a[a.find('foo/bar')], ('*/*', 0.5))
41
42 def test_accept_matches(self):
43 a = http.parse_accept_header('text/xml,application/xml,application/xhtml+xml,'
44 'text/html;q=0.9,text/plain;q=0.8,'
45 'image/png', datastructures.MIMEAccept)
46 self.assert_equal(a.best_match(['text/html', 'application/xhtml+xml']),
47 'application/xhtml+xml')
48 self.assert_equal(a.best_match(['text/html']), 'text/html')
49 self.assert_true(a.best_match(['foo/bar']) is None)
50 self.assert_equal(a.best_match(['foo/bar', 'bar/foo'],
51 default='foo/bar'), 'foo/bar')
52 self.assert_equal(a.best_match(['application/xml', 'text/xml']), 'application/xml')
53
54 def test_charset_accept(self):
55 a = http.parse_accept_header('ISO-8859-1,utf-8;q=0.7,*;q=0.7',
56 datastructures.CharsetAccept)
57 self.assert_equal(a['iso-8859-1'], a['iso8859-1'])
58 self.assert_equal(a['iso-8859-1'], 1)
59 self.assert_equal(a['UTF8'], 0.7)
60 self.assert_equal(a['ebcdic'], 0.7)
61
62 def test_language_accept(self):
63 a = http.parse_accept_header('de-AT,de;q=0.8,en;q=0.5',
64 datastructures.LanguageAccept)
65 self.assert_equal(a.best, 'de-AT')
66 self.assert_true('de_AT' in a)
67 self.assert_true('en' in a)
68 self.assert_equal(a['de-at'], 1)
69 self.assert_equal(a['en'], 0.5)
70
71 def test_set_header(self):
72 hs = http.parse_set_header('foo, Bar, "Blah baz", Hehe')
73 self.assert_true('blah baz' in hs)
74 self.assert_true('foobar' not in hs)
75 self.assert_true('foo' in hs)
76 self.assert_equal(list(hs), ['foo', 'Bar', 'Blah baz', 'Hehe'])
77 hs.add('Foo')
78 self.assert_equal(hs.to_header(), 'foo, Bar, "Blah baz", Hehe')
79
80 def test_list_header(self):
81 hl = http.parse_list_header('foo baz, blah')
82 self.assert_equal(hl, ['foo baz', 'blah'])
83
84 def test_dict_header(self):
85 d = http.parse_dict_header('foo="bar baz", blah=42')
86 self.assert_equal(d, {'foo': 'bar baz', 'blah': '42'})
87
88 def test_cache_control_header(self):
89 cc = http.parse_cache_control_header('max-age=0, no-cache')
90 assert cc.max_age == 0
91 assert cc.no_cache
92 cc = http.parse_cache_control_header('private, community="UCI"', None,
93 datastructures.ResponseCacheControl)
94 assert cc.private
95 assert cc['community'] == 'UCI'
96
97 c = datastructures.ResponseCacheControl()
98 assert c.no_cache is None
99 assert c.private is None
100 c.no_cache = True
101 assert c.no_cache == '*'
102 c.private = True
103 assert c.private == '*'
104 del c.private
105 assert c.private is None
106 assert c.to_header() == 'no-cache'
107
108 def test_authorization_header(self):
109 a = http.parse_authorization_header('Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==')
110 assert a.type == 'basic'
111 assert a.username == 'Aladdin'
112 assert a.password == 'open sesame'
113
114 a = http.parse_authorization_header('''Digest username="Mufasa",
115 realm="testrealm@host.invalid",
116 nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
117 uri="/dir/index.html",
118 qop=auth,
119 nc=00000001,
120 cnonce="0a4f113b",
121 response="6629fae49393a05397450978507c4ef1",
122 opaque="5ccc069c403ebaf9f0171e9517f40e41"''')
123 assert a.type == 'digest'
124 assert a.username == 'Mufasa'
125 assert a.realm == 'testrealm@host.invalid'
126 assert a.nonce == 'dcd98b7102dd2f0e8b11d0f600bfb0c093'
127 assert a.uri == '/dir/index.html'
128 assert 'auth' in a.qop
129 assert a.nc == '00000001'
130 assert a.cnonce == '0a4f113b'
131 assert a.response == '6629fae49393a05397450978507c4ef1'
132 assert a.opaque == '5ccc069c403ebaf9f0171e9517f40e41'
133
134 a = http.parse_authorization_header('''Digest username="Mufasa",
135 realm="testrealm@host.invalid",
136 nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
137 uri="/dir/index.html",
138 response="e257afa1414a3340d93d30955171dd0e",
139 opaque="5ccc069c403ebaf9f0171e9517f40e41"''')
140 assert a.type == 'digest'
141 assert a.username == 'Mufasa'
142 assert a.realm == 'testrealm@host.invalid'
143 assert a.nonce == 'dcd98b7102dd2f0e8b11d0f600bfb0c093'
144 assert a.uri == '/dir/index.html'
145 assert a.response == 'e257afa1414a3340d93d30955171dd0e'
146 assert a.opaque == '5ccc069c403ebaf9f0171e9517f40e41'
147
148 assert http.parse_authorization_header('') is None
149 assert http.parse_authorization_header(None) is None
150 assert http.parse_authorization_header('foo') is None
151
152 def test_www_authenticate_header(self):
153 wa = http.parse_www_authenticate_header('Basic realm="WallyWorld"')
154 assert wa.type == 'basic'
155 assert wa.realm == 'WallyWorld'
156 wa.realm = 'Foo Bar'
157 assert wa.to_header() == 'Basic realm="Foo Bar"'
158
159 wa = http.parse_www_authenticate_header('''Digest
160 realm="testrealm@host.com",
161 qop="auth,auth-int",
162 nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
163 opaque="5ccc069c403ebaf9f0171e9517f40e41"''')
164 assert wa.type == 'digest'
165 assert wa.realm == 'testrealm@host.com'
166 assert 'auth' in wa.qop
167 assert 'auth-int' in wa.qop
168 assert wa.nonce == 'dcd98b7102dd2f0e8b11d0f600bfb0c093'
169 assert wa.opaque == '5ccc069c403ebaf9f0171e9517f40e41'
170
171 wa = http.parse_www_authenticate_header('broken')
172 assert wa.type == 'broken'
173
174 assert not http.parse_www_authenticate_header('').type
175 assert not http.parse_www_authenticate_header('')
176
177 def test_etags(self):
178 assert http.quote_etag('foo') == '"foo"'
179 assert http.quote_etag('foo', True) == 'w/"foo"'
180 assert http.unquote_etag('"foo"') == ('foo', False)
181 assert http.unquote_etag('w/"foo"') == ('foo', True)
182 es = http.parse_etags('"foo", "bar", w/"baz", blar')
183 assert sorted(es) == ['bar', 'blar', 'foo']
184 assert 'foo' in es
185 assert 'baz' not in es
186 assert es.contains_weak('baz')
187 assert 'blar' in es
188 assert es.contains_raw('w/"baz"')
189 assert es.contains_raw('"foo"')
190 assert sorted(es.to_header().split(', ')) == ['"bar"', '"blar"', '"foo"', 'w/"baz"']
191
192 def test_etags_nonzero(self):
193 etags = http.parse_etags('w/"foo"')
194 self.assert_true(bool(etags))
195 self.assert_true(etags.contains_raw('w/"foo"'))
196
197 def test_parse_date(self):
198 assert http.parse_date('Sun, 06 Nov 1994 08:49:37 GMT ') == datetime(1994, 11, 6, 8, 49, 37)
199 assert http.parse_date('Sunday, 06-Nov-94 08:49:37 GMT') == datetime(1994, 11, 6, 8, 49, 37)
200 assert http.parse_date(' Sun Nov 6 08:49:37 1994') == datetime(1994, 11, 6, 8, 49, 37)
201 assert http.parse_date('foo') is None
202
203 def test_parse_date_overflows(self):
204 assert http.parse_date(' Sun 02 Feb 1343 08:49:37 GMT') == datetime(1343, 2, 2, 8, 49, 37)
205 assert http.parse_date('Thu, 01 Jan 1970 00:00:00 GMT') == datetime(1970, 1, 1, 0, 0)
206 assert http.parse_date('Thu, 33 Jan 1970 00:00:00 GMT') is None
207
208 def test_remove_entity_headers(self):
209 now = http.http_date()
210 headers1 = [('Date', now), ('Content-Type', 'text/html'), ('Content-Length', '0')]
211 headers2 = datastructures.Headers(headers1)
212
213 http.remove_entity_headers(headers1)
214 assert headers1 == [('Date', now)]
215
216 http.remove_entity_headers(headers2)
217 self.assert_equal(headers2, datastructures.Headers([(u'Date', now)]))
218
219 def test_remove_hop_by_hop_headers(self):
220 headers1 = [('Connection', 'closed'), ('Foo', 'bar'),
221 ('Keep-Alive', 'wtf')]
222 headers2 = datastructures.Headers(headers1)
223
224 http.remove_hop_by_hop_headers(headers1)
225 assert headers1 == [('Foo', 'bar')]
226
227 http.remove_hop_by_hop_headers(headers2)
228 assert headers2 == datastructures.Headers([('Foo', 'bar')])
229
230 def test_parse_options_header(self):
231 assert http.parse_options_header(r'something; foo="other\"thing"') == \
232 ('something', {'foo': 'other"thing'})
233 assert http.parse_options_header(r'something; foo="other\"thing"; meh=42') == \
234 ('something', {'foo': 'other"thing', 'meh': '42'})
235 assert http.parse_options_header(r'something; foo="other\"thing"; meh=42; bleh') == \
236 ('something', {'foo': 'other"thing', 'meh': '42', 'bleh': None})
237 assert http.parse_options_header('something; foo="other;thing"; meh=42; bleh') == \
238 ('something', {'foo': 'other;thing', 'meh': '42', 'bleh': None})
239 assert http.parse_options_header('something; foo="otherthing"; meh=; bleh') == \
240 ('something', {'foo': 'otherthing', 'meh': None, 'bleh': None})
241
242
243
244 def test_dump_options_header(self):
245 assert http.dump_options_header('foo', {'bar': 42}) == \
246 'foo; bar=42'
247 assert http.dump_options_header('foo', {'bar': 42, 'fizz': None}) in \
248 ('foo; bar=42; fizz', 'foo; fizz; bar=42')
249
250 def test_dump_header(self):
251 assert http.dump_header([1, 2, 3]) == '1, 2, 3'
252 assert http.dump_header([1, 2, 3], allow_token=False) == '"1", "2", "3"'
253 assert http.dump_header({'foo': 'bar'}, allow_token=False) == 'foo="bar"'
254 assert http.dump_header({'foo': 'bar'}) == 'foo=bar'
255
256 def test_is_resource_modified(self):
257 env = create_environ()
258
259 # ignore POST
260 env['REQUEST_METHOD'] = 'POST'
261 assert not http.is_resource_modified(env, etag='testing')
262 env['REQUEST_METHOD'] = 'GET'
263
264 # etagify from data
265 self.assert_raises(TypeError, http.is_resource_modified, env,
266 data='42', etag='23')
267 env['HTTP_IF_NONE_MATCH'] = http.generate_etag(b'awesome')
268 assert not http.is_resource_modified(env, data=b'awesome')
269
270 env['HTTP_IF_MODIFIED_SINCE'] = http.http_date(datetime(2008, 1, 1, 12, 30))
271 assert not http.is_resource_modified(env,
272 last_modified=datetime(2008, 1, 1, 12, 00))
273 assert http.is_resource_modified(env,
274 last_modified=datetime(2008, 1, 1, 13, 00))
275
276 def test_date_formatting(self):
277 assert http.cookie_date(0) == 'Thu, 01-Jan-1970 00:00:00 GMT'
278 assert http.cookie_date(datetime(1970, 1, 1)) == 'Thu, 01-Jan-1970 00:00:00 GMT'
279 assert http.http_date(0) == 'Thu, 01 Jan 1970 00:00:00 GMT'
280 assert http.http_date(datetime(1970, 1, 1)) == 'Thu, 01 Jan 1970 00:00:00 GMT'
281
282 def test_cookies(self):
283 self.assert_strict_equal(
284 dict(http.parse_cookie('dismiss-top=6; CP=null*; PHPSESSID=0a539d42abc001cd'
285 'c762809248d4beed; a=42; b="\\\";"')),
286 {
287 'CP': u'null*',
288 'PHPSESSID': u'0a539d42abc001cdc762809248d4beed',
289 'a': u'42',
290 'dismiss-top': u'6',
291 'b': u'\";'
292 }
293 )
294 self.assert_strict_equal(
295 set(http.dump_cookie('foo', 'bar baz blub', 360, httponly=True,
296 sync_expires=False).split(u'; ')),
297 set([u'HttpOnly', u'Max-Age=360', u'Path=/', u'foo="bar baz blub"'])
298 )
299 self.assert_strict_equal(dict(http.parse_cookie('fo234{=bar; blub=Blah')),
300 {'fo234{': u'bar', 'blub': u'Blah'})
301
302 def test_cookie_quoting(self):
303 val = http.dump_cookie("foo", "?foo")
304 self.assert_strict_equal(val, 'foo="?foo"; Path=/')
305 self.assert_strict_equal(dict(http.parse_cookie(val)), {'foo': u'?foo'})
306
307 self.assert_strict_equal(dict(http.parse_cookie(r'foo="foo\054bar"')),
308 {'foo': u'foo,bar'})
309
310 def test_cookie_domain_resolving(self):
311 val = http.dump_cookie('foo', 'bar', domain=u'\N{SNOWMAN}.com')
312 self.assert_strict_equal(val, 'foo=bar; Domain=xn--n3h.com; Path=/')
313
314 def test_cookie_unicode_dumping(self):
315 val = http.dump_cookie('foo', u'\N{SNOWMAN}')
316 h = datastructures.Headers()
317 h.add('Set-Cookie', val)
318 self.assert_equal(h['Set-Cookie'], 'foo="\\342\\230\\203"; Path=/')
319
320 cookies = http.parse_cookie(h['Set-Cookie'])
321 self.assert_equal(cookies['foo'], u'\N{SNOWMAN}')
322
323 def test_cookie_unicode_keys(self):
324 # Yes, this is technically against the spec but happens
325 val = http.dump_cookie(u'fö', u'fö')
326 self.assert_equal(val, wsgi_encoding_dance(u'fö="f\\303\\266"; Path=/', 'utf-8'))
327 cookies = http.parse_cookie(val)
328 self.assert_equal(cookies[u'fö'], u'fö')
329
330 def test_cookie_unicode_parsing(self):
331 # This is actually a correct test. This is what is being submitted
332 # by firefox if you set an unicode cookie and we get the cookie sent
333 # in on Python 3 under PEP 3333.
334 cookies = http.parse_cookie(u'fö=fö')
335 self.assert_equal(cookies[u'fö'], u'fö')
336
337 def test_cookie_domain_encoding(self):
338 val = http.dump_cookie('foo', 'bar', domain=u'\N{SNOWMAN}.com')
339 self.assert_strict_equal(val, 'foo=bar; Domain=xn--n3h.com; Path=/')
340
341 val = http.dump_cookie('foo', 'bar', domain=u'.\N{SNOWMAN}.com')
342 self.assert_strict_equal(val, 'foo=bar; Domain=.xn--n3h.com; Path=/')
343
344 val = http.dump_cookie('foo', 'bar', domain=u'.foo.com')
345 self.assert_strict_equal(val, 'foo=bar; Domain=.foo.com; Path=/')
346
347
348 class RangeTestCase(WerkzeugTestCase):
349
350 def test_if_range_parsing(self):
351 rv = http.parse_if_range_header('"Test"')
352 assert rv.etag == 'Test'
353 assert rv.date is None
354 assert rv.to_header() == '"Test"'
355
356 # weak information is dropped
357 rv = http.parse_if_range_header('w/"Test"')
358 assert rv.etag == 'Test'
359 assert rv.date is None
360 assert rv.to_header() == '"Test"'
361
362 # broken etags are supported too
363 rv = http.parse_if_range_header('bullshit')
364 assert rv.etag == 'bullshit'
365 assert rv.date is None
366 assert rv.to_header() == '"bullshit"'
367
368 rv = http.parse_if_range_header('Thu, 01 Jan 1970 00:00:00 GMT')
369 assert rv.etag is None
370 assert rv.date == datetime(1970, 1, 1)
371 assert rv.to_header() == 'Thu, 01 Jan 1970 00:00:00 GMT'
372
373 for x in '', None:
374 rv = http.parse_if_range_header(x)
375 assert rv.etag is None
376 assert rv.date is None
377 assert rv.to_header() == ''
378
379 def test_range_parsing():
380 rv = http.parse_range_header('bytes=52')
381 assert rv is None
382
383 rv = http.parse_range_header('bytes=52-')
384 assert rv.units == 'bytes'
385 assert rv.ranges == [(52, None)]
386 assert rv.to_header() == 'bytes=52-'
387
388 rv = http.parse_range_header('bytes=52-99')
389 assert rv.units == 'bytes'
390 assert rv.ranges == [(52, 100)]
391 assert rv.to_header() == 'bytes=52-99'
392
393 rv = http.parse_range_header('bytes=52-99,-1000')
394 assert rv.units == 'bytes'
395 assert rv.ranges == [(52, 100), (-1000, None)]
396 assert rv.to_header() == 'bytes=52-99,-1000'
397
398 rv = http.parse_range_header('bytes = 1 - 100')
399 assert rv.units == 'bytes'
400 assert rv.ranges == [(1, 101)]
401 assert rv.to_header() == 'bytes=1-100'
402
403 rv = http.parse_range_header('AWesomes=0-999')
404 assert rv.units == 'awesomes'
405 assert rv.ranges == [(0, 1000)]
406 assert rv.to_header() == 'awesomes=0-999'
407
408 def test_content_range_parsing():
409 rv = http.parse_content_range_header('bytes 0-98/*')
410 assert rv.units == 'bytes'
411 assert rv.start == 0
412 assert rv.stop == 99
413 assert rv.length is None
414 assert rv.to_header() == 'bytes 0-98/*'
415
416 rv = http.parse_content_range_header('bytes 0-98/*asdfsa')
417 assert rv is None
418
419 rv = http.parse_content_range_header('bytes 0-99/100')
420 assert rv.to_header() == 'bytes 0-99/100'
421 rv.start = None
422 rv.stop = None
423 assert rv.units == 'bytes'
424 assert rv.to_header() == 'bytes */100'
425
426 rv = http.parse_content_range_header('bytes */100')
427 assert rv.start is None
428 assert rv.stop is None
429 assert rv.length == 100
430 assert rv.units == 'bytes'
431
432
433 class RegressionTestCase(WerkzeugTestCase):
434
435 def test_best_match_works(self):
436 # was a bug in 0.6
437 rv = http.parse_accept_header('foo=,application/xml,application/xhtml+xml,'
438 'text/html;q=0.9,text/plain;q=0.8,'
439 'image/png,*/*;q=0.5',
440 datastructures.MIMEAccept).best_match(['foo/bar'])
441 self.assert_equal(rv, 'foo/bar')
442
443
444 def suite():
445 suite = unittest.TestSuite()
446 suite.addTest(unittest.makeSuite(HTTPUtilityTestCase))
447 suite.addTest(unittest.makeSuite(RegressionTestCase))
448 return suite
+0
-81
werkzeug/testsuite/internal.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite.internal
3 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5 Internal tests.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import unittest
11
12 from datetime import datetime
13 from warnings import filterwarnings, resetwarnings
14
15 from werkzeug.testsuite import WerkzeugTestCase
16 from werkzeug.wrappers import Request, Response
17
18 from werkzeug import _internal as internal
19 from werkzeug.test import create_environ
20
21
22 class InternalTestCase(WerkzeugTestCase):
23
24 def test_date_to_unix(self):
25 assert internal._date_to_unix(datetime(1970, 1, 1)) == 0
26 assert internal._date_to_unix(datetime(1970, 1, 1, 1, 0, 0)) == 3600
27 assert internal._date_to_unix(datetime(1970, 1, 1, 1, 1, 1)) == 3661
28 x = datetime(2010, 2, 15, 16, 15, 39)
29 assert internal._date_to_unix(x) == 1266250539
30
31 def test_easteregg(self):
32 req = Request.from_values('/?macgybarchakku')
33 resp = Response.force_type(internal._easteregg(None), req)
34 assert b'About Werkzeug' in resp.get_data()
35 assert b'the Swiss Army knife of Python web development' in resp.get_data()
36
37 def test_wrapper_internals(self):
38 req = Request.from_values(data={'foo': 'bar'}, method='POST')
39 req._load_form_data()
40 assert req.form.to_dict() == {'foo': 'bar'}
41
42 # second call does not break
43 req._load_form_data()
44 assert req.form.to_dict() == {'foo': 'bar'}
45
46 # check reprs
47 assert repr(req) == "<Request 'http://localhost/' [POST]>"
48 resp = Response()
49 assert repr(resp) == '<Response 0 bytes [200 OK]>'
50 resp.set_data('Hello World!')
51 assert repr(resp) == '<Response 12 bytes [200 OK]>'
52 resp.response = iter(['Test'])
53 assert repr(resp) == '<Response streamed [200 OK]>'
54
55 # unicode data does not set content length
56 response = Response([u'Hällo Wörld'])
57 headers = response.get_wsgi_headers(create_environ())
58 assert u'Content-Length' not in headers
59
60 response = Response([u'Hällo Wörld'.encode('utf-8')])
61 headers = response.get_wsgi_headers(create_environ())
62 assert u'Content-Length' in headers
63
64 # check for internal warnings
65 filterwarnings('error', category=Warning)
66 response = Response()
67 environ = create_environ()
68 response.response = 'What the...?'
69 self.assert_raises(Warning, lambda: list(response.iter_encoded()))
70 self.assert_raises(Warning, lambda: list(response.get_app_iter(environ)))
71 response.direct_passthrough = True
72 self.assert_raises(Warning, lambda: list(response.iter_encoded()))
73 self.assert_raises(Warning, lambda: list(response.get_app_iter(environ)))
74 resetwarnings()
75
76
77 def suite():
78 suite = unittest.TestSuite()
79 suite.addTest(unittest.makeSuite(InternalTestCase))
80 return suite
+0
-159
werkzeug/testsuite/local.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite.local
3 ~~~~~~~~~~~~~~~~~~~~~~~~
4
5 Local and local proxy tests.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import time
11 import unittest
12 from threading import Thread
13
14 from werkzeug.testsuite import WerkzeugTestCase
15
16 from werkzeug import local
17
18
19 class LocalTestCase(WerkzeugTestCase):
20
21 def test_basic_local(self):
22 l = local.Local()
23 l.foo = 0
24 values = []
25 def value_setter(idx):
26 time.sleep(0.01 * idx)
27 l.foo = idx
28 time.sleep(0.02)
29 values.append(l.foo)
30 threads = [Thread(target=value_setter, args=(x,))
31 for x in [1, 2, 3]]
32 for thread in threads:
33 thread.start()
34 time.sleep(0.2)
35 assert sorted(values) == [1, 2, 3]
36
37 def delfoo():
38 del l.foo
39 delfoo()
40 self.assert_raises(AttributeError, lambda: l.foo)
41 self.assert_raises(AttributeError, delfoo)
42
43 local.release_local(l)
44
45 def test_local_release(self):
46 loc = local.Local()
47 loc.foo = 42
48 local.release_local(loc)
49 assert not hasattr(loc, 'foo')
50
51 ls = local.LocalStack()
52 ls.push(42)
53 local.release_local(ls)
54 assert ls.top is None
55
56 def test_local_proxy(self):
57 foo = []
58 ls = local.LocalProxy(lambda: foo)
59 ls.append(42)
60 ls.append(23)
61 ls[1:] = [1, 2, 3]
62 assert foo == [42, 1, 2, 3]
63 assert repr(foo) == repr(ls)
64 assert foo[0] == 42
65 foo += [1]
66 assert list(foo) == [42, 1, 2, 3, 1]
67
68 def test_local_proxy_operations_math(self):
69 foo = 2
70 ls = local.LocalProxy(lambda: foo)
71 assert ls + 1 == 3
72 assert 1 + ls == 3
73 assert ls - 1 == 1
74 assert 1 - ls == -1
75 assert ls * 1 == 2
76 assert 1 * ls == 2
77 assert ls / 1 == 2
78 assert 1.0 / ls == 0.5
79 assert ls // 1.0 == 2.0
80 assert 1.0 // ls == 0.0
81 assert ls % 2 == 0
82 assert 2 % ls == 0
83
84 def test_local_proxy_operations_strings(self):
85 foo = "foo"
86 ls = local.LocalProxy(lambda: foo)
87 assert ls + "bar" == "foobar"
88 assert "bar" + ls == "barfoo"
89 assert ls * 2 == "foofoo"
90
91 foo = "foo %s"
92 assert ls % ("bar",) == "foo bar"
93
94 def test_local_stack(self):
95 ident = local.get_ident()
96
97 ls = local.LocalStack()
98 assert ident not in ls._local.__storage__
99 assert ls.top is None
100 ls.push(42)
101 assert ident in ls._local.__storage__
102 assert ls.top == 42
103 ls.push(23)
104 assert ls.top == 23
105 ls.pop()
106 assert ls.top == 42
107 ls.pop()
108 assert ls.top is None
109 assert ls.pop() is None
110 assert ls.pop() is None
111
112 proxy = ls()
113 ls.push([1, 2])
114 assert proxy == [1, 2]
115 ls.push((1, 2))
116 assert proxy == (1, 2)
117 ls.pop()
118 ls.pop()
119 assert repr(proxy) == '<LocalProxy unbound>'
120
121 assert ident not in ls._local.__storage__
122
123 def test_local_proxies_with_callables(self):
124 foo = 42
125 ls = local.LocalProxy(lambda: foo)
126 assert ls == 42
127 foo = [23]
128 ls.append(42)
129 assert ls == [23, 42]
130 assert foo == [23, 42]
131
132 def test_custom_idents(self):
133 ident = 0
134 loc = local.Local()
135 stack = local.LocalStack()
136 mgr = local.LocalManager([loc, stack], ident_func=lambda: ident)
137
138 loc.foo = 42
139 stack.push({'foo': 42})
140 ident = 1
141 loc.foo = 23
142 stack.push({'foo': 23})
143 ident = 0
144 assert loc.foo == 42
145 assert stack.top['foo'] == 42
146 stack.pop()
147 assert stack.top is None
148 ident = 1
149 assert loc.foo == 23
150 assert stack.top['foo'] == 23
151 stack.pop()
152 assert stack.top is None
153
154
155 def suite():
156 suite = unittest.TestSuite()
157 suite.addTest(unittest.makeSuite(LocalTestCase))
158 return suite
+0
-56
werkzeug/testsuite/multipart/collect.py less more
0 #!/usr/bin/env python
1 """
2 Hacky helper application to collect form data.
3 """
4 from werkzeug.serving import run_simple
5 from werkzeug.wrappers import Request, Response
6
7
8 def copy_stream(request):
9 from os import mkdir
10 from time import time
11 folder = 'request-%d' % time()
12 mkdir(folder)
13 environ = request.environ
14 f = open(folder + '/request.txt', 'wb+')
15 f.write(environ['wsgi.input'].read(int(environ['CONTENT_LENGTH'])))
16 f.flush()
17 f.seek(0)
18 environ['wsgi.input'] = f
19 request.stat_folder = folder
20
21
22 def stats(request):
23 copy_stream(request)
24 f1 = request.files['file1']
25 f2 = request.files['file2']
26 text = request.form['text']
27 f1.save(request.stat_folder + '/file1.bin')
28 f2.save(request.stat_folder + '/file2.bin')
29 open(request.stat_folder + '/text.txt', 'w').write(text.encode('utf-8'))
30 return Response('Done.')
31
32
33 def upload_file(request):
34 return Response('''
35 <h1>Upload File</h1>
36 <form action="" method="post" enctype="multipart/form-data">
37 <input type="file" name="file1"><br>
38 <input type="file" name="file2"><br>
39 <textarea name="text"></textarea><br>
40 <input type="submit" value="Send">
41 </form>
42 ''', mimetype='text/html')
43
44
45 def application(environ, start_responseonse):
46 request = Request(environ)
47 if request.method == 'POST':
48 response = stats(request)
49 else:
50 response = upload_file(request)
51 return response(environ, start_responseonse)
52
53
54 if __name__ == '__main__':
55 run_simple('localhost', 5000, application, use_debugger=True)
werkzeug/testsuite/multipart/firefox3-2png1txt/file1.png less more
Binary diff not shown
werkzeug/testsuite/multipart/firefox3-2png1txt/file2.png less more
Binary diff not shown
werkzeug/testsuite/multipart/firefox3-2png1txt/request.txt less more
Binary diff not shown
+0
-1
werkzeug/testsuite/multipart/firefox3-2png1txt/text.txt less more
0 example text
werkzeug/testsuite/multipart/firefox3-2pnglongtext/file1.png less more
Binary diff not shown
werkzeug/testsuite/multipart/firefox3-2pnglongtext/file2.png less more
Binary diff not shown
werkzeug/testsuite/multipart/firefox3-2pnglongtext/request.txt less more
Binary diff not shown
+0
-3
werkzeug/testsuite/multipart/firefox3-2pnglongtext/text.txt less more
0 --long text
1 --with boundary
2 --lookalikes--
werkzeug/testsuite/multipart/ie6-2png1txt/file1.png less more
Binary diff not shown
werkzeug/testsuite/multipart/ie6-2png1txt/file2.png less more
Binary diff not shown
werkzeug/testsuite/multipart/ie6-2png1txt/request.txt less more
Binary diff not shown
+0
-1
werkzeug/testsuite/multipart/ie6-2png1txt/text.txt less more
0 ie6 sucks :-/
werkzeug/testsuite/multipart/ie7_full_path_request.txt less more
Binary diff not shown
werkzeug/testsuite/multipart/opera8-2png1txt/file1.png less more
Binary diff not shown
werkzeug/testsuite/multipart/opera8-2png1txt/file2.png less more
Binary diff not shown
werkzeug/testsuite/multipart/opera8-2png1txt/request.txt less more
Binary diff not shown
+0
-1
werkzeug/testsuite/multipart/opera8-2png1txt/text.txt less more
0 blafasel öäü
werkzeug/testsuite/multipart/webkit3-2png1txt/file1.png less more
Binary diff not shown
werkzeug/testsuite/multipart/webkit3-2png1txt/file2.png less more
Binary diff not shown
werkzeug/testsuite/multipart/webkit3-2png1txt/request.txt less more
Binary diff not shown
+0
-1
werkzeug/testsuite/multipart/webkit3-2png1txt/text.txt less more
0 this is another text with ümläüts
+0
-1
werkzeug/testsuite/res/test.txt less more
0 FOUND
+0
-689
werkzeug/testsuite/routing.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite.routing
3 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5 Routing tests.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import unittest
11
12 from werkzeug.testsuite import WerkzeugTestCase
13
14 from werkzeug import routing as r
15 from werkzeug.wrappers import Response
16 from werkzeug.datastructures import ImmutableDict
17 from werkzeug.test import create_environ
18
19
20 class RoutingTestCase(WerkzeugTestCase):
21
22 def test_basic_routing(self):
23 map = r.Map([
24 r.Rule('/', endpoint='index'),
25 r.Rule('/foo', endpoint='foo'),
26 r.Rule('/bar/', endpoint='bar')
27 ])
28 adapter = map.bind('example.org', '/')
29 assert adapter.match('/') == ('index', {})
30 assert adapter.match('/foo') == ('foo', {})
31 assert adapter.match('/bar/') == ('bar', {})
32 self.assert_raises(r.RequestRedirect, lambda: adapter.match('/bar'))
33 self.assert_raises(r.NotFound, lambda: adapter.match('/blub'))
34
35 adapter = map.bind('example.org', '/test')
36 try:
37 adapter.match('/bar')
38 except r.RequestRedirect as e:
39 assert e.new_url == 'http://example.org/test/bar/'
40 else:
41 self.fail('Expected request redirect')
42
43 adapter = map.bind('example.org', '/')
44 try:
45 adapter.match('/bar')
46 except r.RequestRedirect as e:
47 assert e.new_url == 'http://example.org/bar/'
48 else:
49 self.fail('Expected request redirect')
50
51 adapter = map.bind('example.org', '/')
52 try:
53 adapter.match('/bar', query_args={'aha': 'muhaha'})
54 except r.RequestRedirect as e:
55 assert e.new_url == 'http://example.org/bar/?aha=muhaha'
56 else:
57 self.fail('Expected request redirect')
58
59 adapter = map.bind('example.org', '/')
60 try:
61 adapter.match('/bar', query_args='aha=muhaha')
62 except r.RequestRedirect as e:
63 assert e.new_url == 'http://example.org/bar/?aha=muhaha'
64 else:
65 self.fail('Expected request redirect')
66
67 adapter = map.bind_to_environ(create_environ('/bar?foo=bar',
68 'http://example.org/'))
69 try:
70 adapter.match()
71 except r.RequestRedirect as e:
72 assert e.new_url == 'http://example.org/bar/?foo=bar'
73 else:
74 self.fail('Expected request redirect')
75
76 def test_environ_defaults(self):
77 environ = create_environ("/foo")
78 self.assert_strict_equal(environ["PATH_INFO"], '/foo')
79 m = r.Map([r.Rule("/foo", endpoint="foo"), r.Rule("/bar", endpoint="bar")])
80 a = m.bind_to_environ(environ)
81 self.assert_strict_equal(a.match("/foo"), ('foo', {}))
82 self.assert_strict_equal(a.match(), ('foo', {}))
83 self.assert_strict_equal(a.match("/bar"), ('bar', {}))
84 self.assert_raises(r.NotFound, a.match, "/bars")
85
86 def test_environ_nonascii_pathinfo(self):
87 environ = create_environ(u'/лошадь')
88 m = r.Map([
89 r.Rule(u'/', endpoint='index'),
90 r.Rule(u'/лошадь', endpoint='horse')
91 ])
92 a = m.bind_to_environ(environ)
93 self.assert_strict_equal(a.match(u'/'), ('index', {}))
94 self.assert_strict_equal(a.match(u'/лошадь'), ('horse', {}))
95 self.assert_raises(r.NotFound, a.match, u'/барсук')
96
97 def test_basic_building(self):
98 map = r.Map([
99 r.Rule('/', endpoint='index'),
100 r.Rule('/foo', endpoint='foo'),
101 r.Rule('/bar/<baz>', endpoint='bar'),
102 r.Rule('/bar/<int:bazi>', endpoint='bari'),
103 r.Rule('/bar/<float:bazf>', endpoint='barf'),
104 r.Rule('/bar/<path:bazp>', endpoint='barp'),
105 r.Rule('/hehe', endpoint='blah', subdomain='blah')
106 ])
107 adapter = map.bind('example.org', '/', subdomain='blah')
108
109 assert adapter.build('index', {}) == 'http://example.org/'
110 assert adapter.build('foo', {}) == 'http://example.org/foo'
111 assert adapter.build('bar', {'baz': 'blub'}) == 'http://example.org/bar/blub'
112 assert adapter.build('bari', {'bazi': 50}) == 'http://example.org/bar/50'
113 assert adapter.build('barf', {'bazf': 0.815}) == 'http://example.org/bar/0.815'
114 assert adapter.build('barp', {'bazp': 'la/di'}) == 'http://example.org/bar/la/di'
115 assert adapter.build('blah', {}) == '/hehe'
116 self.assert_raises(r.BuildError, lambda: adapter.build('urks'))
117
118 adapter = map.bind('example.org', '/test', subdomain='blah')
119 assert adapter.build('index', {}) == 'http://example.org/test/'
120 assert adapter.build('foo', {}) == 'http://example.org/test/foo'
121 assert adapter.build('bar', {'baz': 'blub'}) == 'http://example.org/test/bar/blub'
122 assert adapter.build('bari', {'bazi': 50}) == 'http://example.org/test/bar/50'
123 assert adapter.build('barf', {'bazf': 0.815}) == 'http://example.org/test/bar/0.815'
124 assert adapter.build('barp', {'bazp': 'la/di'}) == 'http://example.org/test/bar/la/di'
125 assert adapter.build('blah', {}) == '/test/hehe'
126
127 def test_defaults(self):
128 map = r.Map([
129 r.Rule('/foo/', defaults={'page': 1}, endpoint='foo'),
130 r.Rule('/foo/<int:page>', endpoint='foo')
131 ])
132 adapter = map.bind('example.org', '/')
133
134 assert adapter.match('/foo/') == ('foo', {'page': 1})
135 self.assert_raises(r.RequestRedirect, lambda: adapter.match('/foo/1'))
136 assert adapter.match('/foo/2') == ('foo', {'page': 2})
137 assert adapter.build('foo', {}) == '/foo/'
138 assert adapter.build('foo', {'page': 1}) == '/foo/'
139 assert adapter.build('foo', {'page': 2}) == '/foo/2'
140
141 def test_greedy(self):
142 map = r.Map([
143 r.Rule('/foo', endpoint='foo'),
144 r.Rule('/<path:bar>', endpoint='bar'),
145 r.Rule('/<path:bar>/<path:blub>', endpoint='bar')
146 ])
147 adapter = map.bind('example.org', '/')
148
149 assert adapter.match('/foo') == ('foo', {})
150 assert adapter.match('/blub') == ('bar', {'bar': 'blub'})
151 assert adapter.match('/he/he') == ('bar', {'bar': 'he', 'blub': 'he'})
152
153 assert adapter.build('foo', {}) == '/foo'
154 assert adapter.build('bar', {'bar': 'blub'}) == '/blub'
155 assert adapter.build('bar', {'bar': 'blub', 'blub': 'bar'}) == '/blub/bar'
156
157 def test_path(self):
158 map = r.Map([
159 r.Rule('/', defaults={'name': 'FrontPage'}, endpoint='page'),
160 r.Rule('/Special', endpoint='special'),
161 r.Rule('/<int:year>', endpoint='year'),
162 r.Rule('/<path:name>', endpoint='page'),
163 r.Rule('/<path:name>/edit', endpoint='editpage'),
164 r.Rule('/<path:name>/silly/<path:name2>', endpoint='sillypage'),
165 r.Rule('/<path:name>/silly/<path:name2>/edit', endpoint='editsillypage'),
166 r.Rule('/Talk:<path:name>', endpoint='talk'),
167 r.Rule('/User:<username>', endpoint='user'),
168 r.Rule('/User:<username>/<path:name>', endpoint='userpage'),
169 r.Rule('/Files/<path:file>', endpoint='files'),
170 ])
171 adapter = map.bind('example.org', '/')
172
173 assert adapter.match('/') == ('page', {'name':'FrontPage'})
174 self.assert_raises(r.RequestRedirect, lambda: adapter.match('/FrontPage'))
175 assert adapter.match('/Special') == ('special', {})
176 assert adapter.match('/2007') == ('year', {'year':2007})
177 assert adapter.match('/Some/Page') == ('page', {'name':'Some/Page'})
178 assert adapter.match('/Some/Page/edit') == ('editpage', {'name':'Some/Page'})
179 assert adapter.match('/Foo/silly/bar') == ('sillypage', {'name':'Foo', 'name2':'bar'})
180 assert adapter.match('/Foo/silly/bar/edit') == ('editsillypage', {'name':'Foo', 'name2':'bar'})
181 assert adapter.match('/Talk:Foo/Bar') == ('talk', {'name':'Foo/Bar'})
182 assert adapter.match('/User:thomas') == ('user', {'username':'thomas'})
183 assert adapter.match('/User:thomas/projects/werkzeug') == \
184 ('userpage', {'username':'thomas', 'name':'projects/werkzeug'})
185 assert adapter.match('/Files/downloads/werkzeug/0.2.zip') == \
186 ('files', {'file':'downloads/werkzeug/0.2.zip'})
187
188 def test_dispatch(self):
189 env = create_environ('/')
190 map = r.Map([
191 r.Rule('/', endpoint='root'),
192 r.Rule('/foo/', endpoint='foo')
193 ])
194 adapter = map.bind_to_environ(env)
195
196 raise_this = None
197 def view_func(endpoint, values):
198 if raise_this is not None:
199 raise raise_this
200 return Response(repr((endpoint, values)))
201 dispatch = lambda p, q=False: Response.force_type(adapter.dispatch(view_func, p,
202 catch_http_exceptions=q), env)
203
204 assert dispatch('/').data == b"('root', {})"
205 assert dispatch('/foo').status_code == 301
206 raise_this = r.NotFound()
207 self.assert_raises(r.NotFound, lambda: dispatch('/bar'))
208 assert dispatch('/bar', True).status_code == 404
209
210 def test_http_host_before_server_name(self):
211 env = {
212 'HTTP_HOST': 'wiki.example.com',
213 'SERVER_NAME': 'web0.example.com',
214 'SERVER_PORT': '80',
215 'SCRIPT_NAME': '',
216 'PATH_INFO': '',
217 'REQUEST_METHOD': 'GET',
218 'wsgi.url_scheme': 'http'
219 }
220 map = r.Map([r.Rule('/', endpoint='index', subdomain='wiki')])
221 adapter = map.bind_to_environ(env, server_name='example.com')
222 assert adapter.match('/') == ('index', {})
223 assert adapter.build('index', force_external=True) == 'http://wiki.example.com/'
224 assert adapter.build('index') == '/'
225
226 env['HTTP_HOST'] = 'admin.example.com'
227 adapter = map.bind_to_environ(env, server_name='example.com')
228 assert adapter.build('index') == 'http://wiki.example.com/'
229
230 def test_adapter_url_parameter_sorting(self):
231 map = r.Map([r.Rule('/', endpoint='index')], sort_parameters=True,
232 sort_key=lambda x: x[1])
233 adapter = map.bind('localhost', '/')
234 assert adapter.build('index', {'x': 20, 'y': 10, 'z': 30},
235 force_external=True) == 'http://localhost/?y=10&x=20&z=30'
236
237 def test_request_direct_charset_bug(self):
238 map = r.Map([r.Rule(u'/öäü/')])
239 adapter = map.bind('localhost', '/')
240 try:
241 adapter.match(u'/öäü')
242 except r.RequestRedirect as e:
243 assert e.new_url == 'http://localhost/%C3%B6%C3%A4%C3%BC/'
244 else:
245 self.fail('expected request redirect exception')
246
247 def test_request_redirect_default(self):
248 map = r.Map([r.Rule(u'/foo', defaults={'bar': 42}),
249 r.Rule(u'/foo/<int:bar>')])
250 adapter = map.bind('localhost', '/')
251 try:
252 adapter.match(u'/foo/42')
253 except r.RequestRedirect as e:
254 assert e.new_url == 'http://localhost/foo'
255 else:
256 self.fail('expected request redirect exception')
257
258 def test_request_redirect_default_subdomain(self):
259 map = r.Map([r.Rule(u'/foo', defaults={'bar': 42}, subdomain='test'),
260 r.Rule(u'/foo/<int:bar>', subdomain='other')])
261 adapter = map.bind('localhost', '/', subdomain='other')
262 try:
263 adapter.match(u'/foo/42')
264 except r.RequestRedirect as e:
265 assert e.new_url == 'http://test.localhost/foo'
266 else:
267 self.fail('expected request redirect exception')
268
269 def test_adapter_match_return_rule(self):
270 rule = r.Rule('/foo/', endpoint='foo')
271 map = r.Map([rule])
272 adapter = map.bind('localhost', '/')
273 assert adapter.match('/foo/', return_rule=True) == (rule, {})
274
275 def test_server_name_interpolation(self):
276 server_name = 'example.invalid'
277 map = r.Map([r.Rule('/', endpoint='index'),
278 r.Rule('/', endpoint='alt', subdomain='alt')])
279
280 env = create_environ('/', 'http://%s/' % server_name)
281 adapter = map.bind_to_environ(env, server_name=server_name)
282 assert adapter.match() == ('index', {})
283
284 env = create_environ('/', 'http://alt.%s/' % server_name)
285 adapter = map.bind_to_environ(env, server_name=server_name)
286 assert adapter.match() == ('alt', {})
287
288 env = create_environ('/', 'http://%s/' % server_name)
289 adapter = map.bind_to_environ(env, server_name='foo')
290 assert adapter.subdomain == '<invalid>'
291
292 def test_rule_emptying(self):
293 rule = r.Rule('/foo', {'meh': 'muh'}, 'x', ['POST'],
294 False, 'x', True, None)
295 rule2 = rule.empty()
296 assert rule.__dict__ == rule2.__dict__
297 rule.methods.add('GET')
298 assert rule.__dict__ != rule2.__dict__
299 rule.methods.discard('GET')
300 rule.defaults['meh'] = 'aha'
301 assert rule.__dict__ != rule2.__dict__
302
303 def test_rule_templates(self):
304 testcase = r.RuleTemplate(
305 [ r.Submount('/test/$app',
306 [ r.Rule('/foo/', endpoint='handle_foo')
307 , r.Rule('/bar/', endpoint='handle_bar')
308 , r.Rule('/baz/', endpoint='handle_baz')
309 ]),
310 r.EndpointPrefix('${app}',
311 [ r.Rule('/${app}-blah', endpoint='bar')
312 , r.Rule('/${app}-meh', endpoint='baz')
313 ]),
314 r.Subdomain('$app',
315 [ r.Rule('/blah', endpoint='x_bar')
316 , r.Rule('/meh', endpoint='x_baz')
317 ])
318 ])
319
320 url_map = r.Map(
321 [ testcase(app='test1')
322 , testcase(app='test2')
323 , testcase(app='test3')
324 , testcase(app='test4')
325 ])
326
327 out = sorted([(x.rule, x.subdomain, x.endpoint)
328 for x in url_map.iter_rules()])
329
330 assert out == ([
331 ('/blah', 'test1', 'x_bar'),
332 ('/blah', 'test2', 'x_bar'),
333 ('/blah', 'test3', 'x_bar'),
334 ('/blah', 'test4', 'x_bar'),
335 ('/meh', 'test1', 'x_baz'),
336 ('/meh', 'test2', 'x_baz'),
337 ('/meh', 'test3', 'x_baz'),
338 ('/meh', 'test4', 'x_baz'),
339 ('/test/test1/bar/', '', 'handle_bar'),
340 ('/test/test1/baz/', '', 'handle_baz'),
341 ('/test/test1/foo/', '', 'handle_foo'),
342 ('/test/test2/bar/', '', 'handle_bar'),
343 ('/test/test2/baz/', '', 'handle_baz'),
344 ('/test/test2/foo/', '', 'handle_foo'),
345 ('/test/test3/bar/', '', 'handle_bar'),
346 ('/test/test3/baz/', '', 'handle_baz'),
347 ('/test/test3/foo/', '', 'handle_foo'),
348 ('/test/test4/bar/', '', 'handle_bar'),
349 ('/test/test4/baz/', '', 'handle_baz'),
350 ('/test/test4/foo/', '', 'handle_foo'),
351 ('/test1-blah', '', 'test1bar'),
352 ('/test1-meh', '', 'test1baz'),
353 ('/test2-blah', '', 'test2bar'),
354 ('/test2-meh', '', 'test2baz'),
355 ('/test3-blah', '', 'test3bar'),
356 ('/test3-meh', '', 'test3baz'),
357 ('/test4-blah', '', 'test4bar'),
358 ('/test4-meh', '', 'test4baz')
359 ])
360
361 def test_non_string_parts(self):
362 m = r.Map([
363 r.Rule('/<foo>', endpoint='foo')
364 ])
365 a = m.bind('example.com')
366 self.assert_equal(a.build('foo', {'foo': 42}), '/42')
367
368 def test_complex_routing_rules(self):
369 m = r.Map([
370 r.Rule('/', endpoint='index'),
371 r.Rule('/<int:blub>', endpoint='an_int'),
372 r.Rule('/<blub>', endpoint='a_string'),
373 r.Rule('/foo/', endpoint='nested'),
374 r.Rule('/foobar/', endpoint='nestedbar'),
375 r.Rule('/foo/<path:testing>/', endpoint='nested_show'),
376 r.Rule('/foo/<path:testing>/edit', endpoint='nested_edit'),
377 r.Rule('/users/', endpoint='users', defaults={'page': 1}),
378 r.Rule('/users/page/<int:page>', endpoint='users'),
379 r.Rule('/foox', endpoint='foox'),
380 r.Rule('/<path:bar>/<path:blub>', endpoint='barx_path_path')
381 ])
382 a = m.bind('example.com')
383
384 assert a.match('/') == ('index', {})
385 assert a.match('/42') == ('an_int', {'blub': 42})
386 assert a.match('/blub') == ('a_string', {'blub': 'blub'})
387 assert a.match('/foo/') == ('nested', {})
388 assert a.match('/foobar/') == ('nestedbar', {})
389 assert a.match('/foo/1/2/3/') == ('nested_show', {'testing': '1/2/3'})
390 assert a.match('/foo/1/2/3/edit') == ('nested_edit', {'testing': '1/2/3'})
391 assert a.match('/users/') == ('users', {'page': 1})
392 assert a.match('/users/page/2') == ('users', {'page': 2})
393 assert a.match('/foox') == ('foox', {})
394 assert a.match('/1/2/3') == ('barx_path_path', {'bar': '1', 'blub': '2/3'})
395
396 assert a.build('index') == '/'
397 assert a.build('an_int', {'blub': 42}) == '/42'
398 assert a.build('a_string', {'blub': 'test'}) == '/test'
399 assert a.build('nested') == '/foo/'
400 assert a.build('nestedbar') == '/foobar/'
401 assert a.build('nested_show', {'testing': '1/2/3'}) == '/foo/1/2/3/'
402 assert a.build('nested_edit', {'testing': '1/2/3'}) == '/foo/1/2/3/edit'
403 assert a.build('users', {'page': 1}) == '/users/'
404 assert a.build('users', {'page': 2}) == '/users/page/2'
405 assert a.build('foox') == '/foox'
406 assert a.build('barx_path_path', {'bar': '1', 'blub': '2/3'}) == '/1/2/3'
407
408 def test_default_converters(self):
409 class MyMap(r.Map):
410 default_converters = r.Map.default_converters.copy()
411 default_converters['foo'] = r.UnicodeConverter
412 assert isinstance(r.Map.default_converters, ImmutableDict)
413 m = MyMap([
414 r.Rule('/a/<foo:a>', endpoint='a'),
415 r.Rule('/b/<foo:b>', endpoint='b'),
416 r.Rule('/c/<c>', endpoint='c')
417 ], converters={'bar': r.UnicodeConverter})
418 a = m.bind('example.org', '/')
419 assert a.match('/a/1') == ('a', {'a': '1'})
420 assert a.match('/b/2') == ('b', {'b': '2'})
421 assert a.match('/c/3') == ('c', {'c': '3'})
422 assert 'foo' not in r.Map.default_converters
423
424 def test_build_append_unknown(self):
425 map = r.Map([
426 r.Rule('/bar/<float:bazf>', endpoint='barf')
427 ])
428 adapter = map.bind('example.org', '/', subdomain='blah')
429 assert adapter.build('barf', {'bazf': 0.815, 'bif' : 1.0}) == \
430 'http://example.org/bar/0.815?bif=1.0'
431 assert adapter.build('barf', {'bazf': 0.815, 'bif' : 1.0},
432 append_unknown=False) == 'http://example.org/bar/0.815'
433
434 def test_method_fallback(self):
435 map = r.Map([
436 r.Rule('/', endpoint='index', methods=['GET']),
437 r.Rule('/<name>', endpoint='hello_name', methods=['GET']),
438 r.Rule('/select', endpoint='hello_select', methods=['POST']),
439 r.Rule('/search_get', endpoint='search', methods=['GET']),
440 r.Rule('/search_post', endpoint='search', methods=['POST'])
441 ])
442 adapter = map.bind('example.com')
443 assert adapter.build('index') == '/'
444 assert adapter.build('index', method='GET') == '/'
445 assert adapter.build('hello_name', {'name': 'foo'}) == '/foo'
446 assert adapter.build('hello_select') == '/select'
447 assert adapter.build('hello_select', method='POST') == '/select'
448 assert adapter.build('search') == '/search_get'
449 assert adapter.build('search', method='GET') == '/search_get'
450 assert adapter.build('search', method='POST') == '/search_post'
451
452 def test_implicit_head(self):
453 url_map = r.Map([
454 r.Rule('/get', methods=['GET'], endpoint='a'),
455 r.Rule('/post', methods=['POST'], endpoint='b')
456 ])
457 adapter = url_map.bind('example.org')
458 assert adapter.match('/get', method='HEAD') == ('a', {})
459 self.assert_raises(r.MethodNotAllowed, adapter.match,
460 '/post', method='HEAD')
461
462 def test_protocol_joining_bug(self):
463 m = r.Map([r.Rule('/<foo>', endpoint='x')])
464 a = m.bind('example.org')
465 assert a.build('x', {'foo': 'x:y'}) == '/x:y'
466 assert a.build('x', {'foo': 'x:y'}, force_external=True) == \
467 'http://example.org/x:y'
468
469 def test_allowed_methods_querying(self):
470 m = r.Map([r.Rule('/<foo>', methods=['GET', 'HEAD']),
471 r.Rule('/foo', methods=['POST'])])
472 a = m.bind('example.org')
473 assert sorted(a.allowed_methods('/foo')) == ['GET', 'HEAD', 'POST']
474
475 def test_external_building_with_port(self):
476 map = r.Map([
477 r.Rule('/', endpoint='index'),
478 ])
479 adapter = map.bind('example.org:5000', '/')
480 built_url = adapter.build('index', {}, force_external=True)
481 assert built_url == 'http://example.org:5000/', built_url
482
483 def test_external_building_with_port_bind_to_environ(self):
484 map = r.Map([
485 r.Rule('/', endpoint='index'),
486 ])
487 adapter = map.bind_to_environ(
488 create_environ('/', 'http://example.org:5000/'),
489 server_name="example.org:5000"
490 )
491 built_url = adapter.build('index', {}, force_external=True)
492 assert built_url == 'http://example.org:5000/', built_url
493
494 def test_external_building_with_port_bind_to_environ_wrong_servername(self):
495 map = r.Map([
496 r.Rule('/', endpoint='index'),
497 ])
498 environ = create_environ('/', 'http://example.org:5000/')
499 adapter = map.bind_to_environ(environ, server_name="example.org")
500 assert adapter.subdomain == '<invalid>'
501
502 def test_converter_parser(self):
503 args, kwargs = r.parse_converter_args(u'test, a=1, b=3.0')
504
505 assert args == ('test',)
506 assert kwargs == {'a': 1, 'b': 3.0 }
507
508 args, kwargs = r.parse_converter_args('')
509 assert not args and not kwargs
510
511 args, kwargs = r.parse_converter_args('a, b, c,')
512 assert args == ('a', 'b', 'c')
513 assert not kwargs
514
515 args, kwargs = r.parse_converter_args('True, False, None')
516 assert args == (True, False, None)
517
518 args, kwargs = r.parse_converter_args('"foo", u"bar"')
519 assert args == ('foo', 'bar')
520
521 def test_alias_redirects(self):
522 m = r.Map([
523 r.Rule('/', endpoint='index'),
524 r.Rule('/index.html', endpoint='index', alias=True),
525 r.Rule('/users/', defaults={'page': 1}, endpoint='users'),
526 r.Rule('/users/index.html', defaults={'page': 1}, alias=True,
527 endpoint='users'),
528 r.Rule('/users/page/<int:page>', endpoint='users'),
529 r.Rule('/users/page-<int:page>.html', alias=True, endpoint='users'),
530 ])
531 a = m.bind('example.com')
532
533 def ensure_redirect(path, new_url, args=None):
534 try:
535 a.match(path, query_args=args)
536 except r.RequestRedirect as e:
537 assert e.new_url == 'http://example.com' + new_url
538 else:
539 assert False, 'expected redirect'
540
541 ensure_redirect('/index.html', '/')
542 ensure_redirect('/users/index.html', '/users/')
543 ensure_redirect('/users/page-2.html', '/users/page/2')
544 ensure_redirect('/users/page-1.html', '/users/')
545 ensure_redirect('/users/page-1.html', '/users/?foo=bar', {'foo': 'bar'})
546
547 assert a.build('index') == '/'
548 assert a.build('users', {'page': 1}) == '/users/'
549 assert a.build('users', {'page': 2}) == '/users/page/2'
550
551 def test_double_defaults(self):
552 for prefix in '', '/aaa':
553 m = r.Map([
554 r.Rule(prefix + '/', defaults={'foo': 1, 'bar': False}, endpoint='x'),
555 r.Rule(prefix + '/<int:foo>', defaults={'bar': False}, endpoint='x'),
556 r.Rule(prefix + '/bar/', defaults={'foo': 1, 'bar': True}, endpoint='x'),
557 r.Rule(prefix + '/bar/<int:foo>', defaults={'bar': True}, endpoint='x')
558 ])
559 a = m.bind('example.com')
560
561 assert a.match(prefix + '/') == ('x', {'foo': 1, 'bar': False})
562 assert a.match(prefix + '/2') == ('x', {'foo': 2, 'bar': False})
563 assert a.match(prefix + '/bar/') == ('x', {'foo': 1, 'bar': True})
564 assert a.match(prefix + '/bar/2') == ('x', {'foo': 2, 'bar': True})
565
566 assert a.build('x', {'foo': 1, 'bar': False}) == prefix + '/'
567 assert a.build('x', {'foo': 2, 'bar': False}) == prefix + '/2'
568 assert a.build('x', {'bar': False}) == prefix + '/'
569 assert a.build('x', {'foo': 1, 'bar': True}) == prefix + '/bar/'
570 assert a.build('x', {'foo': 2, 'bar': True}) == prefix + '/bar/2'
571 assert a.build('x', {'bar': True}) == prefix + '/bar/'
572
573 def test_host_matching(self):
574 m = r.Map([
575 r.Rule('/', endpoint='index', host='www.<domain>'),
576 r.Rule('/', endpoint='files', host='files.<domain>'),
577 r.Rule('/foo/', defaults={'page': 1}, host='www.<domain>', endpoint='x'),
578 r.Rule('/<int:page>', host='files.<domain>', endpoint='x')
579 ], host_matching=True)
580
581 a = m.bind('www.example.com')
582 assert a.match('/') == ('index', {'domain': 'example.com'})
583 assert a.match('/foo/') == ('x', {'domain': 'example.com', 'page': 1})
584 try:
585 a.match('/foo')
586 except r.RequestRedirect as e:
587 assert e.new_url == 'http://www.example.com/foo/'
588 else:
589 assert False, 'expected redirect'
590
591 a = m.bind('files.example.com')
592 assert a.match('/') == ('files', {'domain': 'example.com'})
593 assert a.match('/2') == ('x', {'domain': 'example.com', 'page': 2})
594 try:
595 a.match('/1')
596 except r.RequestRedirect as e:
597 assert e.new_url == 'http://www.example.com/foo/'
598 else:
599 assert False, 'expected redirect'
600
601 def test_server_name_casing(self):
602 m = r.Map([
603 r.Rule('/', endpoint='index', subdomain='foo')
604 ])
605
606 env = create_environ()
607 env['SERVER_NAME'] = env['HTTP_HOST'] = 'FOO.EXAMPLE.COM'
608 a = m.bind_to_environ(env, server_name='example.com')
609 assert a.match('/') == ('index', {})
610
611 env = create_environ()
612 env['SERVER_NAME'] = '127.0.0.1'
613 env['SERVER_PORT'] = '5000'
614 del env['HTTP_HOST']
615 a = m.bind_to_environ(env, server_name='example.com')
616 try:
617 a.match()
618 except r.NotFound:
619 pass
620 else:
621 assert False, 'Expected not found exception'
622
623 def test_redirect_request_exception_code(self):
624 exc = r.RequestRedirect('http://www.google.com/')
625 exc.code = 307
626 env = create_environ()
627 self.assert_strict_equal(exc.get_response(env).status_code, exc.code)
628
629 def test_redirect_path_quoting(self):
630 url_map = r.Map([
631 r.Rule('/<category>', defaults={'page': 1}, endpoint='category'),
632 r.Rule('/<category>/page/<int:page>', endpoint='category')
633 ])
634
635 adapter = url_map.bind('example.com')
636 try:
637 adapter.match('/foo bar/page/1')
638 except r.RequestRedirect as e:
639 response = e.get_response({})
640 self.assert_strict_equal(response.headers['location'],
641 u'http://example.com/foo%20bar')
642 else:
643 self.fail('Expected redirect')
644
645 def test_unicode_rules(self):
646 m = r.Map([
647 r.Rule(u'/войти/', endpoint='enter'),
648 r.Rule(u'/foo+bar/', endpoint='foobar')
649 ])
650 a = m.bind(u'☃.example.com')
651 try:
652 a.match(u'/войти')
653 except r.RequestRedirect as e:
654 self.assert_strict_equal(e.new_url, 'http://xn--n3h.example.com/'
655 '%D0%B2%D0%BE%D0%B9%D1%82%D0%B8/')
656 endpoint, values = a.match(u'/войти/')
657 self.assert_strict_equal(endpoint, 'enter')
658 self.assert_strict_equal(values, {})
659
660 try:
661 a.match(u'/foo+bar')
662 except r.RequestRedirect as e:
663 self.assert_strict_equal(e.new_url, 'http://xn--n3h.example.com/'
664 'foo+bar/')
665 endpoint, values = a.match(u'/foo+bar/')
666 self.assert_strict_equal(endpoint, 'foobar')
667 self.assert_strict_equal(values, {})
668
669 url = a.build('enter', {}, force_external=True)
670 self.assert_strict_equal(url, 'http://xn--n3h.example.com/%D0%B2%D0%BE%D0%B9%D1%82%D0%B8/')
671
672 url = a.build('foobar', {}, force_external=True)
673 self.assert_strict_equal(url, 'http://xn--n3h.example.com/foo+bar/')
674
675 def test_map_repr(self):
676 m = r.Map([
677 r.Rule(u'/wat', endpoint='enter'),
678 r.Rule(u'/woop', endpoint='foobar')
679 ])
680 rv = repr(m)
681 self.assert_strict_equal(rv,
682 "Map([<Rule '/woop' -> foobar>, <Rule '/wat' -> enter>])")
683
684
685 def suite():
686 suite = unittest.TestSuite()
687 suite.addTest(unittest.makeSuite(RoutingTestCase))
688 return suite
+0
-105
werkzeug/testsuite/security.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite.security
3 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5 Tests the security helpers.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import os
11 import unittest
12
13 from werkzeug.testsuite import WerkzeugTestCase
14
15 from werkzeug.security import check_password_hash, generate_password_hash, \
16 safe_join, pbkdf2_hex, safe_str_cmp
17
18
19 class SecurityTestCase(WerkzeugTestCase):
20
21 def test_safe_str_cmp(self):
22 assert safe_str_cmp('a', 'a') is True
23 assert safe_str_cmp(b'a', u'a') is True
24 assert safe_str_cmp('a', 'b') is False
25 assert safe_str_cmp(b'aaa', 'aa') is False
26 assert safe_str_cmp(b'aaa', 'bbb') is False
27 assert safe_str_cmp(b'aaa', u'aaa') is True
28
29 def test_password_hashing(self):
30 hash0 = generate_password_hash('default')
31 assert check_password_hash(hash0, 'default')
32 assert hash0.startswith('pbkdf2:sha1:1000$')
33
34 hash1 = generate_password_hash('default', 'sha1')
35 hash2 = generate_password_hash(u'default', method='sha1')
36 assert hash1 != hash2
37 assert check_password_hash(hash1, 'default')
38 assert check_password_hash(hash2, 'default')
39 assert hash1.startswith('sha1$')
40 assert hash2.startswith('sha1$')
41
42 fakehash = generate_password_hash('default', method='plain')
43 assert fakehash == 'plain$$default'
44 assert check_password_hash(fakehash, 'default')
45
46 mhash = generate_password_hash(u'default', method='md5')
47 assert mhash.startswith('md5$')
48 assert check_password_hash(mhash, 'default')
49
50 legacy = 'md5$$c21f969b5f03d33d43e04f8f136e7682'
51 assert check_password_hash(legacy, 'default')
52
53 legacy = u'md5$$c21f969b5f03d33d43e04f8f136e7682'
54 assert check_password_hash(legacy, 'default')
55
56 def test_safe_join(self):
57 assert safe_join('foo', 'bar/baz') == os.path.join('foo', 'bar/baz')
58 assert safe_join('foo', '../bar/baz') is None
59 if os.name == 'nt':
60 assert safe_join('foo', 'foo\\bar') is None
61
62 def test_pbkdf2(self):
63 def check(data, salt, iterations, keylen, expected):
64 rv = pbkdf2_hex(data, salt, iterations, keylen)
65 self.assert_equal(rv, expected)
66
67 # From RFC 6070
68 check('password', 'salt', 1, None,
69 '0c60c80f961f0e71f3a9b524af6012062fe037a6')
70 check('password', 'salt', 1, 20,
71 '0c60c80f961f0e71f3a9b524af6012062fe037a6')
72 check('password', 'salt', 2, 20,
73 'ea6c014dc72d6f8ccd1ed92ace1d41f0d8de8957')
74 check('password', 'salt', 4096, 20,
75 '4b007901b765489abead49d926f721d065a429c1')
76 check('passwordPASSWORDpassword', 'saltSALTsaltSALTsaltSALTsaltSALTsalt',
77 4096, 25, '3d2eec4fe41c849b80c8d83662c0e44a8b291a964cf2f07038')
78 check('pass\x00word', 'sa\x00lt', 4096, 16,
79 '56fa6aa75548099dcc37d7f03425e0c3')
80 # This one is from the RFC but it just takes for ages
81 ##check('password', 'salt', 16777216, 20,
82 ## 'eefe3d61cd4da4e4e9945b3d6ba2158c2634e984')
83
84 # From Crypt-PBKDF2
85 check('password', 'ATHENA.MIT.EDUraeburn', 1, 16,
86 'cdedb5281bb2f801565a1122b2563515')
87 check('password', 'ATHENA.MIT.EDUraeburn', 1, 32,
88 'cdedb5281bb2f801565a1122b25635150ad1f7a04bb9f3a333ecc0e2e1f70837')
89 check('password', 'ATHENA.MIT.EDUraeburn', 2, 16,
90 '01dbee7f4a9e243e988b62c73cda935d')
91 check('password', 'ATHENA.MIT.EDUraeburn', 2, 32,
92 '01dbee7f4a9e243e988b62c73cda935da05378b93244ec8f48a99e61ad799d86')
93 check('password', 'ATHENA.MIT.EDUraeburn', 1200, 32,
94 '5c08eb61fdf71e4e4ec3cf6ba1f5512ba7e52ddbc5e5142f708a31e2e62b1e13')
95 check('X' * 64, 'pass phrase equals block size', 1200, 32,
96 '139c30c0966bc32ba55fdbf212530ac9c5ec59f1a452f5cc9ad940fea0598ed1')
97 check('X' * 65, 'pass phrase exceeds block size', 1200, 32,
98 '9ccad6d468770cd51b10e6a68721be611a8b4d282601db3b36be9246915ec82a')
99
100
101 def suite():
102 suite = unittest.TestSuite()
103 suite.addTest(unittest.makeSuite(SecurityTestCase))
104 return suite
+0
-117
werkzeug/testsuite/serving.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite.serving
3 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5 Added serving tests.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import sys
11 import time
12 try:
13 import httplib
14 except ImportError:
15 from http import client as httplib
16 try:
17 from urllib2 import urlopen, HTTPError
18 except ImportError: # pragma: no cover
19 from urllib.request import urlopen
20 from urllib.error import HTTPError
21
22 import unittest
23 from functools import update_wrapper
24
25 from werkzeug.testsuite import WerkzeugTestCase
26
27 from werkzeug import __version__ as version, serving
28 from werkzeug.testapp import test_app
29 from werkzeug._compat import StringIO
30 from threading import Thread
31
32
33
34 real_make_server = serving.make_server
35
36
37 def silencestderr(f):
38 def new_func(*args, **kwargs):
39 old_stderr = sys.stderr
40 sys.stderr = StringIO()
41 try:
42 return f(*args, **kwargs)
43 finally:
44 sys.stderr = old_stderr
45 return update_wrapper(new_func, f)
46
47
48 def run_dev_server(application):
49 servers = []
50
51 def tracking_make_server(*args, **kwargs):
52 srv = real_make_server(*args, **kwargs)
53 servers.append(srv)
54 return srv
55 serving.make_server = tracking_make_server
56 try:
57 t = Thread(target=serving.run_simple,
58 args=('localhost', 0, application))
59 t.setDaemon(True)
60 t.start()
61 time.sleep(0.25)
62 finally:
63 serving.make_server = real_make_server
64 if not servers:
65 return None, None
66 server, = servers
67 ip, port = server.socket.getsockname()[:2]
68 if ':' in ip:
69 ip = '[%s]' % ip
70 return server, '%s:%d' % (ip, port)
71
72
73 class ServingTestCase(WerkzeugTestCase):
74
75 @silencestderr
76 def test_serving(self):
77 server, addr = run_dev_server(test_app)
78 rv = urlopen('http://%s/?foo=bar&baz=blah' % addr).read()
79 self.assert_in(b'WSGI Information', rv)
80 self.assert_in(b'foo=bar&amp;baz=blah', rv)
81 self.assert_in(b'Werkzeug/' + version.encode('ascii'), rv)
82
83 @silencestderr
84 def test_broken_app(self):
85 def broken_app(environ, start_response):
86 1 // 0
87 server, addr = run_dev_server(broken_app)
88 try:
89 urlopen('http://%s/?foo=bar&baz=blah' % addr).read()
90 except HTTPError as e:
91 # In Python3 a 500 response causes an exception
92 rv = e.read()
93 assert b'Internal Server Error' in rv
94 else:
95 assert False, 'expected internal server error'
96
97 @silencestderr
98 def test_absolute_requests(self):
99 def asserting_app(environ, start_response):
100 assert environ['HTTP_HOST'] == 'surelynotexisting.example.com:1337'
101 assert environ['PATH_INFO'] == '/index.htm'
102 assert environ['SERVER_PORT'] == addr.split(':')[1]
103 start_response('200 OK', [('Content-Type', 'text/html')])
104 return [b'YES']
105
106 server, addr = run_dev_server(asserting_app)
107 conn = httplib.HTTPConnection(addr)
108 conn.request('GET', 'http://surelynotexisting.example.com:1337/index.htm')
109 res = conn.getresponse()
110 assert res.read() == b'YES'
111
112
113 def suite():
114 suite = unittest.TestSuite()
115 suite.addTest(unittest.makeSuite(ServingTestCase))
116 return suite
+0
-444
werkzeug/testsuite/test.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite.test
3 ~~~~~~~~~~~~~~~~~~~~~~~
4
5 Tests the testing tools.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10
11 from __future__ import with_statement
12
13 import sys
14 import unittest
15 from io import BytesIO
16 from werkzeug._compat import iteritems, to_bytes
17
18 from werkzeug.testsuite import WerkzeugTestCase
19
20 from werkzeug.wrappers import Request, Response, BaseResponse
21 from werkzeug.test import Client, EnvironBuilder, create_environ, \
22 ClientRedirectError, stream_encode_multipart, run_wsgi_app
23 from werkzeug.utils import redirect
24 from werkzeug.formparser import parse_form_data
25 from werkzeug.datastructures import MultiDict, FileStorage
26
27
28 def cookie_app(environ, start_response):
29 """A WSGI application which sets a cookie, and returns as a ersponse any
30 cookie which exists.
31 """
32 response = Response(environ.get('HTTP_COOKIE', 'No Cookie'),
33 mimetype='text/plain')
34 response.set_cookie('test', 'test')
35 return response(environ, start_response)
36
37
38 def redirect_loop_app(environ, start_response):
39 response = redirect('http://localhost/some/redirect/')
40 return response(environ, start_response)
41
42
43 def redirect_with_get_app(environ, start_response):
44 req = Request(environ)
45 if req.url not in ('http://localhost/',
46 'http://localhost/first/request',
47 'http://localhost/some/redirect/'):
48 assert False, 'redirect_demo_app() did not expect URL "%s"' % req.url
49 if '/some/redirect' not in req.url:
50 response = redirect('http://localhost/some/redirect/')
51 else:
52 response = Response('current url: %s' % req.url)
53 return response(environ, start_response)
54
55
56 def redirect_with_post_app(environ, start_response):
57 req = Request(environ)
58 if req.url == 'http://localhost/some/redirect/':
59 assert req.method == 'GET', 'request should be GET'
60 assert not req.form, 'request should not have data'
61 response = Response('current url: %s' % req.url)
62 else:
63 response = redirect('http://localhost/some/redirect/')
64 return response(environ, start_response)
65
66
67 def external_redirect_demo_app(environ, start_response):
68 response = redirect('http://example.com/')
69 return response(environ, start_response)
70
71
72 def external_subdomain_redirect_demo_app(environ, start_response):
73 if 'test.example.com' in environ['HTTP_HOST']:
74 response = Response('redirected successfully to subdomain')
75 else:
76 response = redirect('http://test.example.com/login')
77 return response(environ, start_response)
78
79
80 def multi_value_post_app(environ, start_response):
81 req = Request(environ)
82 assert req.form['field'] == 'val1', req.form['field']
83 assert req.form.getlist('field') == ['val1', 'val2'], req.form.getlist('field')
84 response = Response('ok')
85 return response(environ, start_response)
86
87
88 class TestTestCase(WerkzeugTestCase):
89
90 def test_cookie_forging(self):
91 c = Client(cookie_app)
92 c.set_cookie('localhost', 'foo', 'bar')
93 appiter, code, headers = c.open()
94 self.assert_strict_equal(list(appiter), [b'foo=bar'])
95
96 def test_set_cookie_app(self):
97 c = Client(cookie_app)
98 appiter, code, headers = c.open()
99 self.assert_in('Set-Cookie', dict(headers))
100
101 def test_cookiejar_stores_cookie(self):
102 c = Client(cookie_app)
103 appiter, code, headers = c.open()
104 self.assert_in('test', c.cookie_jar._cookies['localhost.local']['/'])
105
106 def test_no_initial_cookie(self):
107 c = Client(cookie_app)
108 appiter, code, headers = c.open()
109 self.assert_strict_equal(b''.join(appiter), b'No Cookie')
110
111 def test_resent_cookie(self):
112 c = Client(cookie_app)
113 c.open()
114 appiter, code, headers = c.open()
115 self.assert_strict_equal(b''.join(appiter), b'test=test')
116
117 def test_disable_cookies(self):
118 c = Client(cookie_app, use_cookies=False)
119 c.open()
120 appiter, code, headers = c.open()
121 self.assert_strict_equal(b''.join(appiter), b'No Cookie')
122
123 def test_cookie_for_different_path(self):
124 c = Client(cookie_app)
125 c.open('/path1')
126 appiter, code, headers = c.open('/path2')
127 self.assert_strict_equal(b''.join(appiter), b'test=test')
128
129 def test_environ_builder_basics(self):
130 b = EnvironBuilder()
131 self.assert_is_none(b.content_type)
132 b.method = 'POST'
133 self.assert_equal(b.content_type, 'application/x-www-form-urlencoded')
134 b.files.add_file('test', BytesIO(b'test contents'), 'test.txt')
135 self.assert_equal(b.files['test'].content_type, 'text/plain')
136 self.assert_equal(b.content_type, 'multipart/form-data')
137 b.form['test'] = 'normal value'
138
139 req = b.get_request()
140 b.close()
141
142 self.assert_strict_equal(req.url, u'http://localhost/')
143 self.assert_strict_equal(req.method, 'POST')
144 self.assert_strict_equal(req.form['test'], u'normal value')
145 self.assert_equal(req.files['test'].content_type, 'text/plain')
146 self.assert_strict_equal(req.files['test'].filename, u'test.txt')
147 self.assert_strict_equal(req.files['test'].read(), b'test contents')
148
149 def test_environ_builder_headers(self):
150 b = EnvironBuilder(environ_base={'HTTP_USER_AGENT': 'Foo/0.1'},
151 environ_overrides={'wsgi.version': (1, 1)})
152 b.headers['X-Suck-My-Dick'] = 'very well sir'
153 env = b.get_environ()
154 self.assert_strict_equal(env['HTTP_USER_AGENT'], 'Foo/0.1')
155 self.assert_strict_equal(env['HTTP_X_SUCK_MY_DICK'], 'very well sir')
156 self.assert_strict_equal(env['wsgi.version'], (1, 1))
157
158 b.headers['User-Agent'] = 'Bar/1.0'
159 env = b.get_environ()
160 self.assert_strict_equal(env['HTTP_USER_AGENT'], 'Bar/1.0')
161
162 def test_environ_builder_headers_content_type(self):
163 b = EnvironBuilder(headers={'Content-Type': 'text/plain'})
164 env = b.get_environ()
165 self.assert_equal(env['CONTENT_TYPE'], 'text/plain')
166 b = EnvironBuilder(content_type='text/html',
167 headers={'Content-Type': 'text/plain'})
168 env = b.get_environ()
169 self.assert_equal(env['CONTENT_TYPE'], 'text/html')
170
171 def test_environ_builder_paths(self):
172 b = EnvironBuilder(path='/foo', base_url='http://example.com/')
173 self.assert_strict_equal(b.base_url, 'http://example.com/')
174 self.assert_strict_equal(b.path, '/foo')
175 self.assert_strict_equal(b.script_root, '')
176 self.assert_strict_equal(b.host, 'example.com')
177
178 b = EnvironBuilder(path='/foo', base_url='http://example.com/bar')
179 self.assert_strict_equal(b.base_url, 'http://example.com/bar/')
180 self.assert_strict_equal(b.path, '/foo')
181 self.assert_strict_equal(b.script_root, '/bar')
182 self.assert_strict_equal(b.host, 'example.com')
183
184 b.host = 'localhost'
185 self.assert_strict_equal(b.base_url, 'http://localhost/bar/')
186 b.base_url = 'http://localhost:8080/'
187 self.assert_strict_equal(b.host, 'localhost:8080')
188 self.assert_strict_equal(b.server_name, 'localhost')
189 self.assert_strict_equal(b.server_port, 8080)
190
191 b.host = 'foo.invalid'
192 b.url_scheme = 'https'
193 b.script_root = '/test'
194 env = b.get_environ()
195 self.assert_strict_equal(env['SERVER_NAME'], 'foo.invalid')
196 self.assert_strict_equal(env['SERVER_PORT'], '443')
197 self.assert_strict_equal(env['SCRIPT_NAME'], '/test')
198 self.assert_strict_equal(env['PATH_INFO'], '/foo')
199 self.assert_strict_equal(env['HTTP_HOST'], 'foo.invalid')
200 self.assert_strict_equal(env['wsgi.url_scheme'], 'https')
201 self.assert_strict_equal(b.base_url, 'https://foo.invalid/test/')
202
203 def test_environ_builder_content_type(self):
204 builder = EnvironBuilder()
205 self.assert_is_none(builder.content_type)
206 builder.method = 'POST'
207 self.assert_equal(builder.content_type, 'application/x-www-form-urlencoded')
208 builder.form['foo'] = 'bar'
209 self.assert_equal(builder.content_type, 'application/x-www-form-urlencoded')
210 builder.files.add_file('blafasel', BytesIO(b'foo'), 'test.txt')
211 self.assert_equal(builder.content_type, 'multipart/form-data')
212 req = builder.get_request()
213 self.assert_strict_equal(req.form['foo'], u'bar')
214 self.assert_strict_equal(req.files['blafasel'].read(), b'foo')
215
216 def test_environ_builder_stream_switch(self):
217 d = MultiDict(dict(foo=u'bar', blub=u'blah', hu=u'hum'))
218 for use_tempfile in False, True:
219 stream, length, boundary = stream_encode_multipart(
220 d, use_tempfile, threshold=150)
221 self.assert_true(isinstance(stream, BytesIO) != use_tempfile)
222
223 form = parse_form_data({'wsgi.input': stream, 'CONTENT_LENGTH': str(length),
224 'CONTENT_TYPE': 'multipart/form-data; boundary="%s"' %
225 boundary})[1]
226 self.assert_strict_equal(form, d)
227 stream.close()
228
229 def test_environ_builder_unicode_file_mix(self):
230 for use_tempfile in False, True:
231 f = FileStorage(BytesIO(u'\N{SNOWMAN}'.encode('utf-8')),
232 'snowman.txt')
233 d = MultiDict(dict(f=f, s=u'\N{SNOWMAN}'))
234 stream, length, boundary = stream_encode_multipart(
235 d, use_tempfile, threshold=150)
236 self.assert_true(isinstance(stream, BytesIO) != use_tempfile)
237
238 _, form, files = parse_form_data({
239 'wsgi.input': stream,
240 'CONTENT_LENGTH': str(length),
241 'CONTENT_TYPE': 'multipart/form-data; boundary="%s"' %
242 boundary
243 })
244 self.assert_strict_equal(form['s'], u'\N{SNOWMAN}')
245 self.assert_strict_equal(files['f'].name, 'f')
246 self.assert_strict_equal(files['f'].filename, u'snowman.txt')
247 self.assert_strict_equal(files['f'].read(),
248 u'\N{SNOWMAN}'.encode('utf-8'))
249 stream.close()
250
251 def test_create_environ(self):
252 env = create_environ('/foo?bar=baz', 'http://example.org/')
253 expected = {
254 'wsgi.multiprocess': False,
255 'wsgi.version': (1, 0),
256 'wsgi.run_once': False,
257 'wsgi.errors': sys.stderr,
258 'wsgi.multithread': False,
259 'wsgi.url_scheme': 'http',
260 'SCRIPT_NAME': '',
261 'CONTENT_TYPE': '',
262 'CONTENT_LENGTH': '0',
263 'SERVER_NAME': 'example.org',
264 'REQUEST_METHOD': 'GET',
265 'HTTP_HOST': 'example.org',
266 'PATH_INFO': '/foo',
267 'SERVER_PORT': '80',
268 'SERVER_PROTOCOL': 'HTTP/1.1',
269 'QUERY_STRING': 'bar=baz'
270 }
271 for key, value in iteritems(expected):
272 self.assert_equal(env[key], value)
273 self.assert_strict_equal(env['wsgi.input'].read(0), b'')
274 self.assert_strict_equal(create_environ('/foo', 'http://example.com/')['SCRIPT_NAME'], '')
275
276 def test_file_closing(self):
277 closed = []
278 class SpecialInput(object):
279 def read(self):
280 return ''
281 def close(self):
282 closed.append(self)
283
284 env = create_environ(data={'foo': SpecialInput()})
285 self.assert_strict_equal(len(closed), 1)
286 builder = EnvironBuilder()
287 builder.files.add_file('blah', SpecialInput())
288 builder.close()
289 self.assert_strict_equal(len(closed), 2)
290
291 def test_follow_redirect(self):
292 env = create_environ('/', base_url='http://localhost')
293 c = Client(redirect_with_get_app)
294 appiter, code, headers = c.open(environ_overrides=env, follow_redirects=True)
295 self.assert_strict_equal(code, '200 OK')
296 self.assert_strict_equal(b''.join(appiter), b'current url: http://localhost/some/redirect/')
297
298 # Test that the :cls:`Client` is aware of user defined response wrappers
299 c = Client(redirect_with_get_app, response_wrapper=BaseResponse)
300 resp = c.get('/', follow_redirects=True)
301 self.assert_strict_equal(resp.status_code, 200)
302 self.assert_strict_equal(resp.data, b'current url: http://localhost/some/redirect/')
303
304 # test with URL other than '/' to make sure redirected URL's are correct
305 c = Client(redirect_with_get_app, response_wrapper=BaseResponse)
306 resp = c.get('/first/request', follow_redirects=True)
307 self.assert_strict_equal(resp.status_code, 200)
308 self.assert_strict_equal(resp.data, b'current url: http://localhost/some/redirect/')
309
310 def test_follow_external_redirect(self):
311 env = create_environ('/', base_url='http://localhost')
312 c = Client(external_redirect_demo_app)
313 self.assert_raises(RuntimeError, lambda:
314 c.get(environ_overrides=env, follow_redirects=True))
315
316 def test_follow_external_redirect_on_same_subdomain(self):
317 env = create_environ('/', base_url='http://example.com')
318 c = Client(external_subdomain_redirect_demo_app, allow_subdomain_redirects=True)
319 c.get(environ_overrides=env, follow_redirects=True)
320
321 # check that this does not work for real external domains
322 env = create_environ('/', base_url='http://localhost')
323 self.assert_raises(RuntimeError, lambda:
324 c.get(environ_overrides=env, follow_redirects=True))
325
326 # check that subdomain redirects fail if no `allow_subdomain_redirects` is applied
327 c = Client(external_subdomain_redirect_demo_app)
328 self.assert_raises(RuntimeError, lambda:
329 c.get(environ_overrides=env, follow_redirects=True))
330
331 def test_follow_redirect_loop(self):
332 c = Client(redirect_loop_app, response_wrapper=BaseResponse)
333 with self.assert_raises(ClientRedirectError):
334 resp = c.get('/', follow_redirects=True)
335
336 def test_follow_redirect_with_post(self):
337 c = Client(redirect_with_post_app, response_wrapper=BaseResponse)
338 resp = c.post('/', follow_redirects=True, data='foo=blub+hehe&blah=42')
339 self.assert_strict_equal(resp.status_code, 200)
340 self.assert_strict_equal(resp.data, b'current url: http://localhost/some/redirect/')
341
342 def test_path_info_script_name_unquoting(self):
343 def test_app(environ, start_response):
344 start_response('200 OK', [('Content-Type', 'text/plain')])
345 return [environ['PATH_INFO'] + '\n' + environ['SCRIPT_NAME']]
346 c = Client(test_app, response_wrapper=BaseResponse)
347 resp = c.get('/foo%40bar')
348 self.assert_strict_equal(resp.data, b'/foo@bar\n')
349 c = Client(test_app, response_wrapper=BaseResponse)
350 resp = c.get('/foo%40bar', 'http://localhost/bar%40baz')
351 self.assert_strict_equal(resp.data, b'/foo@bar\n/bar@baz')
352
353 def test_multi_value_submit(self):
354 c = Client(multi_value_post_app, response_wrapper=BaseResponse)
355 data = {
356 'field': ['val1','val2']
357 }
358 resp = c.post('/', data=data)
359 self.assert_strict_equal(resp.status_code, 200)
360 c = Client(multi_value_post_app, response_wrapper=BaseResponse)
361 data = MultiDict({
362 'field': ['val1', 'val2']
363 })
364 resp = c.post('/', data=data)
365 self.assert_strict_equal(resp.status_code, 200)
366
367 def test_iri_support(self):
368 b = EnvironBuilder(u'/föö-bar', base_url=u'http://☃.net/')
369 self.assert_strict_equal(b.path, '/f%C3%B6%C3%B6-bar')
370 self.assert_strict_equal(b.base_url, 'http://xn--n3h.net/')
371
372 def test_run_wsgi_apps(self):
373 def simple_app(environ, start_response):
374 start_response('200 OK', [('Content-Type', 'text/html')])
375 return ['Hello World!']
376 app_iter, status, headers = run_wsgi_app(simple_app, {})
377 self.assert_strict_equal(status, '200 OK')
378 self.assert_strict_equal(list(headers), [('Content-Type', 'text/html')])
379 self.assert_strict_equal(app_iter, ['Hello World!'])
380
381 def yielding_app(environ, start_response):
382 start_response('200 OK', [('Content-Type', 'text/html')])
383 yield 'Hello '
384 yield 'World!'
385 app_iter, status, headers = run_wsgi_app(yielding_app, {})
386 self.assert_strict_equal(status, '200 OK')
387 self.assert_strict_equal(list(headers), [('Content-Type', 'text/html')])
388 self.assert_strict_equal(list(app_iter), ['Hello ', 'World!'])
389
390 def test_multiple_cookies(self):
391 @Request.application
392 def test_app(request):
393 response = Response(repr(sorted(request.cookies.items())))
394 response.set_cookie(u'test1', b'foo')
395 response.set_cookie(u'test2', b'bar')
396 return response
397 client = Client(test_app, Response)
398 resp = client.get('/')
399 self.assert_strict_equal(resp.data, b'[]')
400 resp = client.get('/')
401 self.assert_strict_equal(resp.data,
402 to_bytes(repr([('test1', u'foo'), ('test2', u'bar')]), 'ascii'))
403
404 def test_correct_open_invocation_on_redirect(self):
405 class MyClient(Client):
406 counter = 0
407 def open(self, *args, **kwargs):
408 self.counter += 1
409 env = kwargs.setdefault('environ_overrides', {})
410 env['werkzeug._foo'] = self.counter
411 return Client.open(self, *args, **kwargs)
412
413 @Request.application
414 def test_app(request):
415 return Response(str(request.environ['werkzeug._foo']))
416
417 c = MyClient(test_app, response_wrapper=Response)
418 self.assert_strict_equal(c.get('/').data, b'1')
419 self.assert_strict_equal(c.get('/').data, b'2')
420 self.assert_strict_equal(c.get('/').data, b'3')
421
422 def test_correct_encoding(self):
423 req = Request.from_values(u'/\N{SNOWMAN}', u'http://example.com/foo')
424 self.assert_strict_equal(req.script_root, u'/foo')
425 self.assert_strict_equal(req.path, u'/\N{SNOWMAN}')
426
427 def test_full_url_requests_with_args(self):
428 base = 'http://example.com/'
429
430 @Request.application
431 def test_app(request):
432 return Response(request.args['x'])
433 client = Client(test_app, Response)
434 resp = client.get('/?x=42', base)
435 self.assert_strict_equal(resp.data, b'42')
436 resp = client.get('http://www.example.com/?x=23', base)
437 self.assert_strict_equal(resp.data, b'23')
438
439
440 def suite():
441 suite = unittest.TestSuite()
442 suite.addTest(unittest.makeSuite(TestTestCase))
443 return suite
+0
-322
werkzeug/testsuite/urls.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite.urls
3 ~~~~~~~~~~~~~~~~~~~~~~~
4
5 URL helper tests.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import unittest
11
12 from werkzeug.testsuite import WerkzeugTestCase
13
14 from werkzeug.datastructures import OrderedMultiDict
15 from werkzeug import urls
16 from werkzeug._compat import text_type, NativeStringIO, BytesIO
17
18
19 class URLsTestCase(WerkzeugTestCase):
20
21 def test_replace(self):
22 url = urls.url_parse('http://de.wikipedia.org/wiki/Troll')
23 self.assert_strict_equal(url.replace(query='foo=bar'),
24 urls.url_parse('http://de.wikipedia.org/wiki/Troll?foo=bar'))
25 self.assert_strict_equal(url.replace(scheme='https'),
26 urls.url_parse('https://de.wikipedia.org/wiki/Troll'))
27
28 def test_quoting(self):
29 self.assert_strict_equal(urls.url_quote(u'\xf6\xe4\xfc'), '%C3%B6%C3%A4%C3%BC')
30 self.assert_strict_equal(urls.url_unquote(urls.url_quote(u'#%="\xf6')), u'#%="\xf6')
31 self.assert_strict_equal(urls.url_quote_plus('foo bar'), 'foo+bar')
32 self.assert_strict_equal(urls.url_unquote_plus('foo+bar'), u'foo bar')
33 self.assert_strict_equal(urls.url_quote_plus('foo+bar'), 'foo%2Bbar')
34 self.assert_strict_equal(urls.url_unquote_plus('foo%2Bbar'), u'foo+bar')
35 self.assert_strict_equal(urls.url_encode({b'a': None, b'b': b'foo bar'}), 'b=foo+bar')
36 self.assert_strict_equal(urls.url_encode({u'a': None, u'b': u'foo bar'}), 'b=foo+bar')
37 self.assert_strict_equal(urls.url_fix(u'http://de.wikipedia.org/wiki/Elf (Begriffsklärung)'),
38 'http://de.wikipedia.org/wiki/Elf%20(Begriffskl%C3%A4rung)')
39 self.assert_strict_equal(urls.url_quote_plus(42), '42')
40 self.assert_strict_equal(urls.url_quote(b'\xff'), '%FF')
41
42 def test_bytes_unquoting(self):
43 self.assert_strict_equal(urls.url_unquote(urls.url_quote(
44 u'#%="\xf6', charset='latin1'), charset=None), b'#%="\xf6')
45
46 def test_url_decoding(self):
47 x = urls.url_decode(b'foo=42&bar=23&uni=H%C3%A4nsel')
48 self.assert_strict_equal(x['foo'], u'42')
49 self.assert_strict_equal(x['bar'], u'23')
50 self.assert_strict_equal(x['uni'], u'Hänsel')
51
52 x = urls.url_decode(b'foo=42;bar=23;uni=H%C3%A4nsel', separator=b';')
53 self.assert_strict_equal(x['foo'], u'42')
54 self.assert_strict_equal(x['bar'], u'23')
55 self.assert_strict_equal(x['uni'], u'Hänsel')
56
57 x = urls.url_decode(b'%C3%9Ch=H%C3%A4nsel', decode_keys=True)
58 self.assert_strict_equal(x[u'Üh'], u'Hänsel')
59
60 def test_url_bytes_decoding(self):
61 x = urls.url_decode(b'foo=42&bar=23&uni=H%C3%A4nsel', charset=None)
62 self.assert_strict_equal(x[b'foo'], b'42')
63 self.assert_strict_equal(x[b'bar'], b'23')
64 self.assert_strict_equal(x[b'uni'], u'Hänsel'.encode('utf-8'))
65
66 def test_streamed_url_decoding(self):
67 item1 = u'a' * 100000
68 item2 = u'b' * 400
69 string = ('a=%s&b=%s&c=%s' % (item1, item2, item2)).encode('ascii')
70 gen = urls.url_decode_stream(BytesIO(string), limit=len(string),
71 return_iterator=True)
72 self.assert_strict_equal(next(gen), ('a', item1))
73 self.assert_strict_equal(next(gen), ('b', item2))
74 self.assert_strict_equal(next(gen), ('c', item2))
75 self.assert_raises(StopIteration, lambda: next(gen))
76
77 def test_stream_decoding_string_fails(self):
78 self.assert_raises(TypeError, urls.url_decode_stream, 'testing')
79
80 def test_url_encoding(self):
81 self.assert_strict_equal(urls.url_encode({'foo': 'bar 45'}), 'foo=bar+45')
82 d = {'foo': 1, 'bar': 23, 'blah': u'Hänsel'}
83 self.assert_strict_equal(urls.url_encode(d, sort=True), 'bar=23&blah=H%C3%A4nsel&foo=1')
84 self.assert_strict_equal(urls.url_encode(d, sort=True, separator=u';'), 'bar=23;blah=H%C3%A4nsel;foo=1')
85
86 def test_sorted_url_encode(self):
87 self.assert_strict_equal(urls.url_encode({u"a": 42, u"b": 23, 1: 1, 2: 2},
88 sort=True, key=lambda i: text_type(i[0])), '1=1&2=2&a=42&b=23')
89 self.assert_strict_equal(urls.url_encode({u'A': 1, u'a': 2, u'B': 3, 'b': 4}, sort=True,
90 key=lambda x: x[0].lower() + x[0]), 'A=1&a=2&B=3&b=4')
91
92 def test_streamed_url_encoding(self):
93 out = NativeStringIO()
94 urls.url_encode_stream({'foo': 'bar 45'}, out)
95 self.assert_strict_equal(out.getvalue(), 'foo=bar+45')
96
97 d = {'foo': 1, 'bar': 23, 'blah': u'Hänsel'}
98 out = NativeStringIO()
99 urls.url_encode_stream(d, out, sort=True)
100 self.assert_strict_equal(out.getvalue(), 'bar=23&blah=H%C3%A4nsel&foo=1')
101 out = NativeStringIO()
102 urls.url_encode_stream(d, out, sort=True, separator=u';')
103 self.assert_strict_equal(out.getvalue(), 'bar=23;blah=H%C3%A4nsel;foo=1')
104
105 gen = urls.url_encode_stream(d, sort=True)
106 self.assert_strict_equal(next(gen), 'bar=23')
107 self.assert_strict_equal(next(gen), 'blah=H%C3%A4nsel')
108 self.assert_strict_equal(next(gen), 'foo=1')
109 self.assert_raises(StopIteration, lambda: next(gen))
110
111 def test_url_fixing(self):
112 x = urls.url_fix(u'http://de.wikipedia.org/wiki/Elf (Begriffskl\xe4rung)')
113 self.assert_line_equal(x, 'http://de.wikipedia.org/wiki/Elf%20(Begriffskl%C3%A4rung)')
114
115 x = urls.url_fix("http://just.a.test/$-_.+!*'(),")
116 self.assert_equal(x, "http://just.a.test/$-_.+!*'(),")
117
118 def test_url_fixing_qs(self):
119 x = urls.url_fix(b'http://example.com/?foo=%2f%2f')
120 self.assert_line_equal(x, 'http://example.com/?foo=%2f%2f')
121
122 x = urls.url_fix('http://acronyms.thefreedictionary.com/Algebraic+Methods+of+Solving+the+Schr%C3%B6dinger+Equation')
123 self.assert_equal(x, 'http://acronyms.thefreedictionary.com/Algebraic+Methods+of+Solving+the+Schr%C3%B6dinger+Equation')
124
125 def test_iri_support(self):
126 self.assert_strict_equal(urls.uri_to_iri('http://xn--n3h.net/'),
127 u'http://\u2603.net/')
128 self.assert_strict_equal(
129 urls.uri_to_iri(b'http://%C3%BCser:p%C3%A4ssword@xn--n3h.net/p%C3%A5th'),
130 u'http://\xfcser:p\xe4ssword@\u2603.net/p\xe5th')
131 self.assert_strict_equal(urls.iri_to_uri(u'http://☃.net/'), 'http://xn--n3h.net/')
132 self.assert_strict_equal(
133 urls.iri_to_uri(u'http://üser:pässword@☃.net/påth'),
134 'http://%C3%BCser:p%C3%A4ssword@xn--n3h.net/p%C3%A5th')
135
136 self.assert_strict_equal(urls.uri_to_iri('http://test.com/%3Fmeh?foo=%26%2F'),
137 u'http://test.com/%3Fmeh?foo=%26%2F')
138
139 # this should work as well, might break on 2.4 because of a broken
140 # idna codec
141 self.assert_strict_equal(urls.uri_to_iri(b'/foo'), u'/foo')
142 self.assert_strict_equal(urls.iri_to_uri(u'/foo'), '/foo')
143
144 self.assert_strict_equal(urls.iri_to_uri(u'http://föö.com:8080/bam/baz'),
145 'http://xn--f-1gaa.com:8080/bam/baz')
146
147 def test_iri_safe_conversion(self):
148 self.assert_strict_equal(urls.iri_to_uri(u'magnet:?foo=bar'),
149 'magnet:?foo=bar')
150 self.assert_strict_equal(urls.iri_to_uri(u'itms-service://?foo=bar'),
151 'itms-service:?foo=bar')
152 self.assert_strict_equal(urls.iri_to_uri(u'itms-service://?foo=bar',
153 safe_conversion=True),
154 'itms-service://?foo=bar')
155
156 def test_iri_safe_quoting(self):
157 uri = 'http://xn--f-1gaa.com/%2F%25?q=%C3%B6&x=%3D%25#%25'
158 iri = u'http://föö.com/%2F%25?q=ö&x=%3D%25#%25'
159 self.assert_strict_equal(urls.uri_to_iri(uri), iri)
160 self.assert_strict_equal(urls.iri_to_uri(urls.uri_to_iri(uri)), uri)
161
162 def test_ordered_multidict_encoding(self):
163 d = OrderedMultiDict()
164 d.add('foo', 1)
165 d.add('foo', 2)
166 d.add('foo', 3)
167 d.add('bar', 0)
168 d.add('foo', 4)
169 self.assert_equal(urls.url_encode(d), 'foo=1&foo=2&foo=3&bar=0&foo=4')
170
171 def test_multidict_encoding(self):
172 d = OrderedMultiDict()
173 d.add('2013-10-10T23:26:05.657975+0000', '2013-10-10T23:26:05.657975+0000')
174 self.assert_equal(urls.url_encode(d), '2013-10-10T23%3A26%3A05.657975%2B0000=2013-10-10T23%3A26%3A05.657975%2B0000')
175
176 def test_href(self):
177 x = urls.Href('http://www.example.com/')
178 self.assert_strict_equal(x(u'foo'), 'http://www.example.com/foo')
179 self.assert_strict_equal(x.foo(u'bar'), 'http://www.example.com/foo/bar')
180 self.assert_strict_equal(x.foo(u'bar', x=42), 'http://www.example.com/foo/bar?x=42')
181 self.assert_strict_equal(x.foo(u'bar', class_=42), 'http://www.example.com/foo/bar?class=42')
182 self.assert_strict_equal(x.foo(u'bar', {u'class': 42}), 'http://www.example.com/foo/bar?class=42')
183 self.assert_raises(AttributeError, lambda: x.__blah__)
184
185 x = urls.Href('blah')
186 self.assert_strict_equal(x.foo(u'bar'), 'blah/foo/bar')
187
188 self.assert_raises(TypeError, x.foo, {u"foo": 23}, x=42)
189
190 x = urls.Href('')
191 self.assert_strict_equal(x('foo'), 'foo')
192
193 def test_href_url_join(self):
194 x = urls.Href(u'test')
195 self.assert_line_equal(x(u'foo:bar'), u'test/foo:bar')
196 self.assert_line_equal(x(u'http://example.com/'), u'test/http://example.com/')
197 self.assert_line_equal(x.a(), u'test/a')
198
199 def test_href_past_root(self):
200 base_href = urls.Href('http://www.blagga.com/1/2/3')
201 self.assert_strict_equal(base_href('../foo'), 'http://www.blagga.com/1/2/foo')
202 self.assert_strict_equal(base_href('../../foo'), 'http://www.blagga.com/1/foo')
203 self.assert_strict_equal(base_href('../../../foo'), 'http://www.blagga.com/foo')
204 self.assert_strict_equal(base_href('../../../../foo'), 'http://www.blagga.com/foo')
205 self.assert_strict_equal(base_href('../../../../../foo'), 'http://www.blagga.com/foo')
206 self.assert_strict_equal(base_href('../../../../../../foo'), 'http://www.blagga.com/foo')
207
208 def test_url_unquote_plus_unicode(self):
209 # was broken in 0.6
210 self.assert_strict_equal(urls.url_unquote_plus(u'\x6d'), u'\x6d')
211 self.assert_is(type(urls.url_unquote_plus(u'\x6d')), text_type)
212
213 def test_quoting_of_local_urls(self):
214 rv = urls.iri_to_uri(u'/foo\x8f')
215 self.assert_strict_equal(rv, '/foo%C2%8F')
216 self.assert_is(type(rv), str)
217
218 def test_url_attributes(self):
219 rv = urls.url_parse('http://foo%3a:bar%3a@[::1]:80/123?x=y#frag')
220 self.assert_strict_equal(rv.scheme, 'http')
221 self.assert_strict_equal(rv.auth, 'foo%3a:bar%3a')
222 self.assert_strict_equal(rv.username, u'foo:')
223 self.assert_strict_equal(rv.password, u'bar:')
224 self.assert_strict_equal(rv.raw_username, 'foo%3a')
225 self.assert_strict_equal(rv.raw_password, 'bar%3a')
226 self.assert_strict_equal(rv.host, '::1')
227 self.assert_equal(rv.port, 80)
228 self.assert_strict_equal(rv.path, '/123')
229 self.assert_strict_equal(rv.query, 'x=y')
230 self.assert_strict_equal(rv.fragment, 'frag')
231
232 rv = urls.url_parse(u'http://\N{SNOWMAN}.com/')
233 self.assert_strict_equal(rv.host, u'\N{SNOWMAN}.com')
234 self.assert_strict_equal(rv.ascii_host, 'xn--n3h.com')
235
236 def test_url_attributes_bytes(self):
237 rv = urls.url_parse(b'http://foo%3a:bar%3a@[::1]:80/123?x=y#frag')
238 self.assert_strict_equal(rv.scheme, b'http')
239 self.assert_strict_equal(rv.auth, b'foo%3a:bar%3a')
240 self.assert_strict_equal(rv.username, u'foo:')
241 self.assert_strict_equal(rv.password, u'bar:')
242 self.assert_strict_equal(rv.raw_username, b'foo%3a')
243 self.assert_strict_equal(rv.raw_password, b'bar%3a')
244 self.assert_strict_equal(rv.host, b'::1')
245 self.assert_equal(rv.port, 80)
246 self.assert_strict_equal(rv.path, b'/123')
247 self.assert_strict_equal(rv.query, b'x=y')
248 self.assert_strict_equal(rv.fragment, b'frag')
249
250 def test_url_joining(self):
251 self.assert_strict_equal(urls.url_join('/foo', '/bar'), '/bar')
252 self.assert_strict_equal(urls.url_join('http://example.com/foo', '/bar'),
253 'http://example.com/bar')
254 self.assert_strict_equal(urls.url_join('file:///tmp/', 'test.html'),
255 'file:///tmp/test.html')
256 self.assert_strict_equal(urls.url_join('file:///tmp/x', 'test.html'),
257 'file:///tmp/test.html')
258 self.assert_strict_equal(urls.url_join('file:///tmp/x', '../../../x.html'),
259 'file:///x.html')
260
261 def test_partial_unencoded_decode(self):
262 ref = u'foo=정상처리'.encode('euc-kr')
263 x = urls.url_decode(ref, charset='euc-kr')
264 self.assert_strict_equal(x['foo'], u'정상처리')
265
266 def test_iri_to_uri_idempotence_ascii_only(self):
267 uri = u'http://www.idempoten.ce'
268 uri = urls.iri_to_uri(uri)
269 self.assert_equal(urls.iri_to_uri(uri), uri)
270
271 def test_iri_to_uri_idempotence_non_ascii(self):
272 uri = u'http://\N{SNOWMAN}/\N{SNOWMAN}'
273 uri = urls.iri_to_uri(uri)
274 self.assert_equal(urls.iri_to_uri(uri), uri)
275
276 def test_uri_to_iri_idempotence_ascii_only(self):
277 uri = 'http://www.idempoten.ce'
278 uri = urls.uri_to_iri(uri)
279 self.assert_equal(urls.uri_to_iri(uri), uri)
280
281 def test_uri_to_iri_idempotence_non_ascii(self):
282 uri = 'http://xn--n3h/%E2%98%83'
283 uri = urls.uri_to_iri(uri)
284 self.assert_equal(urls.uri_to_iri(uri), uri)
285
286 def test_iri_to_uri_to_iri(self):
287 iri = u'http://föö.com/'
288 uri = urls.iri_to_uri(iri)
289 self.assert_equal(urls.uri_to_iri(uri), iri)
290
291 def test_uri_to_iri_to_uri(self):
292 uri = 'http://xn--f-rgao.com/%C3%9E'
293 iri = urls.uri_to_iri(uri)
294 self.assert_equal(urls.iri_to_uri(iri), uri)
295
296 def test_uri_iri_normalization(self):
297 uri = 'http://xn--f-rgao.com/%E2%98%90/fred?utf8=%E2%9C%93'
298 iri = u'http://föñ.com/\N{BALLOT BOX}/fred?utf8=\u2713'
299
300 tests = [
301 u'http://föñ.com/\N{BALLOT BOX}/fred?utf8=\u2713',
302 u'http://xn--f-rgao.com/\u2610/fred?utf8=\N{CHECK MARK}',
303 b'http://xn--f-rgao.com/%E2%98%90/fred?utf8=%E2%9C%93',
304 u'http://xn--f-rgao.com/%E2%98%90/fred?utf8=%E2%9C%93',
305 u'http://föñ.com/\u2610/fred?utf8=%E2%9C%93',
306 b'http://xn--f-rgao.com/\xe2\x98\x90/fred?utf8=\xe2\x9c\x93',
307 ]
308
309 for test in tests:
310 self.assert_equal(urls.uri_to_iri(test), iri)
311 self.assert_equal(urls.iri_to_uri(test), uri)
312 self.assert_equal(urls.uri_to_iri(urls.iri_to_uri(test)), iri)
313 self.assert_equal(urls.iri_to_uri(urls.uri_to_iri(test)), uri)
314 self.assert_equal(urls.uri_to_iri(urls.uri_to_iri(test)), iri)
315 self.assert_equal(urls.iri_to_uri(urls.iri_to_uri(test)), uri)
316
317
318 def suite():
319 suite = unittest.TestSuite()
320 suite.addTest(unittest.makeSuite(URLsTestCase))
321 return suite
+0
-284
werkzeug/testsuite/utils.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite.utils
3 ~~~~~~~~~~~~~~~~~~~~~~~~
4
5 General utilities.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10
11 from __future__ import with_statement
12
13 import unittest
14 from datetime import datetime
15 from functools import partial
16
17 from werkzeug.testsuite import WerkzeugTestCase
18
19 from werkzeug import utils
20 from werkzeug.datastructures import Headers
21 from werkzeug.http import parse_date, http_date
22 from werkzeug.wrappers import BaseResponse
23 from werkzeug.test import Client, run_wsgi_app
24 from werkzeug._compat import text_type, implements_iterator
25
26
27 class GeneralUtilityTestCase(WerkzeugTestCase):
28
29 def test_redirect(self):
30 resp = utils.redirect(u'/füübär')
31 self.assert_in(b'/f%C3%BC%C3%BCb%C3%A4r', resp.get_data())
32 self.assert_equal(resp.headers['Location'], '/f%C3%BC%C3%BCb%C3%A4r')
33 self.assert_equal(resp.status_code, 302)
34
35 resp = utils.redirect(u'http://☃.net/', 307)
36 self.assert_in(b'http://xn--n3h.net/', resp.get_data())
37 self.assert_equal(resp.headers['Location'], 'http://xn--n3h.net/')
38 self.assert_equal(resp.status_code, 307)
39
40 resp = utils.redirect('http://example.com/', 305)
41 self.assert_equal(resp.headers['Location'], 'http://example.com/')
42 self.assert_equal(resp.status_code, 305)
43
44 def test_redirect_no_unicode_header_keys(self):
45 # Make sure all headers are native keys. This was a bug at one point
46 # due to an incorrect conversion.
47 resp = utils.redirect('http://example.com/', 305)
48 for key, value in resp.headers.items():
49 self.assert_equal(type(key), str)
50 self.assert_equal(type(value), text_type)
51 self.assert_equal(resp.headers['Location'], 'http://example.com/')
52 self.assert_equal(resp.status_code, 305)
53
54 def test_redirect_xss(self):
55 location = 'http://example.com/?xss="><script>alert(1)</script>'
56 resp = utils.redirect(location)
57 self.assert_not_in(b'<script>alert(1)</script>', resp.get_data())
58
59 location = 'http://example.com/?xss="onmouseover="alert(1)'
60 resp = utils.redirect(location)
61 self.assert_not_in(b'href="http://example.com/?xss="onmouseover="alert(1)"', resp.get_data())
62
63 def test_cached_property(self):
64 foo = []
65 class A(object):
66 def prop(self):
67 foo.append(42)
68 return 42
69 prop = utils.cached_property(prop)
70
71 a = A()
72 p = a.prop
73 q = a.prop
74 self.assert_true(p == q == 42)
75 self.assert_equal(foo, [42])
76
77 foo = []
78 class A(object):
79 def _prop(self):
80 foo.append(42)
81 return 42
82 prop = utils.cached_property(_prop, name='prop')
83 del _prop
84
85 a = A()
86 p = a.prop
87 q = a.prop
88 self.assert_true(p == q == 42)
89 self.assert_equal(foo, [42])
90
91 def test_environ_property(self):
92 class A(object):
93 environ = {'string': 'abc', 'number': '42'}
94
95 string = utils.environ_property('string')
96 missing = utils.environ_property('missing', 'spam')
97 read_only = utils.environ_property('number')
98 number = utils.environ_property('number', load_func=int)
99 broken_number = utils.environ_property('broken_number', load_func=int)
100 date = utils.environ_property('date', None, parse_date, http_date,
101 read_only=False)
102 foo = utils.environ_property('foo')
103
104 a = A()
105 self.assert_equal(a.string, 'abc')
106 self.assert_equal(a.missing, 'spam')
107 def test_assign():
108 a.read_only = 'something'
109 self.assert_raises(AttributeError, test_assign)
110 self.assert_equal(a.number, 42)
111 self.assert_equal(a.broken_number, None)
112 self.assert_is_none(a.date)
113 a.date = datetime(2008, 1, 22, 10, 0, 0, 0)
114 self.assert_equal(a.environ['date'], 'Tue, 22 Jan 2008 10:00:00 GMT')
115
116 def test_escape(self):
117 class Foo(str):
118 def __html__(self):
119 return text_type(self)
120 self.assert_equal(utils.escape(None), '')
121 self.assert_equal(utils.escape(42), '42')
122 self.assert_equal(utils.escape('<>'), '&lt;&gt;')
123 self.assert_equal(utils.escape('"foo"'), '&quot;foo&quot;')
124 self.assert_equal(utils.escape(Foo('<foo>')), '<foo>')
125
126 def test_unescape(self):
127 self.assert_equal(utils.unescape('&lt;&auml;&gt;'), u'<ä>')
128
129 def test_run_wsgi_app(self):
130 def foo(environ, start_response):
131 start_response('200 OK', [('Content-Type', 'text/plain')])
132 yield '1'
133 yield '2'
134 yield '3'
135
136 app_iter, status, headers = run_wsgi_app(foo, {})
137 self.assert_equal(status, '200 OK')
138 self.assert_equal(list(headers), [('Content-Type', 'text/plain')])
139 self.assert_equal(next(app_iter), '1')
140 self.assert_equal(next(app_iter), '2')
141 self.assert_equal(next(app_iter), '3')
142 self.assert_raises(StopIteration, partial(next, app_iter))
143
144 got_close = []
145 @implements_iterator
146 class CloseIter(object):
147 def __init__(self):
148 self.iterated = False
149 def __iter__(self):
150 return self
151 def close(self):
152 got_close.append(None)
153 def __next__(self):
154 if self.iterated:
155 raise StopIteration()
156 self.iterated = True
157 return 'bar'
158
159 def bar(environ, start_response):
160 start_response('200 OK', [('Content-Type', 'text/plain')])
161 return CloseIter()
162
163 app_iter, status, headers = run_wsgi_app(bar, {})
164 self.assert_equal(status, '200 OK')
165 self.assert_equal(list(headers), [('Content-Type', 'text/plain')])
166 self.assert_equal(next(app_iter), 'bar')
167 self.assert_raises(StopIteration, partial(next, app_iter))
168 app_iter.close()
169
170 self.assert_equal(run_wsgi_app(bar, {}, True)[0], ['bar'])
171
172 self.assert_equal(len(got_close), 2)
173
174 def test_import_string(self):
175 import cgi
176 from werkzeug.debug import DebuggedApplication
177 self.assert_is(utils.import_string('cgi.escape'), cgi.escape)
178 self.assert_is(utils.import_string(u'cgi.escape'), cgi.escape)
179 self.assert_is(utils.import_string('cgi:escape'), cgi.escape)
180 self.assert_is_none(utils.import_string('XXXXXXXXXXXX', True))
181 self.assert_is_none(utils.import_string('cgi.XXXXXXXXXXXX', True))
182 self.assert_is(utils.import_string(u'cgi.escape'), cgi.escape)
183 self.assert_is(utils.import_string(u'werkzeug.debug.DebuggedApplication'), DebuggedApplication)
184 self.assert_raises(ImportError, utils.import_string, 'XXXXXXXXXXXXXXXX')
185 self.assert_raises(ImportError, utils.import_string, 'cgi.XXXXXXXXXX')
186
187 def test_find_modules(self):
188 self.assert_equal(list(utils.find_modules('werkzeug.debug')), \
189 ['werkzeug.debug.console', 'werkzeug.debug.repr',
190 'werkzeug.debug.tbtools'])
191
192 def test_html_builder(self):
193 html = utils.html
194 xhtml = utils.xhtml
195 self.assert_equal(html.p('Hello World'), '<p>Hello World</p>')
196 self.assert_equal(html.a('Test', href='#'), '<a href="#">Test</a>')
197 self.assert_equal(html.br(), '<br>')
198 self.assert_equal(xhtml.br(), '<br />')
199 self.assert_equal(html.img(src='foo'), '<img src="foo">')
200 self.assert_equal(xhtml.img(src='foo'), '<img src="foo" />')
201 self.assert_equal(html.html(
202 html.head(
203 html.title('foo'),
204 html.script(type='text/javascript')
205 )
206 ), '<html><head><title>foo</title><script type="text/javascript">'
207 '</script></head></html>')
208 self.assert_equal(html('<foo>'), '&lt;foo&gt;')
209 self.assert_equal(html.input(disabled=True), '<input disabled>')
210 self.assert_equal(xhtml.input(disabled=True), '<input disabled="disabled" />')
211 self.assert_equal(html.input(disabled=''), '<input>')
212 self.assert_equal(xhtml.input(disabled=''), '<input />')
213 self.assert_equal(html.input(disabled=None), '<input>')
214 self.assert_equal(xhtml.input(disabled=None), '<input />')
215 self.assert_equal(html.script('alert("Hello World");'), '<script>' \
216 'alert("Hello World");</script>')
217 self.assert_equal(xhtml.script('alert("Hello World");'), '<script>' \
218 '/*<![CDATA[*/alert("Hello World");/*]]>*/</script>')
219
220 def test_validate_arguments(self):
221 take_none = lambda: None
222 take_two = lambda a, b: None
223 take_two_one_default = lambda a, b=0: None
224
225 self.assert_equal(utils.validate_arguments(take_two, (1, 2,), {}), ((1, 2), {}))
226 self.assert_equal(utils.validate_arguments(take_two, (1,), {'b': 2}), ((1, 2), {}))
227 self.assert_equal(utils.validate_arguments(take_two_one_default, (1,), {}), ((1, 0), {}))
228 self.assert_equal(utils.validate_arguments(take_two_one_default, (1, 2), {}), ((1, 2), {}))
229
230 self.assert_raises(utils.ArgumentValidationError,
231 utils.validate_arguments, take_two, (), {})
232
233 self.assert_equal(utils.validate_arguments(take_none, (1, 2,), {'c': 3}), ((), {}))
234 self.assert_raises(utils.ArgumentValidationError,
235 utils.validate_arguments, take_none, (1,), {}, drop_extra=False)
236 self.assert_raises(utils.ArgumentValidationError,
237 utils.validate_arguments, take_none, (), {'a': 1}, drop_extra=False)
238
239 def test_header_set_duplication_bug(self):
240 headers = Headers([
241 ('Content-Type', 'text/html'),
242 ('Foo', 'bar'),
243 ('Blub', 'blah')
244 ])
245 headers['blub'] = 'hehe'
246 headers['blafasel'] = 'humm'
247 self.assert_equal(headers, Headers([
248 ('Content-Type', 'text/html'),
249 ('Foo', 'bar'),
250 ('blub', 'hehe'),
251 ('blafasel', 'humm')
252 ]))
253
254 def test_append_slash_redirect(self):
255 def app(env, sr):
256 return utils.append_slash_redirect(env)(env, sr)
257 client = Client(app, BaseResponse)
258 response = client.get('foo', base_url='http://example.org/app')
259 self.assert_equal(response.status_code, 301)
260 self.assert_equal(response.headers['Location'], 'http://example.org/app/foo/')
261
262 def test_cached_property_doc(self):
263 @utils.cached_property
264 def foo():
265 """testing"""
266 return 42
267 self.assert_equal(foo.__doc__, 'testing')
268 self.assert_equal(foo.__name__, 'foo')
269 self.assert_equal(foo.__module__, __name__)
270
271 def test_secure_filename(self):
272 self.assert_equal(utils.secure_filename('My cool movie.mov'),
273 'My_cool_movie.mov')
274 self.assert_equal(utils.secure_filename('../../../etc/passwd'),
275 'etc_passwd')
276 self.assert_equal(utils.secure_filename(u'i contain cool \xfcml\xe4uts.txt'),
277 'i_contain_cool_umlauts.txt')
278
279
280 def suite():
281 suite = unittest.TestSuite()
282 suite.addTest(unittest.makeSuite(GeneralUtilityTestCase))
283 return suite
+0
-840
werkzeug/testsuite/wrappers.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite.wrappers
3 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4
5 Tests for the response and request objects.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import unittest
11 import pickle
12 from io import BytesIO
13 from datetime import datetime
14 from werkzeug._compat import iteritems
15
16 from werkzeug.testsuite import WerkzeugTestCase
17
18 from werkzeug import wrappers
19 from werkzeug.exceptions import SecurityError
20 from werkzeug.wsgi import LimitedStream
21 from werkzeug.datastructures import MultiDict, ImmutableOrderedMultiDict, \
22 ImmutableList, ImmutableTypeConversionDict, CharsetAccept, \
23 MIMEAccept, LanguageAccept, Accept, CombinedMultiDict
24 from werkzeug.test import Client, create_environ, run_wsgi_app
25 from werkzeug._compat import implements_iterator, text_type
26
27
28 class RequestTestResponse(wrappers.BaseResponse):
29 """Subclass of the normal response class we use to test response
30 and base classes. Has some methods to test if things in the
31 response match.
32 """
33
34 def __init__(self, response, status, headers):
35 wrappers.BaseResponse.__init__(self, response, status, headers)
36 self.body_data = pickle.loads(self.get_data())
37
38 def __getitem__(self, key):
39 return self.body_data[key]
40
41
42 def request_demo_app(environ, start_response):
43 request = wrappers.BaseRequest(environ)
44 assert 'werkzeug.request' in environ
45 start_response('200 OK', [('Content-Type', 'text/plain')])
46 return [pickle.dumps({
47 'args': request.args,
48 'args_as_list': list(request.args.lists()),
49 'form': request.form,
50 'form_as_list': list(request.form.lists()),
51 'environ': prepare_environ_pickle(request.environ),
52 'data': request.get_data()
53 })]
54
55
56 def prepare_environ_pickle(environ):
57 result = {}
58 for key, value in iteritems(environ):
59 try:
60 pickle.dumps((key, value))
61 except Exception:
62 continue
63 result[key] = value
64 return result
65
66
67 class WrappersTestCase(WerkzeugTestCase):
68
69 def assert_environ(self, environ, method):
70 self.assert_strict_equal(environ['REQUEST_METHOD'], method)
71 self.assert_strict_equal(environ['PATH_INFO'], '/')
72 self.assert_strict_equal(environ['SCRIPT_NAME'], '')
73 self.assert_strict_equal(environ['SERVER_NAME'], 'localhost')
74 self.assert_strict_equal(environ['wsgi.version'], (1, 0))
75 self.assert_strict_equal(environ['wsgi.url_scheme'], 'http')
76
77 def test_base_request(self):
78 client = Client(request_demo_app, RequestTestResponse)
79
80 # get requests
81 response = client.get('/?foo=bar&foo=hehe')
82 self.assert_strict_equal(response['args'], MultiDict([('foo', u'bar'), ('foo', u'hehe')]))
83 self.assert_strict_equal(response['args_as_list'], [('foo', [u'bar', u'hehe'])])
84 self.assert_strict_equal(response['form'], MultiDict())
85 self.assert_strict_equal(response['form_as_list'], [])
86 self.assert_strict_equal(response['data'], b'')
87 self.assert_environ(response['environ'], 'GET')
88
89 # post requests with form data
90 response = client.post('/?blub=blah', data='foo=blub+hehe&blah=42',
91 content_type='application/x-www-form-urlencoded')
92 self.assert_strict_equal(response['args'], MultiDict([('blub', u'blah')]))
93 self.assert_strict_equal(response['args_as_list'], [('blub', [u'blah'])])
94 self.assert_strict_equal(response['form'], MultiDict([('foo', u'blub hehe'), ('blah', u'42')]))
95 self.assert_strict_equal(response['data'], b'')
96 # currently we do not guarantee that the values are ordered correctly
97 # for post data.
98 ## self.assert_strict_equal(response['form_as_list'], [('foo', ['blub hehe']), ('blah', ['42'])])
99 self.assert_environ(response['environ'], 'POST')
100
101 # patch requests with form data
102 response = client.patch('/?blub=blah', data='foo=blub+hehe&blah=42',
103 content_type='application/x-www-form-urlencoded')
104 self.assert_strict_equal(response['args'], MultiDict([('blub', u'blah')]))
105 self.assert_strict_equal(response['args_as_list'], [('blub', [u'blah'])])
106 self.assert_strict_equal(response['form'],
107 MultiDict([('foo', u'blub hehe'), ('blah', u'42')]))
108 self.assert_strict_equal(response['data'], b'')
109 self.assert_environ(response['environ'], 'PATCH')
110
111 # post requests with json data
112 json = b'{"foo": "bar", "blub": "blah"}'
113 response = client.post('/?a=b', data=json, content_type='application/json')
114 self.assert_strict_equal(response['data'], json)
115 self.assert_strict_equal(response['args'], MultiDict([('a', u'b')]))
116 self.assert_strict_equal(response['form'], MultiDict())
117
118 def test_query_string_is_bytes(self):
119 req = wrappers.Request.from_values(u'/?foo=%2f')
120 self.assert_strict_equal(req.query_string, b'foo=%2f')
121
122 def test_access_route(self):
123 req = wrappers.Request.from_values(headers={
124 'X-Forwarded-For': '192.168.1.2, 192.168.1.1'
125 })
126 req.environ['REMOTE_ADDR'] = '192.168.1.3'
127 self.assert_equal(req.access_route, ['192.168.1.2', '192.168.1.1'])
128 self.assert_strict_equal(req.remote_addr, '192.168.1.3')
129
130 req = wrappers.Request.from_values()
131 req.environ['REMOTE_ADDR'] = '192.168.1.3'
132 self.assert_strict_equal(list(req.access_route), ['192.168.1.3'])
133
134 def test_url_request_descriptors(self):
135 req = wrappers.Request.from_values('/bar?foo=baz', 'http://example.com/test')
136 self.assert_strict_equal(req.path, u'/bar')
137 self.assert_strict_equal(req.full_path, u'/bar?foo=baz')
138 self.assert_strict_equal(req.script_root, u'/test')
139 self.assert_strict_equal(req.url, u'http://example.com/test/bar?foo=baz')
140 self.assert_strict_equal(req.base_url, u'http://example.com/test/bar')
141 self.assert_strict_equal(req.url_root, u'http://example.com/test/')
142 self.assert_strict_equal(req.host_url, u'http://example.com/')
143 self.assert_strict_equal(req.host, 'example.com')
144 self.assert_strict_equal(req.scheme, 'http')
145
146 req = wrappers.Request.from_values('/bar?foo=baz', 'https://example.com/test')
147 self.assert_strict_equal(req.scheme, 'https')
148
149 def test_url_request_descriptors_query_quoting(self):
150 next = 'http%3A%2F%2Fwww.example.com%2F%3Fnext%3D%2F'
151 req = wrappers.Request.from_values('/bar?next=' + next, 'http://example.com/')
152 self.assert_equal(req.path, u'/bar')
153 self.assert_strict_equal(req.full_path, u'/bar?next=' + next)
154 self.assert_strict_equal(req.url, u'http://example.com/bar?next=' + next)
155
156 def test_url_request_descriptors_hosts(self):
157 req = wrappers.Request.from_values('/bar?foo=baz', 'http://example.com/test')
158 req.trusted_hosts = ['example.com']
159 self.assert_strict_equal(req.path, u'/bar')
160 self.assert_strict_equal(req.full_path, u'/bar?foo=baz')
161 self.assert_strict_equal(req.script_root, u'/test')
162 self.assert_strict_equal(req.url, u'http://example.com/test/bar?foo=baz')
163 self.assert_strict_equal(req.base_url, u'http://example.com/test/bar')
164 self.assert_strict_equal(req.url_root, u'http://example.com/test/')
165 self.assert_strict_equal(req.host_url, u'http://example.com/')
166 self.assert_strict_equal(req.host, 'example.com')
167 self.assert_strict_equal(req.scheme, 'http')
168
169 req = wrappers.Request.from_values('/bar?foo=baz', 'https://example.com/test')
170 self.assert_strict_equal(req.scheme, 'https')
171
172 req = wrappers.Request.from_values('/bar?foo=baz', 'http://example.com/test')
173 req.trusted_hosts = ['example.org']
174 self.assert_raises(SecurityError, lambda: req.url)
175 self.assert_raises(SecurityError, lambda: req.base_url)
176 self.assert_raises(SecurityError, lambda: req.url_root)
177 self.assert_raises(SecurityError, lambda: req.host_url)
178 self.assert_raises(SecurityError, lambda: req.host)
179
180 def test_authorization_mixin(self):
181 request = wrappers.Request.from_values(headers={
182 'Authorization': 'Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ=='
183 })
184 a = request.authorization
185 self.assert_strict_equal(a.type, 'basic')
186 self.assert_strict_equal(a.username, 'Aladdin')
187 self.assert_strict_equal(a.password, 'open sesame')
188
189 def test_stream_only_mixing(self):
190 request = wrappers.PlainRequest.from_values(
191 data=b'foo=blub+hehe',
192 content_type='application/x-www-form-urlencoded'
193 )
194 self.assert_equal(list(request.files.items()), [])
195 self.assert_equal(list(request.form.items()), [])
196 self.assert_raises(AttributeError, lambda: request.data)
197 self.assert_strict_equal(request.stream.read(), b'foo=blub+hehe')
198
199 def test_base_response(self):
200 # unicode
201 response = wrappers.BaseResponse(u'öäü')
202 self.assert_strict_equal(response.get_data(), u'öäü'.encode('utf-8'))
203
204 # writing
205 response = wrappers.Response('foo')
206 response.stream.write('bar')
207 self.assert_strict_equal(response.get_data(), b'foobar')
208
209 # set cookie
210 response = wrappers.BaseResponse()
211 response.set_cookie('foo', 'bar', 60, 0, '/blub', 'example.org')
212 self.assert_strict_equal(response.headers.to_wsgi_list(), [
213 ('Content-Type', 'text/plain; charset=utf-8'),
214 ('Set-Cookie', 'foo=bar; Domain=example.org; Expires=Thu, '
215 '01-Jan-1970 00:00:00 GMT; Max-Age=60; Path=/blub')
216 ])
217
218 # delete cookie
219 response = wrappers.BaseResponse()
220 response.delete_cookie('foo')
221 self.assert_strict_equal(response.headers.to_wsgi_list(), [
222 ('Content-Type', 'text/plain; charset=utf-8'),
223 ('Set-Cookie', 'foo=; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/')
224 ])
225
226 # close call forwarding
227 closed = []
228 @implements_iterator
229 class Iterable(object):
230 def __next__(self):
231 raise StopIteration()
232 def __iter__(self):
233 return self
234 def close(self):
235 closed.append(True)
236 response = wrappers.BaseResponse(Iterable())
237 response.call_on_close(lambda: closed.append(True))
238 app_iter, status, headers = run_wsgi_app(response,
239 create_environ(),
240 buffered=True)
241 self.assert_strict_equal(status, '200 OK')
242 self.assert_strict_equal(''.join(app_iter), '')
243 self.assert_strict_equal(len(closed), 2)
244
245 # with statement
246 del closed[:]
247 response = wrappers.BaseResponse(Iterable())
248 with response:
249 pass
250 self.assert_equal(len(closed), 1)
251
252 def test_response_status_codes(self):
253 response = wrappers.BaseResponse()
254 response.status_code = 404
255 self.assert_strict_equal(response.status, '404 NOT FOUND')
256 response.status = '200 OK'
257 self.assert_strict_equal(response.status_code, 200)
258 response.status = '999 WTF'
259 self.assert_strict_equal(response.status_code, 999)
260 response.status_code = 588
261 self.assert_strict_equal(response.status_code, 588)
262 self.assert_strict_equal(response.status, '588 UNKNOWN')
263 response.status = 'wtf'
264 self.assert_strict_equal(response.status_code, 0)
265 self.assert_strict_equal(response.status, '0 wtf')
266
267 def test_type_forcing(self):
268 def wsgi_application(environ, start_response):
269 start_response('200 OK', [('Content-Type', 'text/html')])
270 return ['Hello World!']
271 base_response = wrappers.BaseResponse('Hello World!', content_type='text/html')
272
273 class SpecialResponse(wrappers.Response):
274 def foo(self):
275 return 42
276
277 # good enough for this simple application, but don't ever use that in
278 # real world examples!
279 fake_env = {}
280
281 for orig_resp in wsgi_application, base_response:
282 response = SpecialResponse.force_type(orig_resp, fake_env)
283 assert response.__class__ is SpecialResponse
284 self.assert_strict_equal(response.foo(), 42)
285 self.assert_strict_equal(response.get_data(), b'Hello World!')
286 self.assert_equal(response.content_type, 'text/html')
287
288 # without env, no arbitrary conversion
289 self.assert_raises(TypeError, SpecialResponse.force_type, wsgi_application)
290
291 def test_accept_mixin(self):
292 request = wrappers.Request({
293 'HTTP_ACCEPT': 'text/xml,application/xml,application/xhtml+xml,'
294 'text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5',
295 'HTTP_ACCEPT_CHARSET': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7',
296 'HTTP_ACCEPT_ENCODING': 'gzip,deflate',
297 'HTTP_ACCEPT_LANGUAGE': 'en-us,en;q=0.5'
298 })
299 self.assert_equal(request.accept_mimetypes, MIMEAccept([
300 ('text/xml', 1), ('image/png', 1), ('application/xml', 1),
301 ('application/xhtml+xml', 1), ('text/html', 0.9),
302 ('text/plain', 0.8), ('*/*', 0.5)
303 ]))
304 self.assert_strict_equal(request.accept_charsets, CharsetAccept([
305 ('ISO-8859-1', 1), ('utf-8', 0.7), ('*', 0.7)
306 ]))
307 self.assert_strict_equal(request.accept_encodings, Accept([
308 ('gzip', 1), ('deflate', 1)]))
309 self.assert_strict_equal(request.accept_languages, LanguageAccept([
310 ('en-us', 1), ('en', 0.5)]))
311
312 request = wrappers.Request({'HTTP_ACCEPT': ''})
313 self.assert_strict_equal(request.accept_mimetypes, MIMEAccept())
314
315 def test_etag_request_mixin(self):
316 request = wrappers.Request({
317 'HTTP_CACHE_CONTROL': 'no-store, no-cache',
318 'HTTP_IF_MATCH': 'w/"foo", bar, "baz"',
319 'HTTP_IF_NONE_MATCH': 'w/"foo", bar, "baz"',
320 'HTTP_IF_MODIFIED_SINCE': 'Tue, 22 Jan 2008 11:18:44 GMT',
321 'HTTP_IF_UNMODIFIED_SINCE': 'Tue, 22 Jan 2008 11:18:44 GMT'
322 })
323 assert request.cache_control.no_store
324 assert request.cache_control.no_cache
325
326 for etags in request.if_match, request.if_none_match:
327 assert etags('bar')
328 assert etags.contains_raw('w/"foo"')
329 assert etags.contains_weak('foo')
330 assert not etags.contains('foo')
331
332 self.assert_equal(request.if_modified_since, datetime(2008, 1, 22, 11, 18, 44))
333 self.assert_equal(request.if_unmodified_since, datetime(2008, 1, 22, 11, 18, 44))
334
335 def test_user_agent_mixin(self):
336 user_agents = [
337 ('Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.8.1.11) '
338 'Gecko/20071127 Firefox/2.0.0.11', 'firefox', 'macos', '2.0.0.11',
339 'en-US'),
340 ('Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; de-DE) Opera 8.54',
341 'opera', 'windows', '8.54', 'de-DE'),
342 ('Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420 '
343 '(KHTML, like Gecko) Version/3.0 Mobile/1A543a Safari/419.3',
344 'safari', 'iphone', '419.3', 'en'),
345 ('Bot Googlebot/2.1 ( http://www.googlebot.com/bot.html)',
346 'google', None, '2.1', None)
347 ]
348 for ua, browser, platform, version, lang in user_agents:
349 request = wrappers.Request({'HTTP_USER_AGENT': ua})
350 self.assert_strict_equal(request.user_agent.browser, browser)
351 self.assert_strict_equal(request.user_agent.platform, platform)
352 self.assert_strict_equal(request.user_agent.version, version)
353 self.assert_strict_equal(request.user_agent.language, lang)
354 assert bool(request.user_agent)
355 self.assert_strict_equal(request.user_agent.to_header(), ua)
356 self.assert_strict_equal(str(request.user_agent), ua)
357
358 request = wrappers.Request({'HTTP_USER_AGENT': 'foo'})
359 assert not request.user_agent
360
361 def test_stream_wrapping(self):
362 class LowercasingStream(object):
363 def __init__(self, stream):
364 self._stream = stream
365 def read(self, size=-1):
366 return self._stream.read(size).lower()
367 def readline(self, size=-1):
368 return self._stream.readline(size).lower()
369
370 data = b'foo=Hello+World'
371 req = wrappers.Request.from_values('/', method='POST', data=data,
372 content_type='application/x-www-form-urlencoded')
373 req.stream = LowercasingStream(req.stream)
374 self.assert_equal(req.form['foo'], 'hello world')
375
376 def test_data_descriptor_triggers_parsing(self):
377 data = b'foo=Hello+World'
378 req = wrappers.Request.from_values('/', method='POST', data=data,
379 content_type='application/x-www-form-urlencoded')
380
381 self.assert_equal(req.data, b'')
382 self.assert_equal(req.form['foo'], u'Hello World')
383
384 def test_get_data_method_parsing_caching_behavior(self):
385 data = b'foo=Hello+World'
386 req = wrappers.Request.from_values('/', method='POST', data=data,
387 content_type='application/x-www-form-urlencoded')
388
389 # get_data() caches, so form stays available
390 self.assert_equal(req.get_data(), data)
391 self.assert_equal(req.form['foo'], u'Hello World')
392 self.assert_equal(req.get_data(), data)
393
394 # here we access the form data first, caching is bypassed
395 req = wrappers.Request.from_values('/', method='POST', data=data,
396 content_type='application/x-www-form-urlencoded')
397 self.assert_equal(req.form['foo'], u'Hello World')
398 self.assert_equal(req.get_data(), b'')
399
400 # Another case is uncached get data which trashes everything
401 req = wrappers.Request.from_values('/', method='POST', data=data,
402 content_type='application/x-www-form-urlencoded')
403 self.assert_equal(req.get_data(cache=False), data)
404 self.assert_equal(req.get_data(cache=False), b'')
405 self.assert_equal(req.form, {})
406
407 # Or we can implicitly start the form parser which is similar to
408 # the old .data behavior
409 req = wrappers.Request.from_values('/', method='POST', data=data,
410 content_type='application/x-www-form-urlencoded')
411 self.assert_equal(req.get_data(parse_form_data=True), b'')
412 self.assert_equal(req.form['foo'], u'Hello World')
413
414 def test_etag_response_mixin(self):
415 response = wrappers.Response('Hello World')
416 self.assert_equal(response.get_etag(), (None, None))
417 response.add_etag()
418 self.assert_equal(response.get_etag(), ('b10a8db164e0754105b7a99be72e3fe5', False))
419 assert not response.cache_control
420 response.cache_control.must_revalidate = True
421 response.cache_control.max_age = 60
422 response.headers['Content-Length'] = len(response.get_data())
423 assert response.headers['Cache-Control'] in ('must-revalidate, max-age=60',
424 'max-age=60, must-revalidate')
425
426 assert 'date' not in response.headers
427 env = create_environ()
428 env.update({
429 'REQUEST_METHOD': 'GET',
430 'HTTP_IF_NONE_MATCH': response.get_etag()[0]
431 })
432 response.make_conditional(env)
433 assert 'date' in response.headers
434
435 # after the thing is invoked by the server as wsgi application
436 # (we're emulating this here), there must not be any entity
437 # headers left and the status code would have to be 304
438 resp = wrappers.Response.from_app(response, env)
439 self.assert_equal(resp.status_code, 304)
440 assert not 'content-length' in resp.headers
441
442 # make sure date is not overriden
443 response = wrappers.Response('Hello World')
444 response.date = 1337
445 d = response.date
446 response.make_conditional(env)
447 self.assert_equal(response.date, d)
448
449 # make sure content length is only set if missing
450 response = wrappers.Response('Hello World')
451 response.content_length = 999
452 response.make_conditional(env)
453 self.assert_equal(response.content_length, 999)
454
455 def test_etag_response_mixin_freezing(self):
456 class WithFreeze(wrappers.ETagResponseMixin, wrappers.BaseResponse):
457 pass
458 class WithoutFreeze(wrappers.BaseResponse, wrappers.ETagResponseMixin):
459 pass
460
461 response = WithFreeze('Hello World')
462 response.freeze()
463 self.assert_strict_equal(response.get_etag(),
464 (text_type(wrappers.generate_etag(b'Hello World')), False))
465 response = WithoutFreeze('Hello World')
466 response.freeze()
467 self.assert_equal(response.get_etag(), (None, None))
468 response = wrappers.Response('Hello World')
469 response.freeze()
470 self.assert_equal(response.get_etag(), (None, None))
471
472 def test_authenticate_mixin(self):
473 resp = wrappers.Response()
474 resp.www_authenticate.type = 'basic'
475 resp.www_authenticate.realm = 'Testing'
476 self.assert_strict_equal(resp.headers['WWW-Authenticate'], u'Basic realm="Testing"')
477 resp.www_authenticate.realm = None
478 resp.www_authenticate.type = None
479 assert 'WWW-Authenticate' not in resp.headers
480
481 def test_response_stream_mixin(self):
482 response = wrappers.Response()
483 response.stream.write('Hello ')
484 response.stream.write('World!')
485 self.assert_equal(response.response, ['Hello ', 'World!'])
486 self.assert_equal(response.get_data(), b'Hello World!')
487
488 def test_common_response_descriptors_mixin(self):
489 response = wrappers.Response()
490 response.mimetype = 'text/html'
491 self.assert_equal(response.mimetype, 'text/html')
492 self.assert_equal(response.content_type, 'text/html; charset=utf-8')
493 self.assert_equal(response.mimetype_params, {'charset': 'utf-8'})
494 response.mimetype_params['x-foo'] = 'yep'
495 del response.mimetype_params['charset']
496 self.assert_equal(response.content_type, 'text/html; x-foo=yep')
497
498 now = datetime.utcnow().replace(microsecond=0)
499
500 assert response.content_length is None
501 response.content_length = '42'
502 self.assert_equal(response.content_length, 42)
503
504 for attr in 'date', 'age', 'expires':
505 assert getattr(response, attr) is None
506 setattr(response, attr, now)
507 self.assert_equal(getattr(response, attr), now)
508
509 assert response.retry_after is None
510 response.retry_after = now
511 self.assert_equal(response.retry_after, now)
512
513 assert not response.vary
514 response.vary.add('Cookie')
515 response.vary.add('Content-Language')
516 assert 'cookie' in response.vary
517 self.assert_equal(response.vary.to_header(), 'Cookie, Content-Language')
518 response.headers['Vary'] = 'Content-Encoding'
519 self.assert_equal(response.vary.as_set(), set(['content-encoding']))
520
521 response.allow.update(['GET', 'POST'])
522 self.assert_equal(response.headers['Allow'], 'GET, POST')
523
524 response.content_language.add('en-US')
525 response.content_language.add('fr')
526 self.assert_equal(response.headers['Content-Language'], 'en-US, fr')
527
528 def test_common_request_descriptors_mixin(self):
529 request = wrappers.Request.from_values(content_type='text/html; charset=utf-8',
530 content_length='23',
531 headers={
532 'Referer': 'http://www.example.com/',
533 'Date': 'Sat, 28 Feb 2009 19:04:35 GMT',
534 'Max-Forwards': '10',
535 'Pragma': 'no-cache',
536 'Content-Encoding': 'gzip',
537 'Content-MD5': '9a3bc6dbc47a70db25b84c6e5867a072'
538 })
539
540 self.assert_equal(request.content_type, 'text/html; charset=utf-8')
541 self.assert_equal(request.mimetype, 'text/html')
542 self.assert_equal(request.mimetype_params, {'charset': 'utf-8'})
543 self.assert_equal(request.content_length, 23)
544 self.assert_equal(request.referrer, 'http://www.example.com/')
545 self.assert_equal(request.date, datetime(2009, 2, 28, 19, 4, 35))
546 self.assert_equal(request.max_forwards, 10)
547 self.assert_true('no-cache' in request.pragma)
548 self.assert_equal(request.content_encoding, 'gzip')
549 self.assert_equal(request.content_md5, '9a3bc6dbc47a70db25b84c6e5867a072')
550
551 def test_shallow_mode(self):
552 request = wrappers.Request({'QUERY_STRING': 'foo=bar'}, shallow=True)
553 self.assert_equal(request.args['foo'], 'bar')
554 self.assert_raises(RuntimeError, lambda: request.form['foo'])
555
556 def test_form_parsing_failed(self):
557 data = (
558 b'--blah\r\n'
559 )
560 data = wrappers.Request.from_values(input_stream=BytesIO(data),
561 content_length=len(data),
562 content_type='multipart/form-data; boundary=foo',
563 method='POST')
564 assert not data.files
565 assert not data.form
566
567 def test_file_closing(self):
568 data = (b'--foo\r\n'
569 b'Content-Disposition: form-data; name="foo"; filename="foo.txt"\r\n'
570 b'Content-Type: text/plain; charset=utf-8\r\n\r\n'
571 b'file contents, just the contents\r\n'
572 b'--foo--')
573 req = wrappers.Request.from_values(
574 input_stream=BytesIO(data),
575 content_length=len(data),
576 content_type='multipart/form-data; boundary=foo',
577 method='POST'
578 )
579 foo = req.files['foo']
580 self.assert_equal(foo.mimetype, 'text/plain')
581 self.assert_equal(foo.filename, 'foo.txt')
582
583 self.assert_equal(foo.closed, False)
584 req.close()
585 self.assert_equal(foo.closed, True)
586
587 def test_file_closing_with(self):
588 data = (b'--foo\r\n'
589 b'Content-Disposition: form-data; name="foo"; filename="foo.txt"\r\n'
590 b'Content-Type: text/plain; charset=utf-8\r\n\r\n'
591 b'file contents, just the contents\r\n'
592 b'--foo--')
593 req = wrappers.Request.from_values(
594 input_stream=BytesIO(data),
595 content_length=len(data),
596 content_type='multipart/form-data; boundary=foo',
597 method='POST'
598 )
599 with req:
600 foo = req.files['foo']
601 self.assert_equal(foo.mimetype, 'text/plain')
602 self.assert_equal(foo.filename, 'foo.txt')
603
604 self.assert_equal(foo.closed, True)
605
606 def test_url_charset_reflection(self):
607 req = wrappers.Request.from_values()
608 req.charset = 'utf-7'
609 self.assert_equal(req.url_charset, 'utf-7')
610
611 def test_response_streamed(self):
612 r = wrappers.Response()
613 assert not r.is_streamed
614 r = wrappers.Response("Hello World")
615 assert not r.is_streamed
616 r = wrappers.Response(["foo", "bar"])
617 assert not r.is_streamed
618 def gen():
619 if 0:
620 yield None
621 r = wrappers.Response(gen())
622 assert r.is_streamed
623
624 def test_response_iter_wrapping(self):
625 def uppercasing(iterator):
626 for item in iterator:
627 yield item.upper()
628 def generator():
629 yield 'foo'
630 yield 'bar'
631 req = wrappers.Request.from_values()
632 resp = wrappers.Response(generator())
633 del resp.headers['Content-Length']
634 resp.response = uppercasing(resp.iter_encoded())
635 actual_resp = wrappers.Response.from_app(resp, req.environ, buffered=True)
636 self.assertEqual(actual_resp.get_data(), b'FOOBAR')
637
638 def test_response_freeze(self):
639 def generate():
640 yield "foo"
641 yield "bar"
642 resp = wrappers.Response(generate())
643 resp.freeze()
644 self.assert_equal(resp.response, [b'foo', b'bar'])
645 self.assert_equal(resp.headers['content-length'], '6')
646
647 def test_other_method_payload(self):
648 data = b'Hello World'
649 req = wrappers.Request.from_values(input_stream=BytesIO(data),
650 content_length=len(data),
651 content_type='text/plain',
652 method='WHAT_THE_FUCK')
653 self.assert_equal(req.get_data(), data)
654 self.assert_is_instance(req.stream, LimitedStream)
655
656 def test_urlfication(self):
657 resp = wrappers.Response()
658 resp.headers['Location'] = u'http://üser:pässword@☃.net/påth'
659 resp.headers['Content-Location'] = u'http://☃.net/'
660 headers = resp.get_wsgi_headers(create_environ())
661 self.assert_equal(headers['location'], \
662 'http://%C3%BCser:p%C3%A4ssword@xn--n3h.net/p%C3%A5th')
663 self.assert_equal(headers['content-location'], 'http://xn--n3h.net/')
664
665 def test_new_response_iterator_behavior(self):
666 req = wrappers.Request.from_values()
667 resp = wrappers.Response(u'Hello Wörld!')
668
669 def get_content_length(resp):
670 headers = resp.get_wsgi_headers(req.environ)
671 return headers.get('content-length', type=int)
672
673 def generate_items():
674 yield "Hello "
675 yield u"Wörld!"
676
677 # werkzeug encodes when set to `data` now, which happens
678 # if a string is passed to the response object.
679 self.assert_equal(resp.response, [u'Hello Wörld!'.encode('utf-8')])
680 self.assert_equal(resp.get_data(), u'Hello Wörld!'.encode('utf-8'))
681 self.assert_equal(get_content_length(resp), 13)
682 assert not resp.is_streamed
683 assert resp.is_sequence
684
685 # try the same for manual assignment
686 resp.set_data(u'Wörd')
687 self.assert_equal(resp.response, [u'Wörd'.encode('utf-8')])
688 self.assert_equal(resp.get_data(), u'Wörd'.encode('utf-8'))
689 self.assert_equal(get_content_length(resp), 5)
690 assert not resp.is_streamed
691 assert resp.is_sequence
692
693 # automatic generator sequence conversion
694 resp.response = generate_items()
695 assert resp.is_streamed
696 assert not resp.is_sequence
697 self.assert_equal(resp.get_data(), u'Hello Wörld!'.encode('utf-8'))
698 self.assert_equal(resp.response, [b'Hello ', u'Wörld!'.encode('utf-8')])
699 assert not resp.is_streamed
700 assert resp.is_sequence
701
702 # automatic generator sequence conversion
703 resp.response = generate_items()
704 resp.implicit_sequence_conversion = False
705 assert resp.is_streamed
706 assert not resp.is_sequence
707 self.assert_raises(RuntimeError, lambda: resp.get_data())
708 resp.make_sequence()
709 self.assert_equal(resp.get_data(), u'Hello Wörld!'.encode('utf-8'))
710 self.assert_equal(resp.response, [b'Hello ', u'Wörld!'.encode('utf-8')])
711 assert not resp.is_streamed
712 assert resp.is_sequence
713
714 # stream makes it a list no matter how the conversion is set
715 for val in True, False:
716 resp.implicit_sequence_conversion = val
717 resp.response = ("foo", "bar")
718 assert resp.is_sequence
719 resp.stream.write('baz')
720 self.assert_equal(resp.response, ['foo', 'bar', 'baz'])
721
722 def test_form_data_ordering(self):
723 class MyRequest(wrappers.Request):
724 parameter_storage_class = ImmutableOrderedMultiDict
725
726 req = MyRequest.from_values('/?foo=1&bar=0&foo=3')
727 self.assert_equal(list(req.args), ['foo', 'bar'])
728 self.assert_equal(list(req.args.items(multi=True)), [
729 ('foo', '1'),
730 ('bar', '0'),
731 ('foo', '3')
732 ])
733 self.assert_is_instance(req.args, ImmutableOrderedMultiDict)
734 self.assert_is_instance(req.values, CombinedMultiDict)
735 self.assert_equal(req.values['foo'], '1')
736 self.assert_equal(req.values.getlist('foo'), ['1', '3'])
737
738 def test_storage_classes(self):
739 class MyRequest(wrappers.Request):
740 dict_storage_class = dict
741 list_storage_class = list
742 parameter_storage_class = dict
743 req = MyRequest.from_values('/?foo=baz', headers={
744 'Cookie': 'foo=bar'
745 })
746 assert type(req.cookies) is dict
747 self.assert_equal(req.cookies, {'foo': 'bar'})
748 assert type(req.access_route) is list
749
750 assert type(req.args) is dict
751 assert type(req.values) is CombinedMultiDict
752 self.assert_equal(req.values['foo'], u'baz')
753
754 req = wrappers.Request.from_values(headers={
755 'Cookie': 'foo=bar'
756 })
757 assert type(req.cookies) is ImmutableTypeConversionDict
758 self.assert_equal(req.cookies, {'foo': 'bar'})
759 assert type(req.access_route) is ImmutableList
760
761 MyRequest.list_storage_class = tuple
762 req = MyRequest.from_values()
763 assert type(req.access_route) is tuple
764
765 def test_response_headers_passthrough(self):
766 headers = wrappers.Headers()
767 resp = wrappers.Response(headers=headers)
768 assert resp.headers is headers
769
770 def test_response_304_no_content_length(self):
771 resp = wrappers.Response('Test', status=304)
772 env = create_environ()
773 assert 'content-length' not in resp.get_wsgi_headers(env)
774
775 def test_ranges(self):
776 # basic range stuff
777 req = wrappers.Request.from_values()
778 assert req.range is None
779 req = wrappers.Request.from_values(headers={'Range': 'bytes=0-499'})
780 self.assert_equal(req.range.ranges, [(0, 500)])
781
782 resp = wrappers.Response()
783 resp.content_range = req.range.make_content_range(1000)
784 self.assert_equal(resp.content_range.units, 'bytes')
785 self.assert_equal(resp.content_range.start, 0)
786 self.assert_equal(resp.content_range.stop, 500)
787 self.assert_equal(resp.content_range.length, 1000)
788 self.assert_equal(resp.headers['Content-Range'], 'bytes 0-499/1000')
789
790 resp.content_range.unset()
791 assert 'Content-Range' not in resp.headers
792
793 resp.headers['Content-Range'] = 'bytes 0-499/1000'
794 self.assert_equal(resp.content_range.units, 'bytes')
795 self.assert_equal(resp.content_range.start, 0)
796 self.assert_equal(resp.content_range.stop, 500)
797 self.assert_equal(resp.content_range.length, 1000)
798
799 def test_auto_content_length(self):
800 resp = wrappers.Response('Hello World!')
801 self.assert_equal(resp.content_length, 12)
802
803 resp = wrappers.Response(['Hello World!'])
804 assert resp.content_length is None
805 self.assert_equal(resp.get_wsgi_headers({})['Content-Length'], '12')
806
807 def test_disabled_auto_content_length(self):
808 class MyResponse(wrappers.Response):
809 automatically_set_content_length = False
810 resp = MyResponse('Hello World!')
811 self.assert_is_none(resp.content_length)
812
813 resp = MyResponse(['Hello World!'])
814 self.assert_is_none(resp.content_length)
815 self.assert_not_in('Content-Length', resp.get_wsgi_headers({}))
816
817 def test_location_header_autocorrect(self):
818 env = create_environ()
819 class MyResponse(wrappers.Response):
820 autocorrect_location_header = False
821 resp = MyResponse('Hello World!')
822 resp.headers['Location'] = '/test'
823 self.assert_equal(resp.get_wsgi_headers(env)['Location'], '/test')
824
825 resp = wrappers.Response('Hello World!')
826 resp.headers['Location'] = '/test'
827 self.assert_equal(resp.get_wsgi_headers(env)['Location'], 'http://localhost/test')
828
829 def test_modified_url_encoding(self):
830 class ModifiedRequest(wrappers.Request):
831 url_charset = 'euc-kr'
832
833 req = ModifiedRequest.from_values(u'/?foo=정상처리'.encode('euc-kr'))
834 self.assert_strict_equal(req.args['foo'], u'정상처리')
835
836 def suite():
837 suite = unittest.TestSuite()
838 suite.addTest(unittest.makeSuite(WrappersTestCase))
839 return suite
+0
-352
werkzeug/testsuite/wsgi.py less more
0 # -*- coding: utf-8 -*-
1 """
2 werkzeug.testsuite.wsgi
3 ~~~~~~~~~~~~~~~~~~~~~~~
4
5 Tests the WSGI utilities.
6
7 :copyright: (c) 2014 by Armin Ronacher.
8 :license: BSD, see LICENSE for more details.
9 """
10 import unittest
11 from os import path
12 from contextlib import closing
13
14 from werkzeug.testsuite import WerkzeugTestCase, get_temporary_directory
15
16 from werkzeug.wrappers import BaseResponse
17 from werkzeug.exceptions import BadRequest, ClientDisconnected
18 from werkzeug.test import Client, create_environ, run_wsgi_app
19 from werkzeug import wsgi
20 from werkzeug._compat import StringIO, BytesIO, NativeStringIO, to_native
21
22
23 class WSGIUtilsTestCase(WerkzeugTestCase):
24
25 def test_shareddatamiddleware_get_file_loader(self):
26 app = wsgi.SharedDataMiddleware(None, {})
27 assert callable(app.get_file_loader('foo'))
28
29 def test_shared_data_middleware(self):
30 def null_application(environ, start_response):
31 start_response('404 NOT FOUND', [('Content-Type', 'text/plain')])
32 yield b'NOT FOUND'
33
34 test_dir = get_temporary_directory()
35 with open(path.join(test_dir, to_native(u'äöü', 'utf-8')), 'w') as test_file:
36 test_file.write(u'FOUND')
37
38 app = wsgi.SharedDataMiddleware(null_application, {
39 '/': path.join(path.dirname(__file__), 'res'),
40 '/sources': path.join(path.dirname(__file__), 'res'),
41 '/pkg': ('werkzeug.debug', 'shared'),
42 '/foo': test_dir
43 })
44
45 for p in '/test.txt', '/sources/test.txt', '/foo/äöü':
46 app_iter, status, headers = run_wsgi_app(app, create_environ(p))
47 self.assert_equal(status, '200 OK')
48 with closing(app_iter) as app_iter:
49 data = b''.join(app_iter).strip()
50 self.assert_equal(data, b'FOUND')
51
52 app_iter, status, headers = run_wsgi_app(
53 app, create_environ('/pkg/debugger.js'))
54 with closing(app_iter) as app_iter:
55 contents = b''.join(app_iter)
56 self.assert_in(b'$(function() {', contents)
57
58 app_iter, status, headers = run_wsgi_app(
59 app, create_environ('/missing'))
60 self.assert_equal(status, '404 NOT FOUND')
61 self.assert_equal(b''.join(app_iter).strip(), b'NOT FOUND')
62
63
64 def test_get_host(self):
65 env = {'HTTP_X_FORWARDED_HOST': 'example.org',
66 'SERVER_NAME': 'bullshit', 'HOST_NAME': 'ignore me dammit'}
67 self.assert_equal(wsgi.get_host(env), 'example.org')
68 self.assert_equal(
69 wsgi.get_host(create_environ('/', 'http://example.org')),
70 'example.org')
71
72 def test_get_host_multiple_forwarded(self):
73 env = {'HTTP_X_FORWARDED_HOST': 'example.com, example.org',
74 'SERVER_NAME': 'bullshit', 'HOST_NAME': 'ignore me dammit'}
75 self.assert_equal(wsgi.get_host(env), 'example.com')
76 self.assert_equal(
77 wsgi.get_host(create_environ('/', 'http://example.com')),
78 'example.com')
79
80 def test_get_host_validation(self):
81 env = {'HTTP_X_FORWARDED_HOST': 'example.org',
82 'SERVER_NAME': 'bullshit', 'HOST_NAME': 'ignore me dammit'}
83 self.assert_equal(wsgi.get_host(env, trusted_hosts=['.example.org']),
84 'example.org')
85 self.assert_raises(BadRequest, wsgi.get_host, env,
86 trusted_hosts=['example.com'])
87
88 def test_responder(self):
89 def foo(environ, start_response):
90 return BaseResponse(b'Test')
91 client = Client(wsgi.responder(foo), BaseResponse)
92 response = client.get('/')
93 self.assert_equal(response.status_code, 200)
94 self.assert_equal(response.data, b'Test')
95
96 def test_pop_path_info(self):
97 original_env = {'SCRIPT_NAME': '/foo', 'PATH_INFO': '/a/b///c'}
98
99 # regular path info popping
100 def assert_tuple(script_name, path_info):
101 self.assert_equal(env.get('SCRIPT_NAME'), script_name)
102 self.assert_equal(env.get('PATH_INFO'), path_info)
103 env = original_env.copy()
104 pop = lambda: wsgi.pop_path_info(env)
105
106 assert_tuple('/foo', '/a/b///c')
107 self.assert_equal(pop(), 'a')
108 assert_tuple('/foo/a', '/b///c')
109 self.assert_equal(pop(), 'b')
110 assert_tuple('/foo/a/b', '///c')
111 self.assert_equal(pop(), 'c')
112 assert_tuple('/foo/a/b///c', '')
113 self.assert_is_none(pop())
114
115 def test_peek_path_info(self):
116 env = {
117 'SCRIPT_NAME': '/foo',
118 'PATH_INFO': '/aaa/b///c'
119 }
120
121 self.assert_equal(wsgi.peek_path_info(env), 'aaa')
122 self.assert_equal(wsgi.peek_path_info(env), 'aaa')
123 self.assert_equal(wsgi.peek_path_info(env, charset=None), b'aaa')
124 self.assert_equal(wsgi.peek_path_info(env, charset=None), b'aaa')
125
126 def test_path_info_and_script_name_fetching(self):
127 env = create_environ(u'/\N{SNOWMAN}', u'http://example.com/\N{COMET}/')
128 self.assert_equal(wsgi.get_path_info(env), u'/\N{SNOWMAN}')
129 self.assert_equal(wsgi.get_path_info(env, charset=None), u'/\N{SNOWMAN}'.encode('utf-8'))
130 self.assert_equal(wsgi.get_script_name(env), u'/\N{COMET}')
131 self.assert_equal(wsgi.get_script_name(env, charset=None), u'/\N{COMET}'.encode('utf-8'))
132
133 def test_query_string_fetching(self):
134 env = create_environ(u'/?\N{SNOWMAN}=\N{COMET}')
135 qs = wsgi.get_query_string(env)
136 self.assert_strict_equal(qs, '%E2%98%83=%E2%98%84')
137
138 def test_limited_stream(self):
139 class RaisingLimitedStream(wsgi.LimitedStream):
140 def on_exhausted(self):
141 raise BadRequest('input stream exhausted')
142
143 io = BytesIO(b'123456')
144 stream = RaisingLimitedStream(io, 3)
145 self.assert_strict_equal(stream.read(), b'123')
146 self.assert_raises(BadRequest, stream.read)
147
148 io = BytesIO(b'123456')
149 stream = RaisingLimitedStream(io, 3)
150 self.assert_strict_equal(stream.tell(), 0)
151 self.assert_strict_equal(stream.read(1), b'1')
152 self.assert_strict_equal(stream.tell(), 1)
153 self.assert_strict_equal(stream.read(1), b'2')
154 self.assert_strict_equal(stream.tell(), 2)
155 self.assert_strict_equal(stream.read(1), b'3')
156 self.assert_strict_equal(stream.tell(), 3)
157 self.assert_raises(BadRequest, stream.read)
158
159 io = BytesIO(b'123456\nabcdefg')
160 stream = wsgi.LimitedStream(io, 9)
161 self.assert_strict_equal(stream.readline(), b'123456\n')
162 self.assert_strict_equal(stream.readline(), b'ab')
163
164 io = BytesIO(b'123456\nabcdefg')
165 stream = wsgi.LimitedStream(io, 9)
166 self.assert_strict_equal(stream.readlines(), [b'123456\n', b'ab'])
167
168 io = BytesIO(b'123456\nabcdefg')
169 stream = wsgi.LimitedStream(io, 9)
170 self.assert_strict_equal(stream.readlines(2), [b'12'])
171 self.assert_strict_equal(stream.readlines(2), [b'34'])
172 self.assert_strict_equal(stream.readlines(), [b'56\n', b'ab'])
173
174 io = BytesIO(b'123456\nabcdefg')
175 stream = wsgi.LimitedStream(io, 9)
176 self.assert_strict_equal(stream.readline(100), b'123456\n')
177
178 io = BytesIO(b'123456\nabcdefg')
179 stream = wsgi.LimitedStream(io, 9)
180 self.assert_strict_equal(stream.readlines(100), [b'123456\n', b'ab'])
181
182 io = BytesIO(b'123456')
183 stream = wsgi.LimitedStream(io, 3)
184 self.assert_strict_equal(stream.read(1), b'1')
185 self.assert_strict_equal(stream.read(1), b'2')
186 self.assert_strict_equal(stream.read(), b'3')
187 self.assert_strict_equal(stream.read(), b'')
188
189 io = BytesIO(b'123456')
190 stream = wsgi.LimitedStream(io, 3)
191 self.assert_strict_equal(stream.read(-1), b'123')
192
193 io = BytesIO(b'123456')
194 stream = wsgi.LimitedStream(io, 0)
195 self.assert_strict_equal(stream.read(-1), b'')
196
197 io = StringIO(u'123456')
198 stream = wsgi.LimitedStream(io, 0)
199 self.assert_strict_equal(stream.read(-1), u'')
200
201 io = StringIO(u'123\n456\n')
202 stream = wsgi.LimitedStream(io, 8)
203 self.assert_strict_equal(list(stream), [u'123\n', u'456\n'])
204
205 def test_limited_stream_disconnection(self):
206 io = BytesIO(b'A bit of content')
207
208 # disconnect detection on out of bytes
209 stream = wsgi.LimitedStream(io, 255)
210 with self.assert_raises(ClientDisconnected):
211 stream.read()
212
213 # disconnect detection because file close
214 io = BytesIO(b'x' * 255)
215 io.close()
216 stream = wsgi.LimitedStream(io, 255)
217 with self.assert_raises(ClientDisconnected):
218 stream.read()
219
220 def test_path_info_extraction(self):
221 x = wsgi.extract_path_info('http://example.com/app', '/app/hello')
222 self.assert_equal(x, u'/hello')
223 x = wsgi.extract_path_info('http://example.com/app',
224 'https://example.com/app/hello')
225 self.assert_equal(x, u'/hello')
226 x = wsgi.extract_path_info('http://example.com/app/',
227 'https://example.com/app/hello')
228 self.assert_equal(x, u'/hello')
229 x = wsgi.extract_path_info('http://example.com/app/',
230 'https://example.com/app')
231 self.assert_equal(x, u'/')
232 x = wsgi.extract_path_info(u'http://☃.net/', u'/fööbär')
233 self.assert_equal(x, u'/fööbär')
234 x = wsgi.extract_path_info(u'http://☃.net/x', u'http://☃.net/x/fööbär')
235 self.assert_equal(x, u'/fööbär')
236
237 env = create_environ(u'/fööbär', u'http://☃.net/x/')
238 x = wsgi.extract_path_info(env, u'http://☃.net/x/fööbär')
239 self.assert_equal(x, u'/fööbär')
240
241 x = wsgi.extract_path_info('http://example.com/app/',
242 'https://example.com/a/hello')
243 self.assert_is_none(x)
244 x = wsgi.extract_path_info('http://example.com/app/',
245 'https://example.com/app/hello',
246 collapse_http_schemes=False)
247 self.assert_is_none(x)
248
249 def test_get_host_fallback(self):
250 self.assert_equal(wsgi.get_host({
251 'SERVER_NAME': 'foobar.example.com',
252 'wsgi.url_scheme': 'http',
253 'SERVER_PORT': '80'
254 }), 'foobar.example.com')
255 self.assert_equal(wsgi.get_host({
256 'SERVER_NAME': 'foobar.example.com',
257 'wsgi.url_scheme': 'http',
258 'SERVER_PORT': '81'
259 }), 'foobar.example.com:81')
260
261 def test_get_current_url_unicode(self):
262 env = create_environ()
263 env['QUERY_STRING'] = 'foo=bar&baz=blah&meh=\xcf'
264 rv = wsgi.get_current_url(env)
265 self.assert_strict_equal(rv,
266 u'http://localhost/?foo=bar&baz=blah&meh=\ufffd')
267
268 def test_multi_part_line_breaks(self):
269 data = 'abcdef\r\nghijkl\r\nmnopqrstuvwxyz\r\nABCDEFGHIJK'
270 test_stream = NativeStringIO(data)
271 lines = list(wsgi.make_line_iter(test_stream, limit=len(data),
272 buffer_size=16))
273 self.assert_equal(lines, ['abcdef\r\n', 'ghijkl\r\n',
274 'mnopqrstuvwxyz\r\n', 'ABCDEFGHIJK'])
275
276 data = 'abc\r\nThis line is broken by the buffer length.' \
277 '\r\nFoo bar baz'
278 test_stream = NativeStringIO(data)
279 lines = list(wsgi.make_line_iter(test_stream, limit=len(data),
280 buffer_size=24))
281 self.assert_equal(lines, ['abc\r\n', 'This line is broken by the '
282 'buffer length.\r\n', 'Foo bar baz'])
283
284 def test_multi_part_line_breaks_bytes(self):
285 data = b'abcdef\r\nghijkl\r\nmnopqrstuvwxyz\r\nABCDEFGHIJK'
286 test_stream = BytesIO(data)
287 lines = list(wsgi.make_line_iter(test_stream, limit=len(data),
288 buffer_size=16))
289 self.assert_equal(lines, [b'abcdef\r\n', b'ghijkl\r\n',
290 b'mnopqrstuvwxyz\r\n', b'ABCDEFGHIJK'])
291
292 data = b'abc\r\nThis line is broken by the buffer length.' \
293 b'\r\nFoo bar baz'
294 test_stream = BytesIO(data)
295 lines = list(wsgi.make_line_iter(test_stream, limit=len(data),
296 buffer_size=24))
297 self.assert_equal(lines, [b'abc\r\n', b'This line is broken by the '
298 b'buffer length.\r\n', b'Foo bar baz'])
299
300 def test_multi_part_line_breaks_problematic(self):
301 data = 'abc\rdef\r\nghi'
302 for x in range(1, 10):
303 test_stream = NativeStringIO(data)
304 lines = list(wsgi.make_line_iter(test_stream, limit=len(data),
305 buffer_size=4))
306 self.assert_equal(lines, ['abc\r', 'def\r\n', 'ghi'])
307
308 def test_iter_functions_support_iterators(self):
309 data = ['abcdef\r\nghi', 'jkl\r\nmnopqrstuvwxyz\r', '\nABCDEFGHIJK']
310 lines = list(wsgi.make_line_iter(data))
311 self.assert_equal(lines, ['abcdef\r\n', 'ghijkl\r\n',
312 'mnopqrstuvwxyz\r\n', 'ABCDEFGHIJK'])
313
314 def test_make_chunk_iter(self):
315 data = [u'abcdefXghi', u'jklXmnopqrstuvwxyzX', u'ABCDEFGHIJK']
316 rv = list(wsgi.make_chunk_iter(data, 'X'))
317 self.assert_equal(rv, [u'abcdef', u'ghijkl', u'mnopqrstuvwxyz',
318 u'ABCDEFGHIJK'])
319
320 data = u'abcdefXghijklXmnopqrstuvwxyzXABCDEFGHIJK'
321 test_stream = StringIO(data)
322 rv = list(wsgi.make_chunk_iter(test_stream, 'X', limit=len(data),
323 buffer_size=4))
324 self.assert_equal(rv, [u'abcdef', u'ghijkl', u'mnopqrstuvwxyz',
325 u'ABCDEFGHIJK'])
326
327 def test_make_chunk_iter_bytes(self):
328 data = [b'abcdefXghi', b'jklXmnopqrstuvwxyzX', b'ABCDEFGHIJK']
329 rv = list(wsgi.make_chunk_iter(data, 'X'))
330 self.assert_equal(rv, [b'abcdef', b'ghijkl', b'mnopqrstuvwxyz',
331 b'ABCDEFGHIJK'])
332
333 data = b'abcdefXghijklXmnopqrstuvwxyzXABCDEFGHIJK'
334 test_stream = BytesIO(data)
335 rv = list(wsgi.make_chunk_iter(test_stream, 'X', limit=len(data),
336 buffer_size=4))
337 self.assert_equal(rv, [b'abcdef', b'ghijkl', b'mnopqrstuvwxyz',
338 b'ABCDEFGHIJK'])
339
340 def test_lines_longer_buffer_size(self):
341 data = '1234567890\n1234567890\n'
342 for bufsize in range(1, 15):
343 lines = list(wsgi.make_line_iter(NativeStringIO(data), limit=len(data),
344 buffer_size=4))
345 self.assert_equal(lines, ['1234567890\n', '1234567890\n'])
346
347
348 def suite():
349 suite = unittest.TestSuite()
350 suite.addTest(unittest.makeSuite(WSGIUtilsTestCase))
351 return suite
22 werkzeug.urls
33 ~~~~~~~~~~~~~
44
5 This module implements various URL related functions.
5 ``werkzeug.urls`` used to provide several wrapper functions for Python 2
6 urlparse, whose main purpose were to work around the behavior of the Py2
7 stdlib and its lack of unicode support. While this was already a somewhat
8 inconvenient situation, it got even more complicated because Python 3's
9 ``urllib.parse`` actually does handle unicode properly. In other words,
10 this module would wrap two libraries with completely different behavior. So
11 now this module contains a 2-and-3-compatible backport of Python 3's
12 ``urllib.parse``, which is mostly API-compatible.
613
714 :copyright: (c) 2014 by the Werkzeug Team, see AUTHORS for more details.
815 :license: BSD, see LICENSE for more details.
916 """
17 import os
1018 import re
1119 from werkzeug._compat import text_type, PY2, to_unicode, \
1220 to_native, implements_to_string, try_coerce_native, \
3543 ['scheme', 'netloc', 'path', 'query', 'fragment']))
3644
3745
38 class _URLMixin(object):
46 class BaseURL(_URLTuple):
47 '''Superclass of :py:class:`URL` and :py:class:`BytesURL`.'''
3948 __slots__ = ()
4049
4150 def replace(self, **kwargs):
173182 """
174183 return url_parse(uri_to_iri(self))
175184
185 def get_file_location(self, pathformat=None):
186 """Returns a tuple with the location of the file in the form
187 ``(server, location)``. If the netloc is empty in the URL or
188 points to localhost, it's represented as ``None``.
189
190 The `pathformat` by default is autodetection but needs to be set
191 when working with URLs of a specific system. The supported values
192 are ``'windows'`` when working with Windows or DOS paths and
193 ``'posix'`` when working with posix paths.
194
195 If the URL does not point to to a local file, the server and location
196 are both represented as ``None``.
197
198 :param pathformat: The expected format of the path component.
199 Currently ``'windows'`` and ``'posix'`` are
200 supported. Defaults to ``None`` which is
201 autodetect.
202 """
203 if self.scheme != 'file':
204 return None, None
205
206 path = url_unquote(self.path)
207 host = self.netloc or None
208
209 if pathformat is None:
210 if os.name == 'nt':
211 pathformat = 'windows'
212 else:
213 pathformat = 'posix'
214
215 if pathformat == 'windows':
216 if path[:1] == '/' and path[1:2].isalpha() and path[2:3] in '|:':
217 path = path[1:2] + ':' + path[3:]
218 windows_share = path[:3] in ('\\' * 3, '/' * 3)
219 import ntpath
220 path = ntpath.normpath(path)
221 # Windows shared drives are represented as ``\\host\\directory``.
222 # That results in a URL like ``file://///host/directory``, and a
223 # path like ``///host/directory``. We need to special-case this
224 # because the path contains the hostname.
225 if windows_share and host is None:
226 parts = path.lstrip('\\').split('\\', 1)
227 if len(parts) == 2:
228 host, path = parts
229 else:
230 host = parts[0]
231 path = ''
232 elif pathformat == 'posix':
233 import posixpath
234 path = posixpath.normpath(path)
235 else:
236 raise TypeError('Invalid path format %s' % repr(pathformat))
237
238 if host in ('127.0.0.1', '::1', 'localhost'):
239 host = None
240
241 return host, path
242
176243 def _split_netloc(self):
177244 if self._at in self.netloc:
178245 return self.netloc.split(self._at, 1)
208275
209276
210277 @implements_to_string
211 class URL(_URLTuple, _URLMixin):
278 class URL(BaseURL):
212279 """Represents a parsed URL. This behaves like a regular tuple but
213280 also has some extra attributes that give further insight into the
214281 URL.
236303 ]))
237304 if auth:
238305 rv = '%s@%s' % (auth, rv)
239 return rv.encode('ascii')
306 return to_native(rv)
240307
241308 def encode(self, charset='utf-8', errors='replace'):
242309 """Encodes the URL to a tuple made out of bytes. The charset is
251318 )
252319
253320
254 class BytesURL(_URLTuple, _URLMixin):
321 class BytesURL(BaseURL):
255322 """Represents a parsed URL in bytes."""
256323 __slots__ = ()
257324 _at = b'@'
490557 :param charset: The target charset for the URL if the url was given as
491558 unicode string.
492559 """
493 scheme, netloc, path, qs, anchor = url_parse(to_unicode(s, charset, 'replace'))
494 path = url_quote(path, charset, safe='/%+$!*\'(),')
495 qs = url_quote_plus(qs, charset, safe=':&%=+$!*\'(),')
496 return to_native(url_unparse((scheme, netloc, path, qs, anchor)))
560 # First step is to switch to unicode processing and to convert
561 # backslashes (which are invalid in URLs anyways) to slashes. This is
562 # consistent with what Chrome does.
563 s = to_unicode(s, charset, 'replace').replace('\\', '/')
564
565 # For the specific case that we look like a malformed windows URL
566 # we want to fix this up manually:
567 if s.startswith('file://') and s[7:8].isalpha() and s[8:10] in (':/', '|/'):
568 s = 'file:///' + s[7:]
569
570 url = url_parse(s)
571 path = url_quote(url.path, charset, safe='/%+$!*\'(),')
572 qs = url_quote_plus(url.query, charset, safe=':&%=+$!*\'(),')
573 anchor = url_quote_plus(url.fragment, charset, safe=':&%=+$!*\'(),')
574 return to_native(url_unparse((url.scheme, url.encode_netloc(),
575 path, qs, anchor)))
497576
498577
499578 def uri_to_iri(uri, charset='utf-8', errors='replace'):
584663
585664 iri = url_parse(to_unicode(iri, charset, errors))
586665
587 netloc = iri.encode_netloc().decode('ascii')
666 netloc = iri.encode_netloc()
588667 path = url_quote(iri.path, charset, errors, '/:~+%')
589668 query = url_quote(iri.query, charset, errors, '%&[]:;$*()+,!?*/=')
590669 fragment = url_quote(iri.fragment, charset, errors, '=%&[]:;$()+,!?*/')
1717 """A simple user agent parser. Used by the `UserAgent`."""
1818
1919 platforms = (
20 ('cros', 'chromeos'),
2021 ('iphone|ios', 'iphone'),
2122 ('ipad', 'ipad'),
2223 (r'darwin|mac|os\s*x', 'macos'),
3132 ('sco|unix_sv', 'sco'),
3233 ('bsd', 'bsd'),
3334 ('amiga', 'amiga'),
34 ('blackberry|playbook', 'blackberry')
35 ('blackberry|playbook', 'blackberry'),
36 ('symbian','symbian')
3537 )
3638 browsers = (
3739 ('googlebot', 'google'),
4951 ('konqueror', 'konqueror'),
5052 ('k-meleon', 'kmeleon'),
5153 ('netscape', 'netscape'),
52 (r'msie|microsoft\s+internet\s+explorer', 'msie'),
54 (r'msie|microsoft\s+internet\s+explorer|trident/.+? rv:', 'msie'),
5355 ('lynx', 'lynx'),
5456 ('links', 'links'),
5557 ('seamonkey|mozilla', 'seamonkey')
106108 - `amiga`
107109 - `android`
108110 - `bsd`
111 - `chromeos`
109112 - `hpux`
110113 - `iphone`
111114 - `ipad`
206206
207207
208208 def get_content_type(mimetype, charset):
209 """Return the full content type string with charset for a mimetype.
209 """Returns the full content type string with charset for a mimetype.
210210
211211 If the mimetype represents text the charset will be appended as charset
212212 parameter, otherwise the mimetype is returned unchanged.
249249 to :func:`os.path.join`. The filename returned is an ASCII only string
250250 for maximum portability.
251251
252 On windows system the function also makes sure that the file is not
252 On windows systems the function also makes sure that the file is not
253253 named after one of the special device files.
254254
255255 >>> secure_filename("My cool movie.mov")
334334 return _entity_re.sub(handle_match, s)
335335
336336
337 def redirect(location, code=302):
338 """Return a response object (a WSGI application) that, if called,
337 def redirect(location, code=302, Response=None):
338 """Returns a response object (a WSGI application) that, if called,
339339 redirects the client to the target location. Supported codes are 301,
340340 302, 303, 305, and 307. 300 is not supported because it's not a real
341341 redirect and 304 because it's the answer for a request with a request
345345 The location can now be a unicode string that is encoded using
346346 the :func:`iri_to_uri` function.
347347
348 .. versionadded:: 0.10
349 The class used for the Response object can now be passed in.
350
348351 :param location: the location the response should redirect to.
349352 :param code: the redirect status code. defaults to 302.
350 """
351 from werkzeug.wrappers import Response
353 :param class Response: a Response class to use when instantiating a
354 response. The default is :class:`werkzeug.wrappers.Response` if
355 unspecified.
356 """
357 if Response is None:
358 from werkzeug.wrappers import Response
359
352360 display_location = escape(location)
353361 if isinstance(location, text_type):
354362 # Safe conversion is necessary here as we might redirect
367375
368376
369377 def append_slash_redirect(environ, code=301):
370 """Redirect to the same URL but with a slash appended. The behavior
378 """Redirects to the same URL but with a slash appended. The behavior
371379 of this function is undefined if the path ends with a slash already.
372380
373381 :param environ: the WSGI environment for the request that triggers
394402 `None` is returned instead.
395403 :return: imported object
396404 """
397 #XXX: py3 review needed
398 assert isinstance(import_name, string_types)
399405 # force the import name to automatically convert to strings
400 import_name = str(import_name)
406 # __import__ is not able to handle unicode strings in the fromlist
407 # if the module is a package
408 import_name = str(import_name).replace(':', '.')
401409 try:
402 if ':' in import_name:
403 module, obj = import_name.split(':', 1)
404 elif '.' in import_name:
405 module, obj = import_name.rsplit('.', 1)
410 try:
411 __import__(import_name)
412 except ImportError:
413 if '.' not in import_name:
414 raise
406415 else:
407 return __import__(import_name)
408 # __import__ is not able to handle unicode strings in the fromlist
409 # if the module is a package
410 if PY2 and isinstance(obj, unicode):
411 obj = obj.encode('utf-8')
416 return sys.modules[import_name]
417
418 module_name, obj_name = import_name.rsplit('.', 1)
412419 try:
413 return getattr(__import__(module, None, None, [obj]), obj)
414 except (ImportError, AttributeError):
420 module = __import__(module_name, None, None, [obj_name])
421 except ImportError:
415422 # support importing modules not yet set up by the parent module
416423 # (or package for that matter)
417 modname = module + '.' + obj
418 __import__(modname)
419 return sys.modules[modname]
424 module = import_string(module_name)
425
426 try:
427 return getattr(module, obj_name)
428 except AttributeError as e:
429 raise ImportError(e)
430
420431 except ImportError as e:
421432 if not silent:
422433 reraise(
426437
427438
428439 def find_modules(import_path, include_packages=False, recursive=False):
429 """Find all the modules below a package. This can be useful to
440 """Finds all the modules below a package. This can be useful to
430441 automatically import all views / controllers so that their metaclasses /
431442 function decorators have a chance to register themselves on the
432443 application.
458469
459470
460471 def validate_arguments(func, args, kwargs, drop_extra=True):
461 """Check if the function accepts the arguments and keyword arguments.
472 """Checks if the function accepts the arguments and keyword arguments.
462473 Returns a new ``(args, kwargs)`` tuple that can safely be passed to
463474 the function without causing a `TypeError` because the function signature
464475 is incompatible. If `drop_extra` is set to `True` (which is the default)
189189
190190 #: Optionally a list of hosts that is trusted by this request. By default
191191 #: all hosts are trusted which means that whatever the client sends the
192 #: host is will be accepted. This is the recommended setup as a webserver
193 #: should manually be set up to not route invalid hosts to the application.
192 #: host is will be accepted.
193 #:
194 #: This is the recommended setup as a webserver should manually be set up
195 #: to only route correct hosts to the application, and remove the
196 #: `X-Forwarded-Host` header if it is not being used (see
197 #: :func:`werkzeug.wsgi.get_host`).
194198 #:
195199 #: .. versionadded:: 0.9
196200 trusted_hosts = None
197201
198 #: Indicates weather the data descriptor should be allowed to read and
202 #: Indicates whether the data descriptor should be allowed to read and
199203 #: buffer up the input stream. By default it's enabled.
200204 #:
201205 #: .. versionadded:: 0.9
213217 # in a debug session we don't want the repr to blow up.
214218 args = []
215219 try:
216 args.append("'%s'" % self.url)
220 args.append("'%s'" % to_native(self.url, self.url_charset))
217221 args.append('[%s]' % self.method)
218222 except Exception:
219223 args.append('(invalid WSGI environ)')
549553
550554 @cached_property
551555 def url(self):
552 """The reconstructed current URL as IRI."""
556 """The reconstructed current URL as IRI.
557 See also: :attr:`trusted_hosts`.
558 """
553559 return get_current_url(self.environ,
554560 trusted_hosts=self.trusted_hosts)
555561
556562 @cached_property
557563 def base_url(self):
558 """Like :attr:`url` but without the querystring"""
564 """Like :attr:`url` but without the querystring
565 See also: :attr:`trusted_hosts`.
566 """
559567 return get_current_url(self.environ, strip_querystring=True,
560568 trusted_hosts=self.trusted_hosts)
561569
563571 def url_root(self):
564572 """The full URL root (with hostname), this is the application
565573 root as IRI.
574 See also: :attr:`trusted_hosts`.
566575 """
567576 return get_current_url(self.environ, True,
568577 trusted_hosts=self.trusted_hosts)
569578
570579 @cached_property
571580 def host_url(self):
572 """Just the host with scheme as IRI."""
581 """Just the host with scheme as IRI.
582 See also: :attr:`trusted_hosts`.
583 """
573584 return get_current_url(self.environ, host_only=True,
574585 trusted_hosts=self.trusted_hosts)
575586
576587 @cached_property
577588 def host(self):
578 """Just the host including the port if available."""
589 """Just the host including the port if available.
590 See also: :attr:`trusted_hosts`.
591 """
579592 return get_host(self.environ, trusted_hosts=self.trusted_hosts)
580593
581594 query_string = environ_property('QUERY_STRING', '', read_only=True,
582595 load_func=wsgi_get_bytes, doc=
583596 '''The URL parameters as raw bytestring.''')
584 method = environ_property('REQUEST_METHOD', 'GET', read_only=True, doc=
597 method = environ_property('REQUEST_METHOD', 'GET', read_only=True,
598 load_func=lambda x: x.upper(), doc=
585599 '''The transmission method. (For example ``'GET'`` or ``'POST'``).''')
586600
587601 @cached_property
14041418 # wsgiref.
14051419 if 'date' not in self.headers:
14061420 self.headers['Date'] = http_date()
1407 if 'content-length' not in self.headers:
1421 if self.automatically_set_content_length and 'content-length' not in self.headers:
14081422 length = self.calculate_content_length()
14091423 if length is not None:
14101424 self.headers['Content-Length'] = length
14931507 raise ValueError('I/O operation on closed file')
14941508 self.response._ensure_sequence(mutable=True)
14951509 self.response.response.append(value)
1510 self.response.headers.pop('Content-Length', None)
14961511
14971512 def writelines(self, seq):
14981513 for item in seq:
128128
129129
130130 def get_host(environ, trusted_hosts=None):
131 """Return the real host for the given WSGI environment. This takes care
132 of the `X-Forwarded-Host` header. Optionally it verifies that the host
133 is in a list of trusted hosts. If the host is not in there it will raise
134 a :exc:`~werkzeug.exceptions.SecurityError`.
131 """Return the real host for the given WSGI environment. This first checks
132 the `X-Forwarded-Host` header, then the normal `Host` header, and finally
133 the `SERVER_NAME` environment variable (using the first one it finds).
134
135 Optionally it verifies that the host is in a list of trusted hosts.
136 If the host is not in there it will raise a
137 :exc:`~werkzeug.exceptions.SecurityError`.
135138
136139 :param environ: the WSGI environment to get the host of.
137140 :param trusted_hosts: a list of trusted hosts, see :func:`host_is_trusted`
138141 for more information.
139142 """
140143 if 'HTTP_X_FORWARDED_HOST' in environ:
141 rv = environ['HTTP_X_FORWARDED_HOST'].split(',')[0].strip()
144 rv = environ['HTTP_X_FORWARDED_HOST'].split(',', 1)[0].strip()
142145 elif 'HTTP_HOST' in environ:
143146 rv = environ['HTTP_HOST']
144147 else:
474477 :param disallow: a list of :func:`~fnmatch.fnmatch` rules.
475478 :param fallback_mimetype: the fallback mimetype for unknown files.
476479 :param cache: enable or disable caching headers.
477 :Param cache_timeout: the cache timeout in seconds for the headers.
480 :param cache_timeout: the cache timeout in seconds for the headers.
478481 """
479482
480483 def __init__(self, app, exports, disallow=None, cache=True,
569572 for sep in os.sep, os.altsep:
570573 if sep and sep != '/':
571574 cleaned_path = cleaned_path.replace(sep, '/')
572 path = '/'.join([''] + [x for x in cleaned_path.split('/')
573 if x and x != '..'])
575 path = '/' + '/'.join(x for x in cleaned_path.split('/')
576 if x and x != '..')
574577 file_loader = None
575578 for search_path, loader in iteritems(self.exports):
576579 if search_path == path:
636639 if script in self.mounts:
637640 app = self.mounts[script]
638641 break
639 items = script.split('/')
640 script = '/'.join(items[:-1])
641 path_info = '/%s%s' % (items[-1], path_info)
642 script, last_item = script.rsplit('/', 1)
643 path_info = '/%s%s' % (last_item, path_info)
642644 else:
643645 app = self.mounts.get(script, self.app)
644646 original_script_name = environ.get('SCRIPT_NAME', '')