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 test_parse_urls_without_image(): options = Url.parse_options( 'unsafe/meta/10x11:12x13/-300x-200/left/top/smart/') assert options options = Url.parse_options( '/unsafe/meta/10x11:12x13/-300x-200/left/top/smart/') assert options assert options['meta'] == True assert options['crop']['left'] == 10 assert options['crop']['top'] == 11 assert options['crop']['right'] == 12 assert options['crop']['bottom'] == 13 assert options['width'] == 300 assert options['height'] == 200 assert options['horizontal_flip'] == True assert options['vertical_flip'] == True assert options['halign'] == 'left' assert options['valign'] == 'top' assert options['smart'] == True
def test_parse_urls_without_image(): options = Url.parse_options("unsafe/meta/10x11:12x13/-300x-200/left/top/smart/") assert options options = Url.parse_options("/unsafe/meta/10x11:12x13/-300x-200/left/top/smart/") assert options assert options['meta'] == True assert options['crop']['left'] == 10 assert options['crop']['top'] == 11 assert options['crop']['right'] == 12 assert options['crop']['bottom'] == 13 assert options['width'] == 300 assert options['height'] == 200 assert options['horizontal_flip'] == True assert options['vertical_flip'] == True assert options['halign'] == 'left' assert options['valign'] == 'top' assert options['smart'] == True
def test_parse_urls_with_image(): image_url = 's.glbimg.com/es/ge/f/original/2011/03/29/orlandosilva_60.jpg' options = Url.parse('/unsafe/meta/10x11:12x13/-300x-200/left/top/smart/%s' % image_url) assert options['image'] assert options['image'] == image_url options = Url.parse('unsafe/meta/10x11:12x13/-300x-200/left/top/smart/%s' % image_url) assert options['image'] assert options['image'] == image_url
def test_parse_urls_with_image(): image_url = 's.glbimg.com/es/ge/f/original/2011/03/29/orlandosilva_60.jpg' options = Url.parse( '/unsafe/meta/10x11:12x13/-300x-200/left/top/smart/%s' % image_url) assert options['image'] assert options['image'] == image_url options = Url.parse('unsafe/meta/10x11:12x13/-300x-200/left/top/smart/%s' % image_url) assert options['image'] assert options['image'] == image_url
def encrypt(self, width, height, smart, fit_in, flip_horizontal, flip_vertical, halign, valign, crop_left, crop_top, crop_right, crop_bottom, image): url = "%s/%s" % (Url.generate_options(width, height, smart, False, fit_in, flip_horizontal, flip_vertical, halign, valign, crop_left, crop_top, crop_right, crop_bottom), hashlib.md5(image).hexdigest()) pad = lambda s: s + (16 - len(s) % 16) * "{" cipher = AES.new(self.salt) encrypted = base64.urlsafe_b64encode(cipher.encrypt(pad(url))) return encrypted
def get_handlers(self): handlers = [ (r'/', HealthcheckHandler), (r'/favicon.ico', FaviconHandler), ] if self.context.config.UPLOAD_ENABLED: # TODO: Old handler to upload images. Will be deprecated soon. handlers.append( (r'/upload', LegacyImageUploadHandler, {'context': self.context}) ) # 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(), ImagingHandlerMine, {'context': self.context}) ) return handlers
def send_data(event, result, start_time): time_now = datetime.datetime.utcnow().isoformat() time_stamp = str(time_now) postDict = {} size = '-' filters = Url.parse_decrypted(event['path']) del filters['image'] if int(result['statusCode']) == 200: size = (len(result['body'] * 3)) / 4 postDict['Data'] = { 'Version': get_distribution('image_handler').version, 'Company': 'AWS', 'Name': 'AWS Serverless Image Handler', 'Region': os.environ.get('AWS_DEFAULT_REGION'), 'Filters': filters, 'StatusCode': result['statusCode'], 'ResponseSize': size, 'ResponseTime': round(timeit.default_timer() - start_time, 3) } postDict['TimeStamp'] = time_stamp postDict['Solution'] = 'SO0023' postDict['UUID'] = os.environ.get('UUID') # API Gateway URL to make HTTP POST call url = 'https://metrics.awssolutionsbuilder.com/generic' data = json.dumps(postDict) headers = {'content-type': 'application/json'} req = Request(url, data, headers) rsp = urlopen(req) content = rsp.read() rspcode = rsp.getcode() logging.debug('Response Code: {}'.format(rspcode)) logging.debug('Response Content: {}'.format(content)) return req
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) # 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 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, MODE_ECB) encrypted = base64.urlsafe_b64encode( cipher.encrypt(pad(url.encode('utf-8')))) return encrypted
def get_handlers(self): handlers = [ (r'/healthcheck', HealthcheckHandler), ] if self.context.config.UPLOAD_ENABLED: # TODO Old handler to upload images handlers.append((r'/upload', UploadHandler, { 'context': self.context })) # Handler to upload images (POST). handlers.append((r'/image', ImagesHandler, { 'context': self.context })) # Handler to retrieve or modify existing images (GET, PUT, DELETE) handlers.append((r'/image/(.*)', ImageHandler, { 'context': self.context })) # Imaging handler (GET) handlers.append((Url.regex(), ImagingHandler, { 'context': self.context })) return handlers
def test_usage_new_format(): key = "my-security-key" image = "s.glbimg.com/et/bb/f/original/2011/03/24/VN0JiwzmOw0b0lg.jpg" thumbor_signer = Signer(key) thumbor_url = Url.generate_options(width=300, height=200, smart=True, adaptive=False, fit_in=False, horizontal_flip=False, vertical_flip=False, halign='center', valign='middle', crop_left=0, crop_top=0, crop_right=0, crop_bottom=0, filters=[]) thumbor_url = ('%s/%s' % (thumbor_url, image)).lstrip('/') thumbor_url = '/%s/%s' % (thumbor_signer.signature(thumbor_url), thumbor_url) crypto = CryptoURL(key=key) url = crypto.generate(width=300, height=200, smart=True, image_url=image) assert url == thumbor_url
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 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 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 test_usage_new_format(): key = "my-security-key" image = "s.glbimg.com/et/bb/f/original/2011/03/24/VN0JiwzmOw0b0lg.jpg" thumbor_signer = Signer(key) thumbor_url = Url.generate_options( width=300, height=200, smart=True, adaptive=False, fit_in=False, horizontal_flip=False, vertical_flip=False, halign='center', valign='middle', crop_left=0, crop_top=0, crop_right=0, crop_bottom=0, filters=[] ) thumbor_url = ('%s/%s' % (thumbor_url, image)).lstrip('/') thumbor_url = '/%s/%s' % (thumbor_signer.signature(thumbor_url), thumbor_url) crypto = CryptoURL(key=key) url = crypto.generate( width=300, height=200, smart=True, image_url=image ) assert url == thumbor_url
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()
def test_url_generate_with_alignments(): url = Url.generate_options( halign='left', valign='top' ) assert url == "0x0/left/top", url
def test_url_generate_min(): url = Url.generate_options( width=300, height=200 ) assert url == "300x200"
def get_handlers(self): handlers = [ (r'/healthcheck', HealthcheckHandler), ] if self.context.config.UPLOAD_ENABLED: # TODO: Old handler to upload images. Will be deprecated soon. handlers.append( (r'/upload', LegacyImageUploadHandler, {'context': self.context}) ) # 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(not self.context.config.SECURITY_DISABLE_KEY), 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, 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 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()
def test_returns_route_regex_with_filters(): class TestFilter(object): regex = r'some-filter-fake-regex' url = Url.regex(filters=[TestFilter]) assert url == '/?unsafe/(?:(?P<meta>meta)/)?(?:(?P<crop_left>\d+)x(?P<crop_top>\d+):(?P<crop_right>\d+)x(?P<crop_bottom>\d+)/)?(?:(?P<fit_in>fit-in)/)?(?:(?P<horizontal_flip>-)?(?P<width>\d+)?x(?P<vertical_flip>-)?(?P<height>\d+)?/)?(?:(?P<halign>left|right|center)/)?(?:(?P<valign>top|bottom|middle)/)?(?:(?P<smart>smart)/)?some-filter-fake-regex(?P<image>.+)'
def get_handlers(self): # Imaging handler (GET) return [(BuzzFeedHandler.regex(), BuzzFeedHandler, { 'context': self.context }), (Url.regex(), ImagingHandler, { 'context': self.context })]
def __init__(self, context): self.context = context handlers = [ (r'/healthcheck', HealthcheckHandler), ] if context.config.UPLOAD_ENABLED: # TODO Old handler to upload images handlers.append( (r'/upload', UploadHandler, {'context': self.context}) ) # Handler to upload images (POST). handlers.append( (r'/image', ImagesHandler, {'context': self.context}) ) # Handler to retrieve or modify existing images (GET, PUT, DELETE) handlers.append( (r'/image/(.*)', ImageHandler, {'context': self.context}) ) # Imaging handler (GET) handlers.append( (Url.regex(), ImagingHandler, {'context': self.context}) ) super(ThumborServiceApp, self).__init__(handlers)
def check_image(self, kw): if self.context.config.MAX_ID_LENGTH > 0: # Check if an image with an uuid exists in storage exists = yield gen.maybe_future( self.context.modules.storage.exists(kw["image"][: self.context.config.MAX_ID_LENGTH]) ) if exists: kw["image"] = kw["image"][: self.context.config.MAX_ID_LENGTH] url = self.request.uri if not self.validate(kw["image"]): self._error(400, "No original image was specified in the given URL") return kw["request"] = self.request kw["image"] = quote(kw["image"].encode("utf-8")) kw["config"] = self.context.config self.context.request = RequestParameters(**kw) has_none = not self.context.request.unsafe and not self.context.request.hash has_both = self.context.request.unsafe and self.context.request.hash if has_none or has_both: self._error(400, "URL does not have hash or unsafe, or has both: %s" % url) return if self.context.request.unsafe and not self.context.config.ALLOW_UNSAFE_URL: self._error(400, "URL has unsafe but unsafe is not allowed by the config: %s" % url) return if self.context.config.USE_BLACKLIST: blacklist = yield self.get_blacklist_contents() if self.context.request.image_url in blacklist: self._error(400, "Source image url has been blacklisted: %s" % self.context.request.image_url) return url_signature = self.context.request.hash if url_signature: signer = self.context.modules.url_signer(self.context.server.security_key) url_to_validate = Url.encode_url(url).replace("/%s/" % self.context.request.hash, "") valid = signer.validate(url_signature, url_to_validate) if not valid and self.context.config.STORES_CRYPTO_KEY_FOR_EACH_IMAGE: # Retrieves security key for this image if it has been seen before security_key = yield gen.maybe_future( self.context.modules.storage.get_crypto(self.context.request.image_url) ) if security_key is not None: signer = self.context.modules.url_signer(security_key) valid = signer.validate(url_signature, url_to_validate) if not valid: self._error(400, "Malformed URL: %s" % url) return self.execute_image_operations()
def test_url_generate_with_flipping(): url = Url.generate_options(width=300, height=200, smart=True, horizontal_flip=True, vertical_flip=True) assert url == '-300x-200/smart'
def test_url_generate_with_smart(): url = Url.generate_options( width=300, height=200, smart=True ) assert url == "300x200/smart"
def test_url_generate_with_manual_crop(): url = Url.generate_options(width=300, height=200, crop_left=10, crop_top=11, crop_right=12, crop_bottom=13) assert url == '10x11:12x13/300x200'
def test_url_generate_with_flipping(): url = Url.generate_options( width=300, height=200, smart=True, horizontal_flip=True, vertical_flip=True ) assert url == "-300x-200/smart"
def get_handlers(self): handlers = ThumborServiceApp.get_handlers(self) # Remove the default image handler handlers.pop() # Then install our own image handler handlers.append((Url.regex(), RewriteHandler, { 'context': self.context })) return handlers
def test_url_generate_with_manual_crop(): url = Url.generate_options( width=300, height=200, crop_left=10, crop_top=11, crop_right=12, crop_bottom=13 ) assert url == "10x11:12x13/300x200"
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 check_image(self, kw): if self.context.config.MAX_ID_LENGTH > 0: # Check if an image with an uuid exists in storage exists = yield gen.maybe_future(self.context.modules.storage.exists(kw['image'][:self.context.config.MAX_ID_LENGTH])) if exists: kw['image'] = kw['image'][:self.context.config.MAX_ID_LENGTH] url = self.request.uri if not self.validate(kw['image']): self._error(400, 'No original image was specified in the given URL') return kw['request'] = self.request kw['image'] = quote(kw['image'].encode('utf-8')) self.context.request = RequestParameters(**kw) has_none = not self.context.request.unsafe and not self.context.request.hash has_both = self.context.request.unsafe and self.context.request.hash if has_none or has_both: self._error(400, 'URL does not have hash or unsafe, or has both: %s' % url) return if self.context.request.unsafe and not self.context.config.ALLOW_UNSAFE_URL: self._error(400, 'URL has unsafe but unsafe is not allowed by the config: %s' % url) return if self.context.config.USE_BLACKLIST: blacklist = yield self.get_blacklist_contents() if self.context.request.image_url in blacklist: self._error(400, 'Source image url has been blacklisted: %s' % self.context.request.image_url) return url_signature = self.context.request.hash if url_signature: signer = self.context.modules.url_signer(self.context.server.security_key) url_to_validate = Url.encode_url(url).replace('/%s/' % self.context.request.hash, '') valid = signer.validate(url_signature, url_to_validate) if not valid and self.context.config.STORES_CRYPTO_KEY_FOR_EACH_IMAGE: # Retrieves security key for this image if it has been seen before security_key = yield gen.maybe_future(self.context.modules.storage.get_crypto(self.context.request.image_url)) if security_key is not None: signer = self.context.modules.url_signer(security_key) valid = signer.validate(url_signature, url_to_validate) if not valid: self._error(400, 'Malformed URL: %s' % url) return self.execute_image_operations()
def __init__(self, context): self.context = context handlers = [(r"/healthcheck", HealthcheckHandler)] if context.config.ENABLE_ORIGINAL_PHOTO_UPLOAD: handlers.append((r"/upload", UploadHandler, {"context": context})) handlers.append((Url.regex(), ImageProcessHandler, {"context": context})) super(ThumborServiceApp, self).__init__(handlers)
def decrypt(self, encrypted): cipher = AES.new(self.salt) debased = base64.urlsafe_b64decode(encrypted.encode("utf-8")) decrypted = cipher.decrypt(debased).rstrip('{') result = Url.parse('/%s' % decrypted, with_unsafe=False) result['image_hash'] = result['image'] del result['image'] return result
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_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_complete_url(): url = Url.generate_options(width=300, height=200, smart=True, meta=True, horizontal_flip=True, vertical_flip=True, crop_left=10, crop_top=11, crop_right=12, crop_bottom=13) assert url == 'meta/10x11:12x13/-300x-200/smart', url
def __init__(self, context): self.context = context handlers = [ (r'/healthcheck', HealthcheckHandler), ] if context.config.UPLOAD_ENABLED: handlers.append( (r'/upload', UploadHandler, { 'context': context }) ) handlers.append( (Url.regex(has_unsafe_or_hash=False,with_save=True), SaveHandler, { 'context': context }) ) handlers.append( (Url.regex(), ImageProcessHandler, { 'context': context }) ) super(ThumborServiceApp, self).__init__(handlers)
def __init__(self, conf_file=None, security_key=None, custom_handlers=None): if conf_file is None: conf_file = ThumborServiceApp.get_conf_file(conf_file) logger.info('Config file: %s' % conf_file) parse_config_file(conf_file) self.loader = real_import(conf.LOADER) self.storage = real_import(conf.STORAGE) self.engine = real_import(conf.ENGINE) self.detectors = [real_import(detector_name).Detector for detector_name in conf.DETECTORS] self.filters = [real_import(filter_name).Filter for filter_name in conf.FILTERS] filters.compile_filters(self.filters) # run again to overwrite the default settings on the # imported modules with the ones defined into the config file parse_config_file(conf_file) #storage = storage.Storage() #self.engine = self.engine.Engine() if security_key: options.SECURITY_KEY = security_key handler_context = { 'loader': self.loader, 'storage': self.storage, 'engine': self.engine, 'detectors': self.detectors, 'filters': self.filters } handlers = [ (r'/healthcheck', HealthcheckHandler) ] if conf.ALLOW_UNSAFE_URL: handlers.append( (Url.regex(), MainHandler, handler_context), ) if custom_handlers: for handler in custom_handlers: handlers.append((handler[0], handler[1], handler_context)) else: handlers.append( (r'/(?P<crypto>[^/]+)/(?P<image>(.+))', CryptoHandler, handler_context) ) super(ThumborServiceApp, self).__init__(handlers)
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() 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 encrypt(self, width, height, smart, fit_in, flip_horizontal, flip_vertical, halign, valign, crop_left, crop_top, crop_right, crop_bottom, image): url = "%s/%s" % (Url.generate_options( width, height, smart, False, fit_in, flip_horizontal, flip_vertical, halign, valign, crop_left, crop_top, crop_right, crop_bottom), hashlib.md5(image).hexdigest()) pad = lambda s: s + (16 - len(s) % 16) * "{" cipher = AES.new(self.salt) encrypted = base64.urlsafe_b64encode(cipher.encrypt(pad(url))) return encrypted
def __init__(self, context): self.context = context handlers = [(r'/healthcheck', HealthcheckHandler)] if context.config.ALLOW_UNSAFE_URL: handlers.append((Url.regex(), MainHandler, {'context': context}), ) handlers.append( (r'/(?P<crypto>[^/]+)/(?P<image>(?:.+))', CryptoHandler, { 'context': context })) super(ThumborServiceApp, self).__init__(handlers)
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("/%s" % decrypted) result["image_hash"] = result["image"] del result["image"] return result
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 decrypt(self, encrypted): cipher = AES.new(self.security_key, MODE_ECB) 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 __init__(self, context): self.context = context handlers = [ (r'/healthcheck', HealthcheckHandler), ] if context.config.UPLOAD_ENABLED: handlers.append((r'/upload', UploadHandler, {'context': context})) handlers.append((Url.regex(), ImageProcessHandler, { 'context': context })) super(ThumborServiceApp, self).__init__(handlers)
def test_complete_url(): url = Url.generate_options( width=300, height=200, smart=True, meta=True, horizontal_flip=True, vertical_flip=True, crop_left=10, crop_top=11, crop_right=12, crop_bottom=13 ) assert url == "meta/10x11:12x13/-300x-200/smart", url
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, MODE_ECB) encrypted = base64.urlsafe_b64encode(cipher.encrypt(pad(url.encode('utf-8')))) return encrypted
def get(self, url): url = url.encode('utf-8') image = "" if self.context.config.get('SHORTENER_GENERATOR_PRESERVE_NAME'): reg = re.compile(Url.regex()) result = reg.match(url) if result is None: raise ValueError("URL does not match thumbor's URL pattern") result = result.groupdict() image = "/{image}".format(image=os.path.basename(result['image'])) return "{hash}{image}".format(hash=self.shorten(url), image=image)
def __init__(self, conf_file=None, custom_handlers=None): if conf_file is None: conf_file = ThumborServiceApp.get_conf_file(conf_file) logger.info('Config file: %s' % conf_file) parse_config_file(conf_file) loader = real_import(options.LOADER) storage = real_import(options.STORAGE) engine = real_import(options.ENGINE) detectors = [ real_import(detector_name).Detector for detector_name in options.DETECTORS ] filters = [ real_import(filter_name).Filter for filter_name in options.FILTERS ] # run again to overwrite the default settings on the # imported modules with the ones defined into the config file parse_config_file(conf_file) storage = storage.Storage() engine = engine.Engine() handler_context = { 'loader': loader, 'storage': storage, 'engine': engine, 'detectors': detectors, 'filters': filters } handlers = [(r'/healthcheck', HealthcheckHandler)] if options.ALLOW_UNSAFE_URL: handlers.append((Url.regex(), MainHandler, handler_context), ) if custom_handlers: for handler in custom_handlers: handlers.append((handler[0], handler[1], handler_context)) else: handlers.append((r'/(?P<crypto>[^/]+)/(?P<image>(.+))', EncryptedHandler, handler_context)) super(ThumborServiceApp, self).__init__(handlers)
def __init__(self, context): self.context = context handlers = [ (r'/healthcheck', HealthcheckHandler) ] if context.config.ALLOW_UNSAFE_URL: handlers.append( (Url.regex(), MainHandler, { 'context': context }), ) handlers.append( (r'/(?P<crypto>[^/]+)/(?P<image>(?:.+))', CryptoHandler, { 'context': context }) ) super(ThumborServiceApp, self).__init__(handlers)
def get_handlers(self): handlers = [(r"/healthcheck", HealthcheckHandler)] if self.context.config.UPLOAD_ENABLED: # TODO Old handler to upload images handlers.append((r"/upload", UploadHandler, {"context": self.context})) # Handler to upload images (POST). handlers.append((r"/image", ImagesHandler, {"context": self.context})) # Handler to retrieve or modify existing images (GET, PUT, DELETE) handlers.append((r"/image/(.*)", ImageHandler, {"context": self.context})) # Imaging handler (GET) handlers.append((Url.regex(), ImagingHandler, {"context": self.context})) return handlers
def decrypt(self, encrypted): cipher = AES.new(self.salt) debased = base64.urlsafe_b64decode(encrypted) decrypted = cipher.decrypt(debased).rstrip('{') if not decrypted or not urlparse.urlparse(decrypted): return None result = Url.parse('/%s' % decrypted, with_unsafe=False) if not result: return None result['image_hash'] = result['image'] del result['image'] return result
def get(self, **kw): hash = kw['hash'] url = kw['image'] unsafe = kw['unsafe'] try: decrypted = decode_uri(url, self.cipher, is_decrypted=True) except Exception as e: logger = logging.getLogger(__name__) logger.error(u"GetError: %s" % str(e)) return self.check_image(kw) kwargs = Url.parse_decrypted(decrypted) kwargs['hash'] = hash kwargs['unsafe'] = unsafe crop = kwargs.pop("crop", {}) kwargs.update({"crop_{0}".format(key): crop[key] for key in ["bottom", "left", "right", "top"] if crop.has_key(key)}) self.check_image(kwargs)