diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index 293a32df..2faf4516 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -8,6 +8,7 @@ This project adheres to `Semantic Versioning `_.
`2.1.0-dev`_ (unreleased)
-------------------------
+* Add ``--path-as-is`` to bypass dot segment (``/../`` or ``/./``) URL squashing.
* Fixed ``--form`` file upload mixed with redirected ``stdin`` error handling.
diff --git a/httpie/cli/definition.py b/httpie/cli/definition.py
index 61ed3d3b..43fb5348 100644
--- a/httpie/cli/definition.py
+++ b/httpie/cli/definition.py
@@ -552,6 +552,15 @@ network.add_argument(
"""
)
+network.add_argument(
+ '--path-as-is',
+ default=False,
+ action='store_true',
+ help="""
+ Bypass dot segment (/../ or /./) URL squashing.
+
+ """
+)
#######################################################################
# SSL
diff --git a/httpie/client.py b/httpie/client.py
index 431bee4f..6c412e73 100644
--- a/httpie/client.py
+++ b/httpie/client.py
@@ -6,6 +6,7 @@ import zlib
from contextlib import contextmanager
from pathlib import Path
from typing import Iterable, Union
+from urllib.parse import urlparse, urlunparse
import requests
from requests.adapters import HTTPAdapter
@@ -77,6 +78,11 @@ def collect_messages(
request = requests.Request(**request_kwargs)
prepared_request = requests_session.prepare_request(request)
+ if args.path_as_is:
+ prepared_request.url = ensure_path_as_is(
+ orig_url=args.url,
+ prepped_url=prepared_request.url,
+ )
if args.compress and prepared_request.body:
compress_body(prepared_request, always=args.compress > 1)
response_count = 0
@@ -278,3 +284,30 @@ def make_request_kwargs(
}
return kwargs
+
+
+def ensure_path_as_is(orig_url: str, prepped_url: str) -> str:
+ """
+ Handle `--path-as-is` by replacing the path component of the prepared
+ URL with the path component from the original URL. Other parts stay
+ untouched because other (welcome) processing on the URL might have
+ taken place.
+
+
+
+
+
+
+
+ >>> ensure_path_as_is('http://foo/../', 'http://foo/?foo=bar')
+ 'http://foo/../?foo=bar'
+
+ """
+ parsed_orig, parsed_prepped = urlparse(orig_url), urlparse(prepped_url)
+ final_dict = {
+ # noinspection PyProtectedMember
+ **parsed_prepped._asdict(),
+ 'path': parsed_orig.path,
+ }
+ final_url = urlunparse(tuple(final_dict.values()))
+ return final_url
diff --git a/tests/test_httpie.py b/tests/test_httpie.py
index 70bc2f9d..66caba52 100644
--- a/tests/test_httpie.py
+++ b/tests/test_httpie.py
@@ -55,6 +55,25 @@ def test_GET(httpbin_both):
assert HTTP_OK in r
+def test_path_dot_normalization():
+ r = http(
+ '--offline',
+ 'example.org/../../etc/password',
+ 'param==value'
+ )
+ assert 'GET /etc/password?param=value' in r
+
+
+def test_path_as_is():
+ r = http(
+ '--offline',
+ '--path-as-is',
+ 'example.org/../../etc/password',
+ 'param==value'
+ )
+ assert 'GET /../../etc/password?param=value' in r
+
+
def test_DELETE(httpbin_both):
r = http('DELETE', httpbin_both + '/delete')
assert HTTP_OK in r