From ac7ec124c8a3d94ec33ff770e60633744682f9b9 Mon Sep 17 00:00:00 2001 From: dave Date: Mon, 6 Feb 2017 23:11:40 -0800 Subject: [PATCH] initial commit --- README.txt | 11 ++++ imghost/__init__.py | 1 + imghost/api.py | 119 ++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 11 ++++ setup.py | 23 +++++++++ ui/i/.gitignore | 1 + ui/index.html | 8 +++ 7 files changed, 174 insertions(+) create mode 100644 README.txt create mode 100644 imghost/__init__.py create mode 100644 imghost/api.py create mode 100644 requirements.txt create mode 100644 setup.py create mode 100644 ui/i/.gitignore create mode 100644 ui/index.html diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..e5333a4 --- /dev/null +++ b/README.txt @@ -0,0 +1,11 @@ +IMGHOST + +The simplest imghost + +Install like: + +pip3 install -r requirements.txt +python3 setup.py install +imghost + +Then visit http://127.0.0.1:3000/ diff --git a/imghost/__init__.py b/imghost/__init__.py new file mode 100644 index 0000000..6c8e6b9 --- /dev/null +++ b/imghost/__init__.py @@ -0,0 +1 @@ +__version__ = "0.0.0" diff --git a/imghost/api.py b/imghost/api.py new file mode 100644 index 0000000..0fba243 --- /dev/null +++ b/imghost/api.py @@ -0,0 +1,119 @@ +import cherrypy +import logging +import os +import cgi +import tempfile +import subprocess + + +class Mountable(object): + """ + Macro for encapsulating a component's config and methods into one object. + :param conf: cherrypy config dict for use when mounting this component + """ + def __init__(self, conf=None): + self.conf = conf if conf else {'/': {}} + + def mount(self, path): + """ + Mount this component into the cherrypy tree + :param path: where to mount it e.g. /v1 + :return: self + """ + cherrypy.tree.mount(self, path, self.conf) + return self + + +class tempFileStorage(cgi.FieldStorage): + def make_file(self, binary=None): + return tempfile.NamedTemporaryFile() + + +def noBodyProcess(): + cherrypy.request.process_request_body = False + +cherrypy.tools.noBodyProcess = cherrypy.Tool('before_request_body', noBodyProcess) + + +type2ext = { + "image/png": "png", + "image/jpg": "jpg", + "image/gif": "gif" +} + + +class ImgHostApiV1(Mountable): + + @cherrypy.expose + @cherrypy.tools.noBodyProcess() + def upload(self, theFile=None): + + # convert the header keys to lower case + lcHDRS = {} + for key, val in cherrypy.request.headers.items(): + lcHDRS[key.lower()] = val + + if int(lcHDRS['content-length']) > 10**7: + raise Exception("File size max of 10MB exceeded") + + formFields = tempFileStorage(fp=cherrypy.request.rfile, + headers=lcHDRS, + environ={'REQUEST_METHOD': 'POST'}, + keep_blank_values=True) + + theFile = formFields['theFile'] + + # os.link(theFile.file.name, '/tmp/' + theFile.filename) + try: + ext = type2ext[str(theFile.type)] + except KeyError: + raise Exception("File type not allowed") + + sha = subprocess.check_output(["sha512sum", theFile.file.name]) + sha = sha.decode("UTF-8").split(" ")[0] + imgpath = os.path.join("i", "{}.{}".format(sha[0:8], ext)) + os.link(theFile.file.name, os.path.join("ui", imgpath)) + raise cherrypy.HTTPRedirect("/" + imgpath) + + +class ImgHostApi(object): + def __init__(self): + + ui_path = os.path.join(os.getcwd(), 'ui') + self.ui = Mountable(conf={'/': { + 'tools.staticdir.on': True, + 'tools.staticdir.dir': ui_path, + 'tools.staticdir.index': 'index.html'}}).mount('/') + + self.app_v1 = ImgHostApiV1().mount('/api/v1') + + cherrypy.config.update({ + 'sessionFilter.on': False, + 'tools.sessions.on': False, + 'tools.sessions.locking': 'explicit', + # 'tools.sessions.timeout': 525600, + 'request.show_tracebacks': True, + 'server.socket_port': 3000, # TODO configurable port + 'server.thread_pool': 25, + 'server.socket_host': '0.0.0.0', + 'server.socket_timeout': 5, + 'log.screen': False, + 'engine.autoreload.on': False + }) + + def run(self): + cherrypy.engine.start() + cherrypy.engine.block() + logging.info("API has shut down") + + def stop(self): + cherrypy.engine.exit() + logging.info("API shutting down...") + + +def main(): + logging.basicConfig(level=logging.DEBUG, + format="%(asctime)-15s %(levelname)-8s %(name)s %(filename)s:%(lineno)d %(message)s") + + api = ImgHostApi() + api.run() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..359c964 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,11 @@ +appdirs==1.4.0 +cheroot==5.1.0 +CherryPy==10.0.0 +imghost==0.0.0 +packaging==16.8 +pkg-resources==0.0.0 +portend==1.8 +pyparsing==2.1.10 +pytz==2016.10 +six==1.10.0 +tempora==1.6.1 diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..81acb4d --- /dev/null +++ b/setup.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +from setuptools import setup +from imghost import __version__ + +with open("./requirements.txt") as reqf: + deps = reqf.read().strip().split("\n") + +setup(name='imghost', + version=__version__, + description='minimal img hoster', + url='http://', + author='dpedu', + author_email='git@davepedu.com', + packages=[ + 'imghost' + ], + entry_points={ + 'console_scripts': [ + 'imghost = imghost.api:main' + ] + }, + install_requires=deps, + zip_safe=False) diff --git a/ui/i/.gitignore b/ui/i/.gitignore new file mode 100644 index 0000000..72e8ffc --- /dev/null +++ b/ui/i/.gitignore @@ -0,0 +1 @@ +* diff --git a/ui/index.html b/ui/index.html new file mode 100644 index 0000000..83c67b8 --- /dev/null +++ b/ui/index.html @@ -0,0 +1,8 @@ + + +
+ File:
+ +
+ +