From e1685342581604b83b4b1b6ab9b10bcde5ef9d01 Mon Sep 17 00:00:00 2001 From: Mia Herkt Date: Wed, 30 Nov 2022 02:16:19 +0100 Subject: [PATCH] Allow changing expiration date --- fhost.py | 71 +++++++++++++++++++++++++------------------- templates/index.html | 10 +++++-- 2 files changed, 48 insertions(+), 33 deletions(-) diff --git a/fhost.py b/fhost.py index c771d3e..2e2f8af 100755 --- a/fhost.py +++ b/fhost.py @@ -158,6 +158,34 @@ class File(db.Model): self.removed = permanent self.getpath().unlink(missing_ok=True) + # Returns the epoch millisecond that a file should expire + # + # Uses the expiration time provided by the user (requested_expiration) + # upper-bounded by an algorithm that computes the size based on the size of the + # file. + # + # That is, all files are assigned a computed expiration, which can voluntarily + # shortened by the user either by providing a timestamp in epoch millis or a + # duration in hours. + def get_expiration(requested_expiration, size) -> int: + current_epoch_millis = time.time() * 1000; + + # Maximum lifetime of the file in milliseconds + this_files_max_lifespan = get_max_lifespan(size); + + # The latest allowed expiration date for this file, in epoch millis + this_files_max_expiration = this_files_max_lifespan + 1000 * time.time(); + + if requested_expiration is None: + return this_files_max_expiration + elif requested_expiration < 1650460320000: + # Treat the requested expiration time as a duration in hours + requested_expiration_ms = requested_expiration * 60 * 60 * 1000 + return min(this_files_max_expiration, current_epoch_millis + requested_expiration_ms) + else: + # Treat the requested expiration time as a timestamp in epoch millis + return min(this_files_max_expiration, requested_expiration) + """ requested_expiration can be: - None, to use the longest allowed file lifespan @@ -203,33 +231,7 @@ class File(db.Model): return ext[:app.config["FHOST_MAX_EXT_LENGTH"]] or ".bin" - # Returns the epoch millisecond that this file should expire - # - # Uses the expiration time provided by the user (requested_expiration) - # upper-bounded by an algorithm that computes the size based on the size of the - # file. - # - # That is, all files are assigned a computed expiration, which can voluntarily - # shortened by the user either by providing a timestamp in epoch millis or a - # duration in hours. - def get_expiration() -> int: - current_epoch_millis = time.time() * 1000; - - # Maximum lifetime of the file in milliseconds - this_files_max_lifespan = get_max_lifespan(len(data)); - - # The latest allowed expiration date for this file, in epoch millis - this_files_max_expiration = this_files_max_lifespan + 1000 * time.time(); - - if requested_expiration is None: - return this_files_max_expiration - elif requested_expiration < 1650460320000: - # Treat the requested expiration time as a duration in hours - requested_expiration_ms = requested_expiration * 60 * 60 * 1000 - return min(this_files_max_expiration, current_epoch_millis + requested_expiration_ms) - else: - # Treat the requested expiration time as a timestamp in epoch millis - return min(this_files_max_expiration, requested_expiration); + expiration = File.get_expiration(requested_expiration, len(data)) isnew = True f = File.query.filter_by(sha256=digest).first() @@ -240,18 +242,17 @@ class File(db.Model): abort(451) if f.expiration is None: # The file has expired, so give it a new expiration date - f.expiration = get_expiration() + f.expiration = expiration # Also generate a new management token f.mgmt_token = secrets.token_urlsafe() else: # The file already exists, update the expiration if needed - f.expiration = max(f.expiration, get_expiration()) + f.expiration = max(f.expiration, expiration) isnew = False else: mime = get_mime() ext = get_ext(mime) - expiration = get_expiration() mgmt_token = secrets.token_urlsafe() f = File(digest, ext, mime, addr, expiration, mgmt_token) @@ -386,6 +387,16 @@ def manage_file(f): f.delete() db.session.commit() return "" + if "expires" in request.form: + try: + requested_expiration = int(request.form["expires"]) + except ValueError: + abort(400) + + fsize = f.getpath().stat().st_size + f.expiration = File.get_expiration(requested_expiration, fsize) + db.session.commit() + return "", 202 abort(400) diff --git a/templates/index.html b/templates/index.html index 9d5e398..d98482c 100644 --- a/templates/index.html +++ b/templates/index.html @@ -13,17 +13,21 @@ File URLs are valid for at least 30 days and up to a year (see below). Shortened URLs do not expire. Files can be set to expire sooner by adding an "expires" parameter (in hours) - curl -F'file=@yourfile.png' -F'expires=24' {{ fhost_url }} + curl -F'file=@yourfile.png' -Fexpires=24 {{ fhost_url }} OR by setting "expires" to a timestamp in epoch milliseconds - curl -F'file=@yourfile.png' -F'expires=1681996320000' {{ fhost_url }} + curl -F'file=@yourfile.png' -Fexpires=1681996320000 {{ fhost_url }} Expired files won't be removed immediately, but will be removed as part of the next purge. Whenever a file that does not already exist or has expired is uploaded, the HTTP response header includes an X-Token field. You can use this -to delete the file immediately: +to perform management operations on the file. + +To delete the file immediately: curl -Ftoken=token_here -Fdelete= {{ fhost_url }}/abc.txt +To change the expiration date (see above): + curl -Ftoken=token_here -Fexpires=3 {{ fhost_url }}/abc.txt {% set max_size = config["MAX_CONTENT_LENGTH"]|filesizeformat(True) %} Maximum file size: {{ max_size }}