def test_parsing_complete_url(self): url = ( "/debug/meta/trim/300x200:400x500/adaptive-full-fit-in/-300x-400/" "left/top/smart/filters:brightness(100)/some/image.jpg" ) expected = { "trim": "trim", "full": True, "halign": "left", "fit_in": True, "vertical_flip": True, "image": "some/image.jpg", "crop": {"top": 200, "right": 400, "bottom": 500, "left": 300}, "height": 400, "width": 300, "meta": True, "horizontal_flip": True, "filters": "brightness(100)", "valign": "top", "debug": True, "adaptive": True, "smart": True, } result = Url.parse_decrypted(url) expect(result).not_to_be_null() expect(result).to_be_like(expected) # do it again to use compiled regex result = Url.parse_decrypted(url) expect(result).not_to_be_null() expect(result).to_be_like(expected)
def get_handlers(self): handlers = [ (self.context.config.HEALTHCHECK_ROUTE, HealthcheckHandler), ] if self.context.config.UPLOAD_ENABLED: # Handler to upload images (POST). handlers.append((r"/image", ImageUploadHandler, { "context": self.context })) # Handler to retrieve or modify existing images (GET, PUT, DELETE) handlers.append(( r"/image/(.*)", ImageResourceHandler, { "context": self.context }, )) if self.context.config.USE_BLACKLIST: handlers.append((r"/blacklist", BlacklistHandler, { "context": self.context })) # Imaging handler (GET) handlers.append((Url.regex(), ImagingHandler, { "context": self.context })) return handlers
def test_can_generate_url(self): url = Url.generate_options( debug=True, width=300, height=200, smart=True, meta=True, trim=True, adaptive=True, full=True, fit_in=True, horizontal_flip=True, vertical_flip=True, halign="left", valign="top", crop_left=100, crop_top=100, crop_right=400, crop_bottom=400, auth= "requester(CVZzFCbz4Rcf2Lmu9mvtC1CmvPukHy5kS2LNtNaBFM2N):contract(Du2kswW2h1gNVnTWdfNdSxBrC2F9ofoaZsXA6ki1PhG6):document(profile):field(avatarURL):owner(8vagK6r9BnYct3k8WWYXNFadY1hG8TjikR3SfXceFnRQ):updatedAt(1602042152761)", filters="brightness(100)", ) expect(url).to_equal( "debug/meta/trim/100x100:400x400/adaptive-full-fit-in/-300x-200/left/top/smart/auth:requester(CVZzFCbz4Rcf2Lmu9mvtC1CmvPukHy5kS2LNtNaBFM2N):contract(Du2kswW2h1gNVnTWdfNdSxBrC2F9ofoaZsXA6ki1PhG6):document(profile):field(avatarURL):owner(8vagK6r9BnYct3k8WWYXNFadY1hG8TjikR3SfXceFnRQ):updatedAt(1602042152761)/filters:brightness(100)" )
def get_handlers(self): handlers = [ (r'/healthcheck', HealthcheckHandler), ] if self.context.config.UPLOAD_ENABLED: # Handler to upload images (POST). handlers.append( (r'/image', ImageUploadHandler, {'context': self.context}) ) # Handler to retrieve or modify existing images (GET, PUT, DELETE) handlers.append( (r'/image/(.*)', ImageResourceHandler, {'context': self.context}) ) if self.context.config.USE_BLACKLIST: handlers.append( (r'/blacklist', BlacklistHandler, {'context': self.context}) ) # Imaging handler (GET) handlers.append( (Url.regex(), ImagingHandler, {'context': self.context}) ) return handlers
def path_to_parameters(cls, path): ''' :param path: url path :return: A dictionary of parameters to be used with ImagingHandler instances ''' if not cls._url_regex: cls._url_regex = re.compile(Url.regex()) if cls._url_regex.groups: match = cls._url_regex.match(path) # See https://github.com/tornadoweb/tornado/blob/01c78ebfcc993ff4f1d8336c2c45844fe9dab60e/tornado/web.py#L1951 # Pass matched groups to the handler. Since # match.groups() includes both named and # unnamed groups, we want to use either groups # or groupdict but not both. if cls._url_regex.groupindex: parameters = dict((str(k), _unquote_or_none(v)) for (k, v) in match.groupdict().items()) else: parameters = [_unquote_or_none(s) for s in match.groups()] else: parameters = dict() return parameters
def test_can_generate_url(self): url = Url.generate_options( debug=True, width=300, height=200, smart=True, meta=True, trim=True, adaptive=True, full=True, fit_in=True, horizontal_flip=True, vertical_flip=True, halign="left", valign="top", crop_left=100, crop_top=100, crop_right=400, crop_bottom=400, filters="brightness(100)", ) expect(url).to_equal( "debug/meta/trim/100x100:400x400/adaptive-full-fit-in/-300x-200/left/top/smart/filters:brightness(100)" )
def encrypt(self, width, height, smart, adaptive, full, fit_in, flip_horizontal, flip_vertical, halign, valign, trim, crop_left, crop_top, crop_right, crop_bottom, filters, image): generated_url = Url.generate_options(width=width, height=height, smart=smart, meta=False, adaptive=adaptive, full=full, fit_in=fit_in, horizontal_flip=flip_horizontal, vertical_flip=flip_vertical, halign=halign, valign=valign, trim=trim, crop_left=crop_left, crop_top=crop_top, crop_right=crop_right, crop_bottom=crop_bottom, filters=filters) url = "%s/%s" % (generated_url, hashlib.md5(image).hexdigest()) def pad(s): return s + (16 - len(s) % 16) * "{" cipher = AES.new(self.security_key) encrypted = base64.urlsafe_b64encode( cipher.encrypt(pad(url.encode('utf-8')))) return encrypted
def path_to_parameters(cls, path): ''' :param path: url path :return: A dictionary of parameters to be used with ImagingHandler instances ''' if not cls._url_regex: cls._url_regex = re.compile(Url.regex()) if cls._url_regex.groups: match = cls._url_regex.match(path) # See https://github.com/tornadoweb/tornado/blob/01c78ebfcc993ff4f1d8336c2c45844fe9dab60e/tornado/web.py#L1951 # Pass matched groups to the handler. Since # match.groups() includes both named and # unnamed groups, we want to use either groups # or groupdict but not both. if cls._url_regex.groupindex: parameters = dict( (str(k), tornado.web._unquote_or_none(v)) for (k, v) in match.groupdict().items()) else: parameters = [ tornado.web._unquote_or_none(s) for s in match.groups() ] else: parameters = dict() return parameters
def post(self, **kw): paths = self.get_arguments('paths[]') if len(paths) > MultiHandler.paths_limit: self.set_status(400) super(MultiHandler, self).write( 'Too many paths: %d max' % MultiHandler.paths_limit ) super(MultiHandler, self).finish() return for path in paths: request = HTTPServerRequest( method='GET', uri=path, host=self.request.host, connection=self.request.connection ) handler = MultiHandler( self.application, request, context=self.context ) # Copy over the storage as-is, which allows those requests to # share storage if needed (useful for request-storage) handler.context.modules.storage = self.context.modules.storage m = re.match(Url.regex(), path) yield handler.check_image(m.groupdict()) # Close the request ASAP, the work is to be done async self.set_status(200) super(MultiHandler, self).finish()
class Filter(BaseFilter): phase = PHASE_PRE_LOAD domain_regex = re.compile(r"^(https?://)?.*?/") url_regex = re.compile(Url.regex()) def parse_url(self, url): level = 0 while level < MAX_LEVEL: url = self.domain_regex.sub("", url) result = self.url_regex.match(url) if not result: return None parts = result.groupdict() image = parts.get("image", None) if not (image and (parts.get("hash", None) or parts.get("unsafe", None))): return None top, right, left, bottom = ( parts.get("crop_top", None), parts.get("crop_right", None), parts.get("crop_left", None), parts.get("crop_bottom", None), ) if top and right and left and bottom: return (image, top, right, left, bottom) url = image level += 1 return None @filter_method() async def extract_focal(self): parts = self.parse_url(self.context.request.image_url) if parts: image, top, right, left, bottom = parts top, right, left, bottom = ( int(top), int(right), int(left), int(bottom), ) width = right - left height = bottom - top self.context.request.focal_points.append( FocalPoint.from_square(left, top, width, height, origin="Original Extraction")) self.context.request.image_url = image
def test_can_get_handlers(self): ctx = mock.Mock(config=mock.Mock( UPLOAD_ENABLED=False, USE_BLACKLIST=False, )) app = ThumborServiceApp(ctx) handlers = app.get_handlers() expect(handlers).to_length(2) expect(handlers[0][0]).to_equal(r'/healthcheck') expect(handlers[1][0]).to_equal(Url.regex())
def test_can_get_handlers(self): ctx = self.get_context() ctx.config.UPLOAD_ENABLED = False ctx.config.USE_BLACKLIST = False ctx.config.HEALTHCHECK_ROUTE = "/health" app = ThumborServiceApp(ctx) handlers = app.get_handlers() expect(handlers).to_length(2) expect(handlers[0][0]).to_equal(r"/health") expect(handlers[1][0]).to_equal(Url.regex())
def test_can_get_regex_without_unsafe(self): regex = Url.regex(False) expect(regex).to_equal( '/?(?:(?P<debug>debug)/)?(?:(?P<meta>meta)/)?' '(?:(?P<trim>trim(?::(?:top-left|bottom-right))?(?::\\d+)?)/)?' '(?:(?P<crop_left>\\d+)x(?P<crop_top>\\d+):(?P<crop_right>\\d+)x(?P<crop_bottom>\\d+)/)?' '(?:(?P<adaptive>adaptive-)?(?P<full>full-)?(?P<fit_in>fit-in)/)?(?:(?P<horizontal_flip>-)?' '(?P<width>(?:\\d+|orig))?x(?P<vertical_flip>-)?(?P<height>(?:\\d+|orig))?/)?' '(?:(?P<halign>left|right|center)/)?(?:(?P<valign>top|bottom|middle)/)?' '(?:(?P<smart>smart)/)?(?:filters:(?P<filters>.+?\\))/)?(?P<image>.+)' )
def test_can_get_regex_without_unsafe(self): regex = Url.regex(False) expect(regex).to_equal( "/?(?:(?P<debug>debug)/)?(?:(?P<meta>meta)/)?" "(?:(?P<trim>trim(?::(?:top-left|bottom-right))?(?::\\d+)?)/)?" "(?:(?P<crop_left>\\d+)x(?P<crop_top>\\d+):(?P<crop_right>\\d+)x(?P<crop_bottom>\\d+)/)?" "(?:(?P<adaptive>adaptive-)?(?P<full>full-)?(?P<fit_in>fit-in)/)?(?:(?P<horizontal_flip>-)?" "(?P<width>(?:\\d+|orig))?x(?P<vertical_flip>-)?(?P<height>(?:\\d+|orig))?/)?" "(?:(?P<halign>left|right|center)/)?(?:(?P<valign>top|bottom|middle)/)?" "(?:(?P<smart>smart)/)?(?:filters:(?P<filters>.+?\\))/)?(?P<image>.+)" )
def get_handlers(self): handlers = [] for handler_list in self.context.modules.importer.handler_lists: get_handlers = getattr(handler_list, 'get_handlers', None) if get_handlers is None: continue handlers.extend(get_handlers(self.context)) # Imaging handler (GET) handlers.append((Url.regex(), ImagingHandler, {"context": self.context})) return handlers
def test_parsing_complete_url(self): url = ( "/debug/meta/trim/300x200:400x500/adaptive-full-fit-in/-300x-400/" "left/top/smart/auth:requester(CVZzFCbz4Rcf2Lmu9mvtC1CmvPukHy5kS2LNtNaBFM2N):contract(Du2kswW2h1gNVnTWdfNdSxBrC2F9ofoaZsXA6ki1PhG6):document(profile):field(avatarURL):owner(8vagK6r9BnYct3k8WWYXNFadY1hG8TjikR3SfXceFnRQ):updatedAt(1602042152761)/filters:brightness(100)/some/image.jpg" ) expected = { "trim": "trim", "full": True, "halign": "left", "fit_in": True, "vertical_flip": True, "image": "some/image.jpg", "crop": { "top": 200, "right": 400, "bottom": 500, "left": 300 }, "height": 400, "width": 300, "meta": True, "horizontal_flip": True, "auth": "requester(CVZzFCbz4Rcf2Lmu9mvtC1CmvPukHy5kS2LNtNaBFM2N):contract(Du2kswW2h1gNVnTWdfNdSxBrC2F9ofoaZsXA6ki1PhG6):document(profile):field(avatarURL):owner(8vagK6r9BnYct3k8WWYXNFadY1hG8TjikR3SfXceFnRQ):updatedAt(1602042152761)", "filters": "brightness(100)", "valign": "top", "debug": True, "adaptive": True, "smart": True, } result = Url.parse_decrypted(url) expect(result).not_to_be_null() expect(result).to_be_like(expected) # do it again to use compiled regex result = Url.parse_decrypted(url) expect(result).not_to_be_null() expect(result).to_be_like(expected)
def test_can_get_handlers(self): ctx = mock.Mock( config=mock.Mock( UPLOAD_ENABLED=False, USE_BLACKLIST=False, ) ) app = ThumborServiceApp(ctx) handlers = app.get_handlers() expect(handlers).to_length(2) expect(handlers[0][0]).to_equal(r'/healthcheck') expect(handlers[1][0]).to_equal(Url.regex())
def test_parsing_complete_url(self): url = ( "/debug/meta/trim/300x200:400x500/adaptive-full-fit-in/-300x-400/" "left/top/smart/filters:brightness(100)/some/image.jpg") expected = { "trim": "trim", "full": True, "halign": "left", "fit_in": True, "vertical_flip": True, "image": "some/image.jpg", "crop": { "top": 200, "right": 400, "bottom": 500, "left": 300 }, "height": 400, "width": 300, "meta": True, "horizontal_flip": True, "filters": "brightness(100)", "valign": "top", "debug": True, "adaptive": True, "smart": True, } result = Url.parse_decrypted(url) expect(result).not_to_be_null() expect(result).to_be_like(expected) # do it again to use compiled regex result = Url.parse_decrypted(url) expect(result).not_to_be_null() expect(result).to_be_like(expected)
def decrypt(self, encrypted): cipher = AES.new(self.security_key) try: debased = base64.urlsafe_b64decode(encrypted.encode("utf-8")) decrypted = cipher.decrypt(debased).rstrip('{') except TypeError: return None result = Url.parse_decrypted('/%s' % decrypted) result['image_hash'] = result['image'] del result['image'] return result
def test_parsing_complete_url(self): url = '/debug/meta/trim/300x200:400x500/adaptive-full-fit-in/-300x-400/' \ 'left/top/smart/filters:brightness(100)/some/image.jpg' expected = { 'trim': 'trim', 'full': True, 'halign': 'left', 'fit_in': True, 'vertical_flip': True, 'image': 'some/image.jpg', 'crop': { 'top': 200, 'right': 400, 'bottom': 500, 'left': 300 }, 'height': 400, 'width': 300, 'meta': True, 'horizontal_flip': True, 'filters': 'brightness(100)', 'valign': 'top', 'debug': True, 'adaptive': True, 'smart': True, } result = Url.parse_decrypted(url) expect(result).not_to_be_null() expect(result).to_be_like(expected) # do it again to use compiled regex result = Url.parse_decrypted(url) expect(result).not_to_be_null() expect(result).to_be_like(expected)
def encrypt(self, width, height, smart, adaptive, full, fit_in, flip_horizontal, flip_vertical, halign, valign, trim, crop_left, crop_top, crop_right, crop_bottom, filters, image): generated_url = Url.generate_options( width=width, height=height, smart=smart, meta=False, adaptive=adaptive, full=full, fit_in=fit_in, horizontal_flip=flip_horizontal, vertical_flip=flip_vertical, halign=halign, valign=valign, trim=trim, crop_left=crop_left, crop_top=crop_top, crop_right=crop_right, crop_bottom=crop_bottom, filters=filters ) url = "%s/%s" % (generated_url, hashlib.md5(image).hexdigest()) def pad(s): return s + (16 - len(s) % 16) * "{" cipher = AES.new(self.security_key) encrypted = base64.urlsafe_b64encode(cipher.encrypt(pad(url.encode('utf-8')))) return encrypted
def test_can_generate_url_with_custom_trim(self): url = Url.generate_options(debug=True, width=300, height=200, smart=True, meta=True, trim='300x200', adaptive=True, full=True, fit_in=True, horizontal_flip=True, vertical_flip=True, halign='left', valign='top', crop_left=100, crop_top=100, crop_right=400, crop_bottom=400, filters='brightness(100)') expect(url).to_equal( 'debug/meta/trim:300x200/100x100:400x400/adaptive-full-fit-in/-300x-200/left/top/smart/filters:brightness(100)' )
def test_can_generate_url_with_fitin(self): url = Url.generate_options(fit_in=True, adaptive=False, full=False) expect(url).to_equal('fit-in')
def test_can_generate_url_with_defaults(self): url = Url.generate_options() expect(url).to_be_empty()
def test_can_generate_url_with_fitin(self): url = Url.generate_options(fit_in=True, adaptive=False, full=False) expect(url).to_equal("fit-in")
def check_image(self, kw): result = re.match(Url.regex(), kw['request']) return super(CoreHandler, self).check_image(result.groupdict())
def test_parsing_invalid_url(self): expect(Url.compiled_regex).to_be_null() url = "" expect(Url.parse_decrypted(url)).to_be_null()