Skip to content

Commit 04e58b6

Browse files
committed
Update API router code to throw a more user-friendly exception in case
the request URL contains invalid or incorrectly URL encoded characters. Previously we didn't handle this scenario which means original UnicodeDecodeError error with the stack trace got propagated to the user which is a no go. Related issue - GoogleCloudPlatform/webapp2#152.
1 parent f3a92b1 commit 04e58b6

File tree

3 files changed

+43
-1
lines changed

3 files changed

+43
-1
lines changed

CHANGELOG.rst

+9
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,15 @@ Fixed
2828

2929
Contributed by @Kami.
3030

31+
* Fix a bug in the API router code and make sure we return correct and user-friendly error to the
32+
user in case we fail to parse the request URL / path because it contains invalid or incorrectly
33+
URL encoded data.
34+
35+
Previously such errors weren't handled correctly which meant original exception with a stack
36+
trace got propagated to the user. (bug fix) #5189
37+
38+
Contributed by @Kami.
39+
3140
Changed
3241
~~~~~~~
3342

st2common/st2common/middleware/instrumentation.py

+12
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
from st2common.metrics.base import get_driver
2121
from st2common.util.date import get_datetime_utc_now
2222
from st2common.router import NotFoundException
23+
from st2common.router import Response
24+
from st2common.util.jsonify import json_encode
2325

2426
__all__ = ["RequestInstrumentationMiddleware", "ResponseInstrumentationMiddleware"]
2527

@@ -47,6 +49,16 @@ def __call__(self, environ, start_response):
4749
endpoint, _ = self.router.match(request)
4850
except NotFoundException:
4951
endpoint = {}
52+
except Exception as e:
53+
# Special case to make sure we return friendly error to the user.
54+
# If we don't do that and router.match() throws an exception, we will return stack trace
55+
# to the end user which is not good.
56+
status_code = getattr(e, "status_code", 500)
57+
headers = {"Content-Type": "application/json"}
58+
body = {"faultstring": getattr(e, "detail", str(e))}
59+
response_body = json_encode(body)
60+
resp = Response(response_body, status=status_code, headers=headers)
61+
return resp(environ, start_response)
5062

5163
# NOTE: We don't track per request and response metrics for /v1/executions/<id> and some
5264
# other endpoints because this would result in a lot of unique metrics which is an

st2common/st2common/router.py

+22-1
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,28 @@ def match(self, req):
242242
# NOTE: webob.url_unquote doesn't work correctly under Python 3 when paths contain non-ascii
243243
# characters. That method supposed to handle Python 2 and Python 3 compatibility, but it
244244
# doesn't work correctly under Python 3.
245-
path = urllib.parse.unquote(req.path)
245+
try:
246+
path = urllib.parse.unquote(req.path)
247+
except Exception as e:
248+
# This exception being thrown indicates that the URL / path contains bad or incorrectly
249+
# URL escaped characters. Instead of returning this stack track + 500 error to the
250+
# user we return a friendly and more correct exception
251+
# NOTE: We should not access or log req.path here since it's a property which results
252+
# in exception and if we try to log it, it will fail.
253+
try:
254+
path = req.environ["PATH_INFO"]
255+
except Exception:
256+
path = "unknown"
257+
258+
LOG.error('Failed to parse request URL / path "%s": %s' % (path, str(e)))
259+
260+
abort(
261+
400,
262+
'Failed to parse request path "%s". URL likely contains invalid or incorrectly '
263+
"URL encoded values." % (path),
264+
)
265+
return
266+
246267
LOG.debug("Match path: %s", path)
247268

248269
if len(path) > 1 and path.endswith("/"):

0 commit comments

Comments
 (0)