From adbcd1367c3945e668ce1a22e67d88844573b30c Mon Sep 17 00:00:00 2001 From: lorenzori Date: Fri, 11 Jun 2021 12:04:21 +0200 Subject: [PATCH] Case insensitive query parameters (#321) * GetCapabilities returns a 404 * make only query params lowercase * changed middleware to LowerCaseQueryStringMiddleware and made it optional with API setting. * changed middleware to LowerCaseQueryStringMiddleware and made it optional with API setting. Only lowercase of query keys. * changed middleware to LowerCaseQueryStringMiddleware and made it optional with API setting. Hack to lowercase only query keys. * add test case * linting * update changelog Co-authored-by: vincentsarago --- CHANGES.md | 4 +++ .../application/tests/test_case_middleware.py | 28 +++++++++++++++++++ .../application/titiler/application/main.py | 4 +++ .../titiler/application/middleware.py | 21 ++++++++++++++ .../titiler/application/settings.py | 2 ++ 5 files changed, 59 insertions(+) create mode 100644 titiler/application/tests/test_case_middleware.py diff --git a/CHANGES.md b/CHANGES.md index 55191862e..617ac2498 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,10 @@ * update `tilejson` and `WMTSCapabilities.xml` endpoints to allow list querystrings (as done previously in https://github.com/developmentseed/titiler/issues/319) +### titiler.application + +* add `titiler.application.middleware.LowerCaseQueryStringMiddleware` to cast all query string parameter to lowercase (author @lorenzori, https://github.com/developmentseed/titiler/pull/321) + ## 0.3.2 (2021-05-26) ### titiler.core diff --git a/titiler/application/tests/test_case_middleware.py b/titiler/application/tests/test_case_middleware.py new file mode 100644 index 000000000..3fcc125fe --- /dev/null +++ b/titiler/application/tests/test_case_middleware.py @@ -0,0 +1,28 @@ +"""Test titiler.application.middleware.LowerCaseQueryStringMiddleware.""" + + +from titiler.application.middleware import LowerCaseQueryStringMiddleware + +from fastapi import FastAPI, Query + +from starlette.testclient import TestClient + + +def test_lowercase_middleware(): + """Make sure upper and lower case QS are accepted.""" + app = FastAPI() + + @app.get("/route1") + async def route1(value: str = Query(...)): + """route1.""" + return {"value": value} + + app.add_middleware(LowerCaseQueryStringMiddleware) + + client = TestClient(app) + + response = client.get("/route1?value=lorenzori") + assert response.json() == {"value": "lorenzori"} + + response = client.get("/route1?VALUE=lorenzori") + assert response.json() == {"value": "lorenzori"} diff --git a/titiler/application/titiler/application/main.py b/titiler/application/titiler/application/main.py index e028a2522..d5ed02a00 100644 --- a/titiler/application/titiler/application/main.py +++ b/titiler/application/titiler/application/main.py @@ -8,6 +8,7 @@ from titiler.application.middleware import ( CacheControlMiddleware, LoggerMiddleware, + LowerCaseQueryStringMiddleware, TotalTimeMiddleware, ) from titiler.application.routers import cog, mosaic, stac, tms @@ -71,6 +72,9 @@ app.add_middleware(LoggerMiddleware, headers=True, querystrings=True) app.add_middleware(TotalTimeMiddleware) +if api_settings.lower_case_query_parameters: + app.add_middleware(LowerCaseQueryStringMiddleware) + @app.get("/healthz", description="Health Check", tags=["Health Check"]) def ping(): diff --git a/titiler/application/titiler/application/middleware.py b/titiler/application/titiler/application/middleware.py index 17b67a06a..490e98351 100644 --- a/titiler/application/titiler/application/middleware.py +++ b/titiler/application/titiler/application/middleware.py @@ -88,3 +88,24 @@ async def dispatch(self, request: Request, call_next): response = await call_next(request) return response + + +class LowerCaseQueryStringMiddleware(BaseHTTPMiddleware): + """Middleware to make URL parameters case-insensitive. + taken from: https://github.com/tiangolo/fastapi/issues/826 + """ + + async def dispatch(self, request: Request, call_next): + """dispatch request.""" + + self.DECODE_FORMAT = "latin-1" + + query_string = "" + for k in request.query_params: + query_string += k.lower() + "=" + request.query_params[k] + "&" + + query_string = query_string[:-1] + request.scope["query_string"] = query_string.encode(self.DECODE_FORMAT) + + response = await call_next(request) + return response diff --git a/titiler/application/titiler/application/settings.py b/titiler/application/titiler/application/settings.py index 6a4909a5d..8d32c247b 100644 --- a/titiler/application/titiler/application/settings.py +++ b/titiler/application/titiler/application/settings.py @@ -15,6 +15,8 @@ class ApiSettings(pydantic.BaseSettings): disable_stac: bool = False disable_mosaic: bool = False + lower_case_query_parameters: bool = False + @pydantic.validator("cors_origins") def parse_cors_origin(cls, v): """Parse CORS origins."""