1002 lines
32 KiB
Python
1002 lines
32 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
import os, sys, time
|
|
import json
|
|
from datetime import datetime
|
|
from flask import Flask, render_template, jsonify, current_app, Response, \
|
|
redirect, url_for, request, g, session, send_file, send_from_directory, \
|
|
abort
|
|
from werkzeug.utils import secure_filename
|
|
import zipfile
|
|
from multiprocessing import Process
|
|
from revprox import ReverseProxied
|
|
from utils import *
|
|
|
|
|
|
__FLEES_VERSION__ = "20191031.0"
|
|
app = Flask(__name__)
|
|
app.config.from_object(__name__)
|
|
config_values = read_config(app)
|
|
if 'notifier' in config_values:
|
|
if len(config_values['notifier']) > 0:
|
|
notifier_config = config_values['notifier'].split(":")
|
|
imported = getattr(__import__(
|
|
notifier_config[0],
|
|
fromlist=[notifier_config[1]]
|
|
),
|
|
notifier_config[1]
|
|
)
|
|
app.config['notifier'] = imported()
|
|
|
|
app.secret_key = config_values['app_secret_key']
|
|
app.wsgi_app = ReverseProxied(app.wsgi_app)
|
|
with app.app_context():
|
|
expire_database_create()
|
|
|
|
@app.before_request
|
|
def before_request():
|
|
g.shares = json.load(open(app.config['SHARES_FILE'],'rt'))
|
|
g.version = __FLEES_VERSION__
|
|
g.site_name = app.config['SITE_NAME']
|
|
g.max_zip_size = app.config['MAX_ZIP_SIZE']
|
|
g.logger = app.config['LOGGER']
|
|
|
|
|
|
@app.route("/")
|
|
def index():
|
|
public_shares = []
|
|
for share in g.shares:
|
|
public = get_or_none('public', share)
|
|
expired = is_expired(share)
|
|
authenticated_share = get_share(share['name'])
|
|
password_set = False
|
|
if authenticated_share[0]:
|
|
password_set = authenticated_share[1]['authenticated'] == 'hash'
|
|
if not expired:
|
|
if public or password_set:
|
|
public_shares.append({
|
|
'name': share['name'],
|
|
'expire': get_or_none('expire', share),
|
|
'upload': get_or_none('upload', share),
|
|
'password_set': password_set,
|
|
'description': get_or_none('description', share, '')
|
|
})
|
|
|
|
return render_template("index.html", entries=public_shares)
|
|
|
|
|
|
@app.route('/authenticate/<name>', methods=['GET','POST'])
|
|
def authenticate(name):
|
|
if request.method == 'GET':
|
|
return render_template('authenticate.html', name = name)
|
|
if request.method == 'POST':
|
|
user_password = request.form['password'].encode('utf-8')
|
|
session[name] = password_hash(user_password, app.secret_key)
|
|
if name + 'Token' in session:
|
|
del session[name + 'Token']
|
|
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):
|
|
if request.method == 'POST':
|
|
file = request.files['file']
|
|
if name == None:
|
|
name = request.form['name']
|
|
(ok,share) = get_share(name, token = token)
|
|
if not ok:
|
|
notify({
|
|
"share": name,
|
|
"operation": "unauthorized_upload"
|
|
})
|
|
return share
|
|
if not get_or_none('upload', share) == True:
|
|
return "Upload not allowed\n",400
|
|
if file:
|
|
secure_filename = secure_filename_hidden(
|
|
file.filename
|
|
)
|
|
filename = os.path.join(
|
|
share['path'],
|
|
secure_filename
|
|
)
|
|
if get_or_none('overwrite', share) == False:
|
|
if os.path.exists(filename):
|
|
file_versionize(filename)
|
|
#~ return "Overwrite forbidden", 403
|
|
print_debug("Saving " + filename)
|
|
file.save(filename)
|
|
set_rights(filename)
|
|
notify({
|
|
"recipient": get_or_none('recipient', share),
|
|
"share": name,
|
|
"filename": filename,
|
|
"operation": "upload"
|
|
})
|
|
if 'from_gui' in request.form:
|
|
if request.form['from_gui'] == "true":
|
|
return redirect(url_for('list_view',name=name))
|
|
download_url = get_download_url(share, secure_filename, token)
|
|
return "File uploaded\n%s\n"%( download_url, ), 200
|
|
else:
|
|
return "Use the 'file' variable to upload\n",400
|
|
|
|
|
|
@app.route('/upload_join/<name>/<token>', methods=['POST'])
|
|
def upload_join_splitted(name, token):
|
|
if request.method == 'POST':
|
|
(ok,share) = get_share(name, token = token)
|
|
if not ok:
|
|
notify({
|
|
"share": name,
|
|
"operation": "unauthorized_split_upload"
|
|
})
|
|
return share
|
|
if not get_or_none('upload', share) == True:
|
|
return "Upload not allowed",400
|
|
if not 'filename' in request.form:
|
|
return "No filename given", 400
|
|
if not 'parts' in request.form:
|
|
return "No parts count given", 400
|
|
try:
|
|
no_parts = int(request.form['parts'])
|
|
except:
|
|
return "Parts not parseable", 400
|
|
parts = []
|
|
for part in range(no_parts):
|
|
filename = os.path.join(
|
|
share['path'],
|
|
secure_filename_hidden(
|
|
".%s.part.%03d"%(
|
|
request.form['filename'],
|
|
part
|
|
)
|
|
)
|
|
)
|
|
print_debug("Checking for join: " + filename)
|
|
if os.path.exists(filename):
|
|
parts.append(filename)
|
|
part_existed = part
|
|
if len(parts) == 0:
|
|
return "Invalid partial filename %s -> %s\n"%( request.form['filename'], filename), 400
|
|
if not len(parts) == part_existed + 1:
|
|
return "Parts missing\n", 400
|
|
secure_name = secure_filename(request.form['filename'])
|
|
target_name = os.path.join(
|
|
share['path'],
|
|
secure_name
|
|
)
|
|
if get_or_none('overwrite', share) == False:
|
|
if os.path.exists(target_name):
|
|
file_versionize(target_name)
|
|
|
|
try:
|
|
begin = uploadJoiner(target_name, parts)
|
|
except:
|
|
return "Joining failed\n", 400
|
|
download_url = get_download_url(share, secure_name, token)
|
|
return "Joining started\n%s\n"%( download_url, ), 200
|
|
|
|
|
|
@app.route('/upload/url', methods=['POST'])
|
|
def upload_url():
|
|
if request.method == 'POST':
|
|
name = request.form['name']
|
|
url = request.form['url']
|
|
if url == "https://...":
|
|
return "", 200
|
|
if not is_valid_url(url):
|
|
return "URL not valid", 400
|
|
(ok,share) = get_share(name)
|
|
if not ok:
|
|
notify({
|
|
"share": name,
|
|
"operation": "unauthorized_URL_upload"
|
|
})
|
|
return share
|
|
if not get_or_none('upload', share) == True:
|
|
return "Upload not allowed\n",400
|
|
filename = os.path.join(
|
|
share['path'],
|
|
secure_filename(
|
|
safe_string(url, ".[]()- ", no_repeat = True)
|
|
)
|
|
)
|
|
if os.path.exists(filename):
|
|
file_versionize(filename)
|
|
ok,error = download_url(url, filename)
|
|
if not ok:
|
|
return error
|
|
set_rights(filename)
|
|
notify({
|
|
"recipient": get_or_none('recipient', share),
|
|
"share": name,
|
|
"filename": filename,
|
|
"operation": "upload"
|
|
})
|
|
if 'from_gui' in request.form:
|
|
if request.form['from_gui'] == "true":
|
|
return redirect(url_for('list_view',name=name))
|
|
return "File uploaded\n", 200
|
|
|
|
|
|
@app.route('/editor', methods = ['GET','POST'])
|
|
@app.route('/editor/<name>', methods = ['GET','POST'])
|
|
def editor(name = None):
|
|
filename = 'paste.txt'
|
|
if request.method == 'POST':
|
|
name = request.form['editor_name']
|
|
filename = request.form['editor_filename']
|
|
|
|
(ok,share) = get_share(name)
|
|
if not ok:
|
|
notify({
|
|
"share": name,
|
|
"operation": "unauthorized_editor"
|
|
})
|
|
return share
|
|
pathname = os.path.join(
|
|
share['path'],
|
|
filename
|
|
)
|
|
content = ""
|
|
if os.path.isfile(pathname):
|
|
if pathname.endswith(".txt"):
|
|
content = open(pathname, 'rt').read(65536)
|
|
|
|
return render_template(
|
|
'editor.html',
|
|
name = name,
|
|
filename = filename,
|
|
content = content
|
|
)
|
|
|
|
|
|
@app.route('/paste/<name>/<token>', methods=['POST'])
|
|
@app.route('/paste/<name>', methods=['POST'])
|
|
def paste(name = None, token = None):
|
|
if request.method == 'POST':
|
|
file = request.form['filename']
|
|
paste = request.form['paste']
|
|
if name == None:
|
|
name = request.form['name']
|
|
(ok,share) = get_share(name, token = token)
|
|
if not ok:
|
|
notify({
|
|
"share": name,
|
|
"operation": "unauthorized_paste"
|
|
})
|
|
return share
|
|
if not get_or_none('upload', share) == True:
|
|
return "Upload not allowed\n",400
|
|
if file:
|
|
filename = os.path.join(
|
|
share['path'],
|
|
secure_filename(
|
|
file
|
|
)
|
|
)
|
|
if get_or_none('overwrite', share) == False:
|
|
if os.path.exists(filename):
|
|
file_versionize(filename)
|
|
#~ return "Overwrite forbidden", 403
|
|
print_debug("Saving " + filename)
|
|
with open(filename, 'wt') as fp:
|
|
fp.write(paste)
|
|
fp.close()
|
|
set_rights(filename)
|
|
notify({
|
|
"recipient": get_or_none('recipient', share),
|
|
"share": name,
|
|
"filename": filename,
|
|
"operation": "paste"
|
|
})
|
|
if 'from_gui' in request.form:
|
|
if request.form['from_gui'] == "true":
|
|
return redirect(url_for('list_view',name=name))
|
|
return "File uploaded\n", 200
|
|
else:
|
|
return "Use the 'file' variable to paste\n",400
|
|
|
|
|
|
|
|
@app.route('/file/list/<name>/<token>', methods=['GET'])
|
|
def file_list(name, token):
|
|
(ok,share) = get_share(name, token = token)
|
|
if not ok:
|
|
return share
|
|
files = []
|
|
for file in iter_folder_files(share['path'], version_folder = app.config['VERSION_FOLDER']):
|
|
if file_autoremove(file, share, notify):
|
|
continue
|
|
files.append(path2url(file))
|
|
files.append("")
|
|
return "\n".join(files), 200
|
|
|
|
|
|
@app.route('/file/details/<name>/<token>', methods=['GET'])
|
|
def file_details(name, token):
|
|
(ok,share) = get_share(name, token = token)
|
|
if not ok:
|
|
return share
|
|
files = []
|
|
for file in iter_folder_files(share['path'], version_folder = app.config['VERSION_FOLDER']):
|
|
status = file_stat(share,file)
|
|
files.append(status)
|
|
return jsonify(files), 200
|
|
|
|
|
|
@app.route('/file/delete/<name>/<token>/<path:filename>', methods=['GET'])
|
|
def file_delete(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 "Not allowed", 403
|
|
upload = get_or_none('upload', share)
|
|
if not upload:
|
|
return "Not allowed", 403
|
|
overwrite = get_or_none('overwrite', share)
|
|
if overwrite:
|
|
os.remove(full_path)
|
|
else:
|
|
file_versionize(full_path)
|
|
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'])
|
|
def file_direct(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
|
|
return get_download_url(share, filename, token), 200
|
|
|
|
|
|
@app.route('/file/ls/<name>/<token>', methods=['GET'])
|
|
def file_ls(name, token):
|
|
(ok,share) = get_share(name, token = token)
|
|
if not ok:
|
|
return share
|
|
files = []
|
|
maxlen = 4
|
|
for file in iter_folder_files(share['path'], version_folder = app.config['VERSION_FOLDER']):
|
|
if file_autoremove(file, share, notify):
|
|
continue
|
|
files.append(file)
|
|
maxlen = max(maxlen, len(file))
|
|
details = []
|
|
details.append(
|
|
"%%16s %%4s %%8s %%-%ds %%s"%( maxlen, )%(
|
|
'Modified',
|
|
'ToRm',
|
|
'Size',
|
|
'Name',
|
|
'Type',
|
|
)
|
|
)
|
|
details.append("="*80)
|
|
for file in files:
|
|
status = file_stat(share,file)
|
|
details.append(
|
|
"%%16s %%4s %%8s %%-%ds %%s"%( maxlen, )%(
|
|
status['mtime'],
|
|
status['to_remove'],
|
|
status['hsize'],
|
|
status['name'],
|
|
status['mime'],
|
|
)
|
|
)
|
|
|
|
return "\n".join(details), 200
|
|
|
|
|
|
@app.route('/file/size/<name>/<token>/<path:filename>', methods=['GET'])
|
|
def file_size(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", 200
|
|
size = os.stat(full_path).st_size
|
|
return str(size), 200
|
|
|
|
|
|
@app.route('/list/<name>/<token>', methods=['GET'])
|
|
@app.route('/list/<name>', methods=['GET'])
|
|
def list_view(name, token = None):
|
|
(ok,share) = get_share(name, token = token)
|
|
if not ok:
|
|
notify({
|
|
"share": name,
|
|
"operation": "unauthorized_list"
|
|
})
|
|
return share
|
|
if token != None and 'pass_hash' in share:
|
|
session[name] = share['pass_hash']
|
|
session[name + 'Token'] = token
|
|
return redirect(url_for('list_view',name=name))
|
|
|
|
files = []
|
|
for file in iter_folder_files(share['path'], version_folder = app.config['VERSION_FOLDER']):
|
|
if file_autoremove(file, share, notify):
|
|
continue
|
|
status = file_stat(share,file)
|
|
status.update({
|
|
'token': get_direct_token(share, file),
|
|
})
|
|
files.append(status)
|
|
files.sort(key = lambda x: x['name'])
|
|
# direct share links not allowed if password isnt set
|
|
allow_direct = get_or_none('direct_links', share) if get_or_none('pass_hash', share) else False
|
|
upload = get_or_none('upload', share)
|
|
overwrite = get_or_none('overwrite', share)
|
|
if name + 'Token' in session:
|
|
used_token = session[name + 'Token']
|
|
else:
|
|
used_token = '[TOKEN]'
|
|
script_api = [get_script_url(app.config['PUBLIC_URL'], share, x, used_token) for x in ('client', 'download', 'upload_split', 'flip')]
|
|
if not upload:
|
|
overwrite = False
|
|
return render_template(
|
|
"list.html",
|
|
name = share['name'],
|
|
entries = files,
|
|
password = get_or_none('pass_hash', share),
|
|
public = get_or_none('public', share),
|
|
upload = upload,
|
|
overwrite = overwrite,
|
|
direct = allow_direct,
|
|
expire = get_or_none('expire', share),
|
|
description = get_or_none('description', share, ""),
|
|
script_api = script_api,
|
|
autoremove = get_or_none('autoremove', share, 0)
|
|
)
|
|
|
|
|
|
@app.route('/logout/<name>', methods=['GET'])
|
|
def logout(name):
|
|
if name in session:
|
|
del session[name]
|
|
if name + 'Token' in session:
|
|
del session[name + 'Token']
|
|
return render_template(
|
|
"logout.html",
|
|
name = 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 file_path == None:
|
|
return 'No such link', 404
|
|
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'])
|
|
def download_direct(name,token,filename):
|
|
(ok,share) = get_share(name, require_auth = False)
|
|
if not ok:
|
|
return share
|
|
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
|
|
if file_token != token:
|
|
notify({
|
|
"recipient": get_or_none('recipient', share),
|
|
"share": name,
|
|
"operation": "unauthorized_direct_download"
|
|
})
|
|
return 'Incorrect token', 403
|
|
file_autoremove(filename, share, notify)
|
|
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": "direct_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'])
|
|
@app.route('/zip/<name>', methods=['GET'])
|
|
def download_zip(name, token = None):
|
|
(ok,share) = get_share(name, token = token)
|
|
if not ok:
|
|
notify({
|
|
"share": name,
|
|
"operation": "unauthorized_zip_download"
|
|
})
|
|
return share
|
|
folder_size = get_folder_size(share['path'])
|
|
if folder_size/(1024*1024) > app.config['MAX_ZIP_SIZE']:
|
|
return "Maximum ZIP size exceeded", 400
|
|
zip_clean()
|
|
zip_path = zip_share(share)
|
|
notify({
|
|
"recipient": get_or_none('recipient', share),
|
|
"share": name,
|
|
"filename": name + ".zip",
|
|
"operation": "zip_download"
|
|
})
|
|
return send_file(
|
|
zip_path,
|
|
as_attachment = True,
|
|
attachment_filename = name + ".zip"
|
|
)
|
|
|
|
|
|
@app.route('/script/client', methods=['GET'])
|
|
@app.route('/script/client/<name>/<token>', methods=['GET'])
|
|
def script_client(name = "", token = ""):
|
|
notify({
|
|
'share': name,
|
|
'operation': 'script_client'
|
|
})
|
|
return render_template(
|
|
"client.py",
|
|
name = name,
|
|
token = token,
|
|
rooturl = request.url_root
|
|
)
|
|
|
|
|
|
@app.route('/script/flip/<name>/<token>', methods=['GET'])
|
|
@app.route('/script/flip', methods=['GET'])
|
|
def script_flip(name = "", token = ""):
|
|
notify({
|
|
'share': name,
|
|
'operation': 'script_flip'
|
|
})
|
|
return render_template(
|
|
"flip",
|
|
name = name,
|
|
token = token,
|
|
rooturl = request.url_root,
|
|
version = __FLEES_VERSION__
|
|
)
|
|
|
|
|
|
@app.route('/script/upload/<name>/<token>', methods=['GET'])
|
|
def script_upload(name = None, token = None):
|
|
(ok,share) = get_share(name, token = token)
|
|
if not ok:
|
|
return share
|
|
if not get_or_none('upload', share) == True:
|
|
return "Upload not allowed",400
|
|
return render_template(
|
|
"upload.sh",
|
|
name = name,
|
|
token = token,
|
|
rooturl = request.url_root
|
|
)
|
|
|
|
|
|
@app.route('/script/download/<name>/<token>', methods=['GET'])
|
|
def script_download(name = None, token = None):
|
|
(ok,share) = get_share(name, token = token)
|
|
if not ok:
|
|
return share
|
|
commands = []
|
|
for file in iter_folder_files(share['path'], version_folder = app.config['VERSION_FOLDER']):
|
|
status = file_stat(share, file)
|
|
commands.append('get_file "%s"'%(
|
|
status['url'],
|
|
))
|
|
return render_template(
|
|
"download.sh",
|
|
name = name,
|
|
token = token,
|
|
rooturl = request.url_root,
|
|
commands = "\n".join(commands)
|
|
)
|
|
|
|
|
|
@app.route('/script/direct/<name>/<token>', methods=['GET'])
|
|
def script_direct(name = None, token = None):
|
|
(ok,share) = get_share(name, token = token)
|
|
if not ok:
|
|
return share
|
|
commands = []
|
|
for file in iter_folder_files(share['path'], version_folder = app.config['VERSION_FOLDER']):
|
|
status = file_stat(share, file)
|
|
commands.append('get_file "%s" "%s"'%(
|
|
status['url'],
|
|
get_direct_token(share, file)
|
|
))
|
|
return render_template(
|
|
"download_direct.sh",
|
|
name = name,
|
|
rooturl = request.url_root,
|
|
commands = "\n".join(commands)
|
|
)
|
|
|
|
|
|
@app.route('/script/upload_split/<name>/<token>', methods=['GET'])
|
|
def script_upload_split(name = None, token = None):
|
|
(ok,share) = get_share(name, token = token)
|
|
if not ok:
|
|
return share
|
|
if not get_or_none('upload', share) == True:
|
|
return "Upload not allowed",400
|
|
return render_template(
|
|
"upload_split.py",
|
|
name = name,
|
|
token = token,
|
|
rooturl = request.url_root
|
|
)
|
|
|
|
|
|
|
|
#~ mmmmm # ""#
|
|
#~ # "# mmm mmm m m mmmmm mmm #mmm # mmm
|
|
#~ #mmmm" #" # # " # # # # # " # #" "# # #" #
|
|
#~ # "m #"""" """m # # # # # m"""# # # # #""""
|
|
#~ # " "#mm" "mmm" "mm"# # # # "mm"# ##m#" "mm "#mm"
|
|
|
|
#~ @app.route("/resumable/send")
|
|
#~ def resumable_example():
|
|
#~ return render_template("resumable_upload.html")
|
|
|
|
#~ # resumable.js uses a GET request to check if it uploaded the file already.
|
|
#~ # NOTE: your validation here needs to match whatever you do in the POST (otherwise it will NEVER find the files)
|
|
#~ @app.route("/resumable/get", methods=['GET'])
|
|
#~ def resumable():
|
|
#~ resumableIdentfier = request.args.get('resumableIdentifier', type=str)
|
|
#~ resumableFilename = request.args.get('resumableFilename', type=str)
|
|
#~ resumableChunkNumber = request.args.get('resumableChunkNumber', type=int)
|
|
|
|
#~ if not resumableIdentfier or not resumableFilename or not resumableChunkNumber:
|
|
#~ # Parameters are missing or invalid
|
|
#~ abort(500, 'Parameter error')
|
|
|
|
#~ # chunk folder path based on the parameters
|
|
#~ temp_dir = os.path.join(temp_base, resumableIdentfier)
|
|
|
|
#~ # chunk path based on the parameters
|
|
#~ chunk_file = os.path.join(temp_dir, get_chunk_name(resumableFilename, resumableChunkNumber))
|
|
#~ app.logger.debug('Getting chunk: %s', chunk_file)
|
|
|
|
#~ if os.path.isfile(chunk_file):
|
|
#~ # Let resumable.js know this chunk already exists
|
|
#~ return 'OK'
|
|
#~ else:
|
|
#~ # Let resumable.js know this chunk does not exists and needs to be uploaded
|
|
#~ abort(404, 'Not found')
|
|
|
|
|
|
#~ # if it didn't already upload, resumable.js sends the file here
|
|
#~ @app.route("/resumable/post", methods=['POST'])
|
|
#~ def resumable_post():
|
|
#~ resumableTotalChunks = request.form.get('resumableTotalChunks', type=int)
|
|
#~ resumableChunkNumber = request.form.get('resumableChunkNumber', default=1, type=int)
|
|
#~ resumableFilename = request.form.get('resumableFilename', default='error', type=str)
|
|
#~ resumableIdentfier = request.form.get('resumableIdentifier', default='error', type=str)
|
|
|
|
#~ # get the chunk data
|
|
#~ chunk_data = request.files['file']
|
|
|
|
#~ # make our temp directory
|
|
#~ temp_dir = os.path.join(temp_base, resumableIdentfier)
|
|
#~ if not os.path.isdir(temp_dir):
|
|
#~ os.makedirs(temp_dir, 0777)
|
|
|
|
#~ # save the chunk data
|
|
#~ chunk_name = get_chunk_name(resumableFilename, resumableChunkNumber)
|
|
#~ chunk_file = os.path.join(temp_dir, chunk_name)
|
|
#~ chunk_data.save(chunk_file)
|
|
#~ app.logger.debug('Saved chunk: %s', chunk_file)
|
|
|
|
#~ # check if the upload is complete
|
|
#~ chunk_paths = [os.path.join(temp_dir, get_chunk_name(resumableFilename, x)) for x in range(1, resumableTotalChunks+1)]
|
|
#~ upload_complete = all([os.path.exists(p) for p in chunk_paths])
|
|
|
|
#~ # combine all the chunks to create the final file
|
|
#~ if upload_complete:
|
|
#~ target_file_name = os.path.join(temp_base, resumableFilename)
|
|
#~ with open(target_file_name, "ab") as target_file:
|
|
#~ for p in chunk_paths:
|
|
#~ stored_chunk_file_name = p
|
|
#~ stored_chunk_file = open(stored_chunk_file_name, 'rb')
|
|
#~ target_file.write(stored_chunk_file.read())
|
|
#~ stored_chunk_file.close()
|
|
#~ os.unlink(stored_chunk_file_name)
|
|
#~ target_file.close()
|
|
#~ os.rmdir(temp_dir)
|
|
#~ app.logger.debug('File saved to: %s', target_file_name)
|
|
|
|
#~ return 'OK'
|
|
|
|
|
|
#~ def get_chunk_name(uploaded_filename, chunk_number):
|
|
#~ return uploaded_filename + "_part_%03d" % chunk_number
|
|
|
|
|
|
class uploadJoiner:
|
|
def __init__(self, target_name, parts):
|
|
self.target_name = target_name
|
|
self.parts = parts
|
|
self.chunk_size = 10 * 1024 * 1024
|
|
p = Process(target=self.run, args=())
|
|
p.daemon = True
|
|
p.start()
|
|
|
|
|
|
def run(self):
|
|
with open(self.target_name,'wb') as writer:
|
|
for part in self.parts:
|
|
with open(part,'rb') as reader:
|
|
for chunk in iter(lambda: reader.read(self.chunk_size), b""):
|
|
writer.write(chunk)
|
|
set_rights(self.target_name)
|
|
for part in self.parts:
|
|
os.remove(part)
|
|
|
|
|
|
def download_file(name, filename, token = None):
|
|
(ok,share) = get_share(name, token = token)
|
|
if not ok:
|
|
notify({
|
|
"share": name,
|
|
"operation": "unauthorized_download"
|
|
})
|
|
return share
|
|
if not is_path_safe(filename):
|
|
return "Incorrect path", 403
|
|
file_autoremove(filename, share, notify)
|
|
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(full_path):
|
|
""" Move file to versioned with integer """
|
|
file_dir = os.path.dirname(full_path)
|
|
file_name = os.path.basename(full_path)
|
|
new_name = file_name_version(full_path)
|
|
new_path = os.path.join(
|
|
file_dir,
|
|
app.config['VERSION_FOLDER'],
|
|
new_name
|
|
)
|
|
|
|
if os.path.exists(new_path):
|
|
return
|
|
makedirs_rights(os.path.join(
|
|
file_dir,
|
|
app.config['VERSION_FOLDER']
|
|
))
|
|
os.rename(full_path, new_path)
|
|
|
|
|
|
def get_share(name, require_auth = True, token = None):
|
|
share = [x for x in g.shares if x['name'] == name]
|
|
if len(share) < 1:
|
|
return (False,redirect(url_for('authenticate',name=name)))
|
|
share = share[0]
|
|
if is_expired(share):
|
|
return (False, 'Share has expired')
|
|
authenticated = "no-pass"
|
|
if not token == None:
|
|
require_auth = False
|
|
if has_token(token, share):
|
|
authenticated = "token"
|
|
else:
|
|
authenticated = False
|
|
if require_auth:
|
|
if 'pass_hash' in share:
|
|
authenticated = False
|
|
if name in session:
|
|
if session[name] == share['pass_hash']:
|
|
authenticated = "hash"
|
|
|
|
if not authenticated:
|
|
return (False,redirect(url_for('authenticate',name=name)))
|
|
if not 'path' in share:
|
|
return (False,'no path defined')
|
|
share.update({
|
|
"path": os.path.join(
|
|
app.config['UPLOAD_FOLDER'],
|
|
share['path']
|
|
),
|
|
"authenticated": authenticated
|
|
})
|
|
if not os.path.exists(share['path']):
|
|
makedirs_rights(share['path'])
|
|
return (True,share)
|
|
|
|
|
|
def is_expired(share):
|
|
expires = get_or_none('expire', share)
|
|
if expires:
|
|
if datetime.now() > datetime.strptime(expires, app.config['DATE_FORMAT']):
|
|
return True
|
|
return False
|
|
|
|
|
|
def print_debug(s):
|
|
if app.config['DEBUG']:
|
|
sys.stderr.write(str(s)+"\n")
|
|
sys.stderr.flush()
|
|
|
|
|
|
def makedirs_rights(path):
|
|
# os.makedirs with chown
|
|
path_list = path.split(os.sep)
|
|
for p in range(len(path_list)):
|
|
current_path = os.sep.join(path_list[0:(p+1)])
|
|
if not os.path.exists(current_path):
|
|
os.mkdir(current_path)
|
|
set_rights(current_path)
|
|
|
|
|
|
def notify(msg):
|
|
""" Log notifier messages, and run the custom notifier """
|
|
address = request.environ['REMOTE_ADDR']
|
|
if 'HTTP_X_FORWARDED_FOR' in request.environ:
|
|
address += ':' + request.environ['HTTP_X_FORWARDED_FOR']
|
|
log_msg = "%s: %s"%(
|
|
address,
|
|
', '.join(["%s:%s"%( k, msg[k] ) for k in sorted(msg)])
|
|
)
|
|
g.logger.info(log_msg)
|
|
if 'notifier' in app.config:
|
|
msg['environment'] = request.environ
|
|
app.config['notifier'].notify(msg)
|
|
|
|
|
|
def secure_filename_hidden(filename):
|
|
secure = secure_filename(filename)
|
|
if filename.startswith("."):
|
|
secure = "." + secure
|
|
return secure
|
|
|
|
|
|
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'])
|
|
|
|
zip_path = os.path.join(
|
|
app.config['ZIP_FOLDER'],
|
|
"%s-%d.zip"%(
|
|
share['name'],
|
|
time.time()
|
|
)
|
|
)
|
|
zf = zipfile.ZipFile(zip_path, 'w', zipfile.ZIP_DEFLATED)
|
|
for file in iter_folder_files(share['path'], version_folder = app.config['VERSION_FOLDER']):
|
|
if file_autoremove(file, share, notify):
|
|
continue
|
|
fp = os.path.join(share['path'], file)
|
|
if os.path.isdir(fp):
|
|
continue
|
|
print_debug(fp)
|
|
zf.write(
|
|
fp,
|
|
arcname = os.path.join(share['name'],file)
|
|
)
|
|
zf.close()
|
|
set_rights(zip_path)
|
|
return zip_path
|
|
|
|
|
|
def zip_clean():
|
|
""" delete zip files older than 1 hour """
|
|
if not os.path.exists(app.config['ZIP_FOLDER']):
|
|
return
|
|
for file in os.listdir(app.config['ZIP_FOLDER']):
|
|
if not file.endswith("zip"):
|
|
continue
|
|
mtime = os.stat(
|
|
os.path.join(app.config['ZIP_FOLDER'],file)
|
|
).st_mtime
|
|
if mtime + 3600 < time.time():
|
|
os.remove(os.path.join(app.config['ZIP_FOLDER'],file))
|
|
|
|
|
|
zip_clean()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
app.run(debug=True)
|
|
|
|
|