new short expiring url for files
This commit is contained in:
65
code/app.py
65
code/app.py
@@ -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)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ pycrypto
|
|||||||
requests
|
requests
|
||||||
python-magic
|
python-magic
|
||||||
python-dateutil
|
python-dateutil
|
||||||
|
apsw
|
||||||
|
|||||||
@@ -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 $?
|
||||||
|
}
|
||||||
|
|||||||
@@ -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(" ")
|
||||||
|
|||||||
@@ -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,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
Reference in New Issue
Block a user