Back to index

moin  1.9.0~rc2
fixers.py
Go to the documentation of this file.
00001 # -*- coding: utf-8 -*-
00002 """
00003     werkzeug.contrib.fixers
00004     ~~~~~~~~~~~~~~~~~~~~~~~
00005 
00006     .. versionadded:: 0.5
00007 
00008     This module includes various helpers that fix bugs in web servers.  They may
00009     be necessary for some versions of a buggy web server but not others.  We try
00010     to stay updated with the status of the bugs as good as possible but you have
00011     to make sure whether they fix the problem you encounter.
00012 
00013     If you notice bugs in webservers not fixed in this module consider
00014     contributing a patch.
00015 
00016     :copyright: Copyright 2009 by the Werkzeug Team, see AUTHORS for more details.
00017     :license: BSD, see LICENSE for more details.
00018 """
00019 from urllib import unquote
00020 from werkzeug.http import parse_options_header, parse_cache_control_header, \
00021      parse_set_header, dump_header
00022 from werkzeug.useragents import UserAgent
00023 from werkzeug.datastructures import Headers, ResponseCacheControl
00024 
00025 
00026 class LighttpdCGIRootFix(object):
00027     """Wrap the application in this middleware if you are using lighttpd
00028     with FastCGI or CGI and the application is mounted on the URL root.
00029 
00030     :param app: the WSGI application
00031     """
00032 
00033     def __init__(self, app):
00034         self.app = app
00035 
00036     def __call__(self, environ, start_response):
00037         environ['PATH_INFO'] = environ.get('SCRIPT_NAME', '') + \
00038                                environ.get('PATH_INFO', '')
00039         environ['SCRIPT_NAME'] = ''
00040         return self.app(environ, start_response)
00041 
00042 
00043 class PathInfoFromRequestUriFix(object):
00044     """On windows environment variables are limited to the system charset
00045     which makes it impossible to store the `PATH_INFO` variable in the
00046     environment without loss of information on some systems.
00047 
00048     This is for example a problem for CGI scripts on a Windows Apache.
00049 
00050     This fixer works by recreating the `PATH_INFO` from `REQUEST_URI`,
00051     `REQUEST_URL`, or `UNENCODED_URL` (whatever is available).  Thus the
00052     fix can only be applied if the webserver supports either of these
00053     variables.
00054 
00055     :param app: the WSGI application
00056     """
00057 
00058     def __init__(self, app):
00059         self.app = app
00060 
00061     def __call__(self, environ, start_response):
00062         for key in 'REQUEST_URL', 'REQUEST_URI', 'UNENCODED_URL':
00063             if key not in environ:
00064                 continue
00065             request_uri = unquote(environ[key])
00066             script_name = unquote(environ.get('SCRIPT_NAME', ''))
00067             if request_uri.startswith(script_name):
00068                 environ['PATH_INFO'] = request_uri[len(script_name):] \
00069                     .split('?', 1)[0]
00070                 break
00071         return self.app(environ, start_response)
00072 
00073 
00074 class ProxyFix(object):
00075     """This middleware can be applied to add HTTP proxy support to an
00076     application that was not designed with HTTP proxies in mind.  It
00077     sets `REMOTE_ADDR`, `HTTP_HOST` from `X-Forwarded` headers.
00078 
00079     Werkzeug wrappers have builtin support for this by setting the
00080     :attr:`~werkzeug.BaseRequest.is_behind_proxy` attribute to `True`.
00081 
00082     Do not use this middleware in non-proxy setups for security reasons.
00083 
00084     The original values of `REMOTE_ADDR` and `HTTP_HOST` are stored in
00085     the WSGI environment as `werkzeug.proxy_fix.orig_remote_addr` and
00086     `werkzeug.proxy_fix.orig_http_host`.
00087 
00088     :param app: the WSGI application
00089     """
00090 
00091     def __init__(self, app):
00092         self.app = app
00093 
00094     def __call__(self, environ, start_response):
00095         getter = environ.get
00096         forwarded_for = getter('HTTP_X_FORWARDED_FOR', '').split(',')
00097         forwarded_host = getter('HTTP_X_FORWARDED_HOST', '')
00098         environ.update({
00099             'werkzeug.proxy_fix.orig_remote_addr':  getter('REMOTE_ADDR'),
00100             'werkzeug.proxy_fix.orig_http_host':    getter('HTTP_HOST')
00101         })
00102         if forwarded_for:
00103             environ['REMOTE_ADDR'] = forwarded_for[0].strip()
00104         if forwarded_host:
00105             environ['HTTP_HOST'] = forwarded_host
00106         return self.app(environ, start_response)
00107 
00108 
00109 class HeaderRewriterFix(object):
00110     """This middleware can remove response headers and add others.  This
00111     is for example useful to remove the `Date` header from responses if you
00112     are using a server that adds that header, no matter if it's present or
00113     not or to add `X-Powered-By` headers::
00114 
00115         app = HeaderRewriterFix(app, remove_headers=['Date'],
00116                                 add_headers=[('X-Powered-By', 'WSGI')])
00117 
00118     :param app: the WSGI application
00119     :param remove_headers: a sequence of header keys that should be
00120                            removed.
00121     :param add_headers: a sequence of ``(key, value)`` tuples that should
00122                         be added.
00123     """
00124 
00125     def __init__(self, app, remove_headers=None, add_headers=None):
00126         self.app = app
00127         self.remove_headers = set(x.lower() for x in (remove_headers or ()))
00128         self.add_headers = list(add_headers or ())
00129 
00130     def __call__(self, environ, start_response):
00131         def rewriting_start_response(status, headers, exc_info=None):
00132             new_headers = []
00133             for key, value in headers:
00134                 if key.lower() not in self.remove_headers:
00135                     new_headers.append((key, value))
00136             new_headers += self.add_headers
00137             return start_response(status, new_headers, exc_info)
00138         return self.app(environ, rewriting_start_response)
00139 
00140 
00141 class InternetExplorerFix(object):
00142     """This middleware fixes a couple of bugs with Microsoft Internet
00143     Explorer.  Currently the following fixes are applied:
00144 
00145     -   removing of `Vary` headers for unsupported mimetypes which
00146         causes troubles with caching.  Can be disabled by passing
00147         ``fix_vary=False`` to the constructor.
00148         see: http://support.microsoft.com/kb/824847/en-us
00149 
00150     -   removes offending headers to work around caching bugs in
00151         Internet Explorer if `Content-Disposition` is set.  Can be
00152         disabled by passing ``fix_attach=False`` to the constructor.
00153 
00154     If it does not detect affected Internet Explorer versions it won't touch
00155     the request / response.
00156     """
00157 
00158     # This code was inspired by Django fixers for the same bugs.  The
00159     # fix_vary and fix_attach fixers were originally implemented in Django
00160     # by Michael Axiak and is available as part of the Django project:
00161     #     http://code.djangoproject.com/ticket/4148
00162 
00163     def __init__(self, app, fix_vary=True, fix_attach=True):
00164         self.app = app
00165         self.fix_vary = fix_vary
00166         self.fix_attach = fix_attach
00167 
00168     def fix_headers(self, environ, headers, status=None):
00169         if self.fix_vary:
00170             header = headers.get('content-type', '')
00171             mimetype, options = parse_options_header(header)
00172             if mimetype not in ('text/html', 'text/plain', 'text/sgml'):
00173                 headers.pop('vary', None)
00174 
00175         if self.fix_attach and 'content-disposition' in headers:
00176             pragma = parse_set_header(headers.get('pragma', ''))
00177             pragma.discard('no-cache')
00178             header = pragma.to_header()
00179             if not header:
00180                 headers.pop('pragma', '')
00181             else:
00182                 headers['Pragma'] = header
00183             header = headers.get('cache-control', '')
00184             if header:
00185                 cc = parse_cache_control_header(header,
00186                                                 cls=ResponseCacheControl)
00187                 cc.no_cache = None
00188                 cc.no_store = False
00189                 header = cc.to_header()
00190                 if not header:
00191                     headers.pop('cache-control', '')
00192                 else:
00193                     headers['Cache-Control'] = header
00194 
00195     def run_fixed(self, environ, start_response):
00196         def fixing_start_response(status, headers, exc_info=None):
00197             self.fix_headers(environ, Headers.linked(headers), status)
00198             return start_response(status, headers, exc_info)
00199         return self.app(environ, fixing_start_response)
00200 
00201     def __call__(self, environ, start_response):
00202         ua = UserAgent(environ)
00203         if ua.browser != 'msie':
00204             return self.app(environ, start_response)
00205         return self.run_fixed(environ, start_response)