Code

Ticket #7930: handle-force-script-name.diff

File handle-force-script-name.diff, 10.8 KB (added by nkryptic, 15 months ago)
Line 
1diff --git a/django/contrib/staticfiles/handlers.py b/django/contrib/staticfiles/handlers.py
2index 5174586..f041f74 100644
3--- a/django/contrib/staticfiles/handlers.py
4+++ b/django/contrib/staticfiles/handlers.py
5@@ -7,12 +7,12 @@ except ImportError:     # Python 2
6 
7 from django.conf import settings
8 from django.core.handlers.base import get_path_info
9-from django.core.handlers.wsgi import WSGIHandler
10+from django.core.handlers.wsgi import WSGIHandler, UrlPrefixAwareMixin
11 
12 from django.contrib.staticfiles import utils
13 from django.contrib.staticfiles.views import serve
14 
15-class StaticFilesHandler(WSGIHandler):
16+class StaticFilesHandler(UrlPrefixAwareMixin, WSGIHandler):
17     """
18     WSGI middleware that intercepts calls to the static files directory, as
19     defined by the STATIC_URL setting, and serves those files.
20@@ -31,7 +31,7 @@ class StaticFilesHandler(WSGIHandler):
21 
22     def get_base_url(self):
23         utils.check_settings()
24-        return settings.STATIC_URL
25+        return self.fix_path(settings.STATIC_URL)
26 
27     def _should_handle(self, path):
28         """
29@@ -53,12 +53,13 @@ class StaticFilesHandler(WSGIHandler):
30         """
31         Actually serves the request path.
32         """
33-        return serve(request, self.file_path(request.path), insecure=True)
34+        path = self.fix_path(request.path)
35+        return serve(request, self.file_path(path), insecure=True)
36 
37     def get_response(self, request):
38         from django.http import Http404
39 
40-        if self._should_handle(request.path):
41+        if self._should_handle(self.fix_path(request.path)):
42             try:
43                 return self.serve(request)
44             except Http404 as e:
45diff --git a/django/core/handlers/wsgi.py b/django/core/handlers/wsgi.py
46index a9fa094..c25249c 100644
47--- a/django/core/handlers/wsgi.py
48+++ b/django/core/handlers/wsgi.py
49@@ -266,3 +266,61 @@ class WSGIHandler(base.BaseHandler):
50             response_headers.append((str('Set-Cookie'), str(c.output(header=''))))
51         start_response(force_str(status), response_headers)
52         return response
53+
54+
55+class UrlPrefixAwareMixin(object):
56+    _url_prefix = None
57+    _url_prefix_fetched = False
58+   
59+    @property
60+    def url_prefix(self):
61+        """
62+        Get the FORCE_SCRIPT_NAME from settings or None
63+        """
64+        if not self._url_prefix_fetched:
65+            from django.conf import settings
66+            self._url_prefix = getattr(settings, 'FORCE_SCRIPT_NAME', None)
67+            self._url_prefix_fetched = True
68+        return self._url_prefix
69+
70+    def fix_path(self, path):
71+        """
72+        Strips FORCE_SCRIPT_NAME from the front of the url
73+        """
74+        if self.url_prefix and path.startswith(self.url_prefix):
75+            path = path[len(self.url_prefix):]
76+        return path
77+   
78+    def is_prefixed(self, path):
79+        return not(self.url_prefix) or path.startswith(self.url_prefix)
80+
81+
82+class UrlPrefixAwareHandler(UrlPrefixAwareMixin, WSGIHandler):
83+    """
84+    WSGI middleware that does nothing if FORCE_SCRIPT_NAME is not defined.
85+   
86+    When FORCE_SCRIPT_NAME is defined:
87+      * this middleware will strip it from PATH_INFO in the environ before
88+        passing environ to the next handler
89+      * requests that do not begin with FORCE_SCRIPT_NAME will receive a
90+        400 Bad Request response
91+    """
92+    def __init__(self, application):
93+        self.application = application
94+        super(UrlPrefixAwareHandler, self).__init__()
95+
96+    def get_response(self, request):
97+        logger.warning('Bad Request (Prefixed url required when using FORCE_SCRIPT_NAME)',
98+            extra={
99+                'status_code': 400,
100+            }
101+        )
102+        return http.HttpResponseBadRequest()
103+
104+    def __call__(self, environ, start_response):
105+        if self.url_prefix:
106+            path_info = base.get_path_info(environ)
107+            if not self.is_prefixed(path_info):
108+                return super(UrlPrefixAwareHandler, self).__call__(environ, start_response)
109+            environ['PATH_INFO'] = self.fix_path(path_info)
110+        return self.application(environ, start_response)
111diff --git a/django/core/management/commands/runserver.py b/django/core/management/commands/runserver.py
112index 391e0b4..a4f71be 100644
113--- a/django/core/management/commands/runserver.py
114+++ b/django/core/management/commands/runserver.py
115@@ -7,6 +7,7 @@ import socket
116 
117 from django.core.management.base import BaseCommand, CommandError
118 from django.core.servers.basehttp import run, WSGIServerException, get_internal_wsgi_application
119+from django.core.handlers.wsgi import UrlPrefixAwareHandler
120 from django.utils import autoreload
121 
122 naiveip_re = re.compile(r"""^(?:
123@@ -110,6 +111,7 @@ class Command(BaseCommand):
124 
125         try:
126             handler = self.get_handler(*args, **options)
127+            handler = UrlPrefixAwareHandler(handler)
128             run(self.addr, int(self.port), handler,
129                 ipv6=self.use_ipv6, threading=threading)
130         except WSGIServerException as e:
131diff --git a/django/test/signals.py b/django/test/signals.py
132index a96bdff..3e8caf2 100644
133--- a/django/test/signals.py
134+++ b/django/test/signals.py
135@@ -79,3 +79,9 @@ def file_storage_changed(**kwargs):
136     if kwargs['setting'] in ('MEDIA_ROOT', 'DEFAULT_FILE_STORAGE'):
137         from django.core.files.storage import default_storage
138         default_storage._wrapped = empty
139+
140+@receiver(setting_changed)
141+def fix_script_prefix(**kwargs):
142+    if kwargs['setting'] == 'FORCE_SCRIPT_NAME':
143+        from django.core.urlresolvers import set_script_prefix
144+        set_script_prefix('/')
145\ No newline at end of file
146diff --git a/django/test/testcases.py b/django/test/testcases.py
147index c311540..12b2151 100644
148--- a/django/test/testcases.py
149+++ b/django/test/testcases.py
150@@ -17,6 +17,7 @@ import threading
151 import errno
152 
153 from django.conf import settings
154+from django.core.handlers.wsgi import UrlPrefixAwareHandler
155 from django.contrib.staticfiles.handlers import StaticFilesHandler
156 from django.core import mail
157 from django.core.exceptions import ValidationError, ImproperlyConfigured
158@@ -1014,10 +1015,11 @@ class _MediaFilesHandler(StaticFilesHandler):
159         return settings.MEDIA_ROOT
160 
161     def get_base_url(self):
162-        return settings.MEDIA_URL
163+        return self.fix_path(settings.MEDIA_URL)
164 
165     def serve(self, request):
166-        relative_url = request.path[len(self.base_url[2]):]
167+        path = self.fix_path(request.path)
168+        relative_url = path[len(self.base_url[2]):]
169         return serve(request, relative_url, document_root=self.get_base_dir())
170 
171 
172@@ -1049,6 +1051,7 @@ class LiveServerThread(threading.Thread):
173         try:
174             # Create the handler for serving static and media files
175             handler = StaticFilesHandler(_MediaFilesHandler(WSGIHandler()))
176+            handler = UrlPrefixAwareHandler(handler)
177 
178             # Go through the list of possible ports, hoping that we can find
179             # one that is free to use for the WSGI server.
180diff --git a/tests/regressiontests/handlers/tests.py b/tests/regressiontests/handlers/tests.py
181index 3557a63..5e92062 100644
182--- a/tests/regressiontests/handlers/tests.py
183+++ b/tests/regressiontests/handlers/tests.py
184@@ -1,4 +1,4 @@
185-from django.core.handlers.wsgi import WSGIHandler
186+from django.core.handlers.wsgi import WSGIHandler, UrlPrefixAwareHandler
187 from django.core import signals
188 from django.test import RequestFactory, TestCase
189 from django.test.utils import override_settings
190@@ -58,3 +58,20 @@ class SignalsTests(TestCase):
191         self.assertEqual(self.signals, ['started'])
192         self.assertEqual(b''.join(response.streaming_content), b"streaming content")
193         self.assertEqual(self.signals, ['started', 'finished'])
194+
195+
196+@override_settings(FORCE_SCRIPT_NAME='/prefixed-example')
197+class PrefixedHandlerTests(TestCase):
198+    urls = 'regressiontests.handlers.urls'
199+
200+    def test_unprefixed_path(self):
201+        environ = RequestFactory().get('/regular/').environ
202+        handler = UrlPrefixAwareHandler(WSGIHandler())
203+        response = handler(environ, lambda *a, **k: None)
204+        self.assertEqual(response.status_code, 400)
205+
206+    def test_prefix_path(self):
207+        environ = RequestFactory().get('/prefixed-example/regular/').environ
208+        handler = UrlPrefixAwareHandler(WSGIHandler())
209+        response = handler(environ, lambda *a, **k: None)
210+        self.assertEqual(response.content, b"regular content")
211diff --git a/tests/regressiontests/servers/tests.py b/tests/regressiontests/servers/tests.py
212index 1a7552e..084fd78 100644
213--- a/tests/regressiontests/servers/tests.py
214+++ b/tests/regressiontests/servers/tests.py
215@@ -144,6 +144,70 @@ class LiveServerViews(LiveServerBase):
216         self.assertIn(b"QUERY_STRING: 'q=%D1%82%D0%B5%D1%81%D1%82'", f.read())
217 
218 
219+class LiveServerUrlPrefixedViews(LiveServerBase):
220+   
221+    @classmethod
222+    def setUpClass(cls):
223+        # Override settings
224+        newsettings = {}
225+        newsettings.update(TEST_SETTINGS)
226+        newsettings.update({
227+            'FORCE_SCRIPT_NAME': '/live-example',
228+            'MEDIA_URL': '/live-example/media/',
229+            'STATIC_URL': '/live-example/static/',
230+        })
231+        cls.settings_override = override_settings(**newsettings)
232+        cls.settings_override.enable()
233+        super(LiveServerBase, cls).setUpClass()
234+
235+    def test_bad_request(self):
236+        """
237+        Ensure that the LiveServerTestCase serves 400s.
238+        """
239+        try:
240+            self.urlopen('/')
241+        except HTTPError as err:
242+            self.assertEqual(err.code, 400, 'Expected 400 response')
243+        else:
244+            self.fail('Expected 400 response')
245+   
246+    def test_404(self):
247+        """
248+        Ensure that the LiveServerTestCase serves 404s.
249+        Refs #2879.
250+        """
251+        try:
252+            self.urlopen('/live-example/')
253+        except HTTPError as err:
254+            self.assertEqual(err.code, 404, 'Expected 404 response')
255+        else:
256+            self.fail('Expected 404 response')
257+
258+    def test_view(self):
259+        """
260+        Ensure that the LiveServerTestCase serves views.
261+        Refs #2879.
262+        """
263+        f = self.urlopen('/live-example/example_view/')
264+        self.assertEqual(f.read(), b'example view')
265+
266+    def test_static_files(self):
267+        """
268+        Ensure that the LiveServerTestCase serves static files.
269+        Refs #2879.
270+        """
271+        f = self.urlopen('/live-example/static/example_static_file.txt')
272+        self.assertEqual(f.read().rstrip(b'\r\n'), b'example static file')
273+
274+    def test_media_files(self):
275+        """
276+        Ensure that the LiveServerTestCase serves media files.
277+        Refs #2879.
278+        """
279+        f = self.urlopen('/live-example/media/example_media_file.txt')
280+        self.assertEqual(f.read().rstrip(b'\r\n'), b'example media file')
281+
282+
283 class LiveServerDatabase(LiveServerBase):
284 
285     def test_fixtures_loaded(self):