Repository: pallets/werkzeug

Issue:
Raw request url path info
It looks like once the make_environ function creates an environ dictionary the original request path encoded with the `path_info = url_unquote(request_url.path)` call will be discarded. For example, if my request URL path contains encoded `:` and `,` characters `/1%2C2/a%3Ab%3Ac%3Ad/test.json` then both chars will be discarded and converted into decoded path like this `/1,2/a:b:c:d/test.json`. Where this becomes an issue is when clients use encoded path and query_string inside of the HMAC signature, and server side running werkzeug app is unable to verify signature because encodings have been discarded.

My current way of handling this issue is to use url_quote on already decoded path, but the ideal solution would be to have an access to a raw URL string. 

Is there a way to obtain a raw request path_info from the werkzeug Request object?



Discussion:
due to bad history, wsgi as a standard does a lossy unquote by design, 
the suggested fix is to use a wsgi server that puts the raw path info into the environment and then add a mixing with a environment property to obtain the path

@RonnyPfannschmidt Thank you for the suggestion. I understand this, but sometimes we have no control over wsgi container. I'll take a look what flup, tornado, gunicorn or other containers do. 

Also, would you be open to a patch where a raw path info is injected into the environ dictionary, something similar to the `werkzeug.server.shutdown` key. For example, can we have a `werkzeug._raw_path_info` key which would preserver the original url path encoding?

Gunicorn adds `RAW_URI`. uWSGI adds `REQUEST_URI`. They both include the query string as well.

The problem is that this isn't part of the PEP, so any application that relies on it is not portable unless it uses middleware to alias other keys to the one they expect.
Going to add both keys, like https://github.com/etianen/aiohttp-wsgi/pull/17

Resolving patch:
diff --git a/werkzeug/serving.py b/werkzeug/serving.py
--- a/werkzeug/serving.py
+++ b/werkzeug/serving.py
@@ -202,6 +202,10 @@ def shutdown_server():
             'SCRIPT_NAME':          '',
             'PATH_INFO':            wsgi_encoding_dance(path_info),
             'QUERY_STRING':         wsgi_encoding_dance(request_url.query),
+            # Non-standard, added by mod_wsgi, uWSGI
+            "REQUEST_URI": wsgi_encoding_dance(self.path),
+            # Non-standard, added by gunicorn
+            "RAW_URI": wsgi_encoding_dance(self.path),
             'REMOTE_ADDR':          self.address_string(),
             'REMOTE_PORT':          self.port_integer(),
             'SERVER_NAME':          self.server.server_address[0],


Test patch:
diff --git a/tests/test_test.py b/tests/test_test.py
--- a/tests/test_test.py
+++ b/tests/test_test.py
@@ -756,3 +756,16 @@ def test_app(request):
 
     resp = client.get('/', data=b'testing', mimetype='application/octet-stream')
     strict_eq(resp.data, b'application/octet-stream')
+
+
+def test_raw_request_uri():
+    @Request.application
+    def app(request):
+        path_info = request.path
+        request_uri = request.environ["REQUEST_URI"]
+        return Response("\n".join((path_info, request_uri)))
+
+    client = Client(app, Response)
+    response = client.get("/hello%2fworld")
+    data = response.get_data(as_text=True)
+    assert data == "/hello/world\n/hello%2fworld"
diff --git a/werkzeug/test.py b/werkzeug/test.py
--- a/werkzeug/test.py
+++ b/werkzeug/test.py
@@ -288,6 +288,11 @@ class EnvironBuilder(object):
     .. versionadded:: 0.15
         The ``json`` param and :meth:`json_dumps` method.
 
+    .. versionadded:: 0.15
+        The environ has keys ``REQUEST_URI`` and ``RAW_URI`` containing
+        the path before perecent-decoding. This is not part of the WSGI
+        PEP, but many WSGI servers include it.
+
     .. versionchanged:: 0.6
        ``path`` and ``base_url`` can now be unicode strings that are
        encoded with :func:`iri_to_uri`.
@@ -676,6 +681,10 @@ def _path_encode(x):
             'SCRIPT_NAME':          _path_encode(self.script_root),
             'PATH_INFO':            _path_encode(self.path),
             'QUERY_STRING':         qs,
+            # Non-standard, added by mod_wsgi, uWSGI
+            "REQUEST_URI": wsgi_encoding_dance(self.path),
+            # Non-standard, added by gunicorn
+            "RAW_URI": wsgi_encoding_dance(self.path),
             'SERVER_NAME':          self.server_name,
             'SERVER_PORT':          str(self.server_port),
             'HTTP_HOST':            self.host,