Esempio n. 1
0
 def __init__(self, config="config.json"):
     # Only called once, so can do config in init 
     self.config = self.make_config(config)
  
     # Make our Resolver and Cacher
     self.resolverClass = FileSystemResolver
     self.resolver = self.resolverClass(self)
     self.cacherClass = FileSystemCacher
     self.cacher = self.cacherClass(self)
     if self.config.AUTH_TYPE == "oauth":
         self.auth = OAuthHandler(self)
     elif self.config.AUTH_TYPE == "basic":
         self.auth = BasicAuthHandler(self) 
     else:
         self.auth = NullAuthHandler(self)
Esempio n. 2
0
    def __init__(self, config="config.json"):
        # Only called once, so can do config in init
        self.config = self.make_config(config)

        # Make our Resolver and Cacher
        self.resolverClass = FileSystemResolver
        self.resolver = self.resolverClass(self)
        self.cacherClass = FileSystemCacher
        self.cacher = self.cacherClass(self)
        if self.config.AUTH_TYPE == "oauth":
            self.auth = OAuthHandler(self)
        elif self.config.AUTH_TYPE == "basic":
            self.auth = BasicAuthHandler(self)
        else:
            self.auth = NullAuthHandler(self)
Esempio n. 3
0
class ImageApp(object):

    def __init__(self, config="config.json"):
        # Only called once, so can do config in init 
        self.config = self.make_config(config)
     
        # Make our Resolver and Cacher
        self.resolverClass = FileSystemResolver
        self.resolver = self.resolverClass(self)
        self.cacherClass = FileSystemCacher
        self.cacher = self.cacherClass(self)
        if self.config.AUTH_TYPE == "oauth":
            self.auth = OAuthHandler(self)
        elif self.config.AUTH_TYPE == "basic":
            self.auth = BasicAuthHandler(self) 
        else:
            self.auth = NullAuthHandler(self)

    def make_config(self, path):
        fh = file(path)
        data = fh.read()
        fh.close()
        info = json.loads(data)
        return Config(info)

    def send(self, data, status=200, ct="text/plain"):
        response["content_type"] = ct
        response.status = status
        return data

    def handle(self, path):
        # We are responsible for processing the URL into params
        # And checking auth at the appropriate times
        # ImageRequest looks after the params and image

        cf = self.config
        # First check auth
        isAuthed = self.auth.check_auth()
        response['Access-Control-Allow-Origin'] =  '*'
        response['Link'] = '<{0}>;rel="profile"'.format(cf.compliance)

        # Assumption is that there really is no / (even encoded) in an identifier
        # Parse the path
        bits = path.split('/')

        imgReq = ImageRequest(self, path)
        identifier  = imgReq.make_param('identifier', bits.pop(0))
        filename = self.resolver.resolve_identifier(identifier)
 
        if not bits or not bits[0]:
            # Redirect identifier or identifier/ to info.json
            redirect("{0}{1}/info.json".format(cf.BASEPREF, identifier.requested), 303)

        isInfoRequest = bits[-1] == "info.json"
        if not isAuthed and not identifier.endswith(cf.DEGRADED_IDENTIFIER):
            if isInfoRequest:
                if cf.DEGRADED_NOACCESS:
                    # No access is special degraded
                    redirect('{0}{1}/info.json'.format(cf.BASEPREF, cf.AUTH_URL_NOACCESS_ID))
                else:
                    # Or redirect to degraded  
                    redirect('{0}{1}{2}/info.json'.format(cf.BASEPREF, identifier, cf.DEGRADED_IDENTIFIER))
            else:
                # Block access to images
                abort(401, identifier.make_error_message("Not Authenticated"))

        # Early Cache Check
        if self.cacher.exists(path):
            # Will only ever be canonical, otherwise would have redirected
            response['Link'] += ', <{0}{1}>;rel="canonical"'.format(cf.BASEPREF, path)
            return self.cacher.send_file(path)                    

        image = imgReq.make_image(filename)

        rg = bits.pop(0)
        if rg == "info.json":
            # does not exist, otherwise cache check would have caught it
            image.make_info()
            image.close()
            return self.cacher.send_file(identifier.value + "/info.json")

        # From here on, we're only pixel requests
        imgReq.make_param('region', rg)
        if bits:
            imgReq.make_param('size', bits.pop(0))
        else:
            imgReq.make_param('size', '')

        if bits:
            imgReq.make_param('rotation', bits.pop(0))
        else:
            imgReq.make_param('rotation', '')

        if bits:
            quality = bits.pop(0)
            dotidx = quality.rfind('.')
            if dotidx > -1:
                format = quality[dotidx+1:]
                quality = quality[:dotidx]
            else:
                imgReq.make_param('format', '')
            imgReq.make_param('quality', quality)
            imgReq.make_param('format', format)
        else:
            imgReq.make_param('quality', '')              

        if bits:
            # Extra cruft after the format :(
            imgReq.make_param('unknown', '/'.join(bits))

        image.cache_info()           
        imgReq.configure_params()

        if not imgReq.isCanonical():
            new_url = cf.BASEPREF + imgReq.canonicalize()
            response['Link'] += ', <{0}>;rel="canonical"'.format(new_url)
            redirect(new_url, 301)

        # Process and send the image!
        image.process(imgReq)
        return self.cacher.send_file(path)



    def handle_submit(self):
        imgurl = request.query.get('url', '')
        if not imgurl:
            # Need a url
            abort(400, "Missing required parameter: url")

        # cache check
        md = hashlib.md5(imgurl)
        imgid = md.hexdigest()
        done = glob.glob("{0}*".format(os.path.join(cf.UPLOADDIR, imgid)))
        if not done:
            # fetch URL
            fh = urllib.urlopen(imgurl)
            ct = fh.headers['content-type']
            if ct.find('image/') == -1:
                # Not an image!
                abort(400, "That resource is not an image")
            data = fh.read()
            fh.close()

            # store the url / hash somewhere
            fn = os.path.join(cf.UPLOADLINKDIR, "{0}.url.txt".format(imgid))
            fh = file(fn, 'w')
            fh.write(imgurl)
            fh.close()

            ext = cf.content_types.get(ct, 'jpg')
            filename = "{0}.{1}".format(imgid, ext)
            fn = os.path.join(cf.UPLOADDIR, filename)
            fh = file(fn, 'w')
            fh.write(data)
            fh.close()

            files = os.listdir(cf.UPLOADDIR)
            if len(files) > cf.MAXUPLOADFILES:
                # trash oldest one
                mtime = lambda f: os.stat(os.path.join(cf.UPLOADDIR, f)).st_mtime
                ofiles = list(sorted(files, key=mtime))
                os.remove(os.path.join(cf.UPLOADDIR, ofiles[0]))

        link = cf.BASEPREF + imgid
        redirect(link+'/info.json')

    def opts(self, *args, **kw):
        response.headers['Access-Control-Allow-Origin'] = '*'
        response.headers['Access-Control-Allow-Methods'] = "GET,OPTIONS"
        response.headers['Access-Control-Allow-Headers'] = 'Origin, Accept, Authorization'
        return self.send("", ct="text/plain")

    def debug_list(self):
        return self.send(repr(self.resolver.identifiers), status=200, ct="application/json");

    def dispatch_views(self):
        # Auth and Submission
        cf = self.config
        if cf.AUTH_TYPE:
            # must support login and token
            self.app.route('/{0}'.format(cf.AUTH_URL_LOGIN), ["GET"], getattr(self.auth, "login", self.not_implemented))               
            self.app.route('/{0}'.format(cf.AUTH_URL_TOKEN), ["GET"], getattr(self.auth, "get_iiif_token", self.not_implemented))
            if cf.AUTH_URL_LOGOUT:
                self.app.route('/{0}'.format(cf.AUTH_URL_LOGOUT), ["GET"], getattr(self.auth, "logout", self.not_implemented)) 
            if cf.AUTH_URL_HOME:
                self.app.route('/{0}'.format(cf.AUTH_URL_HOME), ["GET"], getattr(self.auth, "home", self.not_implemented))
            if cf.AUTH_URL_CLIENTCODE:
                self.app.route('/{0}'.format(cf.AUTH_URL_CLIENTCODE), ["POST"], getattr(self.auth, "get_client_code", self.not_implemented))
            if cf.AUTH_URL_NOACCESS_ID:
                self.app.route('/{0}/info.json'.format(cf.AUTH_URL_NOACCESS_ID), ["GET"], getattr(self.auth, "noaccess", self.not_implemented))

        if cf.SUBMIT_URL:
            self.app.route('/submit', ["GET", "POST"], getattr(self, "handle_submit", self.not_implemented))

        if cf.ENABLE_DEBUG_LIST:
            self.app.route('/list', ["GET"], getattr(self, "debug_list", self.not_implemented))

        # Send everything to the one function for pixels and info.json
        self.app.route('/<path:re:.*>', ["GET"], getattr(self, "handle", self.not_implemented))

        # Add OPTIONS support for cross domain preflight
        # Send everything to the one function for OPTIONS
        self.app.route('/<path:re:.*>', ["OPTIONS"], getattr(self, "opts", self.not_implemented))


    def not_implemented(self, *args, **kwargs):
        """Returns not implemented status."""
        abort(501)

    def empty_response(self, *args, **kwargs):
        """Empty response"""
        pass

    def get_bottle_app(self):
        """Returns bottle instance"""
        self.app = Bottle()
        self.dispatch_views()
        return self.app
Esempio n. 4
0
class ImageApp(object):
    def __init__(self, config="config.json"):
        # Only called once, so can do config in init
        self.config = self.make_config(config)

        # Make our Resolver and Cacher
        self.resolverClass = FileSystemResolver
        self.resolver = self.resolverClass(self)
        self.cacherClass = FileSystemCacher
        self.cacher = self.cacherClass(self)
        if self.config.AUTH_TYPE == "oauth":
            self.auth = OAuthHandler(self)
        elif self.config.AUTH_TYPE == "basic":
            self.auth = BasicAuthHandler(self)
        else:
            self.auth = NullAuthHandler(self)

    def make_config(self, path):
        fh = file(path)
        data = fh.read()
        fh.close()
        info = json.loads(data)
        return Config(info)

    def send(self, data, status=200, ct="text/plain"):
        response["content_type"] = ct
        response.status = status
        return data

    def handle(self, path):
        # We are responsible for processing the URL into params
        # And checking auth at the appropriate times
        # ImageRequest looks after the params and image

        cf = self.config
        # First check auth
        isAuthed = self.auth.check_auth()
        response['Access-Control-Allow-Origin'] = '*'
        response['Link'] = '<{0}>;rel="profile"'.format(cf.compliance)

        # Assumption is that there really is no / (even encoded) in an identifier
        # Parse the path
        bits = path.split('/')

        imgReq = ImageRequest(self, path)
        identifier = imgReq.make_param('identifier', bits.pop(0))
        filename = self.resolver.resolve_identifier(identifier)

        if not bits or not bits[0]:
            # Redirect identifier or identifier/ to info.json
            redirect(
                "{0}{1}/info.json".format(cf.BASEPREF, identifier.requested),
                303)

        isInfoRequest = bits[-1] == "info.json"
        if not isAuthed and not identifier.degraded:
            if isInfoRequest:
                if cf.DEGRADED_NOACCESS:
                    # No access is special degraded
                    redirect('{0}{1}/info.json'.format(
                        cf.BASEPREF, cf.AUTH_URL_NOACCESS_ID))
                else:
                    # Or redirect to degraded
                    redirect('{0}{1}{2}/info.json'.format(
                        cf.BASEPREF, identifier.value, cf.DEGRADED_IDENTIFIER))
            else:
                # Block access to images
                abort(401, identifier.make_error_message("Not Authenticated"))

        # Early Cache Check
        if self.cacher.exists(path):
            # Will only ever be canonical, otherwise would have redirected
            response['Link'] += ', <{0}{1}>;rel="canonical"'.format(
                cf.BASEPREF, path)
            return self.cacher.send_file(path)

        image = imgReq.make_image(filename)

        rg = bits.pop(0)
        if rg == "info.json":
            # does not exist, otherwise cache check would have caught it
            image.make_info()
            image.close()
            return self.cacher.send_file(identifier.value + "/info.json")

        # From here on, we're only pixel requests
        imgReq.make_param('region', rg)
        if bits:
            imgReq.make_param('size', bits.pop(0))
        else:
            imgReq.make_param('size', '')

        if bits:
            imgReq.make_param('rotation', bits.pop(0))
        else:
            imgReq.make_param('rotation', '')

        if bits:
            quality = bits.pop(0)
            dotidx = quality.rfind('.')
            if dotidx > -1:
                format = quality[dotidx + 1:]
                quality = quality[:dotidx]
            else:
                imgReq.make_param('format', '')
            imgReq.make_param('quality', quality)
            imgReq.make_param('format', format)
        else:
            imgReq.make_param('quality', '')

        if bits:
            # Extra cruft after the format :(
            imgReq.make_param('unknown', '/'.join(bits))

        image.cache_info()
        imgReq.configure_params()

        if not imgReq.isCanonical():
            new_url = cf.BASEPREF + imgReq.canonicalize()
            response['Link'] += ', <{0}>;rel="canonical"'.format(new_url)
            redirect(new_url, 301)

        # Process and send the image!
        image.process(imgReq)
        return self.cacher.send_file(path)

    def handle_submit(self):
        imgurl = request.query.get('url', '')
        if not imgurl:
            # Need a url
            abort(400, "Missing required parameter: url")

        # cache check
        md = hashlib.md5(imgurl)
        imgid = md.hexdigest()
        done = glob.glob("{0}*".format(os.path.join(cf.UPLOADDIR, imgid)))
        if not done:
            # fetch URL
            fh = urllib.urlopen(imgurl)
            ct = fh.headers['content-type']
            if ct.find('image/') == -1:
                # Not an image!
                abort(400, "That resource is not an image")
            data = fh.read()
            fh.close()

            # store the url / hash somewhere
            fn = os.path.join(cf.UPLOADLINKDIR, "{0}.url.txt".format(imgid))
            fh = file(fn, 'w')
            fh.write(imgurl)
            fh.close()

            ext = cf.content_types.get(ct, 'jpg')
            filename = "{0}.{1}".format(imgid, ext)
            fn = os.path.join(cf.UPLOADDIR, filename)
            fh = file(fn, 'w')
            fh.write(data)
            fh.close()

            files = os.listdir(cf.UPLOADDIR)
            if len(files) > cf.MAXUPLOADFILES:
                # trash oldest one
                mtime = lambda f: os.stat(os.path.join(cf.UPLOADDIR, f)
                                          ).st_mtime
                ofiles = list(sorted(files, key=mtime))
                os.remove(os.path.join(cf.UPLOADDIR, ofiles[0]))

        link = cf.BASEPREF + imgid
        redirect(link + '/info.json')

    def opts(self, *args, **kw):
        response.headers['Access-Control-Allow-Origin'] = '*'
        response.headers['Access-Control-Allow-Methods'] = "GET,OPTIONS"
        response.headers[
            'Access-Control-Allow-Headers'] = 'Origin, Accept, Authorization'
        return self.send("", ct="text/plain")

    def debug_list(self):
        return self.send(repr(self.resolver.identifiers),
                         status=200,
                         ct="application/json")

    def dispatch_views(self):
        # Auth and Submission
        cf = self.config
        if cf.AUTH_TYPE:
            # must support login and token
            self.app.route('/{0}'.format(cf.AUTH_URL_LOGIN), ["GET"],
                           getattr(self.auth, "login", self.not_implemented))
            self.app.route(
                '/{0}'.format(cf.AUTH_URL_TOKEN), ["GET"],
                getattr(self.auth, "get_iiif_token", self.not_implemented))
            if cf.AUTH_URL_LOGOUT:
                self.app.route(
                    '/{0}'.format(cf.AUTH_URL_LOGOUT), ["GET"],
                    getattr(self.auth, "logout", self.not_implemented))
            if cf.AUTH_URL_HOME:
                self.app.route(
                    '/{0}'.format(cf.AUTH_URL_HOME), ["GET"],
                    getattr(self.auth, "home", self.not_implemented))
            if cf.AUTH_URL_CLIENTCODE:
                self.app.route(
                    '/{0}'.format(cf.AUTH_URL_CLIENTCODE), ["POST"],
                    getattr(self.auth, "get_client_code",
                            self.not_implemented))
            if cf.AUTH_URL_NOACCESS_ID:
                self.app.route(
                    '/{0}/info.json'.format(cf.AUTH_URL_NOACCESS_ID), ["GET"],
                    getattr(self.auth, "noaccess", self.not_implemented))

        if cf.SUBMIT_URL:
            self.app.route(
                '/submit', ["GET", "POST"],
                getattr(self, "handle_submit", self.not_implemented))

        if cf.ENABLE_DEBUG_LIST:
            self.app.route('/list', ["GET"],
                           getattr(self, "debug_list", self.not_implemented))

        # Send everything to the one function for pixels and info.json
        self.app.route('/<path:re:.*>', ["GET"],
                       getattr(self, "handle", self.not_implemented))

        # Add OPTIONS support for cross domain preflight
        # Send everything to the one function for OPTIONS
        self.app.route('/<path:re:.*>', ["OPTIONS"],
                       getattr(self, "opts", self.not_implemented))

    def not_implemented(self, *args, **kwargs):
        """Returns not implemented status."""
        abort(501)

    def empty_response(self, *args, **kwargs):
        """Empty response"""
        pass

    def get_bottle_app(self):
        """Returns bottle instance"""
        self.app = Bottle()
        self.dispatch_views()
        return self.app