Move storage to classes, support double suffixes
This commit is contained in:
parent
cd083a7f83
commit
73045dc5e5
1 changed files with 93 additions and 88 deletions
171
fhost.py
171
fhost.py
|
@ -26,7 +26,7 @@ from jinja2.exceptions import *
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from magic import Magic
|
from magic import Magic
|
||||||
from mimetypes import guess_extension
|
from mimetypes import guess_extension
|
||||||
import os, sys
|
import sys
|
||||||
import requests
|
import requests
|
||||||
from short_url import UrlEncoder
|
from short_url import UrlEncoder
|
||||||
from validators import url as url_valid
|
from validators import url as url_valid
|
||||||
|
@ -66,6 +66,7 @@ app.config.update(
|
||||||
URL_ALPHABET = "DEQhd2uFteibPwq0SWBInTpA_jcZL5GKz3YCR14Ulk87Jors9vNHgfaOmMXy6Vx-",
|
URL_ALPHABET = "DEQhd2uFteibPwq0SWBInTpA_jcZL5GKz3YCR14Ulk87Jors9vNHgfaOmMXy6Vx-",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if not app.config["TESTING"]:
|
||||||
app.config.from_pyfile("config.py")
|
app.config.from_pyfile("config.py")
|
||||||
|
|
||||||
if app.config["DEBUG"]:
|
if app.config["DEBUG"]:
|
||||||
|
@ -82,9 +83,6 @@ except:
|
||||||
Please install python-magic.""")
|
Please install python-magic.""")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
storage = Path(app.config["FHOST_STORAGE_PATH"])
|
|
||||||
storage.mkdir(parents=True, exist_ok=True)
|
|
||||||
|
|
||||||
db = SQLAlchemy(app)
|
db = SQLAlchemy(app)
|
||||||
migrate = Migrate(app, db)
|
migrate = Migrate(app, db)
|
||||||
|
|
||||||
|
@ -103,6 +101,16 @@ class URL(db.Model):
|
||||||
def geturl(self):
|
def geturl(self):
|
||||||
return url_for("get", path=self.getname(), _external=True) + "\n"
|
return url_for("get", path=self.getname(), _external=True) + "\n"
|
||||||
|
|
||||||
|
def get(url):
|
||||||
|
u = URL.query.filter_by(url=url).first()
|
||||||
|
|
||||||
|
if not u:
|
||||||
|
u = URL(url)
|
||||||
|
db.session.add(u)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return u
|
||||||
|
|
||||||
class File(db.Model):
|
class File(db.Model):
|
||||||
id = db.Column(db.Integer, primary_key = True)
|
id = db.Column(db.Integer, primary_key = True)
|
||||||
sha256 = db.Column(db.String, unique = True)
|
sha256 = db.Column(db.String, unique = True)
|
||||||
|
@ -112,12 +120,11 @@ class File(db.Model):
|
||||||
removed = db.Column(db.Boolean, default=False)
|
removed = db.Column(db.Boolean, default=False)
|
||||||
nsfw_score = db.Column(db.Float)
|
nsfw_score = db.Column(db.Float)
|
||||||
|
|
||||||
def __init__(self, sha256, ext, mime, addr, nsfw_score):
|
def __init__(self, sha256, ext, mime, addr):
|
||||||
self.sha256 = sha256
|
self.sha256 = sha256
|
||||||
self.ext = ext
|
self.ext = ext
|
||||||
self.mime = mime
|
self.mime = mime
|
||||||
self.addr = addr
|
self.addr = addr
|
||||||
self.nsfw_score = nsfw_score
|
|
||||||
|
|
||||||
def getname(self):
|
def getname(self):
|
||||||
return u"{0}{1}".format(su.enbase(self.id, 1), self.ext)
|
return u"{0}{1}".format(su.enbase(self.id, 1), self.ext)
|
||||||
|
@ -130,6 +137,71 @@ class File(db.Model):
|
||||||
else:
|
else:
|
||||||
return url_for("get", path=n, _external=True) + "\n"
|
return url_for("get", path=n, _external=True) + "\n"
|
||||||
|
|
||||||
|
def store(file_, addr):
|
||||||
|
data = file_.stream.read()
|
||||||
|
digest = sha256(data).hexdigest()
|
||||||
|
|
||||||
|
def get_mime():
|
||||||
|
guess = mimedetect.from_buffer(data)
|
||||||
|
app.logger.debug(f"MIME - specified: '{file_.content_type}' - detected: '{guess}'")
|
||||||
|
|
||||||
|
if not file_.content_type or not "/" in file_.content_type or file_.content_type == "application/octet-stream":
|
||||||
|
mime = guess
|
||||||
|
else:
|
||||||
|
mime = file_.content_type
|
||||||
|
|
||||||
|
if mime in app.config["FHOST_MIME_BLACKLIST"] or guess in app.config["FHOST_MIME_BLACKLIST"]:
|
||||||
|
abort(415)
|
||||||
|
|
||||||
|
if mime.startswith("text/") and not "charset" in mime:
|
||||||
|
mime += "; charset=utf-8"
|
||||||
|
|
||||||
|
return mime
|
||||||
|
|
||||||
|
def get_ext(mime):
|
||||||
|
ext = "".join(Path(file_.filename).suffixes[-2:])
|
||||||
|
gmime = mime[:mime.find(";")]
|
||||||
|
guess = guess_extension(gmime)
|
||||||
|
|
||||||
|
app.logger.debug(f"extension - specified: '{ext}' - detected: '{guess}'")
|
||||||
|
|
||||||
|
if not ext:
|
||||||
|
if gmime in app.config["FHOST_EXT_OVERRIDE"]:
|
||||||
|
ext = app.config["FHOST_EXT_OVERRIDE"][gmime]
|
||||||
|
else:
|
||||||
|
ext = guess_extension(gmime)
|
||||||
|
|
||||||
|
return ext[:app.config["FHOST_MAX_EXT_LENGTH"]] or ".bin"
|
||||||
|
|
||||||
|
f = File.query.filter_by(sha256=digest).first()
|
||||||
|
|
||||||
|
if f:
|
||||||
|
if f.removed:
|
||||||
|
abort(451)
|
||||||
|
else:
|
||||||
|
mime = get_mime()
|
||||||
|
ext = get_ext(mime)
|
||||||
|
f = File(digest, ext, mime, addr)
|
||||||
|
|
||||||
|
f.addr = addr
|
||||||
|
|
||||||
|
storage = Path(app.config["FHOST_STORAGE_PATH"])
|
||||||
|
storage.mkdir(parents=True, exist_ok=True)
|
||||||
|
p = storage / digest
|
||||||
|
|
||||||
|
if not p.is_file():
|
||||||
|
file_.stream.seek(0)
|
||||||
|
file_.save(p)
|
||||||
|
else:
|
||||||
|
p.touch()
|
||||||
|
|
||||||
|
if not f.nsfw_score and app.config["NSFW_DETECT"]:
|
||||||
|
f.nsfw_score = nsfw.detect(p)
|
||||||
|
|
||||||
|
db.session.add(f)
|
||||||
|
db.session.commit()
|
||||||
|
return f
|
||||||
|
|
||||||
def fhost_url(scheme=None):
|
def fhost_url(scheme=None):
|
||||||
if not scheme:
|
if not scheme:
|
||||||
return url_for(".fhost", _external=True).rstrip("/")
|
return url_for(".fhost", _external=True).rstrip("/")
|
||||||
|
@ -146,14 +218,7 @@ def shorten(url):
|
||||||
if not url_valid(url) or is_fhost_url(url) or "\n" in url:
|
if not url_valid(url) or is_fhost_url(url) or "\n" in url:
|
||||||
abort(400)
|
abort(400)
|
||||||
|
|
||||||
existing = URL.query.filter_by(url=url).first()
|
u = URL.get(url)
|
||||||
|
|
||||||
if existing:
|
|
||||||
return existing.geturl()
|
|
||||||
else:
|
|
||||||
u = URL(url)
|
|
||||||
db.session.add(u)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
return u.geturl()
|
return u.geturl()
|
||||||
|
|
||||||
|
@ -172,69 +237,7 @@ def store_file(f, addr):
|
||||||
if in_upload_bl(addr):
|
if in_upload_bl(addr):
|
||||||
return "Your host is blocked from uploading files.\n", 451
|
return "Your host is blocked from uploading files.\n", 451
|
||||||
|
|
||||||
data = f.stream.read()
|
sf = File.store(f, addr)
|
||||||
digest = sha256(data).hexdigest()
|
|
||||||
existing = File.query.filter_by(sha256=digest).first()
|
|
||||||
|
|
||||||
if existing:
|
|
||||||
if existing.removed:
|
|
||||||
abort(451)
|
|
||||||
|
|
||||||
epath = storage / existing.sha256
|
|
||||||
|
|
||||||
if not epath.is_file():
|
|
||||||
f.save(epath)
|
|
||||||
|
|
||||||
if existing.nsfw_score == None:
|
|
||||||
if app.config["NSFW_DETECT"]:
|
|
||||||
existing.nsfw_score = nsfw.detect(epath)
|
|
||||||
|
|
||||||
epath.touch()
|
|
||||||
existing.addr = addr
|
|
||||||
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
return existing.geturl()
|
|
||||||
else:
|
|
||||||
guessmime = mimedetect.from_buffer(data)
|
|
||||||
|
|
||||||
if not f.content_type or not "/" in f.content_type or f.content_type == "application/octet-stream":
|
|
||||||
mime = guessmime
|
|
||||||
else:
|
|
||||||
mime = f.content_type
|
|
||||||
|
|
||||||
if mime in app.config["FHOST_MIME_BLACKLIST"] or guessmime in app.config["FHOST_MIME_BLACKLIST"]:
|
|
||||||
abort(415)
|
|
||||||
|
|
||||||
if mime.startswith("text/") and not "charset" in mime:
|
|
||||||
mime += "; charset=utf-8"
|
|
||||||
|
|
||||||
ext = os.path.splitext(f.filename)[1]
|
|
||||||
|
|
||||||
if not ext:
|
|
||||||
gmime = mime.split(";")[0]
|
|
||||||
|
|
||||||
if not gmime in app.config["FHOST_EXT_OVERRIDE"]:
|
|
||||||
ext = guess_extension(gmime)
|
|
||||||
else:
|
|
||||||
ext = app.config["FHOST_EXT_OVERRIDE"][gmime]
|
|
||||||
else:
|
|
||||||
ext = ext[:8]
|
|
||||||
|
|
||||||
if not ext:
|
|
||||||
ext = ".bin"
|
|
||||||
|
|
||||||
spath = storage / digest
|
|
||||||
f.save(spath)
|
|
||||||
|
|
||||||
if app.config["NSFW_DETECT"]:
|
|
||||||
nsfw_score = nsfw.detect(spath)
|
|
||||||
else:
|
|
||||||
nsfw_score = None
|
|
||||||
|
|
||||||
sf = File(digest, ext, mime, addr, nsfw_score)
|
|
||||||
db.session.add(sf)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
return sf.geturl()
|
return sf.geturl()
|
||||||
|
|
||||||
|
@ -267,17 +270,19 @@ def store_url(url, addr):
|
||||||
|
|
||||||
@app.route("/<path:path>")
|
@app.route("/<path:path>")
|
||||||
def get(path):
|
def get(path):
|
||||||
p = os.path.splitext(path)
|
path = Path(path)
|
||||||
id = su.debase(p[0])
|
sufs = "".join(path.suffixes[-2:])
|
||||||
|
name = path.name[:-len(sufs) or None]
|
||||||
|
id = su.debase(name)
|
||||||
|
|
||||||
if p[1]:
|
if sufs:
|
||||||
f = File.query.get(id)
|
f = File.query.get(id)
|
||||||
|
|
||||||
if f and f.ext == p[1]:
|
if f and f.ext == sufs:
|
||||||
if f.removed:
|
if f.removed:
|
||||||
abort(451)
|
abort(451)
|
||||||
|
|
||||||
fpath = storage / f.sha256
|
fpath = Path(app.config["FHOST_STORAGE_PATH"]) / f.sha256
|
||||||
|
|
||||||
if not fpath.is_file():
|
if not fpath.is_file():
|
||||||
abort(404)
|
abort(404)
|
||||||
|
@ -286,7 +291,7 @@ def get(path):
|
||||||
response = make_response()
|
response = make_response()
|
||||||
response.headers["Content-Type"] = f.mime
|
response.headers["Content-Type"] = f.mime
|
||||||
response.headers["Content-Length"] = fpath.stat().st_size
|
response.headers["Content-Length"] = fpath.stat().st_size
|
||||||
response.headers["X-Accel-Redirect"] = "/" + fpath
|
response.headers["X-Accel-Redirect"] = "/" + str(fpath)
|
||||||
return response
|
return response
|
||||||
else:
|
else:
|
||||||
return send_from_directory(app.config["FHOST_STORAGE_PATH"], f.sha256, mimetype = f.mime)
|
return send_from_directory(app.config["FHOST_STORAGE_PATH"], f.sha256, mimetype = f.mime)
|
||||||
|
|
Loading…
Reference in a new issue