123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254 |
- # -*- coding: utf-8 -*-
- """
- werkzeug.contrib.fixers
- ~~~~~~~~~~~~~~~~~~~~~~~
- .. versionadded:: 0.5
- This module includes various helpers that fix bugs in web servers. They may
- be necessary for some versions of a buggy web server but not others. We try
- to stay updated with the status of the bugs as good as possible but you have
- to make sure whether they fix the problem you encounter.
- If you notice bugs in webservers not fixed in this module consider
- contributing a patch.
- :copyright: Copyright 2009 by the Werkzeug Team, see AUTHORS for more details.
- :license: BSD, see LICENSE for more details.
- """
- try:
- from urllib import unquote
- except ImportError:
- from urllib.parse import unquote
- from werkzeug.http import parse_options_header, parse_cache_control_header, \
- parse_set_header
- from werkzeug.useragents import UserAgent
- from werkzeug.datastructures import Headers, ResponseCacheControl
- class CGIRootFix(object):
- """Wrap the application in this middleware if you are using FastCGI or CGI
- and you have problems with your app root being set to the cgi script's path
- instead of the path users are going to visit
- .. versionchanged:: 0.9
- Added `app_root` parameter and renamed from `LighttpdCGIRootFix`.
- :param app: the WSGI application
- :param app_root: Defaulting to ``'/'``, you can set this to something else
- if your app is mounted somewhere else.
- """
- def __init__(self, app, app_root='/'):
- self.app = app
- self.app_root = app_root
- def __call__(self, environ, start_response):
- # only set PATH_INFO for older versions of Lighty or if no
- # server software is provided. That's because the test was
- # added in newer Werkzeug versions and we don't want to break
- # people's code if they are using this fixer in a test that
- # does not set the SERVER_SOFTWARE key.
- if 'SERVER_SOFTWARE' not in environ or \
- environ['SERVER_SOFTWARE'] < 'lighttpd/1.4.28':
- environ['PATH_INFO'] = environ.get('SCRIPT_NAME', '') + \
- environ.get('PATH_INFO', '')
- environ['SCRIPT_NAME'] = self.app_root.strip('/')
- return self.app(environ, start_response)
- # backwards compatibility
- LighttpdCGIRootFix = CGIRootFix
- class PathInfoFromRequestUriFix(object):
- """On windows environment variables are limited to the system charset
- which makes it impossible to store the `PATH_INFO` variable in the
- environment without loss of information on some systems.
- This is for example a problem for CGI scripts on a Windows Apache.
- This fixer works by recreating the `PATH_INFO` from `REQUEST_URI`,
- `REQUEST_URL`, or `UNENCODED_URL` (whatever is available). Thus the
- fix can only be applied if the webserver supports either of these
- variables.
- :param app: the WSGI application
- """
- def __init__(self, app):
- self.app = app
- def __call__(self, environ, start_response):
- for key in 'REQUEST_URL', 'REQUEST_URI', 'UNENCODED_URL':
- if key not in environ:
- continue
- request_uri = unquote(environ[key])
- script_name = unquote(environ.get('SCRIPT_NAME', ''))
- if request_uri.startswith(script_name):
- environ['PATH_INFO'] = request_uri[len(script_name):] \
- .split('?', 1)[0]
- break
- return self.app(environ, start_response)
- class ProxyFix(object):
- """This middleware can be applied to add HTTP proxy support to an
- application that was not designed with HTTP proxies in mind. It
- sets `REMOTE_ADDR`, `HTTP_HOST` from `X-Forwarded` headers. While
- Werkzeug-based applications already can use
- :py:func:`werkzeug.wsgi.get_host` to retrieve the current host even if
- behind proxy setups, this middleware can be used for applications which
- access the WSGI environment directly.
- If you have more than one proxy server in front of your app, set
- `num_proxies` accordingly.
- Do not use this middleware in non-proxy setups for security reasons.
- The original values of `REMOTE_ADDR` and `HTTP_HOST` are stored in
- the WSGI environment as `werkzeug.proxy_fix.orig_remote_addr` and
- `werkzeug.proxy_fix.orig_http_host`.
- :param app: the WSGI application
- :param num_proxies: the number of proxy servers in front of the app.
- """
- def __init__(self, app, num_proxies=1):
- self.app = app
- self.num_proxies = num_proxies
- def get_remote_addr(self, forwarded_for):
- """Selects the new remote addr from the given list of ips in
- X-Forwarded-For. By default it picks the one that the `num_proxies`
- proxy server provides. Before 0.9 it would always pick the first.
- .. versionadded:: 0.8
- """
- if len(forwarded_for) >= self.num_proxies:
- return forwarded_for[-self.num_proxies]
- def __call__(self, environ, start_response):
- getter = environ.get
- forwarded_proto = getter('HTTP_X_FORWARDED_PROTO', '')
- forwarded_for = getter('HTTP_X_FORWARDED_FOR', '').split(',')
- forwarded_host = getter('HTTP_X_FORWARDED_HOST', '')
- environ.update({
- 'werkzeug.proxy_fix.orig_wsgi_url_scheme': getter('wsgi.url_scheme'),
- 'werkzeug.proxy_fix.orig_remote_addr': getter('REMOTE_ADDR'),
- 'werkzeug.proxy_fix.orig_http_host': getter('HTTP_HOST')
- })
- forwarded_for = [x for x in [x.strip() for x in forwarded_for] if x]
- remote_addr = self.get_remote_addr(forwarded_for)
- if remote_addr is not None:
- environ['REMOTE_ADDR'] = remote_addr
- if forwarded_host:
- environ['HTTP_HOST'] = forwarded_host
- if forwarded_proto:
- environ['wsgi.url_scheme'] = forwarded_proto
- return self.app(environ, start_response)
- class HeaderRewriterFix(object):
- """This middleware can remove response headers and add others. This
- is for example useful to remove the `Date` header from responses if you
- are using a server that adds that header, no matter if it's present or
- not or to add `X-Powered-By` headers::
- app = HeaderRewriterFix(app, remove_headers=['Date'],
- add_headers=[('X-Powered-By', 'WSGI')])
- :param app: the WSGI application
- :param remove_headers: a sequence of header keys that should be
- removed.
- :param add_headers: a sequence of ``(key, value)`` tuples that should
- be added.
- """
- def __init__(self, app, remove_headers=None, add_headers=None):
- self.app = app
- self.remove_headers = set(x.lower() for x in (remove_headers or ()))
- self.add_headers = list(add_headers or ())
- def __call__(self, environ, start_response):
- def rewriting_start_response(status, headers, exc_info=None):
- new_headers = []
- for key, value in headers:
- if key.lower() not in self.remove_headers:
- new_headers.append((key, value))
- new_headers += self.add_headers
- return start_response(status, new_headers, exc_info)
- return self.app(environ, rewriting_start_response)
- class InternetExplorerFix(object):
- """This middleware fixes a couple of bugs with Microsoft Internet
- Explorer. Currently the following fixes are applied:
- - removing of `Vary` headers for unsupported mimetypes which
- causes troubles with caching. Can be disabled by passing
- ``fix_vary=False`` to the constructor.
- see: http://support.microsoft.com/kb/824847/en-us
- - removes offending headers to work around caching bugs in
- Internet Explorer if `Content-Disposition` is set. Can be
- disabled by passing ``fix_attach=False`` to the constructor.
- If it does not detect affected Internet Explorer versions it won't touch
- the request / response.
- """
- # This code was inspired by Django fixers for the same bugs. The
- # fix_vary and fix_attach fixers were originally implemented in Django
- # by Michael Axiak and is available as part of the Django project:
- # http://code.djangoproject.com/ticket/4148
- def __init__(self, app, fix_vary=True, fix_attach=True):
- self.app = app
- self.fix_vary = fix_vary
- self.fix_attach = fix_attach
- def fix_headers(self, environ, headers, status=None):
- if self.fix_vary:
- header = headers.get('content-type', '')
- mimetype, options = parse_options_header(header)
- if mimetype not in ('text/html', 'text/plain', 'text/sgml'):
- headers.pop('vary', None)
- if self.fix_attach and 'content-disposition' in headers:
- pragma = parse_set_header(headers.get('pragma', ''))
- pragma.discard('no-cache')
- header = pragma.to_header()
- if not header:
- headers.pop('pragma', '')
- else:
- headers['Pragma'] = header
- header = headers.get('cache-control', '')
- if header:
- cc = parse_cache_control_header(header,
- cls=ResponseCacheControl)
- cc.no_cache = None
- cc.no_store = False
- header = cc.to_header()
- if not header:
- headers.pop('cache-control', '')
- else:
- headers['Cache-Control'] = header
- def run_fixed(self, environ, start_response):
- def fixing_start_response(status, headers, exc_info=None):
- headers = Headers(headers)
- self.fix_headers(environ, headers, status)
- return start_response(status, headers.to_wsgi_list(), exc_info)
- return self.app(environ, fixing_start_response)
- def __call__(self, environ, start_response):
- ua = UserAgent(environ)
- if ua.browser != 'msie':
- return self.app(environ, start_response)
- return self.run_fixed(environ, start_response)
|