Compare commits

...

7 commits

Author SHA1 Message Date
7d7bcd045e Merge branch 'master' into boop 2023-06-04 09:02:13 +03:00
polina4096
8a912e8744
Fix remote URL content length check off-by-one
Fixes #85
2023-06-04 06:35:11 +02:00
Mia Herkt
c2b5e95903
ModUI: Handle opening filter panel with NULL user agent 2023-03-29 07:49:56 +02:00
Mia Herkt
c189c47306
ModUI: Allow LIKE matching for address filtering 2023-03-29 07:38:36 +02:00
Mia Herkt
3d1facaec3
Store user agent with files
Needed for moderation.
2023-03-29 07:36:49 +02:00
Mia Herkt
e00866f5e4
URL: Explicitly set upper-case table name
Looks like recent SQLAlchemy/Alembic chose to lower-case it by
default. Try not to break existing schemas.
2023-03-29 07:19:47 +02:00
jonas-w
3950f6e8eb
fix 500 error when file extension could not be guessed
when a file without an extension was uploaded
and the mimetypes.guess_extension returned None
because there is no official file extension
for that mimetype a NoneType was subscripted
which yielded a 500 http error
2023-01-15 20:36:39 +01:00
3 changed files with 53 additions and 10 deletions

View file

@ -107,6 +107,7 @@ db = SQLAlchemy(app)
migrate = Migrate(app, db)
class URL(db.Model):
__tablename__ = "URL"
id = db.Column(db.Integer, primary_key = True)
url = db.Column(db.UnicodeText, unique = True)
@ -135,6 +136,7 @@ class File(db.Model):
ext = db.Column(db.UnicodeText)
mime = db.Column(db.UnicodeText)
addr = db.Column(db.UnicodeText)
ua = db.Column(db.UnicodeText)
removed = db.Column(db.Boolean, default=False)
nsfw_score = db.Column(db.Float)
expiration = db.Column(db.BigInteger)
@ -143,11 +145,12 @@ class File(db.Model):
last_vscan = db.Column(db.DateTime)
size = db.Column(db.BigInteger)
def __init__(self, sha256, ext, mime, addr, expiration, mgmt_token):
def __init__(self, sha256, ext, mime, addr, ua, expiration, mgmt_token):
self.sha256 = sha256
self.ext = ext
self.mime = mime
self.addr = addr
self.ua = ua
self.expiration = expiration
self.mgmt_token = mgmt_token
@ -212,7 +215,7 @@ class File(db.Model):
Any value greater that the longest allowed file lifespan will be rounded down to that
value.
"""
def store(file_, requested_expiration: typing.Optional[int], addr, secret: bool):
def store(file_, requested_expiration: typing.Optional[int], addr, ua, secret: bool):
data = file_.read()
digest = sha256(data).hexdigest()
@ -248,8 +251,10 @@ class File(db.Model):
if not ext:
if gmime in app.config["FHOST_EXT_OVERRIDE"]:
ext = app.config["FHOST_EXT_OVERRIDE"][gmime]
elif guess:
ext = guess
else:
ext = guess_extension(gmime)
ext = ""
return ext[:app.config["FHOST_MAX_EXT_LENGTH"]] or ".bin"
@ -276,9 +281,10 @@ class File(db.Model):
mime = get_mime()
ext = get_ext(mime)
mgmt_token = secrets.token_urlsafe()
f = File(digest, ext, mime, addr, expiration, mgmt_token)
f = File(digest, ext, mime, addr, ua, expiration, mgmt_token)
f.addr = addr
f.ua = ua
if isnew:
f.secret = None
@ -366,11 +372,11 @@ requested_expiration can be:
Any value greater that the longest allowed file lifespan will be rounded down to that
value.
"""
def store_file(f, requested_expiration: typing.Optional[int], addr, secret: bool):
def store_file(f, requested_expiration: typing.Optional[int], addr, ua, secret: bool):
if in_upload_bl(addr):
return "Your host is blocked from uploading files.\n", 451
sf, isnew = File.store(f, requested_expiration, addr, secret)
sf, isnew = File.store(f, requested_expiration, addr, ua, secret)
response = make_response(sf.geturl())
response.headers["X-Expires"] = sf.expiration
@ -380,7 +386,7 @@ def store_file(f, requested_expiration: typing.Optional[int], addr, secret: boo
return response
def store_url(url, addr, secret: bool):
def store_url(url, addr, ua, secret: bool):
if is_fhost_url(url):
abort(400)
@ -395,13 +401,13 @@ def store_url(url, addr, secret: bool):
if "content-length" in r.headers:
l = int(r.headers["content-length"])
if l < app.config["MAX_CONTENT_LENGTH"]:
if l <= app.config["MAX_CONTENT_LENGTH"]:
def urlfile(**kwargs):
return type('',(),kwargs)()
f = urlfile(read=r.raw.read, content_type=r.headers["content-type"], filename="")
return store_file(f, None, addr, secret)
return store_file(f, None, addr, ua, secret)
else:
abort(413)
else:
@ -496,6 +502,7 @@ def fhost():
request.files["file"],
int(request.form["expires"]),
request.remote_addr,
request.user_agent.string,
secret
)
except ValueError:
@ -507,12 +514,14 @@ def fhost():
request.files["file"],
None,
request.remote_addr,
request.user_agent.string,
secret
)
elif "url" in request.form:
return store_url(
request.form["url"],
request.remote_addr,
request.user_agent.string,
secret
)
elif "shorten" in request.form:

View file

@ -0,0 +1,30 @@
"""Store user agent string with files
Revision ID: dd0766afb7d2
Revises: 30bfe33aa328
Create Date: 2023-03-29 07:18:49.113200
"""
# revision identifiers, used by Alembic.
revision = 'dd0766afb7d2'
down_revision = '30bfe33aa328'
from alembic import op
import sqlalchemy as sa
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('file', schema=None) as batch_op:
batch_op.add_column(sa.Column('ua', sa.UnicodeText(), nullable=True))
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
with op.batch_alter_table('file', schema=None) as batch_op:
batch_op.drop_column('ua')
# ### end Alembic commands ###

6
mod.py
View file

@ -26,6 +26,7 @@ class NullptrMod(Screen):
("f4", "filter(4, 'Filter extension:')", "Filter Ext."),
("f5", "refresh", "Refresh"),
("f6", "filter_clear", "Clear filter"),
("f7", "filter(5, 'Filter user agent:')", "Filter UA"),
("r", "remove_file(False)", "Remove file"),
("ctrl+r", "remove_file(True)", "Ban file"),
("p", "ban_ip(False)", "Ban IP"),
@ -60,6 +61,7 @@ class NullptrMod(Screen):
case 2: finput.value = self.current_file.addr
case 3: finput.value = self.current_file.mime
case 4: finput.value = self.current_file.ext
case 5: finput.value = self.current_file.ua or ""
def on_input_submitted(self, message: Input.Submitted) -> None:
self.query_one("#filter_container").display = False
@ -71,9 +73,10 @@ class NullptrMod(Screen):
case 1:
try: ftable.query = ftable.base_query.filter(File.id == su.debase(message.value))
except ValueError: pass
case 2: ftable.query = ftable.base_query.filter(File.addr == message.value)
case 2: ftable.query = ftable.base_query.filter(File.addr.like(message.value))
case 3: ftable.query = ftable.base_query.filter(File.mime.like(message.value))
case 4: ftable.query = ftable.base_query.filter(File.ext.like(message.value))
case 5: ftable.query = ftable.base_query.filter(File.ua.like(message.value))
else:
ftable.query = ftable.base_query
@ -249,6 +252,7 @@ class NullptrMod(Screen):
("MIME type:", f.mime),
("SHA256 checksum:", f.sha256),
("Uploaded by:", Text(f.addr)),
("User agent:", Text(f.ua or "")),
("Management token:", f.mgmt_token),
("Secret:", f.secret),
("Is NSFW:", ("Yes" if f.is_nsfw else "No") + (f" (Score: {f.nsfw_score:0.4f})" if f.nsfw_score else " (Not scanned)")),