addons-server/apps/amo/middleware.py

201 строка
7.0 KiB
Python

"""
Borrowed from: http://code.google.com/p/django-localeurl
Note: didn't make sense to use localeurl since we need to capture app as well
"""
import contextlib
import urllib
from django.conf import settings
from django.contrib.sessions.middleware import SessionMiddleware
from django.core.urlresolvers import is_valid_path
from django.http import (Http404, HttpResponseRedirect,
HttpResponsePermanentRedirect)
from django.middleware import common
from django.utils.cache import patch_vary_headers, patch_cache_control
from django.utils.encoding import iri_to_uri, smart_str
import MySQLdb as mysql
import tower
import jingo
import amo
from . import urlresolvers
from .helpers import urlparams
class LocaleAndAppURLMiddleware(object):
"""
1. search for locale first
2. see if there are acceptable apps
3. save those matched parameters in the request
4. strip them from the URL so we can do stuff
"""
def process_request(self, request):
# Find locale, app
prefixer = urlresolvers.Prefixer(request)
if settings.DEBUG:
redirect_type = HttpResponseRedirect
else:
redirect_type = HttpResponsePermanentRedirect
urlresolvers.set_url_prefix(prefixer)
full_path = prefixer.fix(prefixer.shortened_path)
# In mkt, don't vary headers on User-Agent.
with_app = not getattr(settings, 'MARKETPLACE', False)
if (prefixer.app == amo.MOBILE.short and
request.path.rstrip('/').endswith('/' + amo.MOBILE.short)):
# TODO: Eventually put MOBILE in RETIRED_APPS, but not yet.
return redirect_type(request.path.replace('/mobile', '/android'))
if 'lang' in request.GET:
# Blank out the locale so that we can set a new one. Remove lang
# from query params so we don't have an infinite loop.
prefixer.locale = ''
new_path = prefixer.fix(prefixer.shortened_path)
query = dict((smart_str(k), request.GET[k]) for k in request.GET)
query.pop('lang')
return redirect_type(urlparams(new_path, **query))
if full_path != request.path:
query_string = request.META.get('QUERY_STRING', '')
full_path = urllib.quote(full_path.encode('utf-8'))
if query_string:
full_path = "%s?%s" % (full_path, query_string)
response = redirect_type(full_path)
# Cache the redirect for a year.
if not settings.DEBUG:
patch_cache_control(response, max_age=60 * 60 * 24 * 365)
# Vary on Accept-Language or User-Agent if we changed the locale or
# app.
old_app = prefixer.app
old_locale = prefixer.locale
new_locale, new_app, _ = prefixer.split_path(full_path)
if old_locale != new_locale:
patch_vary_headers(response, ['Accept-Language'])
if with_app and old_app != new_app:
patch_vary_headers(response, ['User-Agent'])
return response
request.path_info = '/' + prefixer.shortened_path
tower.activate(prefixer.locale)
request.APP = amo.APPS.get(prefixer.app, amo.FIREFOX)
request.LANG = prefixer.locale
class NoVarySessionMiddleware(SessionMiddleware):
"""
SessionMiddleware sets Vary: Cookie anytime request.session is accessed.
request.session is accessed indirectly anytime request.user is touched.
We always touch request.user to see if the user is authenticated, so every
request would be sending vary, so we'd get no caching.
We skip the cache in Zeus if someone has an AMOv3 cookie, so varying on
Cookie at this level only hurts us.
"""
def process_request(self, request):
if not getattr(request, 'API', False):
super(NoVarySessionMiddleware, self).process_request(request)
def process_response(self, request, response):
if settings.READ_ONLY:
return response
# Let SessionMiddleware do its processing but prevent it from changing
# the Vary header.
vary = None
if hasattr(response, 'get'):
vary = response.get('Vary', None)
new_response = (super(NoVarySessionMiddleware, self)
.process_response(request, response))
if vary:
new_response['Vary'] = vary
else:
del new_response['Vary']
return new_response
class RemoveSlashMiddleware(object):
"""
Middleware that tries to remove a trailing slash if there was a 404.
If the response is a 404 because url resolution failed, we'll look for a
better url without a trailing slash.
"""
def process_response(self, request, response):
if (response.status_code == 404
and request.path_info.endswith('/')
and not is_valid_path(request.path_info)
and is_valid_path(request.path_info[:-1])):
# Use request.path because we munged app/locale in path_info.
newurl = request.path[:-1]
if request.GET:
with safe_query_string(request):
newurl += '?' + request.META.get('QUERY_STRING', '')
return HttpResponsePermanentRedirect(newurl)
else:
return response
@contextlib.contextmanager
def safe_query_string(request):
"""
Turn the QUERY_STRING into a unicode- and ascii-safe string.
We need unicode so it can be combined with a reversed URL, but it has to be
ascii to go in a Location header. iri_to_uri seems like a good compromise.
"""
qs = request.META.get('QUERY_STRING', '')
try:
request.META['QUERY_STRING'] = iri_to_uri(qs)
yield
finally:
request.META['QUERY_STRING'] = qs
class CommonMiddleware(common.CommonMiddleware):
def process_request(self, request):
with safe_query_string(request):
return super(CommonMiddleware, self).process_request(request)
class ReadOnlyMiddleware(object):
def process_request(self, request):
if request.method == 'POST':
return jingo.render(request, 'amo/read-only.html', status=503)
def process_exception(self, request, exception):
if isinstance(exception, mysql.OperationalError):
return jingo.render(request, 'amo/read-only.html', status=503)
class ViewMiddleware(object):
def get_name(self, view_func):
# Find a function name or used the class based view class name.
if not hasattr(view_func, '__name__'):
name = view_func.__class__.__name__
else:
name = view_func.__name__
return '%s.%s' % (view_func.__module__, name)
class NoAddonsMiddleware(ViewMiddleware):
"""
If enabled will try and stop any requests to addons by 404'ing them.
Here there be dragons. Fortunately this is temporary right?
"""
def process_view(self, request, view_func, view_args, view_kwargs):
name = self.get_name(view_func)
if name.startswith(settings.NO_ADDONS_MODULES):
raise Http404