new short expiring url for files

This commit is contained in:
2019-10-31 12:37:13 +02:00
parent 3195c87840
commit fbbef99305
6 changed files with 173 additions and 3 deletions

View File

@@ -13,7 +13,7 @@ from revprox import ReverseProxied
from utils import * from utils import *
__FLEES_VERSION__ = "20191024.0" __FLEES_VERSION__ = "20191031.0"
app = Flask(__name__) app = Flask(__name__)
app.config.from_object(__name__) app.config.from_object(__name__)
config_values = read_config(app) config_values = read_config(app)
@@ -30,7 +30,8 @@ if 'notifier' in config_values:
app.secret_key = config_values['app_secret_key'] app.secret_key = config_values['app_secret_key']
app.wsgi_app = ReverseProxied(app.wsgi_app) app.wsgi_app = ReverseProxied(app.wsgi_app)
with app.app_context():
expire_database_create()
@app.before_request @app.before_request
def before_request(): def before_request():
@@ -351,6 +352,42 @@ def file_delete(name, token, filename):
return "OK", 200 return "OK", 200
@app.route('/file/expiring/<name>/<token>/<expire>/<path:filename>', methods=['GET'])
def file_expiring(name, token, expire, filename):
(ok,share) = get_share(name, token = token)
if not ok:
return share
full_path = os.path.join(
share['path'],
secure_filename_hidden(filename)
)
if not os.path.exists(full_path):
return "-1", 403
allow_direct = get_or_none('direct_links', share) if get_or_none('pass_hash', share) else False
if not allow_direct:
return "-1", 403
expires = 24 * 3600 * float(expire) + time.time()
return set_expiring_file(share, full_path, expires), 200
@app.route('/file/expiring_remove/<name>/<token>/<path:filename>', methods=['GET'])
def file_expiring_remove(name, token, filename):
(ok,share) = get_share(name, token = token)
if not ok:
return share
full_path = os.path.join(
share['path'],
secure_filename_hidden(filename)
)
if not os.path.exists(full_path):
return "-1", 403
allow_direct = get_or_none('direct_links', share) if get_or_none('pass_hash', share) else False
if not allow_direct:
return "-1", 403
remove_expiring_file(share, full_path)
return "OK", 200
@app.route('/file/direct/<name>/<token>/<path:filename>', methods=['GET']) @app.route('/file/direct/<name>/<token>/<path:filename>', methods=['GET'])
def file_direct(name, token, filename): def file_direct(name, token, filename):
(ok,share) = get_share(name, token = token) (ok,share) = get_share(name, token = token)
@@ -485,6 +522,27 @@ def logout(name):
) )
@app.route('/e/<ehash>', methods=['GET'])
@app.route('/e/<ehash>/<filename>', methods=['GET'])
def download_expiring(ehash, filename = None):
file_path, expiring = get_expiring_file(ehash)
if not os.path.exists(file_path):
return 'No such file', 404
if expiring - time.time() < 0:
return 'Expired', 404
notify({
"filename": file_path,
"operation": "expiring_download"
})
if filename == None:
filename = os.path.basename(file_path)
return send_file(
file_path,
as_attachment = True,
attachment_filename = filename
)
@app.route('/direct/<name>/<token>/<path:filename>', methods=['GET']) @app.route('/direct/<name>/<token>/<path:filename>', methods=['GET'])
def download_direct(name,token,filename): def download_direct(name,token,filename):
(ok,share) = get_share(name, require_auth = False) (ok,share) = get_share(name, require_auth = False)
@@ -932,9 +990,10 @@ def zip_clean():
os.remove(os.path.join(app.config['ZIP_FOLDER'],file)) os.remove(os.path.join(app.config['ZIP_FOLDER'],file))
zip_clean()
if __name__ == "__main__": if __name__ == "__main__":
zip_clean()
app.run(debug=True) app.run(debug=True)

View File

@@ -4,3 +4,4 @@ pycrypto
requests requests
python-magic python-magic
python-dateutil python-dateutil
apsw

View File

@@ -15,6 +15,9 @@ _help() {
(c)opy Copy file/folder (c)opy Copy file/folder
(p)aste Paste file/folder (will overwrite) (p)aste Paste file/folder (will overwrite)
url Get the direct share url url Get the direct share url
short Get a short share url. Add argument as days of expiration
Default: 14 days
short-del Delete short share url for a file.
upload Get URL for uploads [no arguments] upload Get URL for uploads [no arguments]
update Update flip client [no arguments] update Update flip client [no arguments]
self Get URL to install this client to another computer self Get URL to install this client to another computer
@@ -194,6 +197,14 @@ _url() { # name
curl -L -s "$FLEES_ROOTURL/file/direct/$FLEES_SHARE/$FLEES_TOKEN/$1" curl -L -s "$FLEES_ROOTURL/file/direct/$FLEES_SHARE/$FLEES_TOKEN/$1"
echo '' echo ''
} }
_short() {
curl -L -s "$FLEES_ROOTURL/file/expiring/$FLEES_SHARE/$FLEES_TOKEN/$2/$1"
echo ''
}
_short_del() {
curl -L -s "$FLEES_ROOTURL/file/expiring_remove/$FLEES_SHARE/$FLEES_TOKEN/$1"
echo ''
}
_upload_url() { _upload_url() {
echo "This information is a security risk, watch where it's shared" echo "This information is a security risk, watch where it's shared"
echo "# python2 <( curl -L -s $FLEES_ROOTURL/script/upload_split/$FLEES_SHARE/$FLEES_TOKEN ) file_to_upload.ext" echo "# python2 <( curl -L -s $FLEES_ROOTURL/script/upload_split/$FLEES_SHARE/$FLEES_TOKEN ) file_to_upload.ext"
@@ -283,6 +294,8 @@ CMD=list
[[ "$1" = "simplelist" ]] && { CMD=simple_list; ARG1=$CMD; } [[ "$1" = "simplelist" ]] && { CMD=simple_list; ARG1=$CMD; }
[[ "$1" = "autocomplete" ]] && { _get_completer; } [[ "$1" = "autocomplete" ]] && { _get_completer; }
[[ "$1" = "url" ]] && { CMD=url; ARG1=$CMD; } [[ "$1" = "url" ]] && { CMD=url; ARG1=$CMD; }
[[ "$1" = "short" ]] && { CMD=short; ARG1=$CMD; }
[[ "$1" = "short-del" ]] && { CMD=short-del; ARG1=$CMD; }
[[ "$1" = "update" ]] && { _update_client; } [[ "$1" = "update" ]] && { _update_client; }
[[ "$1" = "upload" ]] && { _upload_url; } [[ "$1" = "upload" ]] && { _upload_url; }
[[ "$1" = "self" ]] && { _self_url; } [[ "$1" = "self" ]] && { _self_url; }
@@ -324,3 +337,15 @@ _get_file
_url "$NAME" _url "$NAME"
exit $? exit $?
} }
[[ "$CMD" = short ]] && {
EXPIRY=14
if [ -n "$3" ]; then
EXPIRY="$3"
fi
_short "$NAME" $EXPIRY
exit $?
}
[[ "$CMD" = short-del ]] && {
_short_del "$NAME"
exit $?
}

View File

@@ -4,6 +4,7 @@ import base64
#~ from Crypto.Cipher import AES #~ from Crypto.Cipher import AES
import hashlib import hashlib
#~ class Crypto: #~ class Crypto:
#~ def __init__(self, secret): #~ def __init__(self, secret):
#~ self.secret = add_pad(secret[0:16]) #~ self.secret = add_pad(secret[0:16])
@@ -68,6 +69,14 @@ def random_token():
return token return token
def random_expiring_hash():
codes = []
for i in range(3):
codes.append("".join([random.choice(string.ascii_letters + string.digits) for n in range(4)]))
ehash = "-".join(codes)
return ehash
def remove_pad(string): def remove_pad(string):
""" Remove spaces from right """ """ Remove spaces from right """
return string.rstrip(" ") return string.rstrip(" ")

View File

@@ -5,6 +5,11 @@ import requests
import re import re
import json import json
import stat import stat
import time
try:
import apsw
except ImportError as e:
pass
from .misc import * from .misc import *
from .crypt import * from .crypt import *
@@ -76,6 +81,26 @@ def download_url(url, filename):
return (True, ("OK", 200 )) return (True, ("OK", 200 ))
def expire_database_create():
connection = apsw.Connection(app.config['SQLITE_FILE'])
cursor = connection.cursor()
try:
cursor.execute("""CREATE TABLE IF NOT EXISTS expiring (
hash text PRIMARY KEY,
file text NOT NULL,
expires integer NOT NULL
);""")
cursor.execute("DELETE FROM expiring WHERE expires < ?",
(
time.time(),
)
)
except apsw.BusyError as e:
# Other thread is creating the database
pass
set_rights(app.config['SQLITE_FILE'])
def file_autoremove(path, share, notifier = None): def file_autoremove(path, share, notifier = None):
autoremove = get_or_none('autoremove', share, 0) autoremove = get_or_none('autoremove', share, 0)
if autoremove == 0: if autoremove == 0:
@@ -207,6 +232,17 @@ def get_download_url(share, file, token):
)) ))
def get_expiring_file(ehash):
connection = apsw.Connection(app.config['SQLITE_FILE'])
cursor = connection.cursor()
for row in cursor.execute("SELECT file, expires FROM expiring WHERE hash = ?",
( ehash, )
):
return row[0], row[1]
return None, None
def get_script_url(public_url, share, end_point, token = "[TOKEN]"): def get_script_url(public_url, share, end_point, token = "[TOKEN]"):
cmd = None cmd = None
doc = None doc = None
@@ -267,6 +303,7 @@ def read_config(app):
app.config['SITE_NAME'] = config_values['site_name'] app.config['SITE_NAME'] = config_values['site_name']
app.config['UPLOAD_FOLDER'] = config_values['data_folder'] app.config['UPLOAD_FOLDER'] = config_values['data_folder']
app.config['SHARES_FILE'] = config_values['shares_file'] app.config['SHARES_FILE'] = config_values['shares_file']
app.config['SQLITE_FILE'] = config_values['sqlite_file']
if 'log_file' in config_values: if 'log_file' in config_values:
app.config['LOG_FILE'] = config_values['log_file'] app.config['LOG_FILE'] = config_values['log_file']
else: else:
@@ -294,3 +331,41 @@ def set_rights(path):
os.chmod(path, st.st_mode | stat.S_IRUSR | stat.S_IWUSR) os.chmod(path, st.st_mode | stat.S_IRUSR | stat.S_IWUSR)
if app.config['GID'] > 0: if app.config['GID'] > 0:
os.chmod(path, st.st_mode | stat.S_IRGRP | stat.S_IWGRP) os.chmod(path, st.st_mode | stat.S_IRGRP | stat.S_IWGRP)
def set_expiring_file(share, filename, expires):
connection = apsw.Connection(app.config['SQLITE_FILE'])
cursor = connection.cursor()
while True:
ehash = random_expiring_hash()
matches = len(list(cursor.execute("SELECT file FROM expiring WHERE hash = ?",
( ehash, )
)))
if matches == 0:
break
cursor.execute("INSERT INTO expiring (hash, file, expires) VALUES (?,?,?)",
(
ehash,
filename,
expires
)
)
return "/".join((
app.config['PUBLIC_URL'],
'e',
ehash,
os.path.basename(filename)
))
def remove_expiring_file(share, filename):
connection = apsw.Connection(app.config['SQLITE_FILE'])
cursor = connection.cursor()
cursor.execute("DELETE FROM expiring WHERE file = ?",
(
filename,
)
)

View File

@@ -19,6 +19,7 @@
"notifier": "", "notifier": "",
"__do_not_edit": "most likely you will not change anything after this line", "__do_not_edit": "most likely you will not change anything after this line",
"data_folder": "data", "data_folder": "data",
"sqlite_file": "data/expiring.sqlite",
"shares_file": "data/shares.json", "shares_file": "data/shares.json",
"log_file": "data/flees.log", "log_file": "data/flees.log",
"version_folder": "_version", "version_folder": "_version",