1
# -*- coding: utf-8 -*-
3
werkzeug.contrib.fixers
4
~~~~~~~~~~~~~~~~~~~~~~~
8
This module includes various helpers that fix bugs in web servers. They may
9
be necessary for some versions of a buggy web server but not others. We try
10
to stay updated with the status of the bugs as good as possible but you have
11
to make sure whether they fix the problem you encounter.
13
If you notice bugs in webservers not fixed in this module consider
16
:copyright: Copyright 2009 by the Werkzeug Team, see AUTHORS for more details.
17
:license: BSD, see LICENSE for more details.
19
from urllib import unquote
20
from werkzeug.http import parse_options_header, parse_cache_control_header, \
21
parse_set_header, dump_header
22
from werkzeug.useragents import UserAgent
23
from werkzeug.datastructures import Headers, ResponseCacheControl
26
class LighttpdCGIRootFix(object):
27
"""Wrap the application in this middleware if you are using lighttpd
28
with FastCGI or CGI and the application is mounted on the URL root.
30
:param app: the WSGI application
33
def __init__(self, app):
36
def __call__(self, environ, start_response):
37
environ['PATH_INFO'] = environ.get('SCRIPT_NAME', '') + \
38
environ.get('PATH_INFO', '')
39
environ['SCRIPT_NAME'] = ''
40
return self.app(environ, start_response)
43
class PathInfoFromRequestUriFix(object):
44
"""On windows environment variables are limited to the system charset
45
which makes it impossible to store the `PATH_INFO` variable in the
46
environment without loss of information on some systems.
48
This is for example a problem for CGI scripts on a Windows Apache.
50
This fixer works by recreating the `PATH_INFO` from `REQUEST_URI`,
51
`REQUEST_URL`, or `UNENCODED_URL` (whatever is available). Thus the
52
fix can only be applied if the webserver supports either of these
55
:param app: the WSGI application
58
def __init__(self, app):
61
def __call__(self, environ, start_response):
62
for key in 'REQUEST_URL', 'REQUEST_URI', 'UNENCODED_URL':
63
if key not in environ:
65
request_uri = unquote(environ[key])
66
script_name = unquote(environ.get('SCRIPT_NAME', ''))
67
if request_uri.startswith(script_name):
68
environ['PATH_INFO'] = request_uri[len(script_name):] \
71
return self.app(environ, start_response)
74
class ProxyFix(object):
75
"""This middleware can be applied to add HTTP proxy support to an
76
application that was not designed with HTTP proxies in mind. It
77
sets `REMOTE_ADDR`, `HTTP_HOST` from `X-Forwarded` headers.
79
Werkzeug wrappers have builtin support for this by setting the
80
:attr:`~werkzeug.BaseRequest.is_behind_proxy` attribute to `True`.
82
Do not use this middleware in non-proxy setups for security reasons.
84
The original values of `REMOTE_ADDR` and `HTTP_HOST` are stored in
85
the WSGI environment as `werkzeug.proxy_fix.orig_remote_addr` and
86
`werkzeug.proxy_fix.orig_http_host`.
88
:param app: the WSGI application
91
def __init__(self, app):
94
def __call__(self, environ, start_response):
96
forwarded_for = getter('HTTP_X_FORWARDED_FOR', '').split(',')
97
forwarded_host = getter('HTTP_X_FORWARDED_HOST', '')
99
'werkzeug.proxy_fix.orig_remote_addr': getter('REMOTE_ADDR'),
100
'werkzeug.proxy_fix.orig_http_host': getter('HTTP_HOST')
103
environ['REMOTE_ADDR'] = forwarded_for[0].strip()
105
environ['HTTP_HOST'] = forwarded_host
106
return self.app(environ, start_response)
109
class HeaderRewriterFix(object):
110
"""This middleware can remove response headers and add others. This
111
is for example useful to remove the `Date` header from responses if you
112
are using a server that adds that header, no matter if it's present or
113
not or to add `X-Powered-By` headers::
115
app = HeaderRewriterFix(app, remove_headers=['Date'],
116
add_headers=[('X-Powered-By', 'WSGI')])
118
:param app: the WSGI application
119
:param remove_headers: a sequence of header keys that should be
121
:param add_headers: a sequence of ``(key, value)`` tuples that should
125
def __init__(self, app, remove_headers=None, add_headers=None):
127
self.remove_headers = set(x.lower() for x in (remove_headers or ()))
128
self.add_headers = list(add_headers or ())
130
def __call__(self, environ, start_response):
131
def rewriting_start_response(status, headers, exc_info=None):
133
for key, value in headers:
134
if key.lower() not in self.remove_headers:
135
new_headers.append((key, value))
136
new_headers += self.add_headers
137
return start_response(status, new_headers, exc_info)
138
return self.app(environ, rewriting_start_response)
141
class InternetExplorerFix(object):
142
"""This middleware fixes a couple of bugs with Microsoft Internet
143
Explorer. Currently the following fixes are applied:
145
- removing of `Vary` headers for unsupported mimetypes which
146
causes troubles with caching. Can be disabled by passing
147
``fix_vary=False`` to the constructor.
148
see: http://support.microsoft.com/kb/824847/en-us
150
- removes offending headers to work around caching bugs in
151
Internet Explorer if `Content-Disposition` is set. Can be
152
disabled by passing ``fix_attach=False`` to the constructor.
154
If it does not detect affected Internet Explorer versions it won't touch
155
the request / response.
158
# This code was inspired by Django fixers for the same bugs. The
159
# fix_vary and fix_attach fixers were originally implemented in Django
160
# by Michael Axiak and is available as part of the Django project:
161
# http://code.djangoproject.com/ticket/4148
163
def __init__(self, app, fix_vary=True, fix_attach=True):
165
self.fix_vary = fix_vary
166
self.fix_attach = fix_attach
168
def fix_headers(self, environ, headers, status=None):
170
header = headers.get('content-type', '')
171
mimetype, options = parse_options_header(header)
172
if mimetype not in ('text/html', 'text/plain', 'text/sgml'):
173
headers.pop('vary', None)
175
if self.fix_attach and 'content-disposition' in headers:
176
pragma = parse_set_header(headers.get('pragma', ''))
177
pragma.discard('no-cache')
178
header = pragma.to_header()
180
headers.pop('pragma', '')
182
headers['Pragma'] = header
183
header = headers.get('cache-control', '')
185
cc = parse_cache_control_header(header,
186
cls=ResponseCacheControl)
189
header = cc.to_header()
191
headers.pop('cache-control', '')
193
headers['Cache-Control'] = header
195
def run_fixed(self, environ, start_response):
196
def fixing_start_response(status, headers, exc_info=None):
197
self.fix_headers(environ, Headers.linked(headers), status)
198
return start_response(status, headers, exc_info)
199
return self.app(environ, fixing_start_response)
201
def __call__(self, environ, start_response):
202
ua = UserAgent(environ)
203
if ua.browser != 'msie':
204
return self.app(environ, start_response)
205
return self.run_fixed(environ, start_response)