diff --git a/thumbor_flexible_validation/app.py b/thumbor_flexible_validation/app.py index 35b4e0a..c7c1ee7 100644 --- a/thumbor_flexible_validation/app.py +++ b/thumbor_flexible_validation/app.py @@ -9,6 +9,12 @@ from thumbor.context import RequestParameters from urllib import quote, unquote + +RE_ENCODED_PERCENTAGE = re.compile('https?(%(25)+3A)\/\/') +RE_SINGLE_SLASH_ENCODED = re.compile('(https?%3A\/)[^/]') +RE_SINGLE_SLASH = re.compile('(https?:\/)[^/]') + + class RewriteHandler(ImagingHandler): def validate_url(self, url, security_key): valid = True @@ -49,8 +55,37 @@ def validate_image_permutations(self, kw): load_target = kw['image'] + # Undo `:` decoding + load_target_with_encoded_colon = load_target.replace(':', '%3A') + unescaped_url = "/%s/%s/%s" % (kw['hash'], url_options, load_target_with_encoded_colon) + if self.validate_url(unescaped_url, security_key): + kw['image'] = unquote(load_target_with_encoded_colon) + self.request.path = unescaped_url + return + + # Undo collapsed slashes with encoded `:` + collapsed_slash = RE_SINGLE_SLASH_ENCODED.match(load_target_with_encoded_colon) + if collapsed_slash: + load_target_with_encoded_colon = load_target_with_encoded_colon.replace(collapsed_slash.group(1), collapsed_slash.group(1) + "/") + unescaped_url = "/%s/%s/%s" % (kw['hash'], url_options, load_target_with_encoded_colon) + if self.validate_url(unescaped_url, security_key): + kw['image'] = unquote(load_target_with_encoded_colon) + self.request.path = unescaped_url + return + + # Undo %3A -> %253A encoding (can have multiple 252525...) + quoted_target = quote(load_target.encode('utf-8')) + encoded_percentage = RE_ENCODED_PERCENTAGE.match(quoted_target) + if encoded_percentage: + fixed_target = quoted_target.replace(encoded_percentage.group(1), '%3A') + fixed_url = "/%s/%s/%s" % (kw['hash'], url_options, fixed_target) + if self.validate_url(fixed_url, security_key): + kw['image'] = unquote(fixed_target) + self.request.path = fixed_url + return + # Undo collapsed slashes - collapsed_slash = re.match("(https?:\/)[^/]", load_target) + collapsed_slash = RE_SINGLE_SLASH.match(load_target) if collapsed_slash: load_target = load_target.replace(collapsed_slash.group(1), collapsed_slash.group(1) + "/") unescaped_url = "/%s/%s/%s" % (kw['hash'], url_options, load_target) @@ -60,7 +95,7 @@ def validate_image_permutations(self, kw): return # Attempt to validate with unescaped quoting - load_target = quote(load_target, safe='') + load_target = quote(load_target.encode('utf-8'), safe='') unescaped_url = "/%s/%s/%s" % (kw['hash'], url_options, load_target) if self.validate_url(unescaped_url, security_key): kw['image'] = unquote(load_target) @@ -69,7 +104,7 @@ def validate_image_permutations(self, kw): # Attempt to validate with unquoting load_target = unquote(kw['image']) - unescaped_url = "/%s/%s/%s" % (kw['hash'], url_options, quote(load_target, safe='')) + unescaped_url = "/%s/%s/%s" % (kw['hash'], url_options, quote(load_target.encode('utf-8'), safe='')) if self.validate_url(unescaped_url, security_key): self.request.path = unescaped_url kw['image'] = load_target @@ -85,6 +120,7 @@ def head(self, **kw): self.validate_image_permutations(kw) self.check_image(kw) + class ThumborServiceProxy(ThumborServiceApp): def __init__(self, context): ThumborServiceApp.__init__(self, context) @@ -99,4 +135,4 @@ def get_handlers(self): handlers.append( (Url.regex(), RewriteHandler, {'context': self.context}) ) - return handlers \ No newline at end of file + return handlers