Repository: pallets/werkzeug

Issue:
Relative location headers are "fixed" as if they are relative to root instead of the current url
https://github.com/mitsuhiko/werkzeug/blob/542b2e95d4e39ed8f0ec186b88eb20ff70544168/werkzeug/wrappers.py#L1129

When the location header points to a relative address, werkzeug grabs the root of the url, which for example.com/a/b/c/d/e turns into example.com, and then adds the relative location on top, which doesn't turn out well.

Why not use the full url when correcting it? In my case if I get redirected from example.com/path/to/file to ../abc, I'd want to end up at example.com/path/abc instead of example.com/abc.



Discussion:
The location header can't be relative, as per [RFC 2616](http://tools.ietf.org/html/rfc2616#section-14.30):

> The Location response-header field is used to redirect the recipient to a location other than the Request-URI for completion of the request or identification of a new resource. For 201 (Created) responses, the Location is that of the new resource which was created by the request. For 3xx responses, the location SHOULD indicate the server's preferred URI for automatic redirection to the resource. The field value consists of a single absolute URI.

I'd accept patches that change the behavior (since it'd still be RFC-compliant, just more tolerant) as long as there are no performance penalties.


Resolving patch:
diff --git a/werkzeug/wrappers.py b/werkzeug/wrappers.py
--- a/werkzeug/wrappers.py
+++ b/werkzeug/wrappers.py
@@ -1235,7 +1235,7 @@ def get_wsgi_headers(self, environ):
                 location = iri_to_uri(location, safe_conversion=True)
 
             if self.autocorrect_location_header:
-                current_url = get_current_url(environ, root_only=True)
+                current_url = get_current_url(environ, strip_querystring=True)
                 if isinstance(current_url, text_type):
                     current_url = iri_to_uri(current_url)
                 location = url_join(current_url, location)


Test patch:
diff --git a/tests/test_wrappers.py b/tests/test_wrappers.py
--- a/tests/test_wrappers.py
+++ b/tests/test_wrappers.py
@@ -1146,18 +1146,19 @@ class MyResponse(wrappers.Response):
     assert 'Content-Length' not in resp.get_wsgi_headers({})
 
 
-def test_location_header_autocorrect():
-    env = create_environ()
-
-    class MyResponse(wrappers.Response):
-        autocorrect_location_header = False
-    resp = MyResponse('Hello World!')
-    resp.headers['Location'] = '/test'
-    assert resp.get_wsgi_headers(env)['Location'] == '/test'
-
+@pytest.mark.parametrize(('auto', 'location', 'expect'), (
+    (False, '/test', '/test'),
+    (True, '/test', 'http://localhost/test'),
+    (True, 'test', 'http://localhost/a/b/test'),
+    (True, './test', 'http://localhost/a/b/test'),
+    (True, '../test', 'http://localhost/a/test'),
+))
+def test_location_header_autocorrect(monkeypatch, auto, location, expect):
+    monkeypatch.setattr(wrappers.Response, 'autocorrect_location_header', auto)
+    env = create_environ('/a/b/c')
     resp = wrappers.Response('Hello World!')
-    resp.headers['Location'] = '/test'
-    assert resp.get_wsgi_headers(env)['Location'] == 'http://localhost/test'
+    resp.headers['Location'] = location
+    assert resp.get_wsgi_headers(env)['Location'] == expect
 
 
 def test_204_and_1XX_response_has_no_content_length():