123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227 |
- import cherrypy
- import chevron
- import hashlib
- import bcrypt
- import os
- @cherrypy.tools.register("before_handler")
- def auth(groups):
- if cherrypy.session.get("login") in groups:
- return
- else:
- raise cherrypy.HTTPRedirect("/login")
- def human_readable_size(size):
- units = ("B", "KiB", "MiB", "GiB")
- unit = 0
- while size >= 1024 and unit < len(units):
- size /= 1024
- unit += 1
- return f"{size:.2f}{units[unit]}"
- def ensure_dir_exists(directory):
- if not os.path.exists(directory):
- os.mkdir(directory)
- def load_template(template):
- result = ""
- with open(f"{template}.html") as fp:
- result = fp.read()
- return result
- class WriteFreelyImages:
- def __init__(self, templates, base_dir, db, app_root=""):
- # templates must be a `dict`
- # key => page name like `index` or `login`
- # value => either function returning string or a string
-
- # db must be path to WriteFreely's database, either sqlite or sql
- # examples:
- # - sqlite:///tmp/wf.db
- # - mysql://username:password@host:port/db_name
- self.templates = map(lambda t: (t if callable(t[1]) else (t[0], lambda: t[1])),
- templates.items())
- self.templates = dict(self.templates)
- self.base_dir = base_dir
- self.SUPPORTED_DBS = ("mysql", "sqlite")
- if not db.startswith(self.SUPPORTED_DBS):
- raise ValueError("<db> must be either sqlite or mysql")
- if db.startswith("sqlite"):
- import sqlite3
- self.sqlite = sqlite3
- self.db = db.replace("sqlite://","")
- else:
- import MySQLdb
- parts = db.replace("mysql://", "").split("@")
- parts[0] = parts[0].split(":")
- parts[1] = parts[1].split(":")
- parts[1][1] = parts[1][1].split("/")
- args = dict()
- args["user"] = parts[0][0]
- args["passwd"] = parts[0][1]
- args["host"] = parts[1][0]
- args["port"] = int(parts[1][1][0])
- args["db"] = parts[1][1][1]
- # ^ I think it is only Farooq who understands what's going on here...
- # TODO: find a more "readable" way
- self.db_connection = MySQLdb.connect(**args)
- self.db = None
- self.size_limit = 1 * 1024 * 1024 # 1 MiB
- self.app_root = app_root
- self.message_with_redirect = f"""
- <html>
- <head>
- <meta http-equiv="refresh" content="4; url='{app_root}'
- </head>
- <body>{{{{message}}}}<br><a href="{app_root}">Back to home</a></body>
- </html>
- """
-
- @cherrypy.tools.auth(groups=("admin", "user"))
- @cherrypy.expose
- def index(self):
- username = cherrypy.session["uname"]
- size = os.path.getsize
- images = [
- {
- "name": image,
- "size": human_readable_size(
- size(f"{self.base_dir}/{username}/{image}"))
- }
- for image in
- os.listdir(f"{self.base_dir}/{username}")]
- template = self.templates["index"]()
- is_admin = cherrypy.session["login"] == "admin"
- data = {"files": images,
- "logged_as": username,
- "is_admin": is_admin,
- "app_root": self.app_root}
- return chevron.render(template, data)
-
- @cherrypy.expose
- @cherrypy.tools.auth(groups=("admin",))
- def admin(self, user=""):
- directory = f"{self.base_dir}/{user}"
-
- files = os.listdir(directory)
- template = self.templates["admin"]()
- size = lambda file: human_readable_size(os.path.getsize(file))
- if user:
- files = ({"name": file,
- "size": size(f"{self.base_dir}/{user}/{file}")}
- for file in files)
- else:
- files = ({"name": file} for file in files)
- data = {"files": files, "user": user, "app_root": self.app_root}
- return chevron.render(template, data)
-
- @cherrypy.expose
- @cherrypy.tools.auth(groups=("admin", "user"))
- def rename(self, old, new, user=""):
- if not user and cherrypy.session["login"] != "admin":
- t = self.message_with_template
- msg = "You must be an admin in order to rename somebody else's image"
- return chevron.render(t,
- {
- "message": msg
- })
-
- if not user:
- user = cherrypy.session["uname"]
- redirect = "/"
- else:
- redirect = f"/admin?user={user}"
-
- base = f"{self.base_dir}/{user}"
- os.rename(f"{base}/{old}", f"{base}/{new}")
- print(redirect)
- raise cherrypy.HTTPRedirect(redirect)
- @cherrypy.tools.auth(groups=("admin", "user"))
- @cherrypy.expose
- def upload(self, files):
- if type(files) != list:
- files = [files]
- directory = f"{self.base_dir}/{cherrypy.session['uname']}"
- for file in files:
- with open(f"{directory}/{file.filename}", "wb") as fp:
- buffer = file.file.read(6 * 1024)
- while buffer:
- fp.write(buffer)
- buffer = file.file.read(6 * 1024)
-
- raise cherrypy.HTTPRedirect("/")
- @cherrypy.tools.auth(groups=("admin", "user"))
- @cherrypy.expose
- def logout(self):
- cherrypy.session["login"] = ""
- cherrypy.session["uname"] = ""
- raise cherrypy.HTTPRedirect("/login")
-
- @cherrypy.tools.auth(groups=("admin", "user"))
- @cherrypy.expose
- def delete(self, image, user=""):
- if not user and cherrypy.session["login"] != "admin":
- t = self.message_with_template
- msg = "You must be an admin in order to delete somebody else's image"
- return chevron.render(t,
- {
- "message": msg
- })
-
- if not user:
- user = cherrypy.session["uname"]
- redirect = "/"
- else:
- redirect = f"/admin?user={user}"
- os.remove(f"{self.base_dir}/{user}/{image}")
- raise cherrypy.HTTPRedirect(redirect)
-
- @cherrypy.expose
- def login(self, username="", password=""):
- template = self.templates["login"]()
- incorrect = {"message": "Incorrect username or password"}
- blank = {"message": ""}
- incorrect["app_root"] = blank["app_root"] = self.app_root
- if username and password:
- if self.db:
- conn = self.sqlite.connect(self.db)
- cursor = conn.cursor()
- else:
- cursor = self.db_connection.cursor()
- cursor.execute(f"SELECT * FROM users WHERE username='{username}';")
- record = cursor.fetchone()
- if record:
- if not bcrypt.checkpw(password.encode(), record[2]):
- return chevron.render(template, incorrect)
- else:
- is_admin = record[0] == 1 # TODO: Get it from DB once WF
- # does so...
- else:
- return chevron.render(template, incorrect)
- cherrypy.session["login"] = "admin" if is_admin else "user"
- cherrypy.session["uname"] = username
- ensure_dir_exists(f"{self.base_dir}/{username}")
- if self.db:
- conn.close()
- raise cherrypy.HTTPRedirect("/")
- else:
- return chevron.render(template, blank)
- if __name__ == "__main__":
- templates = dict()
- templates["index"] = lambda: load_template("index")
- templates["admin"] = lambda: load_template("admin")
- templates["login"] = lambda: load_template("login")
- db_path = "sqlite:///home/farooqkz/writefreely/writefreely.db"
- app = WriteFreelyImages(templates, "./images/", db_path)
- conf = {"global": {"tools.sessions.on": True}}
- cherrypy.quickstart(app, "/", conf)
|