Merge branch 'main' of git+ssh://nicole/home/q/repos/mini-flees

This commit is contained in:
2023-08-20 22:24:06 +03:00
7 changed files with 103 additions and 16 deletions

View File

@@ -8,13 +8,14 @@ from flask import (
render_template, render_template,
jsonify, jsonify,
request, request,
url_for,
redirect,
send_from_directory, send_from_directory,
session,
) )
from werkzeug.utils import secure_filename from werkzeug.utils import secure_filename
from revprox import ReverseProxied from revprox import ReverseProxied
from utils.misc import ( from utils.misc import random_token, hash_password, verify_password
random_token,
)
from utils.files import ( from utils.files import (
db_store_file, db_store_file,
file_details, file_details,
@@ -53,6 +54,7 @@ def upload():
-H "Name: my.file.ext" \ -H "Name: my.file.ext" \
-H "Max-Downloads: 4000" \ -H "Max-Downloads: 4000" \
-H "Expires-Days: 14" \ -H "Expires-Days: 14" \
-H "Password: mypass" \
-H "Secret: dff789f0bbe8183d32542" \ -H "Secret: dff789f0bbe8183d32542" \
"$FLASK_PUBLIC_URL"/upload "$FLASK_PUBLIC_URL"/upload
@@ -87,12 +89,17 @@ def upload():
) )
if "Expires-hours" in request.headers: if "Expires-hours" in request.headers:
expires = int(time.time()) + 3600 * int(request.headers.get("Expires-hours")) expires = int(time.time()) + 3600 * int(request.headers.get("Expires-hours"))
password = None
if "Password" in request.headers:
if request.headers["Password"] != "":
password = hash_password(request.headers["Password"])
while True: while True:
token = random_token() token = random_token()
folder = os.path.join(app.config["DATAFOLDER"], token) folder = os.path.join(app.config["DATAFOLDER"], token)
if not os.path.exists(folder): if not os.path.exists(folder):
break break
filename = file_full_path(token, safe_filename) filename = file_full_path(token, safe_filename)
os.mkdir(folder) os.mkdir(folder)
@@ -112,7 +119,7 @@ def upload():
break break
f.write(chunk) f.write(chunk)
db_store_file(token, safe_filename, expires, max_dl) db_store_file(token, safe_filename, expires, max_dl, password)
download_url = file_full_url(token, safe_filename) download_url = file_full_url(token, safe_filename)
return "File uploaded\n%s\n" % (download_url,), 200 return "File uploaded\n%s\n" % (download_url,), 200
@@ -190,6 +197,10 @@ def download(name, token):
""" """
Download a file Download a file
""" """
if "Password" in request.headers:
session[token] = request.headers["Password"]
return download_file(token, name) return download_file(token, name)
@@ -206,6 +217,26 @@ def script_mfl():
) )
@app.route("/login", methods=["GET", "POST"])
def login():
if request.method == "POST":
session[request.form["token"]] = request.form["password"]
return redirect(request.form["redirect"])
return render_template(
"login.html",
filename=session["name"],
redirect=session["redirect"],
token=session["token"],
)
@app.route("/logout", methods=["GET"])
def logout():
session.clear()
return "OK", 200
def download_file(token, name): def download_file(token, name):
""" """
check for file expiry, and send file if allowed check for file expiry, and send file if allowed
@@ -215,13 +246,22 @@ def download_file(token, name):
return "Error", 404 return "Error", 404
db_stat = db_get_file(token, name) db_stat = db_get_file(token, name)
if db_stat: if db_stat:
added, expires, downloads, max_dl = db_stat added, expires, downloads, max_dl, password_hash = db_stat
else: else:
return "Error", 404 return "Error", 404
if downloads >= max_dl and max_dl > -1: if downloads >= max_dl and max_dl > -1:
return "Expired", 401 return "Expired", 401
if expires < time.time(): if expires < time.time():
return "Expired", 401 return "Expired", 401
if password_hash:
if verify_password(session.get(token, ""), password_hash):
pass
else:
session["token"] = token
session["name"] = name
session["redirect"] = url_for("download", name=name, token=token)
return redirect(url_for("login"))
db_add_download(token, name) db_add_download(token, name)
return send_from_directory( return send_from_directory(
directory=os.path.join(app.config["DATAFOLDER"], token), path=name directory=os.path.join(app.config["DATAFOLDER"], token), path=name

View File

@@ -8,6 +8,7 @@ CREATE TABLE IF NOT EXISTS files (
added integer NOT NULL, added integer NOT NULL,
expires integer NOT NULL, expires integer NOT NULL,
downloads integer NOT NULL, downloads integer NOT NULL,
max_downloads integer NOT NULL max_downloads integer NOT NULL,
passhash text
); );
EOF EOF

View File

@@ -1,2 +1,4 @@
flask flask
gunicorn gunicorn
passlib
argon2_cffi

View File

@@ -16,6 +16,7 @@ _help() {
-d max days to keep (default 30) -d max days to keep (default 30)
-m max downloads to keep (default 9999, -1 to disable) -m max downloads to keep (default 9999, -1 to disable)
-p password
Filename: Filename:
When writing to share: When writing to share:
@@ -105,6 +106,7 @@ _write_folder() { # name, file
-H "Max-Downloads: $MAXDL" \ -H "Max-Downloads: $MAXDL" \
-H "Expires-Days: $MAXDAYS" \ -H "Expires-Days: $MAXDAYS" \
-H "Secret: $MFL_TOKEN" \ -H "Secret: $MFL_TOKEN" \
-H "Password: $PASSWORD" \
"$MFL_ROOTURL"/upload | cat "$MFL_ROOTURL"/upload | cat
} }
_write_file() { # name, file _write_file() { # name, file
@@ -113,6 +115,7 @@ _write_file() { # name, file
-H "Max-Downloads: $MAXDL" \ -H "Max-Downloads: $MAXDL" \
-H "Expires-Days: $MAXDAYS" \ -H "Expires-Days: $MAXDAYS" \
-H "Secret: $MFL_TOKEN" \ -H "Secret: $MFL_TOKEN" \
-H "Password: $PASSWORD" \
"$MFL_ROOTURL"/upload | cat "$MFL_ROOTURL"/upload | cat
} }
_write_stdin() { # name _write_stdin() { # name
@@ -122,6 +125,7 @@ _write_stdin() { # name
-H "Max-Downloads: $MAXDL" \ -H "Max-Downloads: $MAXDL" \
-H "Expires-Days: $MAXDAYS" \ -H "Expires-Days: $MAXDAYS" \
-H "Secret: $MFL_TOKEN" \ -H "Secret: $MFL_TOKEN" \
-H "Password: $PASSWORD" \
"$MFL_ROOTURL"/upload | cat "$MFL_ROOTURL"/upload | cat
} }
@@ -200,6 +204,7 @@ for (( i=2; i<=$#; i++ )); do
j=$(( i + 1 )) j=$(( i + 1 ))
[[ "${!i}" = "-m" ]] && { MAXDL=${!j}; i=$j; continue; } [[ "${!i}" = "-m" ]] && { MAXDL=${!j}; i=$j; continue; }
[[ "${!i}" = "-d" ]] && { MAXDAYS=${!j}; i=$j; continue; } [[ "${!i}" = "-d" ]] && { MAXDAYS=${!j}; i=$j; continue; }
[[ "${!i}" = "-p" ]] && { PASSWORD=${!j}; i=$j; continue; }
if [[ -z "$FILE" ]]; then if [[ -z "$FILE" ]]; then
FILE="${!i}" FILE="${!i}"
continue continue

View File

@@ -13,14 +13,14 @@ def get_db():
return db, c return db, c
def db_store_file(token, name, expires, max_dl): def db_store_file(token, name, expires, max_dl, password):
db, c = get_db() db, c = get_db()
c.execute( c.execute(
""" """
insert into files(token,name,added,expires,downloads,max_downloads) insert into files(token,name,added,expires,downloads,max_downloads,passhash)
values (?,?,?,?,?,?) values (?,?,?,?,?,?,?)
""", """,
(token, name, int(time.time()), expires, 0, max_dl), (token, name, int(time.time()), expires, 0, max_dl, password),
) )
if c.rowcount > 0: if c.rowcount > 0:
db.commit() db.commit()
@@ -31,7 +31,7 @@ def db_get_file(token, name):
db, c = get_db() db, c = get_db()
return db.execute( return db.execute(
""" """
SELECT added,expires,downloads,max_downloads SELECT added,expires,downloads,max_downloads,passhash
FROM files WHERE token = ? AND name = ? FROM files WHERE token = ? AND name = ?
""", """,
(token, name), (token, name),
@@ -42,7 +42,7 @@ def db_get_files():
db, c = get_db() db, c = get_db()
return db.execute( return db.execute(
""" """
SELECT token,name,added,expires,downloads,max_downloads SELECT token,name,added,expires,downloads,max_downloads,passhash
FROM files FROM files
WHERE expires > ? and (downloads < max_downloads or max_downloads = -1) WHERE expires > ? and (downloads < max_downloads or max_downloads = -1)
ORDER BY added ORDER BY added
@@ -157,7 +157,7 @@ def file_details(token, name):
s = os.stat(full_path) s = os.stat(full_path)
db_stat = db_get_file(token, name) db_stat = db_get_file(token, name)
if db_stat: if db_stat:
added, expires, downloads, max_dl = db_stat added, expires, downloads, max_dl, password = db_stat
else: else:
return {} return {}
return { return {
@@ -169,6 +169,7 @@ def file_details(token, name):
"expires": file_time_human(expires), "expires": file_time_human(expires),
"downloaded": downloads, "downloaded": downloads,
"max-dl": max_dl, "max-dl": max_dl,
"protected": password is not None,
} }
except FileNotFoundError: except FileNotFoundError:
return {} return {}
@@ -190,7 +191,8 @@ def file_list():
url = file_full_url(file[0], file[1]) url = file_full_url(file[0], file[1])
added = file_date_human(file[2]) added = file_date_human(file[2])
expiry = file_date_human(file[3]) expiry = file_date_human(file[3])
details.append(f"{added}/{expiry} {file[4]:4d}/{file[5]:4d} {url}") pw = " (PW)" if file[6] else ""
details.append(f"{added}/{expiry} {file[4]:4d}/{file[5]:4d} {url}{pw}")
return details return details

View File

@@ -1,6 +1,7 @@
from datetime import datetime from datetime import datetime
import secrets import secrets
import string import string
import passlib.hash
VALID_TOKEN_CHARS = string.digits + string.ascii_letters VALID_TOKEN_CHARS = string.digits + string.ascii_letters
@@ -29,3 +30,11 @@ def file_size_human(num, HTML=True):
def file_size_MB(num): def file_size_MB(num):
return "{:,.2f}".format(num / (1024 * 1024)) return "{:,.2f}".format(num / (1024 * 1024))
def hash_password(password):
return passlib.hash.argon2.hash(password)
def verify_password(password, hash):
return passlib.hash.argon2.verify(password, hash)

View File

@@ -192,8 +192,6 @@ function t08-file-details() {
"$FLASK_PUBLIC_URL"/details/"$token_name" "$FLASK_PUBLIC_URL"/details/"$token_name"
} }
function t09-file-delete() { function t09-file-delete() {
download_url=$( curl -fL -w "\n" \ download_url=$( curl -fL -w "\n" \
-H "Secret: $FLASK_ACCESS_TOKEN" \ -H "Secret: $FLASK_ACCESS_TOKEN" \
"$FLASK_PUBLIC_URL"/ls | grep "$BIGS" | tail -n 1 | sed s,.*http,http, ) "$FLASK_PUBLIC_URL"/ls | grep "$BIGS" | tail -n 1 | sed s,.*http,http, )
@@ -271,8 +269,38 @@ function t14-mfl-upload() {
cat mfl | ./mfl w mfl cat mfl | ./mfl w mfl
./mfl w . "folder with spaces" ./mfl w . "folder with spaces"
./mfl w "$SMALL" ./mfl w "$SMALL"
./mfl w -p "passwordprotected" "$IMAGE"
} }
function t15-upload-password() {
cat "$IMAGE" | \
curl -fL -w "\n" -F file="@-" -X POST \
-H "Name: $IMAGE" \
-H "Password: password" \
-H "Secret: $FLASK_ACCESS_TOKEN" \
"$FLASK_PUBLIC_URL"/upload
}
function t16-download-password() {
download_url=$( curl -fL -w "\n" \
-H "Secret: $FLASK_ACCESS_TOKEN" \
"$FLASK_PUBLIC_URL"/ls | grep "$IMAGE" | tail -n 1 | sed s,.*http,http, )
token_name=$( echo $download_url | sed s,.*/d/,, )
curl -fL -w "\n" \
-H "Secret: $FLASK_ACCESS_TOKEN" \
-H "Password: password" \
"$FLASK_PUBLIC_URL"/d/"$token_name" > downloaded
file downloaded
curl -fL -w "\n" \
-H "Secret: $FLASK_ACCESS_TOKEN" \
-H "Password: wrongpassword" \
"$FLASK_PUBLIC_URL"/d/"$token_name" > downloaded
file downloaded
}
_getlist() { _getlist() {