From 8e2cea9fc12f1e7cc6d4e3a3beb01c14412a278a Mon Sep 17 00:00:00 2001 From: "Daniel J. Rollins" Date: Thu, 10 Oct 2024 15:32:23 +0100 Subject: [PATCH] add permissive options response from all endpoints --- .gitignore | 3 +++ src/gcp_storage_emulator/server.py | 43 +++++++++++++++++++++--------- tests/test_main.py | 10 +++++++ 3 files changed, 44 insertions(+), 12 deletions(-) diff --git a/.gitignore b/.gitignore index 81db975..6f017e9 100644 --- a/.gitignore +++ b/.gitignore @@ -150,3 +150,6 @@ cython_debug/ .cloudstorage .idea + +.envrc +.direnv diff --git a/src/gcp_storage_emulator/server.py b/src/gcp_storage_emulator/server.py index 1ac1533..32aee93 100644 --- a/src/gcp_storage_emulator/server.py +++ b/src/gcp_storage_emulator/server.py @@ -21,6 +21,7 @@ PUT = "PUT" DELETE = "DELETE" PATCH = "PATCH" +OPTIONS = "OPTIONS" def _wipe_data(req, res, storage): @@ -37,23 +38,31 @@ def _wipe_data(req, res, storage): def _health_check(req, res, storage): res.write("OK") +def _options(request, response, storage, *args, **kwargs): + response["content-type"] = "text/html; charset=UTF-8" + response["allow"] = "OPTIONS,GET,POST,PUT,DELETE,PATCH" + response["access-control-allow-origin"] = "*" + response[ "access-control-allow-methods"] = "GET,POST,PUT,PATCH,DELETE,OPTIONS" HANDLERS = ( - (r"^{}/b$".format(settings.API_ENDPOINT), {GET: buckets.ls, POST: buckets.insert}), + ( + r"^{}/b$".format(settings.API_ENDPOINT), + {GET: buckets.ls, POST: buckets.insert, OPTIONS: _options}, + ), ( r"^{}/b/(?P[-.\w]+)$".format(settings.API_ENDPOINT), - {GET: buckets.get, DELETE: buckets.delete}, + {GET: buckets.get, DELETE: buckets.delete, OPTIONS: _options}, ), ( r"^{}/b/(?P[-.\w]+)/o$".format(settings.API_ENDPOINT), - {GET: objects.ls}, + {GET: objects.ls, OPTIONS: _options}, ), ( r"^{}/b/(?P[-.\w]+)/o/(?P.*[^/]+)/copyTo/b/".format( settings.API_ENDPOINT ) + r"(?P[-.\w]+)/o/(?P.*[^/]+)$", - {POST: objects.copy}, + {POST: objects.copy, OPTIONS: _options}, ), ( r"^{}/b/(?P[-.\w]+)/o/(?P.*[^/]+)/rewriteTo/b/".format( @@ -66,36 +75,41 @@ def _health_check(req, res, storage): r"^{}/b/(?P[-.\w]+)/o/(?P.*[^/]+)/compose$".format( settings.API_ENDPOINT ), - {POST: objects.compose}, + {POST: objects.compose, OPTIONS: _options}, ), ( r"^{}/b/(?P[-.\w]+)/o/(?P.*[^/]+)$".format( settings.API_ENDPOINT ), - {GET: objects.get, DELETE: objects.delete, PATCH: objects.patch}, + { + GET: objects.get, + DELETE: objects.delete, + PATCH: objects.patch, + OPTIONS: _options, + }, ), # Non-default API endpoints ( r"^{}/b/(?P[-.\w]+)/o$".format(settings.UPLOAD_API_ENDPOINT), - {POST: objects.insert, PUT: objects.upload_partial}, + {POST: objects.insert, PUT: objects.upload_partial, OPTIONS: _options}, ), ( r"^{}/b/(?P[-.\w]+)/o/(?P.*[^/]+)$".format( settings.DOWNLOAD_API_ENDPOINT ), - {GET: objects.download}, + {GET: objects.download, OPTIONS: _options}, ), ( r"^{}$".format(settings.BATCH_API_ENDPOINT), - {POST: objects.batch}, + {POST: objects.batch, OPTIONS: _options}, ), # Internal API, not supported by the real GCS - (r"^/$", {GET: _health_check}), # Health check endpoint - (r"^/wipe$", {GET: _wipe_data}), # Wipe all data + (r"^/$", {GET: _health_check, OPTIONS: _options}), # Health check endpoint + (r"^/wipe$", {GET: _wipe_data, OPTIONS: _options}), # Wipe all data # Public file serving, same as object.download and signed URLs ( r"^/(?P[-.\w]+)/(?P.*[^/]+)$", - {GET: objects.download, PUT: objects.xml_upload}, + {GET: objects.download, PUT: objects.xml_upload, OPTIONS: _options}, ), ) @@ -377,6 +391,11 @@ def do_PATCH(self): router = Router(self) router.handle(PATCH) + def do_OPTIONS(self): + print("do_OPTIONS") + router = Router(self) + router.handle(OPTIONS) + def log_message(self, format, *args): logger.info(format % args) diff --git a/tests/test_main.py b/tests/test_main.py index 7a22a8d..fa5cbde 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -76,3 +76,13 @@ def test_path_does_not_exist(self): response = requests.get(url) self.assertEqual(response.status_code, 501) self.assertEqual(response.content, "".encode("utf-8")) + + def test_options(self): + url = self._url("/") + response = requests.options(url) + self.assertEqual(response.status_code, 200) + self.assertIn("access-control-allow-methods", response.headers) + self.assertTrue( + "GET,POST,PUT,PATCH,DELETE,OPTIONS" + in response.headers["access-control-allow-methods"] + )