)]}'
{"/PATCHSET_LEVEL":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"b81f9b0cee019bc70777f7f6d97153224ecf5b34","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":1,"id":"ada1499a_ab01c321","updated":"2025-07-31 00:07:45.000000000","message":"I guess I don\u0027t really have any problem the decrypter explicitly checking the swift api version - but it doesn\u0027t seem very DRY \n\nit seems like all it cares about is \"if not valid swift path: continue\" and it\u0027s having to spell that twice two different ways?\n\nIt raises questions like \"why don\u0027t other middleware like keymaster have this problem\" or \"if they do, should they ALL have to validate the swift api version explicitly every time after parsing the swift resource path?\"\n\nI think what would help me the most tho would be a valid repro and possibly lp bug report.  I believe you saw the traceback in prod - so why not share it?\n\nI\u0027d find the unittest more helpful if you added the debug_logger so I could see the actual error/exception handling:\n\n```\ntest ERROR: ERROR get_keys() missing callback: \nTraceback (most recent call last):\n  File \"/home/vagrant/swift/swift/common/middleware/crypto/crypto_utils.py\", line 158, in get_keys\n    fetch_crypto_keys \u003d env[CRYPTO_KEY_CALLBACK]\nKeyError: \u0027swift.callback.fetch_crypto_keys\u0027\n```\n\nor if it maybe didn\u0027t fail with the trivial circumvention you suggested in your self-review comment:\n\n```\n(.venv) cgerrard@NVStation:~/Workspace/vagrant-swift-all-in-one/swift$ git diff\ndiff --git a/test/unit/common/middleware/crypto/test_decrypter.py b/test/unit/common/middleware/crypto/test_decrypter.py\nindex fe6ed0604..fe8a368ce 100644\n--- a/test/unit/common/middleware/crypto/test_decrypter.py\n+++ b/test/unit/common/middleware/crypto/test_decrypter.py\n@@ -1212,7 +1212,7 @@ class TestDecrypter(unittest.TestCase):\n         self.assertEqual(FakeAppThatExcepts.MESSAGE, catcher.exception.body)\n \n     def test_non_swift_path(self):\n-        path \u003d \u0027/\\xC0.\\xC0./\\xC0.\\xC0./\\xC0.\\xC0./\\xC0.\\xC0./winnt/win.ini\u0027\n+        path \u003d \u0027/v1/\\xC0.\\xC0./\\xC0.\\xC0./\\xC0.\\xC0./\\xC0.\\xC0./winnt/win.ini\u0027\n         fake_swift \u003d FakeSwift()\n         fake_swift.register(\u0027GET\u0027, path, HTTPNotFound, {})\n         app \u003d decrypter.Decrypter(fake_swift, {})\n```\n\nperhaps the bug is less \"we should check the version\" and more - crypto_utils shouldn\u0027t assume the callback is installed?\n\n```\ndiff --git a/swift/common/middleware/crypto/decrypter.py b/swift/common/middleware/crypto/decrypter.py\nindex 3431e4efc..9c0739895 100644\n--- a/swift/common/middleware/crypto/decrypter.py\n+++ b/swift/common/middleware/crypto/decrypter.py\n@@ -20,7 +20,7 @@ from swift.common.constraints import valid_api_version\n from swift.common.header_key_dict import HeaderKeyDict\n from swift.common.http import is_success\n from swift.common.middleware.crypto.crypto_utils import CryptoWSGIContext, \\\n-    load_crypto_meta, extract_crypto_meta, Crypto\n+    load_crypto_meta, extract_crypto_meta, Crypto, CRYPTO_KEY_CALLBACK\n from swift.common.exceptions import EncryptionException, UnknownSecretIdError\n from swift.common.request_helpers import get_object_transient_sysmeta, \\\n     get_sys_meta_prefix, get_user_meta_prefix, \\\n@@ -461,6 +461,8 @@ class Decrypter(object):\n         if not valid_api_version(parts[0]):\n             # Not a swift request\n             return self.app(env, start_response)\n+        if not CRYPTO_KEY_CALLBACK in env:\n+            return self.app(env, start_response)\n \n         if parts[3] and req.method in (\u0027GET\u0027, \u0027HEAD\u0027):\n             handler \u003d DecrypterObjContext(self, self.logger).handle\n```\n\nI\u0027m actually not sure ... \n\n```\n    def test_GET_missing_key_callback(self):\n        # Do not provide keys, and do not set override flag\n        env \u003d {\u0027REQUEST_METHOD\u0027: \u0027GET\u0027}\n        req \u003d Request.blank(\u0027/v1/a/c/o\u0027, environ\u003denv)\n        body \u003d b\u0027FAKE APP\u0027\n        enc_body \u003d encrypt(body, fetch_crypto_keys()[\u0027object\u0027], FAKE_IV)\n        hdrs \u003d self._make_response_headers(\n            len(body), md5hex(b\u0027not the body\u0027),\n            fetch_crypto_keys(), b\u0027not used\u0027)\n        self.app.register(\n            \u0027GET\u0027, \u0027/v1/a/c/o\u0027, HTTPOk, body\u003denc_body, headers\u003dhdrs)\n        resp \u003d req.get_response(self.decrypter)\n\u003e       self.assertEqual(\u0027500 Internal Error\u0027, resp.status)\nE       AssertionError: \u0027500 Internal Error\u0027 !\u003d \u0027200 OK\u0027\nE       - 500 Internal Error\nE       + 200 OK\n\n```\n\n... but as you point out: the proposed fix does seem superficial and anemic.","commit_id":"695974c42e23acd87ad1ac85931aa891aa70f712"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"739ab35d9462e2cc0dfe54c1d42b6686d5c20bf8","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":1,"id":"296fab29_7dca047d","in_reply_to":"ada1499a_ab01c321","updated":"2025-07-31 23:38:16.000000000","message":"Sure enough, that test wasn\u0027t demonstrating what I meant to! Thanks for the review. Should be better now, and documented the error in the commit message. Good call on adding the debug logger.","commit_id":"695974c42e23acd87ad1ac85931aa891aa70f712"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"4f89aeedc2431b258c12e48d6fe1de149c5e2edb","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":2,"id":"e7593b3b_9496ad14","updated":"2025-08-06 19:57:13.000000000","message":"on master (w/o encryption)\n\n```\nvagrant@saio:~$ curl \u0027http://saio:8080/v1/%C0.%C0./%C0.%C0./%C0.%C0./%C0.%C0./winnt/win.ini\u0027 -v\n*   Trying 127.0.2.1:8080...\n* Connected to saio (127.0.2.1) port 8080 (#0)\n\u003e GET /v1/%C0.%C0./%C0.%C0./%C0.%C0./%C0.%C0./winnt/win.ini HTTP/1.1\n\u003e Host: saio:8080\n\u003e User-Agent: curl/7.81.0\n\u003e Accept: */*\n\u003e \n* Mark bundle as not supporting multiuse\n\u003c HTTP/1.1 412 Precondition Failed\n\u003c Content-Type: text/html; charset\u003dUTF-8\n\u003c Content-Length: 29\n\u003c X-Trans-Id: tx6872dd2945ad494ab83c8-0068939ca8\n\u003c X-Openstack-Request-Id: tx6872dd2945ad494ab83c8-0068939ca8\n\u003c Date: Wed, 06 Aug 2025 18:19:20 GMT\n\u003c \n* Connection #0 to host saio left intact\nInvalid UTF8 or contains NULL\n```\n\non master (with encryption)\n```\nvagrant@saio:~$ curl \u0027http://saio:8080/v1/%C0.%C0./%C0.%C0./%C0.%C0./%C0.%C0./winnt/win.ini\u0027 -v\n*   Trying 127.0.2.1:8080...\n* Connected to saio (127.0.2.1) port 8080 (#0)\n\u003e GET /v1/%C0.%C0./%C0.%C0./%C0.%C0./%C0.%C0./winnt/win.ini HTTP/1.1\n\u003e Host: saio:8080\n\u003e User-Agent: curl/7.81.0\n\u003e Accept: */*\n\u003e \n* Mark bundle as not supporting multiuse\n\u003c HTTP/1.1 500 Internal Error\n\u003c Content-Type: text/html; charset\u003dUTF-8\n\u003c Content-Length: 35\n\u003c X-Trans-Id: tx5d0317c43bcd47f0862ac-0068939fdc\n\u003c X-Openstack-Request-Id: tx5d0317c43bcd47f0862ac-0068939fdc\n\u003c Date: Wed, 06 Aug 2025 18:33:00 GMT\n\u003c \n* Connection #0 to host saio left intact\nUnable to retrieve encryption keys.\n\nAug  6 18:38:58 saio proxy-server: get_keys(): from callback: \u0027utf-8\u0027 codec can\u0027t encode character \u0027\\udcc0\u0027 in position 1: surrogates not allowed: #012Traceback (most recent call last):#012  File \"/vagrant/swift/swift/common/middleware/crypto/crypto_utils.py\", line 166, in get_keys#012    keys \u003d fetch_crypto_keys(key_id\u003dkey_id)#012  File \"/vagrant/swift/swift/common/middleware/crypto/keymaster.py\", line 148, in fetch_crypto_keys#012    keys[\u0027container\u0027] \u003d self.keymaster.create_key(#012  File \"/vagrant/swift/swift/common/middleware/crypto/keymaster.py\", line 322, in create_key#012    path \u003d path.encode(\u0027utf-8\u0027)#012UnicodeEncodeError: \u0027utf-8\u0027 codec can\u0027t encode character \u0027\\udcc0\u0027 in position 1: surrogates not allowed (txn: tx609ee7189a34418f8d721-006893a142)\n\n```\n\nthis branch (with encryption)\n\n```\nvagrant@saio:~$ curl \u0027http://saio:8080/v1/%C0.%C0./%C0.%C0./%C0.%C0./%C0.%C0./winnt/win.ini\u0027 -v\n*   Trying 127.0.2.1:8080...\n* Connected to saio (127.0.2.1) port 8080 (#0)\n\u003e GET /v1/%C0.%C0./%C0.%C0./%C0.%C0./%C0.%C0./winnt/win.ini HTTP/1.1\n\u003e Host: saio:8080\n\u003e User-Agent: curl/7.81.0\n\u003e Accept: */*\n\u003e \n* Mark bundle as not supporting multiuse\n\u003c HTTP/1.1 500 Internal Error\n\u003c Content-Type: text/html; charset\u003dUTF-8\n\u003c Content-Length: 35\n\u003c X-Trans-Id: tx3f080fc39d154c489118e-0068939f98\n\u003c X-Openstack-Request-Id: tx3f080fc39d154c489118e-0068939f98\n\u003c Date: Wed, 06 Aug 2025 18:31:52 GMT\n\u003c \n* Connection #0 to host saio left intact\nUnable to retrieve encryption keys.\n\nAug  6 18:37:04 saio proxy-server: get_keys(): from callback: \u0027utf-8\u0027 codec can\u0027t encode character \u0027\\udcc0\u0027 in position 1: surrogates not allowed: #012Traceback (most recent call last):#012  File \"/vagrant/swift/swift/common/middleware/crypto/crypto_utils.py\", line 166, in get_keys#012    keys \u003d fetch_crypto_keys(key_id\u003dkey_id)#012  File \"/vagrant/swift/swift/common/middleware/crypto/keymaster.py\", line 148, in fetch_crypto_keys#012    keys[\u0027container\u0027] \u003d self.keymaster.create_key(#012  File \"/vagrant/swift/swift/common/middleware/crypto/keymaster.py\", line 322, in create_key#012    path \u003d path.encode(\u0027utf-8\u0027)#012UnicodeEncodeError: \u0027utf-8\u0027 codec can\u0027t encode character \u0027\\udcc0\u0027 in position 1: surrogates not allowed (txn: txb3535ebbc2844f5f950d4-006893a0d0)\n```\n\nBut in the very specific case of missing version the invalid path is passed down to the proxy for validation:\n\n```\nvagrant@saio:~$ curl \u0027http://saio:8080/%C0.%C0./%C0.%C0./%C0.%C0./%C0.%C0./winnt/win.ini\u0027 -v\n*   Trying 127.0.2.1:8080...\n* Connected to saio (127.0.2.1) port 8080 (#0)\n\u003e GET /%C0.%C0./%C0.%C0./%C0.%C0./%C0.%C0./winnt/win.ini HTTP/1.1\n\u003e Host: saio:8080\n\u003e User-Agent: curl/7.81.0\n\u003e Accept: */*\n\u003e \n* Mark bundle as not supporting multiuse\n\u003c HTTP/1.1 412 Precondition Failed\n\u003c Content-Type: text/html; charset\u003dUTF-8\n\u003c Content-Length: 29\n\u003c X-Trans-Id: tx1da907a2314642f485724-006893a0a5\n\u003c X-Openstack-Request-Id: tx1da907a2314642f485724-006893a0a5\n\u003c Date: Wed, 06 Aug 2025 18:36:21 GMT\n\u003c \n* Connection #0 to host saio left intact\nInvalid UTF8 or contains NULL\n```\n\nFWIW I actually hate this style of bug \"fix\" - but rather than write a \"swift-path-validator\" middleware or adding a new helper like `swob.Request.split_valid_swift_path` I\u0027m of course going to just lean into the absurdity:\n\n956730: Fix another way mw may encouter invalid swift paths | https://review.opendev.org/c/openstack/swift/+/956730","commit_id":"86a1acc9e3f50729f3d95e8979e9ed133e1c24f3"}],"swift/common/middleware/crypto/decrypter.py":[{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"f2de92ae556ec53fda18c0d715ded0060fd7b2d4","unresolved":true,"context_lines":[{"line_number":309,"context_line":"    def _read_crypto_meta(self, header, check):"},{"line_number":310,"context_line":"        crypto_meta \u003d None"},{"line_number":311,"context_line":"        if (is_success(self._get_status_int()) or"},{"line_number":312,"context_line":"                self._get_status_int() in (304, 412)):"},{"line_number":313,"context_line":"            try:"},{"line_number":314,"context_line":"                crypto_meta \u003d self.get_crypto_meta(header, check)"},{"line_number":315,"context_line":"            except EncryptionException as err:"}],"source_content_type":"text/x-python","patch_set":1,"id":"62ed9804_3e741ba9","line":312,"range":{"start_line":312,"start_character":48,"end_line":312,"end_character":51},"updated":"2025-06-27 21:43:07.000000000","message":"OMG I hate how [we abuse this status code](https://github.com/openstack/swift/blob/2.35.0/swift/proxy/server.py#L495-L497)...","commit_id":"695974c42e23acd87ad1ac85931aa891aa70f712"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"b81f9b0cee019bc70777f7f6d97153224ecf5b34","unresolved":true,"context_lines":[{"line_number":309,"context_line":"    def _read_crypto_meta(self, header, check):"},{"line_number":310,"context_line":"        crypto_meta \u003d None"},{"line_number":311,"context_line":"        if (is_success(self._get_status_int()) or"},{"line_number":312,"context_line":"                self._get_status_int() in (304, 412)):"},{"line_number":313,"context_line":"            try:"},{"line_number":314,"context_line":"                crypto_meta \u003d self.get_crypto_meta(header, check)"},{"line_number":315,"context_line":"            except EncryptionException as err:"}],"source_content_type":"text/x-python","patch_set":1,"id":"f0b5298b_fda345f4","line":312,"range":{"start_line":312,"start_character":48,"end_line":312,"end_character":51},"in_reply_to":"62ed9804_3e741ba9","updated":"2025-07-31 00:07:45.000000000","message":"+1","commit_id":"695974c42e23acd87ad1ac85931aa891aa70f712"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"b81f9b0cee019bc70777f7f6d97153224ecf5b34","unresolved":true,"context_lines":[{"line_number":460,"context_line":"            return self.app(env, start_response)"},{"line_number":461,"context_line":"        if not valid_api_version(parts[0]):"},{"line_number":462,"context_line":"            # Not a swift request"},{"line_number":463,"context_line":"            return self.app(env, start_response)"},{"line_number":464,"context_line":""},{"line_number":465,"context_line":"        if parts[3] and req.method in (\u0027GET\u0027, \u0027HEAD\u0027):"},{"line_number":466,"context_line":"            handler \u003d DecrypterObjContext(self, self.logger).handle"}],"source_content_type":"text/x-python","patch_set":1,"id":"fbce9932_8d029b9c","line":463,"updated":"2025-07-31 00:07:45.000000000","message":"there should probably be a kind of utils.split_path() that raises ValueError when `not valid_api_version` so that mw can just \"get me swift path components; oh not swift - then forward\"","commit_id":"695974c42e23acd87ad1ac85931aa891aa70f712"}],"test/unit/common/middleware/crypto/test_decrypter.py":[{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"f2de92ae556ec53fda18c0d715ded0060fd7b2d4","unresolved":true,"context_lines":[{"line_number":1212,"context_line":"        self.assertEqual(FakeAppThatExcepts.MESSAGE, catcher.exception.body)"},{"line_number":1213,"context_line":""},{"line_number":1214,"context_line":"    def test_non_swift_path(self):"},{"line_number":1215,"context_line":"        path \u003d \u0027/\\xC0.\\xC0./\\xC0.\\xC0./\\xC0.\\xC0./\\xC0.\\xC0./winnt/win.ini\u0027"},{"line_number":1216,"context_line":"        fake_swift \u003d FakeSwift()"},{"line_number":1217,"context_line":"        fake_swift.register(\u0027GET\u0027, path, HTTPNotFound, {})"},{"line_number":1218,"context_line":"        app \u003d decrypter.Decrypter(fake_swift, {})"}],"source_content_type":"text/x-python","patch_set":1,"id":"67f4590c_dd2276d5","line":1215,"updated":"2025-06-27 21:43:07.000000000","message":"This is the sort of thing that came up in prod -- probably some vulnerability scanner? The fix should maybe go deeper, though; prefixing with `/v1` would still produce a 500.","commit_id":"695974c42e23acd87ad1ac85931aa891aa70f712"},{"author":{"_account_id":15343,"name":"Tim Burke","email":"tburke@nvidia.com","username":"tburke"},"change_message_id":"739ab35d9462e2cc0dfe54c1d42b6686d5c20bf8","unresolved":true,"context_lines":[{"line_number":1212,"context_line":"        self.assertEqual(FakeAppThatExcepts.MESSAGE, catcher.exception.body)"},{"line_number":1213,"context_line":""},{"line_number":1214,"context_line":"    def test_non_swift_path(self):"},{"line_number":1215,"context_line":"        path \u003d \u0027/\\xC0.\\xC0./\\xC0.\\xC0./\\xC0.\\xC0./\\xC0.\\xC0./winnt/win.ini\u0027"},{"line_number":1216,"context_line":"        fake_swift \u003d FakeSwift()"},{"line_number":1217,"context_line":"        fake_swift.register(\u0027GET\u0027, path, HTTPNotFound, {})"},{"line_number":1218,"context_line":"        app \u003d decrypter.Decrypter(fake_swift, {})"}],"source_content_type":"text/x-python","patch_set":1,"id":"34ed53a2_80d96a8e","line":1215,"in_reply_to":"4c1cd40a_9b1d76d5","updated":"2025-07-31 23:38:16.000000000","message":"```\ncurl http://saio/%C0.%C0./%C0.%C0./%C0.%C0./%C0.%C0./winnt/win.ini\n```\nshould suffice.","commit_id":"695974c42e23acd87ad1ac85931aa891aa70f712"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"b81f9b0cee019bc70777f7f6d97153224ecf5b34","unresolved":true,"context_lines":[{"line_number":1212,"context_line":"        self.assertEqual(FakeAppThatExcepts.MESSAGE, catcher.exception.body)"},{"line_number":1213,"context_line":""},{"line_number":1214,"context_line":"    def test_non_swift_path(self):"},{"line_number":1215,"context_line":"        path \u003d \u0027/\\xC0.\\xC0./\\xC0.\\xC0./\\xC0.\\xC0./\\xC0.\\xC0./winnt/win.ini\u0027"},{"line_number":1216,"context_line":"        fake_swift \u003d FakeSwift()"},{"line_number":1217,"context_line":"        fake_swift.register(\u0027GET\u0027, path, HTTPNotFound, {})"},{"line_number":1218,"context_line":"        app \u003d decrypter.Decrypter(fake_swift, {})"}],"source_content_type":"text/x-python","patch_set":1,"id":"4c1cd40a_9b1d76d5","line":1215,"in_reply_to":"67f4590c_dd2276d5","updated":"2025-07-31 00:07:45.000000000","message":"I always get tripped up with wsgi string literals, I find them ambiguous and not particularly literal:\n\n```\n\u003e\u003e\u003e b\u0027/\\xC0.\\xC0./\\xC0.\\xC0./\\xC0.\\xC0./\\xC0.\\xC0./winnt/win.ini\u0027.decode(\u0027latin1\u0027)\n\u0027/À.À./À.À./À.À./À.À./winnt/win.ini\u0027\n\u003e\u003e\u003e \u0027/\\xC0.\\xC0./\\xC0.\\xC0./\\xC0.\\xC0./\\xC0.\\xC0./winnt/win.ini\u0027 \u003d\u003d \u0027/À.À./À.À./À.À./À.À./winnt/win.ini\u0027\nTrue\n```\n\nI guess part of the problem is like swift most of our editors are going to default to utf-8 encoding, so if you just use some codepoints that happen to exist in the latin-1 character set they may not match the bytes the client actually sent.  But if you *want* to test the bytes that client actually sent... why are we creating and testing on a string?  Might be clearer as `\u0027literal string\u0027.encode(\u0027utf8\u0027).decode(\u0027latin-1\u0027)` to better represent what clients \u0026 WSGI actually do, or even `b\u0027what we actually got`.decode(\u0027latin1\u0027)` if you want to remove any assumptions on the client out of the equation.\n\n\u003e prefixing with /v1 would still produce a 500\n\nI guess we could consider that a separate issue then?\n\nIt\u0027s not obvious from the fix what exactly could go sufficiently wrong with a \"valid swift path\" to generate a 5XX?\n\nbetter logging setup:\n```\ndiff --git a/test/unit/common/middleware/crypto/test_decrypter.py b/test/unit/common/middleware/crypto/test_decrypter.py\nindex fe6ed0604..a9c427fa4 100644\n--- a/test/unit/common/middleware/crypto/test_decrypter.py\n+++ b/test/unit/common/middleware/crypto/test_decrypter.py\n@@ -1216,6 +1216,7 @@ class TestDecrypter(unittest.TestCase):\n         fake_swift \u003d FakeSwift()\n         fake_swift.register(\u0027GET\u0027, path, HTTPNotFound, {})\n         app \u003d decrypter.Decrypter(fake_swift, {})\n+        app.logger \u003d debug_logger()\n         req \u003d Request.blank(path)\n         resp \u003d req.get_response(app)\n         self.assertEqual(resp.status_int, 404)\n```\n... provided *this* clue \n```\n(Pdb) app.logger.logger.lines_dict\n{\u0027critical\u0027: [], \u0027error\u0027: [\u0027ERROR get_keys() missing callback: \u0027], \u0027info\u0027: [], \u0027warning\u0027: [], \u0027debug\u0027: [], \u0027notice\u0027: []}\n```\n\n... but I\"m having trouble reproducing so I can see the actual traceback.  On master:\n\n```\nvagrant@saio:~$ curl http://saio:8080/À.À./À.À./À.À./À.À./winnt/win.ini -v\n*   Trying 127.0.2.1:8080...\n* Connected to saio (127.0.2.1) port 8080 (#0)\n\u003e GET /À.À./À.À./À.À./À.À./winnt/win.ini HTTP/1.1\n\u003e Host: saio:8080\n\u003e User-Agent: curl/7.81.0\n\u003e Accept: */*\n\u003e \n* Mark bundle as not supporting multiuse\n\u003c HTTP/1.1 400 Bad Request\n\u003c Content-Type: text/html; charset\u003dUTF-8\n\u003c Content-Length: 137\n\u003c X-Trans-Id: txdfd53f24589d4736b2e6f-00688aaa40\n\u003c X-Openstack-Request-Id: txdfd53f24589d4736b2e6f-00688aaa40\n\u003c Date: Wed, 30 Jul 2025 23:26:56 GMT\n\u003c \n* Connection #0 to host saio left intact\n\u003chtml\u003e\u003ch1\u003eBad Request\u003c/h1\u003e\u003cp\u003eThe server could not comply with the request since it is either malformed or otherwise incorrect.\u003c/p\u003e\u003c/html\u003evagrant@saio:~$\n```\n\nrequests also thinks this is 400:\n\n```\n\u003e\u003e\u003e requests.get(\u0027http://saio:8080/\\xC0.\\xC0./\\xC0.\\xC0./\\xC0.\\xC0./\\xC0.\\xC0./winnt/win.ini\u0027)\n\u003cResponse [400]\u003e\n```\n\nIs the problem that well behaved clients are doing some encoding?\n\n```\n\u003e\u003e\u003e resp.url\n\u0027http://saio:8080/%C3%80.%C3%80./%C3%80.%C3%80./%C3%80.%C3%80./%C3%80.%C3%80./winnt/win.ini\u0027\n```\n\nI probably need to find some way to send bytes on the wire:\n\n```\n\u003e\u003e\u003e urllib.request.urlopen(\u0027http://saio:8080/\\xC0.\\xC0./\\xC0.\\xC0./\\xC0.\\xC0./\\xC0.\\xC0./winnt/win.ini\u0027)\nTraceback (most recent call last):\n  File \"\u003cstdin\u003e\", line 1, in \u003cmodule\u003e\n  File \"/usr/lib/python3.12/urllib/request.py\", line 215, in urlopen\n    return opener.open(url, data, timeout)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.12/urllib/request.py\", line 515, in open\n    response \u003d self._open(req, data)\n               ^^^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.12/urllib/request.py\", line 532, in _open\n    result \u003d self._call_chain(self.handle_open, protocol, protocol +\n             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.12/urllib/request.py\", line 492, in _call_chain\n    result \u003d func(*args)\n             ^^^^^^^^^^^\n  File \"/usr/lib/python3.12/urllib/request.py\", line 1373, in http_open\n    return self.do_open(http.client.HTTPConnection, req)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.12/urllib/request.py\", line 1344, in do_open\n    h.request(req.get_method(), req.selector, req.data, headers,\n  File \"/usr/lib/python3.12/http/client.py\", line 1336, in request\n    self._send_request(method, url, body, headers, encode_chunked)\n  File \"/usr/lib/python3.12/http/client.py\", line 1347, in _send_request\n    self.putrequest(method, url, **skips)\n  File \"/usr/lib/python3.12/http/client.py\", line 1185, in putrequest\n    self._output(self._encode_request(request))\n                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n  File \"/usr/lib/python3.12/http/client.py\", line 1265, in _encode_request\n    return request.encode(\u0027ascii\u0027)\n           ^^^^^^^^^^^^^^^^^^^^^^^\nUnicodeEncodeError: \u0027ascii\u0027 codec can\u0027t encode character \u0027\\xc0\u0027 in position 5: ordinal not in range(128)\n\n\u003e\u003e\u003e b\u0027/\\xC0.\\xC0./\\xC0.\\xC0./\\xC0.\\xC0./\\xC0.\\xC0./winnt/win.ini\u0027.decode(\u0027utf8\u0027)\nTraceback (most recent call last):\n  File \"\u003cstdin\u003e\", line 1, in \u003cmodule\u003e\nUnicodeDecodeError: \u0027utf-8\u0027 codec can\u0027t decode byte 0xc0 in position 1: invalid start byte\n```","commit_id":"695974c42e23acd87ad1ac85931aa891aa70f712"}]}
