new version allows downloads from subfolders

This commit is contained in:
Ville Rantanen
2018-03-10 15:28:34 +02:00
parent 4b2280049b
commit 00008c9c7d
4 changed files with 108 additions and 56 deletions

View File

@@ -1,4 +1,3 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import os,sys,time,stat
@@ -8,13 +7,13 @@ from flask import Flask, render_template, jsonify, current_app, Response, \
redirect, url_for, request, g, session, send_file, send_from_directory
from werkzeug.utils import secure_filename
import zipfile
import urllib
from multiprocessing import Process
from revprox import ReverseProxied
from utils.utils import *
from utils.crypt import *
__FLEES_VERSION__ = "20180302.0"
__FLEES_VERSION__ = "20180310.0"
app = Flask(__name__)
app.config.from_object(__name__)
# Read config from json !
@@ -42,6 +41,7 @@ if 'notifier' in config_values:
app.secret_key = config_values['app_secret_key']
app.wsgi_app = ReverseProxied(app.wsgi_app)
@app.before_request
def before_request():
g.shares = json.load(open(app.config['SHARES_FILE'],'rt'))
@@ -49,6 +49,7 @@ def before_request():
g.site_name = app.config['SITE_NAME']
g.max_zip_size = app.config['MAX_ZIP_SIZE']
@app.route("/")
def index():
public_shares = []
@@ -71,6 +72,7 @@ def index():
return render_template("index.html", entries=public_shares)
@app.route('/authenticate/<name>', methods=['GET','POST'])
def authenticate(name):
if request.method == 'GET':
@@ -80,6 +82,7 @@ def authenticate(name):
session[name] = password_hash(user_password, app.secret_key)
return redirect(url_for('list_view',name=name))
@app.route('/upload/<name>/<token>', methods=['POST'])
@app.route('/upload', methods=['POST'])
def upload(name = None, token = None):
@@ -158,9 +161,6 @@ def upload_join_splitted(name, token):
except:
return "Joining failed\n", 400
return "Joining started\n", 200
#~ return Response(joiner(target_name, parts), mimetype="text/plain", content_type="text/event-stream")
#~ return "%d parts joined"%(len(parts),), 200
@app.route('/send/<name>', methods=['GET'])
@@ -170,22 +170,19 @@ def send(name):
return share
return render_template('send.html',name=name)
@app.route('/files/<name>/<token>', methods=['GET'])
def list_files(name, token):
(ok,share) = get_share(name, token = token)
if not ok:
return share
files = []
for file in sorted(os.listdir(share['path'])):
fp = os.path.join(share['path'],file)
if os.path.isdir(fp):
continue
if file.startswith("."):
continue
files.append(urllib.parse.quote_plus(file))
for file in iter_folder_files(share['path']):
files.append(path2url(file))
files.append("")
return "\n".join(files), 200
@app.route('/list/<name>/<token>', methods=['GET'])
@app.route('/list/<name>', methods=['GET'])
def list_view(name, token = None):
@@ -197,15 +194,13 @@ def list_view(name, token = None):
return redirect(url_for('list_view',name=name))
files = []
for file in sorted(os.listdir(share['path'])):
for file in iter_folder_files(share['path']):
fp = os.path.join(share['path'],file)
if os.path.isdir(fp):
continue
if file.startswith("."):
continue
status = file_stat(fp)
status.update({
'token': get_direct_token(share, file)
'token': get_direct_token(share, file),
'name': file,
'url': path2url(file)
})
files.append(status)
# direct share links not allowed if password isnt set
@@ -227,6 +222,7 @@ def list_view(name, token = None):
description = get_or_none('description', share, "")
)
@app.route('/logout/<name>', methods=['GET'])
def logout(name):
if name in session:
@@ -236,7 +232,8 @@ def logout(name):
name = name
)
@app.route('/direct/<name>/<token>/<filename>', methods=['GET'])
@app.route('/direct/<name>/<token>/<path:filename>', methods=['GET'])
def download_direct(name,token,filename):
(ok,share) = get_share(name, require_auth = False)
if not ok:
@@ -244,6 +241,8 @@ def download_direct(name,token,filename):
allow_direct = get_or_none('direct_links', share)
if allow_direct != True:
return 'Direct download not allowed', 403
if not is_path_safe(filename):
return 'Incorrect relative path'+filename, 403
file_token = get_direct_token(share, filename)
if file_token == None:
return 'Cannot generate token', 400
@@ -251,7 +250,7 @@ def download_direct(name,token,filename):
return 'Incorrect token', 403
file_path = os.path.join(share['path'], filename)
if not os.path.exists(file_path):
return 'no such file', 404
return 'No such file', 404
notify({
"recipient": get_or_none('recipient', share),
"share": name,
@@ -261,22 +260,14 @@ def download_direct(name,token,filename):
return send_from_directory(directory=share['path'], filename=filename)
@app.route('/download/<name>/<token>/<filename>', methods=['GET'])
@app.route('/download/<name>/<filename>', methods=['GET'])
def download_file(name, filename, token = None):
(ok,share) = get_share(name, token = token)
if not ok:
return share
file_path = os.path.join(share['path'], filename)
if not os.path.exists(file_path):
return 'no such file', 404
notify({
"recipient": get_or_none('recipient', share),
"share": name,
"filename": file_path,
"operation": "download"
})
return send_from_directory(directory=share['path'], filename=filename)
@app.route('/download/gui/<name>/<path:filename>', methods=['GET'])
def download_gui(name, filename):
return download_file(name, filename, token = None)
@app.route('/download/<name>/<token>/<path:filename>', methods=['GET'])
def download_token(name, filename, token):
return download_file(name, filename, token = token)
@app.route('/zip/<name>/<token>', methods=['GET'])
@@ -302,6 +293,7 @@ def download_zip(name, token = None):
attachment_filename = name + ".zip"
)
@app.route('/script/upload/<name>/<token>', methods=['GET'])
def script_upload(name = None, token = None):
(ok,share) = get_share(name, token = token)
@@ -518,8 +510,8 @@ done
token
)
class uploadJoiner:
class uploadJoiner:
def __init__(self, target_name, parts):
self.target_name = target_name
self.parts = parts
@@ -528,6 +520,7 @@ class uploadJoiner:
p.daemon = True
p.start()
def run(self):
with open(self.target_name,'wb') as writer:
for part in self.parts:
@@ -539,6 +532,24 @@ class uploadJoiner:
os.remove(part)
def download_file(name, filename, token = None):
(ok,share) = get_share(name, token = token)
if not ok:
return share
if not is_path_safe(filename):
return "Incorrect path", 403
file_path = os.path.join(share['path'], filename)
if not os.path.exists(file_path):
return 'No such file, '+file_path, 404
notify({
"recipient": get_or_none('recipient', share),
"share": name,
"filename": file_path,
"operation": "download"
})
return send_from_directory(directory=share['path'], filename=filename)
def file_versionize(filename):
""" Move file to versioned with integer """
stats = file_stat(filename)
@@ -635,7 +646,6 @@ def set_rights(path):
def zip_share(share):
if not os.path.exists(app.config['ZIP_FOLDER']):
os.makedirs(app.config['ZIP_FOLDER'])
set_rights(app.config['ZIP_FOLDER'])
@@ -648,7 +658,7 @@ def zip_share(share):
)
)
zf = zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED)
for file in sorted(os.listdir(share['path'])):
for file in iter_folder_files(share['path']):
fp = os.path.join(share['path'], file)
if os.path.isdir(fp):
continue
@@ -677,6 +687,7 @@ def zip_clean():
if __name__ == "__main__":
zip_clean()
app.run(debug=True)

View File

@@ -6,6 +6,7 @@ from datetime import datetime
from utils.utils import *
from utils.crypt import *
def get_root_path(opts):
root_folder = os.path.dirname(
os.path.dirname(
@@ -57,6 +58,7 @@ def list_folders(shares,config):
for path, folders, files in os.walk(data_folder):
full_path = os.path.join(data_folder, path)
share_name = None
parent_is_share = False
for share in shares:
share_path = os.path.join(data_folder, share['path'])
if not os.path.exists(share_path):
@@ -64,6 +66,13 @@ def list_folders(shares,config):
if os.path.samefile(full_path, share_path):
share_name = share['name']
break
parents = full_path.split(os.sep)
for p in range(len(parents)):
test_path = os.sep+os.sep.join(parents[1:(p+2)])
if os.path.samefile(test_path, share_path):
parent_is_share = True
if parent_is_share:
continue
if share_name == None:
# skip folder if it's not a share, and not a leaf
if len(folders) > 0:
@@ -83,6 +92,7 @@ def list_folders(shares,config):
))
print(tabulate(table, headers = "firstrow"))
def add_share(shares, config, opts):
# Make name and path safe:
@@ -214,7 +224,6 @@ def modify_share(shares, config, opts):
if not token in share['tokens']:
share['tokens'].append(token)
if opts.expire:
if opts.expire == "":
# REMOVE EXPIRATION
@@ -306,7 +315,6 @@ def show_share(shares, config, opts):
print_share(share, config)
def print_rest_api(shares, config, opts):
if 'public_url' not in config:
print("Set public_url variable in your config.json")
@@ -412,16 +420,12 @@ def print_rest_api_download(config, share, token):
if not os.path.exists(share_path):
print("no files")
sys.exit(0)
for filename in sorted(os.listdir(share_path)):
if os.path.isdir(os.path.join(share_path,filename)):
continue
if filename.startswith("."):
continue
for filename in iter_folder_files(share_path):
print("%s/download/%s/%s/%s"%(
config['public_url'],
share['name'],
token,
filename
path2url(filename)
))
print("or \n\n# curl -s %s/script/download/%s/%s | bash /dev/stdin [-f]"%(
config['public_url'],
@@ -444,16 +448,12 @@ def print_rest_api_direct(config, share, token):
if not os.path.exists(share_path):
print("no files")
sys.exit(0)
for filename in sorted(os.listdir(share_path)):
if os.path.isdir(os.path.join(share_path,filename)):
continue
if filename.startswith("."):
continue
for filename in iter_folder_files(share_path):
print("%s/direct/%s/%s/%s"%(
config['public_url'],
share['name'],
get_direct_token(share,filename),
filename
path2url(filename)
))
print("or \n\n# curl -s %s/script/direct/%s/%s | bash /dev/stdin [-f]"%(
config['public_url'],

View File

@@ -60,7 +60,7 @@
{% if direct %}
<a href="{{ url_for('download_direct', name = name, token = entry.token, filename = entry.name ) }}" title="Direct share link" class=direct>&#x2756;</a>
{% endif %}
<a href="{{ url_for('download_file', name = name, filename = entry.name) }}">{{ entry.name }}</a>
<a href="{{ url_for('download_gui', name = name, filename = entry.url) }}">{{ entry.name }}</a>
<td class=td_right>{{ entry.size|safe }}
<td>{{ entry.mtime|safe }}
</tr>

View File

@@ -1,7 +1,10 @@
import os
from datetime import datetime
from flask import current_app as app
try:
from urllib.request import pathname2url
except ImportError:
from urllib import pathname2url
def file_date_human(num):
return datetime.fromtimestamp(
@@ -47,12 +50,50 @@ def get_or_none(key,d,none = None):
else:
return none
def is_path_safe(path):
if path.startswith("."):
return False
if "/." in path:
return False
return True
def iter_folder_files(path, recursive = True):
if recursive:
for dirpath, dirnames, filenames in os.walk(path, topdown = False):
relative_path = os.path.relpath(dirpath,path)
dirnames.sort()
if "/." in relative_path:
continue
if relative_path == ".":
relative_path = ""
for f in sorted(filenames):
if f.startswith("."):
continue
fp = os.path.join(relative_path, f)
yield fp
else:
for file in sorted(path):
fp = os.path.join(path,file)
if os.path.isdir(fp):
continue
if file.startswith("."):
continue
yield fp
def path2url(path):
return pathname2url(path)
def safe_name(s):
return safe_string(s, "-_")
def safe_path(s):
return safe_string(s, "-_/")
def safe_string(s, valid):
""" return a safe string, replace non alnum characters with _ . all characters in valid are considered valid. """
return "".join([c if c.isalnum() or c in valid else "_" for c in s])