778 lines
27 KiB
Python
778 lines
27 KiB
Python
|
|
import base64
|
|
import cgi
|
|
import cgitb
|
|
import datetime
|
|
import json
|
|
import os
|
|
import re
|
|
import shutil
|
|
import socket
|
|
import sys
|
|
import time
|
|
import urllib.parse
|
|
from tempfile import TemporaryFile
|
|
|
|
import flask
|
|
from flask import Blueprint, Flask, g, request, session
|
|
|
|
import pycurl
|
|
|
|
scalyfm = Blueprint("fm", __name__)
|
|
|
|
# Default Configuration
|
|
CONFIG = {
|
|
"lang":"en",
|
|
"error_reporting": False,
|
|
"show_hidden": True,
|
|
"hide_Cols": False,
|
|
"calc_folder": False
|
|
}
|
|
|
|
# TODO Artifact from PHP port.
|
|
GLOBALS = {}
|
|
|
|
# TFM version
|
|
VERSION = '2.3.8'
|
|
|
|
# Application Title
|
|
APP_TITLE = 'Tiny File Manager'
|
|
|
|
# Auth with login/password (set true/false to enable/disable it)
|
|
# Is independent from IP white- and blacklisting
|
|
use_auth = True
|
|
|
|
# Users: array('Username' => 'Password', 'Username2' => 'Password2', ...)
|
|
# Generate secure password hash - https://tinyfilemanager.github.io/docs/pwd.html
|
|
auth_users = {
|
|
'admin': '$2y$10$/K.hjNr84lLNDt8fTXjoI.DBp6PpeyoJ.mGwrrLuCZfAwfSAGqhOW', # admin@123
|
|
'user': '$2y$10$Fg6Dz8oH9fPoZ2jJan5tZuv6Z4Kp7avtQ9bDfrdRntXtPeiMAZyGO' # 12345
|
|
}
|
|
|
|
# Readonly users (username array)
|
|
readonly_users = [
|
|
'user'
|
|
]
|
|
|
|
# Possible rules are 'OFF', 'AND' or 'OR'
|
|
# OFF => Don't check connection IP, defaults to OFF
|
|
# AND => Connection must be on the whitelist, and not on the blacklist
|
|
# OR => Connection must be on the whitelist, or not on the blacklist
|
|
ip_ruleset = 'OFF'
|
|
|
|
# Should users be notified of their block?
|
|
ip_silent = True
|
|
|
|
# IP-addresses, both ipv4 and ipv6
|
|
ip_whitelist = [
|
|
'127.0.0.1', # local ipv4
|
|
'::1' # local ipv6
|
|
]
|
|
|
|
# IP-addresses, both ipv4 and ipv6
|
|
ip_blacklist = [
|
|
'0.0.0.0', # non-routable meta ipv4
|
|
'::' # non-routable meta ipv6
|
|
]
|
|
|
|
# user specific directories
|
|
# array('Username' => 'Directory path', 'Username2' => 'Directory path', ...)
|
|
directories_users = {}
|
|
|
|
# Enable highlight.js (https:# highlightjs.org/) on view's page
|
|
use_highlightjs = True
|
|
|
|
# highlight.js style
|
|
highlightjs_style = 'vs'
|
|
|
|
# Enable ace.js (https:# ace.c9.io/) on view's page
|
|
edit_files = True
|
|
|
|
# Default timezone for date() and time() - http:# php.net/manual/en/timezones.php
|
|
default_timezone = 'Etc/UTC' # UTC
|
|
|
|
# Root path for file manager
|
|
# use absolute path of directory i.e: '/var/www/folder' or $_SERVER['DOCUMENT_ROOT'].'/folder'
|
|
# TODO still not sure this is right... Doesn't this allow the fm to see the script, meaning
|
|
# that someone could reclaim the source code and thus the auth_users dict?
|
|
root_path = os.path.dirname(os.path.abspath(__file__)) # $_SERVER['DOCUMENT_ROOT'];
|
|
|
|
# Root url for links in file manager. Relative to $http_host. Variants: '', 'path/to/subfolder'
|
|
# Will not working if $root_path will be outside of server document root
|
|
root_url = ''
|
|
|
|
# Server hostname. Can set manually if wrong
|
|
http_host = socket.getfqdn() # $_SERVER['HTTP_HOST'];
|
|
|
|
# input encoding for iconv
|
|
iconv_input_encoding = 'UTF-8'
|
|
|
|
# date() format for file modification date
|
|
datetime_format = "%d.%m.%y %H.%M"
|
|
|
|
# allowed file extensions for upload and rename
|
|
# e.g. 'gif,png,jpg'
|
|
allowed_extensions = ''
|
|
|
|
# Favicon path. This can be either a full url to an .PNG image, or a path based on the document root.
|
|
# full path, e.g http:# example.com/favicon.png
|
|
# local path, e.g images/icons/favicon.png
|
|
favicon_path = '?img=favicon'
|
|
|
|
# Array of files and folders excluded from listing
|
|
# e.r array('myfile.html', 'personal-folder')
|
|
GLOBALS['exclude_items'] = []
|
|
|
|
# Online office Docs Viewer
|
|
# Availabe rules are 'google', 'microsoft' or false
|
|
# google => View documents using Google Docs Viewer
|
|
# microsoft => View documents using Microsoft Web Apps Viewer
|
|
# false => disable online dov viewer
|
|
GLOBALS['online_viewer'] = 'google'
|
|
|
|
# Sticky Nav bar
|
|
# true => enable sticky header
|
|
# false => disable sticky header
|
|
sticky_navbar = True
|
|
|
|
# max upload file size
|
|
MAX_UPLOAD_SIZE = '2048'
|
|
|
|
# available languages
|
|
lang_list = {
|
|
'en': 'English'
|
|
}
|
|
|
|
_SERVER = {}
|
|
|
|
# TODO Artifact from port; is this needed?
|
|
class StdClass:
|
|
def __init__(self):
|
|
self.name = None
|
|
self.type = None
|
|
|
|
class FM_Config:
|
|
"""Load and save configuration data."""
|
|
|
|
def __init__(self):
|
|
global root_path, root_url, CONFIG
|
|
fm_url = root_url + _SERVER["PHP_SELF"]
|
|
self.data = {
|
|
"lang": "en",
|
|
"error_reporting": True,
|
|
"show_hidden": False
|
|
}
|
|
data = None
|
|
try:
|
|
data = json.loads(CONFIG) # TODO
|
|
except (IOError, json.JSONDecodeError):
|
|
msg = "Tiny File Manager<br>Errror: Cannot load configuration"
|
|
if fm_url[-1] == "/":
|
|
fm_url = fm_url.rstrip("/")
|
|
msg += "<br>"
|
|
msg += "<br>Seems like you have a trailing slash on the URL."
|
|
msg += "<br>Try this link: <a href='{0}'>{0}</a>".format(fm_url)
|
|
|
|
flask.flash(msg)
|
|
|
|
if data is not None:
|
|
self.data = data
|
|
else:
|
|
self.save()
|
|
|
|
def save(self):
|
|
fm_file = root_url + _SERVER["PHP_SELF"]
|
|
current_config = None
|
|
|
|
with open(fm_file) as fobj:
|
|
current_config = json.load(fobj)
|
|
|
|
current_config.update(self.data)
|
|
with open(fm_file, "w") as fobj:
|
|
json.dump(current_config, fobj)
|
|
|
|
# Configuration
|
|
cfg = FM_Config() # TODO
|
|
|
|
# Default language
|
|
lang = cfg.data.get("lang", "en")
|
|
|
|
# Show or hide files and folders that starts with a dot
|
|
show_hidden_files = cfg.data.get("show_hidden", True)
|
|
|
|
# PHP error reporting - false = Turns off Errors, true = Turns on Errors
|
|
report_errors = cfg.data.get('error_reporting', True)
|
|
|
|
# Hide Permissions and Owner cols in file-listing
|
|
hide_Cols = cfg.data.get('hide_Cols', True)
|
|
|
|
# Show Dirsize: true or speedup output: false
|
|
calc_folder = cfg.data.get('calc_folder', True)
|
|
|
|
# **** HELPER FUNCTIONS ***************************************************************
|
|
|
|
def fm_enc(*args):
|
|
pass
|
|
|
|
def fm_rdelete(*args):
|
|
pass
|
|
|
|
def fm_set_msg(*args):
|
|
pass
|
|
|
|
def fm_clean_path(path, trim=True):
|
|
"""Ensure that the given path is valid and free from extra slashes."""
|
|
if trim:
|
|
path = path.strip()
|
|
|
|
path = path.strip("\\/") # TODO
|
|
path = path.replace("../", "").replace("..\\", "") # TODO
|
|
if path == "..":
|
|
path = ""
|
|
|
|
return path.replace("\\", "/")
|
|
|
|
def fm_redirect(url, code=302):
|
|
print("302 Found") # TODO Where to put code?
|
|
print('Content-Type: text/html')
|
|
print('Location: %s' % url)
|
|
print() # HTTP says you have to have a blank line between headers and content
|
|
print('<html>')
|
|
print(' <head>')
|
|
print(' <meta http-equiv="refresh" content="0;url=%s" />' % url)
|
|
print(' <title>You are going to be redirected</title>')
|
|
print(' </head>' )
|
|
print(' <body>')
|
|
print(' Redirecting... <a href="%s">Click here if you are not redirected</a>' % url)
|
|
print(' </body>')
|
|
print('</html>')
|
|
|
|
sys.exit(0)
|
|
|
|
# TODO This entire function
|
|
def fm_get_images():
|
|
"""Get base64-encoded images."""
|
|
return {
|
|
'favicon': """Qk04AgAAAAAAADYAAAAoAAAAEAAAABAAAAABABAAAAAAAAICAAASCwAAEgsAAAAAAAAAAAAAIQQhBCEEIQQhBCEEIQQhBCEEIQ
|
|
QhBCEEIQQhBCEEIQQhBCEEIQQhBHNO3n/ef95/vXetNSEEIQQhBCEEIQQhBCEEIQQhBCEEc07ef95/3n/ef95/1lohBCEEIQQhBCEEIQQhBCEEIQ
|
|
RzTt5/3n8hBDFG3n/efyEEIQQhBCEEIQQhBCEEIQQhBHNO3n/efyEEMUbef95/IQQhBCEEIQQhBCEEIQQhBCEErTVzTnNOIQQxRt5/3n8hBCEEIQ
|
|
QhBCEEIQQhBCEEIQQhBCEEIQQhBDFG3n/efyEEIQQhBCEEIQQhBCEEIQQhBCEEIQQxRt5/3n+cc2stIQQhBCEEIQQhBCEEIQQhBCEEIQQIIZxz3n
|
|
/ef5xzay0hBCEEIQQhBCEEIQQhBCEEIQQhBCEEIQQhBDFG3n/efyEEIQQhBCEEIQQhBCEEIQQhBK01c05zTiEEMUbef95/IQQhBCEEIQQhBCEEIQ
|
|
QhBCEEc07ef95/IQQxRt5/3n8hBCEEIQQhBCEEIQQhBCEEIQRzTt5/3n8hBDFG3n/efyEEIQQhBCEEIQQhBCEEIQQhBKUUOWfef95/3n/ef95/IQ
|
|
QhBCEEIQQhBCEEIQQhBCEEIQQhBJRW3n/ef95/3n8hBCEEIQQhBCEEIQQhBCEEIQQhBCEEIQQhBCEEIQQhBCEEIQQhBCEEIQQAAA=="""
|
|
}
|
|
|
|
# TODO this entire function
|
|
def fm_show_images(img_name):
|
|
fstr = "%A, %d %M %Y %h:%m:%s GMT"
|
|
now = datetime.datetime.now()
|
|
base = datetime.datetime(now.year, now.month, now.day, 0, 0, 0)
|
|
mtime = base.strftime(fstr)
|
|
etime = (base + datetime.timedelta(day=1)).strftime(fstr)
|
|
|
|
img_name = img_name.strip()
|
|
images = fm_get_images()
|
|
default_image = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAEElEQVR42mL4//8/A0CAAQAI/AL+26JNFgAAAABJRU5ErkJggg=='
|
|
image = images.get(img_name, default_image)
|
|
image = base64.b64decode(image)
|
|
size = len(image)
|
|
|
|
print("Cache-Control:")
|
|
print("Pragma:")
|
|
print(f"Last-Modified: {mtime}")
|
|
print(f"Expires: {etime}")
|
|
print(f"Content-Length: {size}")
|
|
print(f"Content-Type: image/png")
|
|
print(image)
|
|
|
|
def password_verify(*args):
|
|
pass # TODO
|
|
|
|
def password_hash(*args):
|
|
pass
|
|
|
|
def fm_show_footer_login(*args):
|
|
pass
|
|
|
|
def fm_show_header_login(*args):
|
|
pass
|
|
|
|
def fm_show_message(*args):
|
|
pass
|
|
|
|
def fm_get_translations(*args):
|
|
pass
|
|
|
|
def fm_set_message(*args):
|
|
pass
|
|
|
|
def trigger_error(*args):
|
|
pass
|
|
|
|
def fm_rcopy(*args):
|
|
pass
|
|
|
|
def fm_rename(*args):
|
|
pass
|
|
|
|
# **** ROUTES ***************************************************************
|
|
|
|
@scalyfm.route("/")
|
|
def scaly_root():
|
|
global _SERVER, lang, show_hidden_files
|
|
|
|
# NOTE: Kludgy, but makes the port easier.
|
|
# See: https://flask.palletsprojects.com/en/1.1.x/api/#flask.Request.url_root
|
|
_SERVER = {
|
|
"DOCUMENT_ROOT": os.path.dirname(os.path.abspath(__file__)),
|
|
"PHP_SELF": request.root,
|
|
"REMOTE_ADDR": request.remote_addr
|
|
}
|
|
|
|
# TODO Wtf is this
|
|
E_USER_WARNING = 0
|
|
|
|
# TODO
|
|
# if report_errors:
|
|
# ini_set('error_reporting', E_ALL);
|
|
# ini_set('display_errors', 1);
|
|
# else:
|
|
# @ini_set('error_reporting', E_ALL);
|
|
# @ini_set('display_errors', 0);
|
|
|
|
# TODO if fm included
|
|
# if FM_EMBED:
|
|
# use_auth = False;
|
|
# sticky_navbar = False;
|
|
# else:
|
|
# @set_time_limit(600);
|
|
|
|
# date_default_timezone_set($default_timezone);
|
|
|
|
# ini_set('default_charset', 'UTF-8');
|
|
# if (version_compare(PHP_VERSION, '5.6.0', '<') && function_exists('mb_internal_encoding')) {
|
|
# mb_internal_encoding('UTF-8');
|
|
# }
|
|
# if (function_exists('mb_regex_encoding')) {
|
|
# mb_regex_encoding('UTF-8');
|
|
# }
|
|
|
|
# session_cache_limiter('');
|
|
# session_name(FM_SESSION_ID );
|
|
# @session_start();
|
|
|
|
use_auth = not (auth_users == {})
|
|
is_https = request.url.startswith("https")
|
|
|
|
# update $root_url based on user specific directories
|
|
# TODO may not be needed with routing
|
|
if session["logged"] is not None and directories_users[session["logged"]] != {}:
|
|
wd = fm_clean_path(os.path.dirname(_SERVER["PHP_SELF"]))
|
|
root_url = root_url + wd + os.sep + directories_users[session["logged"]]
|
|
|
|
# clean root_url
|
|
root_url = fm_clean_path(root_url)
|
|
|
|
# abs path for site
|
|
if not FM_ROOT_URL:
|
|
FM_ROOT_URL = "{}://{}".format(
|
|
"https" if is_https else "http",
|
|
"/" if not root_url else root_url
|
|
)
|
|
|
|
if not FM_SELF_URL:
|
|
FM_SELF_URL = "{}://{}".format(
|
|
"https" if is_https else "http",
|
|
_SERVER["PHP_SELF"]
|
|
)
|
|
|
|
# logout
|
|
if "logout" in request.args:
|
|
# TODO Unset session?
|
|
flask.redirect(FM_SELF_URL)
|
|
|
|
# Show image here
|
|
if "img" in request.args:
|
|
fm_show_image(request.get.args("img"))
|
|
|
|
# Validate connection IP
|
|
if ip_ruleset:
|
|
client_ip = _SERVER["REMOTE_ADDR"]
|
|
proceed = False
|
|
whitelisted = client_ip in ip_whitelist
|
|
blacklisted = client_ip in ip_blacklist
|
|
|
|
if ip_ruleset == "AND":
|
|
if whitelisted and not blacklisted:
|
|
proceed = True
|
|
|
|
elif ip_ruleset == "OR":
|
|
if whitelisted and not blacklisted:
|
|
proceed = True
|
|
|
|
if not proceed:
|
|
trigger_error(f'User connection denied from: {client_ip}', E_USER_WARNING)
|
|
|
|
if not ip_silent:
|
|
fm_set_msg('Access denied. IP restriction applicable', 'error')
|
|
fm_show_header_login()
|
|
fm_show_message()
|
|
|
|
# exit();
|
|
|
|
# Auth
|
|
if use_auth:
|
|
uname = request.post.args("fm_usr")
|
|
pwd = request.post.args("fm_pwd")
|
|
if "logged" in session and session["logged"] in auth_users: # TODO
|
|
# Logged in, don't do anything
|
|
pass
|
|
elif uname is not None and pwd is not None:
|
|
# Logging In
|
|
time.sleep(1) # TODO Why? preventing bruteforce?
|
|
|
|
if uname in auth_users and password_verify(pwd, auth_users[uname]):
|
|
session["logged"] = uname
|
|
fm_set_msg("You are logged in!")
|
|
flask.redirect(FM_SELF_URL + "?p=")
|
|
else:
|
|
del session["logged"]
|
|
fm_set_msg("Login failed; Invalid username or password", "error")
|
|
flask.redirect("FM_SELF_URL")
|
|
else:
|
|
# Form
|
|
del session["logged"] # TODO ??
|
|
fm_show_header_login()
|
|
fm_show_message()
|
|
|
|
print("""<section class="h-100">
|
|
<div class="container h-100">
|
|
<div class="row justify-content-md-center h-100">
|
|
<div class="card-wrapper">
|
|
<div class="brand">
|
|
<svg version="1.0" xmlns="http://www.w3.org/2000/svg" M1008 width="100%" height="121px" viewBox="0 0 238.000000 140.000000" aria-label="H3K Tiny File Manager">
|
|
<g transform="translate(0.000000,140.000000) scale(0.100000,-0.100000)" fill="#000000" stroke="none">
|
|
<path d="M160 700 l0 -600 110 0 110 0 0 260 0 260 70 0 70 0 0 -260 0 -260 110 0 110 0 0 600 0 600 -110 0 -110 0 0 -260 0 -260 -70 0 -70 0 0 260 0 260 -110 0 -110 0 0 -600z"/>
|
|
<path fill="#003500" d="M1008 1227 l-108 -72 0 -117 0 -118 110 0 110 0 0 110 0 110 70 0 70 0 0 -180 0 -180 -125 0 c-69 0 -125 -3 -125 -6 0 -3 23 -39 52 -80 l52 -74 73 0 73 0 0 -185 0 -185 -70 0 -70 0 0 115 0 115 -110 0 -110 0 0 -190 0 -190 181 0 181 0 109 73 108 72 1 181 0 181 -69 48 -68 49 68 50 69 49 0 249 0 248 -182 -1 -183 0 -107 -72z"/>
|
|
<path d="M1640 700 l0 -600 110 0 110 0 0 208 0 208 35 34 35 34 35 -34 35 -34 0 -208 0 -208 110 0 110 0 0 212 0 213 -87 87 -88 88 88 88 87 87 0 213 0 212 -110 0 -110 0 0 -208 0 -208 -70 -69 -70 -69 0 277 0 277 -110 0 -110 0 0 -600z"/></g>
|
|
</svg>
|
|
</div>
|
|
<div class="text-center">
|
|
<h1 class="card-title"><?php echo APP_TITLE; ?></h1>
|
|
</div>
|
|
<div class="card fat">
|
|
<div class="card-body">
|
|
<form class="form-signin" action="" method="post" autocomplete="off">
|
|
<div class="form-group">
|
|
<label for="fm_usr">Username</label>
|
|
<input type="text" class="form-control" id="fm_usr" name="fm_usr" required autofocus>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="fm_pwd">Password</label>
|
|
<input type="password" class="form-control" id="fm_pwd" name="fm_pwd" required>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<div class="custom-checkbox custom-control">
|
|
<input type="checkbox" name="remember" id="remember" class="custom-control-input">
|
|
<label for="remember" class="custom-control-label">Remember me?</label>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<button type="submit" class="btn btn-success btn-block" role="button">
|
|
Login
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div class="footer text-center">
|
|
—— ©
|
|
<a href="https://tinyfilemanager.github.io/" target="_blank" class="text-muted" data-version="0.1.1. TODO">CCP Programmers & Friends</a> ——
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>""")
|
|
|
|
fm_show_footer_login()
|
|
# exit();
|
|
|
|
# update root path
|
|
if use_auth and "logged" in session:
|
|
root_path = directories_users.get(session["logged"], root_path)
|
|
|
|
# clean and check $root_path
|
|
root_path = root_path.rstrip("\\/")
|
|
root_path = root_path.replace('\\', '/')
|
|
if not os.path.isdir(root_path):
|
|
print(f"<h1>Root path \"{root_path}\" not found!</h1>")
|
|
return
|
|
|
|
if not FM_SHOW_HIDDEN: FM_SHOW_HIDDEN = show_hidden_files
|
|
if not FM_ROOT_PATH: FM_ROOT_PATH = root_path
|
|
if not FM_LANG: FM_LANG = lang
|
|
if not FM_EXTENSION: FM_EXTENSION = allowed_extensions
|
|
FM_READONLY = use_auth and readonly_users != [] and "logged" in session and session["logged"] in readonly_users
|
|
FM_IS_WIN = (os.name == "nt")
|
|
|
|
# always use ?p=
|
|
if not request.get.args("p") and not request.files:
|
|
fm_redirect(FM_SELF_URL + "?p=")
|
|
|
|
# get path
|
|
p = request.get.args("p", "")
|
|
|
|
# clean path
|
|
p = fm_clean_path(p)
|
|
|
|
# for ajax request - save
|
|
# TODO
|
|
# $input = file_get_contents('php://input');
|
|
# $_POST = (strpos($input, 'ajax') != FALSE && strpos($input, 'save') != FALSE) ? json_decode($input, true) : $_POST;
|
|
|
|
# instead globals vars
|
|
FM_PATH = p
|
|
FM_USE_AUTH = use_auth
|
|
FM_EDIT_FILE = edit_files
|
|
if not FM_ICONV_INPUT_ENC: FM_ICONV_INPUT_ENC = iconv_input_encoding
|
|
if not FM_USE_HIGHLIGHTJS: FM_USE_HIGHLIGHTJS = use_highlightjs
|
|
if not FM_HIGHLIGHTJS_STYLE: FM_HIGHLIGHTJS_STYLE = highlightjs_style
|
|
if not FM_DATETIME_FORMAT: FM_DATETIME_FORMAT = datetime_format
|
|
|
|
# TODO ... what's the point?
|
|
del p
|
|
del use_auth
|
|
del iconv_input_encoding
|
|
del use_highlightjs
|
|
del highlightjs_style
|
|
|
|
# *************************** ACTIONS ***************************
|
|
|
|
# AJAX Request
|
|
if request.post.args("ajax") is not None and not FM_READONLY:
|
|
# save
|
|
if request.post.args("type") == "save":
|
|
# get current path
|
|
path = FM_ROOT_PATH
|
|
if FM_PATH != '':
|
|
path += '/' + FM_PATH
|
|
|
|
# check path
|
|
if not os.path.isdir(path):
|
|
flask.redirect(FM_SELF_URL + '?p=')
|
|
|
|
file = request.get.args("edit")
|
|
file = fm_clean_path(file)
|
|
file = file.replace("/", "")
|
|
if file == '' or not os.path.isfile(path + os.sep + file):
|
|
flask.flash('File not found', 'error')
|
|
flask.redirect(FM_SELF_URL + '?p=' + urllib.parse.urlencode(FM_PATH))
|
|
|
|
print('X-XSS-Protection: 0')
|
|
file_path = path + '/' + file
|
|
|
|
writedata = request.post.args("content")
|
|
with open(file_path, "w") as fobj:
|
|
fobj.write(writedata)
|
|
|
|
flask.flash('successful save!', 'alert')
|
|
# die(true); # TODO
|
|
return
|
|
|
|
# backup files
|
|
if request.post.args("type") == "backup":
|
|
file = request.post.args("file")
|
|
path = request.post.args("path")
|
|
date = datetime.datetime.now().strftime("%d%M%Y-%H%i%s") # TODO
|
|
new_file = f"{file}-{date}.bak"
|
|
shutil.copy(path + os.sep + file, path + os.sep + new_file) # or die() # TODO
|
|
print("Backup {new_file} created!") # TODO
|
|
|
|
# Save Config
|
|
if request.post.args("type") == "settings":
|
|
global cfg, report_errors, lang_list, hide_cols, calc_folder
|
|
new_lang = request.post.args("js-language")
|
|
fm_get_translations([]) # TODO
|
|
if new_lang not in lang_list:
|
|
new_lang = "en"
|
|
|
|
erp = bool(request.post.args("js-error-report"))
|
|
shf = bool(request.post.args("js-show-hidden"))
|
|
hco = bool(request.post.args('js-hide-cols'))
|
|
caf = bool(request.post.args('js-calc-folder'))
|
|
|
|
# TODO Wtf is the point?
|
|
cfg.data.update({
|
|
"lang": new_lang,
|
|
"error_reporting": erp,
|
|
"show_hidden": shf,
|
|
"hide_Cols": hco,
|
|
"calc_folder": caf,
|
|
})
|
|
|
|
# TODO Why?
|
|
lang = new_lang
|
|
report_errors = erp
|
|
show_hidden_files = shf
|
|
hide_cols = hco
|
|
calc_folder = caf
|
|
|
|
# new password hash
|
|
if request.post.args("type") == "pwdhash":
|
|
print(password_hash(request.post.args("inputPassword2"))) # TODO
|
|
|
|
# upload using url
|
|
# TODO
|
|
if request.post.args("type") == "upload" and request.post.args("uploadurl") is not None:
|
|
path = FM_ROOT_PATH
|
|
if FM_PATH != '':
|
|
path += "/" + FM_PATH
|
|
|
|
url = None
|
|
upload_url = request.post.args("uploadurl")
|
|
if upload_url is not None and re.match(r"^http(s)?://.+$", upload_url.rstrip("/")):
|
|
url = upload_url.rstrip("/")
|
|
|
|
use_curl = False
|
|
temp_file = TemporaryFile(prefix="upload-")
|
|
fileinfo = {
|
|
"type": None,
|
|
# TODO Does this do what I actually think it does?
|
|
# $fileinfo->name = trim(basename($url), ".\x00..\x20");
|
|
"name": os.path.basename(url).strip(".\x00..\x20")
|
|
}
|
|
|
|
def event_callback(message):
|
|
# global callback # TODO ?
|
|
print(json.dumps(message))
|
|
|
|
def get_file_path():
|
|
return path + os.sep + os.path.basename(fileinfo["name"])
|
|
|
|
err = False
|
|
if not url:
|
|
success = False
|
|
elif use_curl:
|
|
with open(temp_file, "w") as fp:
|
|
ch = pycurl.Curl()
|
|
ch.setopt(ch.URL, url)
|
|
ch.setopt(ch.CURLOPT_NOPROGRESS, False)
|
|
ch.setopt(ch.CURLOPT_FOLLOWLOCATION, True)
|
|
# NOTE: changed from CURLOPT_FILE in PHP
|
|
ch.setopt(ch.CURLOPT_WRITEDATA, fp)
|
|
success = ch.exec()
|
|
curl_info = ch.getinfo()
|
|
|
|
if not success:
|
|
err = {"message": ch.curl_error() }
|
|
|
|
ch.close()
|
|
|
|
fileinfo.size = curl_info["size_download"]
|
|
fileinfo.type = curl_info["content_type"]
|
|
else:
|
|
# NOTE: Original code:
|
|
#
|
|
# $ctx = stream_context_create();
|
|
# @$success = copy($url, $temp_file, $ctx);
|
|
# if (!$success) {
|
|
# $err = error_get_last();
|
|
# }
|
|
#
|
|
# I think that this just downloads a file from url and puts it in temp_file...
|
|
|
|
try:
|
|
resp = urllib.request.urlopen(url)
|
|
if resp.status != 200:
|
|
raise RuntimeError(f"HTTP request failed with error code {resp.status}")
|
|
|
|
temp_file.write(resp.read())
|
|
except (urllib.error.HTTPError, RuntimeError) as e:
|
|
err = repr(e) # TODO
|
|
|
|
if success:
|
|
# TODO This is... weird. Better way to do this? I'm not sure it will even work
|
|
shutil.copy(temp_file.name, get_file_path())
|
|
|
|
if success:
|
|
event_callback({"done": fileinfo})
|
|
else:
|
|
temp_file.close()
|
|
# unlink($temp_file);
|
|
if err:
|
|
err = {"message": "Invalid url parameter"}
|
|
|
|
event_callback({"fail": err})
|
|
|
|
return
|
|
|
|
# Delete file / folder
|
|
if "del" not in request.args and not FM_READONLY:
|
|
_del = fm_clean_path(request.args("del")).replace("/", "")
|
|
if _del not in (" ", "..", "."):
|
|
path = FM_ROOT_PATH
|
|
if FM_PATH != "":
|
|
path += os.sep + FM_PATH
|
|
|
|
is_dir = os.path.isdir(path + os.sep + _del)
|
|
if fm_rdelete(path + os.sep + _del):
|
|
msg = 'Folder <b>{}</b> deleted' if is_dir else 'File <b>{}</b> deleted'
|
|
fm_set_msg(msg.format(fm_enc(_del)))
|
|
else:
|
|
msg = 'Folder <b>{}</b> not deleted' if is_dir else 'File <b>{}</b> not deleted'
|
|
fm_set_msg(msg.format(fm_enc(_del)), 'error')
|
|
else:
|
|
fm_set_msg('Wrong file or folder name', 'error')
|
|
|
|
flask.redirect(FM_SELF_URL + '?p=' + urllib.parse.urlencode(FM_PATH))
|
|
|
|
# Copy folder / file
|
|
if ("copy" in request.args or "finish" in request.args) and not FM_READONLY:
|
|
# FROM
|
|
copy = fm_clean_path(request.args.get("copy"))
|
|
|
|
# Check that it's not empty'
|
|
if not copy:
|
|
fm_set_msg("Source path is not defined", "error")
|
|
flask.redirect(FM_SELF_URL + "?p=" + urllib.parse.urlencode(FM_PATH))
|
|
|
|
# Get source absolute path
|
|
_from = FM_ROOT_PATH + "/" + copy
|
|
|
|
# Get dest absolute path
|
|
dest = FM_ROOT_PATH
|
|
if FM_PATH != "":
|
|
dest += "/" + FM_PATH
|
|
|
|
dest += "/" + os.path.basename(_from)
|
|
|
|
# Check if we can move
|
|
move = "move" in request.args
|
|
|
|
# Copy or move the file
|
|
if _from != dest:
|
|
msg_from = FM_PATH + os.sep + os.path.basename(_from)
|
|
msg_from = msg_from.strip(os.sep)
|
|
|
|
if move:
|
|
rename = fm_rename(_from, dest)
|
|
if rename is not None:
|
|
fm_set_msg('Moved from <b>{}</b> to <b>{}</b>'.format(fm_enc(copy), fm_enc(msg_from)))
|
|
elif rename is None:
|
|
fm_set_msg('File or folder with this path already exists', 'alert')
|
|
else:
|
|
fm_set_msg('Error while moving from <b>{}</b> to <b>{}</b>'.format(fm_enc(copy), fm_enc(msg_from)))
|
|
else:
|
|
if fm_rcopy(_from, dest):
|
|
fm_set_msg('Copied from <b>{}</b> to <b>{}</b>'.format(fm_enc(copy), fm_enc(msg_from)))
|
|
else:
|
|
fm_set_msg("Error while copying from <b>{}</b> to <b>{}</b>".format(fm_enc(copy), fm_enc(msg_from)), "error")
|
|
else:
|
|
fm_set_msg('Paths must be not equal', 'alert')
|
|
|
|
fm_redirect(FM_SELF_URL + "?p=" + urllib.parse.urlencode(FM_PATH))
|
|
|