4595 lines
189 KiB
Python
4595 lines
189 KiB
Python
#!/usr/bin/env python
|
||
# coding:utf-8
|
||
# +-------------------------------------------------------------------
|
||
# | YakPanel
|
||
# +-------------------------------------------------------------------
|
||
# | Copyright (c) 2015-2016 YakPanel(www.yakpanel.com) All rights reserved.
|
||
# +-------------------------------------------------------------------
|
||
# | Author: hwliang <hwl@yakpanel.com>
|
||
# +-------------------------------------------------------------------
|
||
from base64 import b64encode
|
||
import sys
|
||
import os
|
||
import public
|
||
import time
|
||
import json
|
||
import pwd
|
||
import cgi
|
||
import shutil
|
||
import re
|
||
import html
|
||
import sqlite3
|
||
import urllib
|
||
import stat
|
||
from YakPanel import session, request
|
||
from public.validate import Param
|
||
from datetime import datetime
|
||
try:
|
||
import chardet
|
||
except:
|
||
os.system('btpip install chardet')
|
||
try:
|
||
import chardet
|
||
except:
|
||
chardet = None
|
||
|
||
|
||
class files:
|
||
run_path = None
|
||
path_permission_list = list()
|
||
path_permission_exclude_list = list()
|
||
file_permission_list = list()
|
||
sqlite_connection = None
|
||
download_list = None
|
||
download_is_rm = None
|
||
recycle_list = []
|
||
download_token_list = None
|
||
|
||
# 检查敏感目录
|
||
|
||
def CheckDir(self, path):
|
||
path = path.replace('//', '/')
|
||
if path[-1:] == '/':
|
||
path = path[:-1]
|
||
|
||
nDirs = ('',
|
||
'/',
|
||
'/*',
|
||
'/www',
|
||
'/root',
|
||
'/boot',
|
||
'/bin',
|
||
'/etc',
|
||
'/home',
|
||
'/dev',
|
||
'/sbin',
|
||
'/var',
|
||
'/usr',
|
||
'/tmp',
|
||
'/sys',
|
||
'/proc',
|
||
'/media',
|
||
'/mnt',
|
||
'/opt',
|
||
'/lib',
|
||
'/srv',
|
||
'/selinux',
|
||
'/www/server',
|
||
'/www/server/data',
|
||
'/www/.Recycle_bin',
|
||
public.GetConfigValue('logs_path'),
|
||
public.GetConfigValue('setup_path'))
|
||
|
||
return not path in nDirs
|
||
|
||
# 网站文件操作前置检测
|
||
def site_path_check(self, get):
|
||
try:
|
||
if not 'site_id' in get:
|
||
return True
|
||
if not self.run_path:
|
||
self.run_path, self.path, self.site_name = self.GetSiteRunPath(
|
||
get.site_id)
|
||
if 'path' in get:
|
||
if get.path.find(self.path) != 0:
|
||
return False
|
||
if 'sfile' in get:
|
||
if get.sfile.find(self.path) != 0:
|
||
return False
|
||
if 'dfile' in get:
|
||
if get.dfile.find(self.path) != 0:
|
||
return False
|
||
return True
|
||
except:
|
||
return True
|
||
|
||
# 网站目录后续安全处理
|
||
def site_path_safe(self, get):
|
||
try:
|
||
if not 'site_id' in get:
|
||
return True
|
||
run_path, path, site_name = self.GetSiteRunPath(get.site_id)
|
||
if not os.path.exists(run_path):
|
||
os.makedirs(run_path)
|
||
ini_path = run_path + '/.user.ini'
|
||
if os.path.exists(ini_path):
|
||
return True
|
||
sess_path = '/www/php_session/%s' % site_name
|
||
if not os.path.exists(sess_path):
|
||
os.makedirs(sess_path)
|
||
ini_conf = '''open_basedir={}/:/tmp/:/proc/:{}/
|
||
session.save_path={}/
|
||
session.save_handler = files'''.format(path, sess_path, sess_path)
|
||
public.writeFile(ini_path, ini_conf)
|
||
public.ExecShell("chmod 644 %s" % ini_path)
|
||
public.ExecShell("chdir +i %s" % ini_path)
|
||
return True
|
||
except:
|
||
return False
|
||
|
||
# 取当站点前运行目录
|
||
def GetSiteRunPath(self, site_id):
|
||
try:
|
||
find = public.M('sites').where(
|
||
'id=?', (site_id,)).field('path,name').find()
|
||
siteName = find['name']
|
||
sitePath = find['path']
|
||
if public.get_webserver() == 'nginx':
|
||
filename = public.get_vhost_path() + '/nginx/' + siteName + '.conf'
|
||
if os.path.exists(filename):
|
||
conf = public.readFile(filename)
|
||
rep = r'\s*root\s+(.+);'
|
||
tmp1 = re.search(rep, conf)
|
||
if tmp1:
|
||
path = tmp1.groups()[0]
|
||
else:
|
||
filename = public.get_vhost_path() + '/apache/' + siteName + '.conf'
|
||
if os.path.exists(filename):
|
||
conf = public.readFile(filename)
|
||
rep = '\\s*DocumentRoot\\s*"(.+)"\\s*\n'
|
||
tmp1 = re.search(rep, conf)
|
||
if tmp1:
|
||
path = tmp1.groups()[0]
|
||
return path, sitePath, siteName
|
||
except:
|
||
return sitePath, sitePath, siteName
|
||
|
||
# 检测文件名
|
||
def CheckFileName(self, filename):
|
||
nots = ['\\', '&', '*', '|', ';', '"', "'", '<', '>']
|
||
if filename.find('/') != -1:
|
||
filename = filename.split('/')[-1]
|
||
for n in nots:
|
||
if n in filename:
|
||
return False
|
||
return True
|
||
|
||
# 名称输出过滤
|
||
def xssencode(self, text):
|
||
list = ['<', '>']
|
||
ret = []
|
||
for i in text:
|
||
if i in list:
|
||
i = ''
|
||
ret.append(i)
|
||
str_convert = ''.join(ret)
|
||
if sys.version_info[0] == 3:
|
||
import html
|
||
text2 = html.escape(str_convert, quote=True)
|
||
else:
|
||
text2 = cgi.escape(str_convert, quote=True)
|
||
|
||
reps = {'&': '&'}
|
||
for rep in reps.keys():
|
||
if text2.find(rep) != -1: text2 = text2.replace(rep, reps[rep])
|
||
return text2
|
||
|
||
# 名称输入系列化
|
||
def xssdecode(self, text):
|
||
try:
|
||
cs = {""": '"', "'": "'"}
|
||
for c in cs.keys():
|
||
text = text.replace(c, cs[c])
|
||
|
||
str_convert = text
|
||
if sys.version_info[0] == 3:
|
||
import html
|
||
text2 = html.unescape(str_convert)
|
||
else:
|
||
text2 = cgi.unescape(str_convert)
|
||
return text2
|
||
except:
|
||
return text
|
||
|
||
# 上传文件
|
||
def UploadFile(self, get):
|
||
from werkzeug.utils import secure_filename
|
||
from YakPanel import request
|
||
if sys.version_info[0] == 2:
|
||
get.path = get.path.encode('utf-8')
|
||
if not os.path.exists(get.path):
|
||
os.makedirs(get.path)
|
||
f = request.files['zunfile']
|
||
filename = os.path.join(get.path, f.filename)
|
||
if sys.version_info[0] == 2:
|
||
filename = filename.encode('utf-8')
|
||
s_path = get.path
|
||
if os.path.exists(filename):
|
||
s_path = filename
|
||
p_stat = os.stat(s_path)
|
||
f.save(filename)
|
||
os.chown(filename, p_stat.st_uid, p_stat.st_gid)
|
||
os.chmod(filename, p_stat.st_mode)
|
||
public.WriteLog('TYPE_FILE', 'FILE_UPLOAD_SUCCESS',
|
||
(filename, get['path']))
|
||
return public.return_message(0, 0, public.lang("uccessfully uploaded!"))
|
||
|
||
def f_name_check(self, filename):
|
||
'''
|
||
@name 文件名检测2
|
||
@author hwliang<2021-03-16>
|
||
@param filename<string> 文件名
|
||
@return bool
|
||
'''
|
||
f_strs = [';', '&', '<', '>']
|
||
for fs in f_strs:
|
||
if filename.find(fs) != -1:
|
||
return False
|
||
return True
|
||
|
||
# 上传前检查文件是否存在
|
||
def upload_file_exists(self, args):
|
||
'''
|
||
@name 上传前检查文件是否存在
|
||
@author hwliang<2021-11-3>
|
||
@param filename<string> 文件名
|
||
@return dict
|
||
'''
|
||
filename = args.filename.strip()
|
||
try:
|
||
filename = filename.encode('utf-8').decode('latin-1')
|
||
except UnicodeEncodeError:
|
||
return public.return_message(0, 0, public.lang("The filename contains illegal characters"))
|
||
|
||
if not os.path.exists(filename):
|
||
return public.return_message(-1, 0, public.lang("File does not exist!"))
|
||
file_info = {}
|
||
_stat = os.stat(filename)
|
||
file_info['size'] = _stat.st_size
|
||
file_info['mtime'] = int(_stat.st_mtime)
|
||
file_info['isfile'] = os.path.isfile(filename)
|
||
return public.return_message(0, 0, file_info)
|
||
# 上传前批量检查文件是否存在
|
||
def upload_files_exists(self, args):
|
||
'''
|
||
@name 上传前批量检查文件是否存在
|
||
@param files<string> 文件列表,多个用\n分隔
|
||
@return dict
|
||
'''
|
||
check_files = []
|
||
if hasattr(args, 'files'):
|
||
check_files = args.files.split('\n')
|
||
file_list = []
|
||
for filename in check_files:
|
||
try:
|
||
if not os.path.exists(filename):
|
||
file_list.append({'filename': filename, 'exists': False, 'size': 0, 'mtime': 0, 'isfile': False})
|
||
continue
|
||
_stat = os.stat(filename)
|
||
file_list.append({
|
||
'filename': filename,
|
||
'exists': True,
|
||
'size': _stat.st_size,
|
||
'mtime': int(_stat.st_mtime),
|
||
'isfile': os.path.isfile(filename)
|
||
})
|
||
except:
|
||
file_list.append({'filename': filename, 'exists': False, 'size': 0, 'mtime': 0, 'isfile': False})
|
||
return public.return_message(0, 0, file_list)
|
||
|
||
def get_real_len(self, string):
|
||
'''
|
||
@name 获取含中文的字符串字精确长度
|
||
@author hwliang<2021-11-3>
|
||
@param string<str>
|
||
@return int
|
||
'''
|
||
real_len = len(string)
|
||
for s in string:
|
||
if '\u2E80' <= s <= '\uFE4F':
|
||
real_len += 1
|
||
return real_len
|
||
|
||
|
||
def upload(self, args):
|
||
if not 'f_name' in args:
|
||
f_name = request.form.get('f_name')
|
||
if not f_name:
|
||
return public.return_message(-1, 0, public.lang("File name cannot be empty"))
|
||
|
||
f_path = request.form.get('f_path')
|
||
if not f_path:
|
||
return public.return_message(-1, 0, public.lang("File path cannot be empty"))
|
||
|
||
args.f_name = f_name.strip()
|
||
args.f_path = f_path.strip()
|
||
args.f_size = request.form.get('f_size')
|
||
args.f_start = request.form.get('f_start')
|
||
|
||
if sys.version_info[0] == 2:
|
||
args.f_name = args.f_name.encode('utf-8')
|
||
args.f_path = args.f_path.encode('utf-8')
|
||
|
||
try:
|
||
if self.get_real_len(args.f_name) > 256:
|
||
return public.return_message(-1, 0, public.lang("The file name contains more than 256 bytes"))
|
||
except:
|
||
pass
|
||
|
||
try:
|
||
temp_filename = args.f_name + '.' + str(int(args.f_size)) + '.upload.tmp'
|
||
if len(temp_filename.encode('utf-8')) > 255:
|
||
return public.return_message(-1, 0, public.lang("The file name is too long (over 255 bytes)."))
|
||
save_path = os.path.join(args.f_path, temp_filename)
|
||
if len(save_path.encode('utf-8')) > 4096:
|
||
return public.return_message(-1, 0, public.lang("The full path is too long (over 4096 bytes)."))
|
||
except Exception as e:
|
||
return public.fail_v2(public.lang("Failed to check file path: {}", str(e)))
|
||
|
||
if not self.f_name_check(args.f_name): return public.return_message(-1, 0, public.lang("No special characters can be included in the file name!"))
|
||
|
||
if args.f_path == '/':
|
||
return public.return_message(-1, 0, public.lang("Cannot upload files to the system document root!"))
|
||
|
||
if args.f_name.find('./') != -1 or args.f_path.find('./') != -1:
|
||
return public.return_message(-1, 0, public.lang("Wrong parameter"))
|
||
|
||
if not os.path.exists(args.f_path):
|
||
os.makedirs(args.f_path, 493, True)
|
||
if not 'dir_mode' in args or not 'file_mode' in args:
|
||
self.set_mode(args.f_path)
|
||
|
||
save_path = os.path.join(
|
||
args.f_path, args.f_name + '.' + str(int(args.f_size)) + '.upload.tmp'
|
||
)
|
||
d_size = 0
|
||
if os.path.exists(save_path):
|
||
d_size = os.path.getsize(save_path)
|
||
|
||
if d_size != int(args.f_start):
|
||
return d_size
|
||
|
||
try:
|
||
f = open(save_path, 'ab')
|
||
if 'b64_data' in args:
|
||
import base64
|
||
b64_data = base64.b64decode(args.b64_data)
|
||
f.write(b64_data)
|
||
else:
|
||
upload_files = request.files.getlist("blob")
|
||
for tmp_f in upload_files:
|
||
f.write(tmp_f.read())
|
||
f.close()
|
||
except Exception as ex:
|
||
ex = str(ex)
|
||
if ex.find('No space left on device') != -1:
|
||
return public.fail_v2(public.lang('Not enough disk space'))
|
||
|
||
f_size = os.path.getsize(save_path)
|
||
if f_size != int(args.f_size):
|
||
return f_size
|
||
|
||
new_name = os.path.join(args.f_path, args.f_name)
|
||
if os.path.exists(new_name):
|
||
if new_name.find('.user.ini') != -1:
|
||
public.ExecShell("chattr -i " + new_name)
|
||
try:
|
||
os.remove(new_name)
|
||
except:
|
||
public.ExecShell("rm -f %s" % new_name)
|
||
|
||
if os.path.isdir(new_name):
|
||
return public.fail_v2(public.lang("If the destination path already has a directory with the same name, change the file name"))
|
||
os.renames(save_path, new_name)
|
||
if 'dir_mode' in args and 'file_mode' in args:
|
||
mode_tmp1 = args.dir_mode.split(',')
|
||
public.set_mode(args.f_path, mode_tmp1[0])
|
||
public.set_own(args.f_path, mode_tmp1[1])
|
||
mode_tmp2 = args.file_mode.split(',')
|
||
public.set_mode(new_name, mode_tmp2[0])
|
||
public.set_own(new_name, mode_tmp2[1])
|
||
|
||
else:
|
||
self.set_mode(new_name)
|
||
|
||
if new_name.find('.user.ini') != -1:
|
||
public.ExecShell("chattr +i " + new_name)
|
||
|
||
public.write_log_gettext('File manager', 'Successfully uploaded [ {} ] !', (new_name,),
|
||
(args.f_name, args.f_path))
|
||
|
||
return public.success_v2(public.lang('Successfully uploaded!'))
|
||
# 设置文件和目录权限
|
||
def set_mode(self, path):
|
||
if path[-1] == '/': path = path[:-1]
|
||
s_path = os.path.dirname(path)
|
||
p_stat = os.stat(s_path)
|
||
os.chown(path, p_stat.st_uid, p_stat.st_gid)
|
||
if os.path.isfile(path):
|
||
os.chmod(path, 0o644)
|
||
else:
|
||
os.chmod(path, p_stat.st_mode)
|
||
|
||
# 是否包含composer.json
|
||
def is_composer_json(self, path):
|
||
if os.path.exists(path + '/composer.json'):
|
||
return '1'
|
||
return '0'
|
||
|
||
def __check_favorite(self, filepath, favorites_info):
|
||
for favorite in favorites_info:
|
||
if filepath == favorite['path']:
|
||
return '1'
|
||
return '0'
|
||
|
||
def __get_topping_data(self):
|
||
"""
|
||
@获取置顶配置
|
||
"""
|
||
data = {}
|
||
conf_file = '{}/data/toping.json'.format(public.get_panel_path())
|
||
try:
|
||
if os.path.exists(conf_file):
|
||
data = json.loads(public.readFile(conf_file))
|
||
except:
|
||
pass
|
||
return data
|
||
|
||
def __check_topping(self, filepath, top_info):
|
||
"""
|
||
@name 检测文件或者目录是否置顶
|
||
@param filepath: 文件路径
|
||
"""
|
||
if filepath in top_info:
|
||
return '1'
|
||
import html
|
||
filepath = html.unescape(filepath)
|
||
if filepath in top_info:
|
||
return '1'
|
||
return '0'
|
||
|
||
def __check_share(self, filename):
|
||
if self.download_token_list == None:
|
||
self.download_token_list = {}
|
||
my_table = 'download_token'
|
||
download_list = public.M(my_table).field('id,filename').select()
|
||
for k in download_list:
|
||
self.download_token_list[k['filename']] = k['id']
|
||
|
||
return str(self.download_token_list.get(filename, '0'))
|
||
|
||
def __filename_flater(self, filename):
|
||
ms = {";": ""}
|
||
for m in ms.keys():
|
||
filename = filename.replace(m, ms[m])
|
||
return filename
|
||
|
||
def files_list(self, path, search=None, my_sort='off', reverse=False):
|
||
'''
|
||
@name 遍历目录,并获取全量文件信息列表
|
||
@param path<string> 目录路径
|
||
@param search<string> 搜索关键词
|
||
@param my_sort<string> 排序字段
|
||
@param reverse<bool> 是否降序
|
||
@return tuple (int,list)
|
||
'''
|
||
|
||
nlist = []
|
||
count = 0
|
||
|
||
# 文件不存在
|
||
if not os.path.exists(path):
|
||
return count, nlist
|
||
|
||
sort_key = -1
|
||
if my_sort == 'off': # 不排序
|
||
sort_key = -1
|
||
elif my_sort == 'name': # 按文件名排序
|
||
sort_key = 0
|
||
elif my_sort == 'size': # 按文件大小排序
|
||
sort_key = 1
|
||
elif my_sort == 'mtime': # 按修改时间排序
|
||
sort_key = 2
|
||
elif my_sort == 'accept': # 按文件权限排序
|
||
sort_key = 3
|
||
elif my_sort == 'user': # 按文件所有者排序
|
||
sort_key = 4
|
||
|
||
with os.scandir(path) as it:
|
||
try:
|
||
for entry in it:
|
||
# 是否搜索
|
||
if search:
|
||
if entry.name.lower().find(search) == -1:
|
||
continue
|
||
|
||
# 是否需要获取文件信息
|
||
sort_val = 0
|
||
if sort_key == 0 or sort_key == -1:
|
||
# 通过文件名或不排序时,不获取文件信息
|
||
sort_val = 0
|
||
else:
|
||
try:
|
||
fstat = entry.stat()
|
||
if sort_key == 1:
|
||
sort_val = fstat.st_size
|
||
elif sort_key == 2:
|
||
sort_val = fstat.st_mtime
|
||
elif sort_key == 3:
|
||
sort_val = fstat.st_mode
|
||
elif sort_key == 4:
|
||
sort_val = fstat.st_uid
|
||
except:
|
||
pass
|
||
|
||
nlist.append((entry.name, sort_val, entry.is_dir()))
|
||
|
||
# 计数
|
||
count += 1
|
||
except:
|
||
pass
|
||
|
||
if sort_key == 0:
|
||
# 按文件名排序
|
||
nlist = sorted(nlist, key=lambda x: [int(text) if text.isdigit() else text.lower() for text in re.split(r'(\d+)', x[0])], reverse=reverse)
|
||
elif sort_key > 0:
|
||
# 按指定字段排序
|
||
nlist = sorted(nlist, key=lambda x: x[1], reverse=reverse)
|
||
else:
|
||
# 否则文件数量小于10000时,按文件名排序
|
||
if count < 10000:
|
||
nlist = sorted(nlist, key=lambda x: [int(text) if text.isdigit() else text.lower() for text in
|
||
re.split(r'(\d+)', x[0])], reverse=False)
|
||
else:
|
||
nlist = sorted(nlist, key=lambda x: x[0], reverse=False)
|
||
nlist = sorted(nlist, key=lambda x: x[2], reverse=True)
|
||
|
||
return count, nlist
|
||
|
||
def files_list_new(self, path, search=None, my_sort='off', reverse=False):
|
||
'''
|
||
@name 遍历目录,确保目录在前(优先显示在第一页),再分别排序
|
||
@param path<string> 目录路径
|
||
@param search<string> 搜索关键词
|
||
@param my_sort<string> 排序字段
|
||
@param reverse<bool> 是否降序
|
||
@return tuple (int,list)
|
||
'''
|
||
|
||
nlist = []
|
||
count = 0
|
||
|
||
if not os.path.exists(path):
|
||
return count, nlist
|
||
|
||
# 排序字段映射
|
||
sort_key = -1
|
||
if my_sort == 'off':
|
||
sort_key = -1
|
||
elif my_sort == 'name':
|
||
sort_key = 0
|
||
elif my_sort == 'size':
|
||
sort_key = 1
|
||
elif my_sort == 'mtime':
|
||
sort_key = 2
|
||
elif my_sort == 'accept':
|
||
sort_key = 3
|
||
elif my_sort == 'user':
|
||
sort_key = 4
|
||
|
||
# 分离目录和文件列表
|
||
dirs = [] # 存储目录:(名称, 排序值)
|
||
files = [] # 存储文件:(名称, 排序值)
|
||
|
||
with os.scandir(path) as it:
|
||
try:
|
||
for entry in it:
|
||
# 搜索过滤
|
||
if search and entry.name.lower().find(search) == -1:
|
||
continue
|
||
|
||
# 获取排序值
|
||
sort_val = 0
|
||
if sort_key not in (-1, 0):
|
||
try:
|
||
fstat = entry.stat()
|
||
if sort_key == 1:
|
||
sort_val = fstat.st_size
|
||
elif sort_key == 2:
|
||
sort_val = fstat.st_mtime
|
||
elif sort_key == 3:
|
||
sort_val = fstat.st_mode
|
||
elif sort_key == 4:
|
||
sort_val = fstat.st_uid
|
||
except:
|
||
pass
|
||
|
||
# 分离目录和文件
|
||
if entry.is_dir():
|
||
dirs.append((entry.name, sort_val))
|
||
else:
|
||
files.append((entry.name, sort_val))
|
||
count += 1
|
||
except:
|
||
pass
|
||
|
||
# 定义二级排序函数(目录和文件内部排序)
|
||
def get_secondary_key(item):
|
||
if sort_key == 0:
|
||
return item[0].lower() # 按名称
|
||
elif sort_key > 0:
|
||
return item[1] # 按指定字段
|
||
else:
|
||
return item[0].lower() # 默认按名称
|
||
|
||
# 目录内部排序
|
||
dirs_sorted = sorted(dirs, key=get_secondary_key, reverse=reverse)
|
||
# 文件内部排序
|
||
files_sorted = sorted(files, key=get_secondary_key, reverse=reverse)
|
||
|
||
# 合并:目录在前,文件在后
|
||
nlist = dirs_sorted + files_sorted
|
||
|
||
return count, nlist
|
||
|
||
# 取文件/目录列表
|
||
def GetDir(self, get: public.dict_obj):
|
||
if not hasattr(get, 'path'):
|
||
get.path = public.get_site_path() # '/www/wwwroot'
|
||
if sys.version_info[0] == 2:
|
||
get.path = get.path.encode('utf-8')
|
||
if get.path == '':
|
||
get.path = '/www'
|
||
|
||
# 转换包含~的路径
|
||
if get.path.find('~') != -1:
|
||
get.path = os.path.expanduser(get.path)
|
||
|
||
get.path = self.xssdecode(get.path)
|
||
if not os.path.exists(get.path):
|
||
get.path = public.get_site_path()
|
||
# return public.return_message(-1, 0, '指定目录不存在!')
|
||
if os.path.basename(get.path) == '.Recycle_bin':
|
||
return public.return_message(-1, 0, public.lang("Recovery failed!"))
|
||
if not os.path.isdir(get.path):
|
||
get.path = os.path.dirname(get.path)
|
||
|
||
if not os.path.isdir(get.path):
|
||
return public.return_message(-1, 0, public.lang("This is not a directory"))
|
||
|
||
dirnames = []
|
||
filenames = []
|
||
|
||
search = None
|
||
if hasattr(get, 'search'):
|
||
search = get.search.strip().lower()
|
||
public.set_search_history('files', 'get_list', search)
|
||
if hasattr(get, 'all'):
|
||
return public.return_message(0, 0, self.SearchFiles(get))
|
||
|
||
# 包含分页类
|
||
import page
|
||
# 实例化分页类
|
||
page = page.Page()
|
||
info = {}
|
||
|
||
if not hasattr(get, 'reverse'): get.reverse = 'False'
|
||
if not hasattr(get, 'sort'): get.sort = 'off'
|
||
reverse = bool(get.reverse)
|
||
if get.reverse == 'False':
|
||
reverse = False
|
||
|
||
info['count'], _nlist = self.files_list(get.path, search, my_sort=get.sort, reverse=reverse)
|
||
# 改1
|
||
# info['count'] = self.GetFilesCount(get.path, search)
|
||
info['row'] = 500
|
||
if 'disk' in get:
|
||
if get.disk == 'true': info['row'] = 2000
|
||
if 'share' in get and get.share:
|
||
info['row'] = 5000
|
||
info['p'] = 1
|
||
if hasattr(get, 'p'):
|
||
try:
|
||
info['p'] = int(get['p'])
|
||
except:
|
||
info['p'] = 1
|
||
|
||
info['uri'] = {}
|
||
info['return_js'] = ''
|
||
if hasattr(get, 'tojs'):
|
||
info['return_js'] = get.tojs
|
||
if hasattr(get, 'showRow'):
|
||
try:
|
||
info['row'] = int(get.showRow)
|
||
except:
|
||
info['row'] = 500
|
||
# 获取分页数据
|
||
data = {}
|
||
data['PAGE'] = page.GetPage(info, '1,2,3,4,5,6,7,8')
|
||
|
||
i = 0
|
||
n = 0
|
||
|
||
top_data = self.__get_topping_data()
|
||
data['STORE'] = self.get_files_store(None)
|
||
data['FILE_RECYCLE'] = os.path.exists('data/recycle_bin.pl')
|
||
|
||
# if info['count'] >= 200 and not os.path.exists('data/max_files_sort.pl'):
|
||
# get.reverse = 'False'
|
||
# reverse = False
|
||
# get.sort = ''
|
||
|
||
# _nlist = self.__default_list_dir(get.path,page.SHIFT,page.ROW)
|
||
# data['SORT'] = 0
|
||
# else:
|
||
# _nlist = self.__list_dir(get.path, get.sort, reverse)
|
||
|
||
for file_info in _nlist:
|
||
|
||
if search:
|
||
if file_info[0].lower().find(search) == -1:
|
||
continue
|
||
i += 1
|
||
if n >= page.ROW:
|
||
break
|
||
if i < page.SHIFT:
|
||
continue
|
||
|
||
try:
|
||
fname = file_info[0].encode('unicode_escape').decode("unicode_escape")
|
||
filename = os.path.join(get.path, fname)
|
||
if not os.path.exists(filename) and not os.path.islink(filename): continue
|
||
file_info = self.__format_stat_old(filename, get.path)
|
||
if not file_info: continue
|
||
favorite = self.__check_favorite(filename, data['STORE'])
|
||
r_file = self.__filename_flater(file_info['name']) + ';' + str(file_info['size']) + ';' + str(
|
||
file_info['mtime']) + ';' + str(
|
||
file_info['accept']) + ';' + file_info['user'] + ';' + file_info['link'] + ';' \
|
||
+ self.get_download_id(filename) + ';' + self.is_composer_json(filename) + ';' \
|
||
+ favorite + ';' + self.__check_share(filename)
|
||
if os.path.isdir(filename):
|
||
dirnames.append(r_file)
|
||
else:
|
||
filenames.append(r_file)
|
||
n += 1
|
||
except:
|
||
continue
|
||
|
||
data['DIR'] = dirnames
|
||
data['FILES'] = filenames
|
||
data['PATH'] = str(get.path)
|
||
|
||
# 2022-07-29,增加置顶排序
|
||
tmp_dirs = []
|
||
for i in range(len(data['DIR'])):
|
||
filepath = os.path.join(data['PATH'], data['DIR'][i].split(';')[0])
|
||
toping = self.__check_topping(filepath, top_data)
|
||
info = data['DIR'][i] + ';' + self.get_file_ps(filepath) + ';' + toping
|
||
if toping == '1':
|
||
tmp_dirs.insert(0, info)
|
||
else:
|
||
tmp_dirs.append(info)
|
||
|
||
tmp_files = []
|
||
for i in range(len(data['FILES'])):
|
||
filepath = os.path.join(data['PATH'], data['FILES'][i].split(';')[0])
|
||
toping = self.__check_topping(filepath, top_data)
|
||
info = data['FILES'][i] + ';' + self.get_file_ps(filepath) + ';' + toping
|
||
if toping == '1':
|
||
tmp_files.insert(0, info)
|
||
else:
|
||
tmp_files.append(info)
|
||
data['DIR'] = tmp_dirs
|
||
data['FILES'] = tmp_files
|
||
|
||
if hasattr(get, 'disk'):
|
||
import system
|
||
data['DISK'] = system.system().GetDiskInfo()
|
||
|
||
data['dir_history'] = public.get_dir_history('files', 'GetDirList')
|
||
data['search_history'] = public.get_search_history('files', 'get_list')
|
||
public.set_dir_history('files', 'GetDirList', data['PATH'])
|
||
|
||
# 2023-3-6,增加融入企业级防篡改
|
||
data = self._check_tamper(data)
|
||
data = self._get_bt_sync_status_old(data)
|
||
return public.return_message(0, 0, data)
|
||
|
||
# 取文件/目录列表New
|
||
def GetDirNew(self, get):
|
||
if not hasattr(get, 'path'):
|
||
get.path = public.get_site_path() # '/www/wwwroot'
|
||
if sys.version_info[0] == 2:
|
||
get.path = get.path.encode('utf-8')
|
||
if get.path == '':
|
||
get.path = '/www'
|
||
# 转换包含~的路径
|
||
if get.path.find('~') != -1:
|
||
get.path = os.path.expanduser(get.path)
|
||
get.path = self.xssdecode(get.path)
|
||
if not os.path.exists(get.path):
|
||
get.path = public.get_site_path()
|
||
# return public.ReturnMsg(False, '指定目录不存在!')
|
||
if os.path.basename(get.path) == '.Recycle_bin':
|
||
return public.return_message(-1, 0, public.lang("This is the Recycle bin directory, please press the [Recycle] button in the upper right corner to open"))
|
||
|
||
if not os.path.isdir(get.path):
|
||
get.path = os.path.dirname(get.path)
|
||
if not os.path.isdir(get.path):
|
||
return public.return_message(-1, 0, public.lang("This is not a directory"))
|
||
|
||
dirnames = []
|
||
filenames = []
|
||
search = None
|
||
if hasattr(get, 'search'):
|
||
search = get.search.strip().lower()
|
||
public.set_search_history('files', 'get_list', search)
|
||
if hasattr(get, 'all'):
|
||
return public.return_message(0, 0, self.SearchFilesNew(get))
|
||
# 获取分页数据
|
||
data = {}
|
||
top_data = self.__get_topping_data()
|
||
data['store'] = self.get_files_store(None)
|
||
data['file_recycle'] = os.path.exists('data/recycle_bin.pl')
|
||
if not hasattr(get, 'reverse'): get.reverse = 'False'
|
||
if not hasattr(get, 'sort'): get.sort = 'name'
|
||
reverse = bool(get.reverse)
|
||
if get.reverse == 'False':
|
||
reverse = False
|
||
# list_data = self.__list_dir(get.path, get.sort, reverse, search)
|
||
n_count, list_data = self.files_list_new(get.path, search, my_sort=get.sort, reverse=reverse)
|
||
# 包含分页类
|
||
import page
|
||
# 实例化分页类
|
||
page = page.Page()
|
||
info = {
|
||
'count': n_count,
|
||
'uri': {},
|
||
'row': int(get.showRow) if hasattr(get,
|
||
'showRow') else 2000 if 'disk' in get and get.disk == 'true' else 5000 if 'share' in get and get.share else 500,
|
||
'p': int(get.get('p', 1)),
|
||
'return_js': get.tojs if hasattr(get, 'tojs') else '',
|
||
}
|
||
# if 'disk' in get:
|
||
# if get.disk == 'true': info['row'] = 2000
|
||
# if 'share' in get and get.share:
|
||
# info['row'] = 5000
|
||
# info['p'] = int(get.get('p', 1))
|
||
# info['uri'] = {}
|
||
# info['return_js'] = ''
|
||
# if hasattr(get, 'tojs'):
|
||
# info['return_js'] = get.tojs
|
||
# if hasattr(get, 'showRow'):
|
||
# info['row'] = int(get.showRow)
|
||
data['page'] = page.GetPage(info, '1,2,3,4,5,6,7,8')
|
||
import html
|
||
pss = self.get_file_ps_list()
|
||
self.get_download_list()
|
||
top_dir = []
|
||
top_file = []
|
||
file_nm = []
|
||
dir_nm = []
|
||
|
||
for file_info in list_data[page.SHIFT:page.SHIFT + page.ROW]:
|
||
filename = os.path.join(get.path, file_info[0])
|
||
|
||
if not os.path.exists(filename) and not os.path.islink(filename): continue
|
||
content = os.stat(filename) if not os.path.islink(filename) or os.path.exists(
|
||
filename) else public.to_dict_obj({'st_size': 0, 'st_mtime': 0, 'st_mode': 0, 'st_uid': 0})
|
||
|
||
try:
|
||
if get.path != "/":
|
||
user_name = pwd.getpwuid(content.st_uid).pw_name
|
||
else:
|
||
user_name = 'root'
|
||
except KeyError:
|
||
user_name = ''
|
||
|
||
if str(filename).endswith(".bt_split_json"):
|
||
pss.update({filename: "PS: Split the recovery profile"})
|
||
if str(filename).endswith(".bt_split"):
|
||
pss.update({filename: "PS: Split unit file"})
|
||
# 备注
|
||
try:
|
||
rmk = public.readFile('data/files_ps/' + public.Md5(filename)) if os.path.exists(
|
||
'data/files_ps/' + public.Md5(filename)) else pss.get(filename, '')
|
||
except:
|
||
rmk = ''
|
||
|
||
try:
|
||
r_file = {
|
||
'nm': html.unescape(file_info[0]), # 文件名
|
||
'sz': content.st_size, # 文件大小
|
||
'mt': int(content.st_mtime), # 修改时间
|
||
'acc': str(oct(content.st_mode)[-3:]), # 权限
|
||
'user': user_name, # 用户
|
||
'lnk': '->' + str(os.readlink(filename)) if os.path.islink(filename) else '', # 链接
|
||
'durl': str(self.download_token_list.get(filename, '')), # 下载链接
|
||
'cmp': 1 if os.path.exists(filename + '/composer.json') else 0, # 是否包含composer.json
|
||
'fav': self.__check_favorite(filename, data['store']), # 是否为收藏
|
||
'rmk': rmk, # 备注
|
||
'top': 1 if html.unescape(filename) in top_data else 0, # 文件或者目录是否置顶
|
||
'sn': file_info[0]
|
||
}
|
||
if os.path.isdir(filename):
|
||
if int(r_file['top']):
|
||
top_dir.append(r_file)
|
||
else:
|
||
dirnames.append(r_file)
|
||
dir_nm.append(r_file['nm'])
|
||
else:
|
||
if int(r_file['top']):
|
||
top_file.append(r_file)
|
||
else:
|
||
filenames.append(r_file)
|
||
file_nm.append(r_file['nm'])
|
||
except:
|
||
pass
|
||
|
||
data['path'] = str(get.path)
|
||
data['dir'] = top_dir + dirnames
|
||
data['files'] = top_file + filenames
|
||
if hasattr(get, 'disk'):
|
||
import system
|
||
data['disk'] = system.system().GetDiskInfo()
|
||
data['dir_history'] = public.get_dir_history('files', 'GetDirList')
|
||
data['search_history'] = public.get_search_history('files', 'get_list')
|
||
public.set_dir_history('files', 'GetDirList', data['path'])
|
||
# 2023-3-6,增加融入企业级防篡改
|
||
data['tamper_data'] = self._new_check_tamper(data)
|
||
data['is_max'] = False
|
||
data = self._get_bt_sync_status(data)
|
||
return public.return_message(0, 0, data)
|
||
|
||
|
||
def get_file_ps_list(self):
|
||
pss = {
|
||
'/www/server/data': 'This is the default data directory for MySQL databases. Do not delete!',
|
||
'/www/server/mysql': 'MySQL program directory',
|
||
'/www/server/redis': 'Redis program directory',
|
||
'/www/server/mongodb': 'MongoDB program directory',
|
||
'/www/server/nvm': 'PM2/NVM/NPM program directory',
|
||
'/www/server/pass': 'Directory for storing website BasicAuth authentication passwords',
|
||
'/www/server/speed': 'Website acceleration data directory',
|
||
'/www/server/docker': 'Docker plugin program and data directory',
|
||
'/www/server/total': 'Website monitoring report data directory',
|
||
'/www/server/btwaf': 'WAF firewall data directory',
|
||
'/www/server/pure-ftpd': 'FTP program directory',
|
||
'/www/server/phpmyadmin': 'phpMyAdmin program directory',
|
||
'/www/server/rar': 'RAR extension library directory. Deleting this will remove support for RAR compressed files.',
|
||
'/www/server/stop': 'Website disable page directory. Do not delete!',
|
||
'/www/server/nginx': 'Nginx program directory',
|
||
'/www/server/apache': 'Apache program directory',
|
||
'/www/server/cron': 'Cron job scripts and logs directory',
|
||
'/www/server/php': 'PHP directory. All PHP version interpreters are located here.',
|
||
'/www/server/tomcat': 'Tomcat program directory',
|
||
'/www/php_session': 'PHP-SESSION isolation directory',
|
||
'/proc': 'System process directory',
|
||
'/dev': 'System device directory',
|
||
'/sys': 'System call directory',
|
||
'/tmp': 'System temporary files directory',
|
||
'/var/log': 'System log directory',
|
||
'/var/run': 'System runtime log directory',
|
||
'/var/spool': 'System queue directory',
|
||
'/var/lock': 'System lock directory',
|
||
'/var/mail': 'System mail directory',
|
||
'/mnt': 'System mount directory',
|
||
'/media': 'System multimedia directory',
|
||
'/dev/shm': 'System shared memory directory',
|
||
'/lib': 'System dynamic library directory',
|
||
'/lib64': 'System dynamic library directory',
|
||
'/lib32': 'System dynamic library directory',
|
||
'/usr/lib': 'System dynamic library directory',
|
||
'/usr/lib64': 'System dynamic library directory',
|
||
'/usr/local/lib': 'System dynamic library directory',
|
||
'/usr/local/lib64': 'System dynamic library directory',
|
||
'/usr/local/libexec': 'System dynamic library directory',
|
||
'/usr/local/sbin': 'System script directory',
|
||
'/usr/local/bin': 'System script directory',
|
||
'/www/reserve_space.pl': 'Panel reserved disk space file. Can be deleted.'
|
||
}
|
||
recycle_list = public.get_recycle_bin_list()
|
||
recycle_list = {i: "PS: Recycle Bin directory" for i in recycle_list}
|
||
pss.update(recycle_list)
|
||
return pss
|
||
|
||
|
||
def SearchFilesNew(self, get):
|
||
if not hasattr(get, 'path'):
|
||
get.path = public.get_site_path()
|
||
if sys.version_info[0] == 2:
|
||
get.path = get.path.encode('utf-8')
|
||
if not os.path.exists(get.path):
|
||
get.path = '/www'
|
||
search = ""
|
||
if hasattr(get, 'search'):
|
||
search = get.search.strip().lower()
|
||
my_dirs = []
|
||
my_files = []
|
||
count = 0
|
||
max = 3000
|
||
is_max = False
|
||
for d_list in os.walk(get.path):
|
||
if count >= max:
|
||
is_max = True
|
||
break
|
||
for d in d_list[1]:
|
||
if count >= max:
|
||
break
|
||
sn = d
|
||
d = self.xssencode(d)
|
||
if d.lower().find(search) != -1:
|
||
filename = '{}/{}'.format(d_list[0] if d_list[0] != '/' else '', d)
|
||
if not os.path.exists(filename):
|
||
continue
|
||
my_dirs.append(self.__get_stat(filename, get.path, sn=sn))
|
||
count += 1
|
||
|
||
for f in d_list[2]:
|
||
if count >= max:
|
||
break
|
||
sn = f
|
||
f = self.xssencode(f)
|
||
if f.lower().find(search) != -1:
|
||
filename = '{}/{}'.format(d_list[0] if d_list[0] != '/' else '', f)
|
||
if not os.path.exists(filename):
|
||
continue
|
||
my_files.append(self.__get_stat(filename, get.path, sn=sn))
|
||
count += 1
|
||
data = {}
|
||
# data['DIR'] = sorted(my_dirs)
|
||
# data['FILES'] = sorted(my_files)
|
||
sort = 'nm'
|
||
reverse = False
|
||
if 'sort' in get and get.sort:
|
||
sort = 'nm' if get.sort == 'name' else 'sz' if get.sort == 'size' else 'mt' if get.sort == 'mtime' else 'nm'
|
||
if 'reverse' in get and get.reverse in ('True', 'true', '1', 1):
|
||
reverse = True
|
||
# 先对目录和文件进行排序
|
||
sorted_dirs = sorted(my_dirs, key=lambda file: file[sort], reverse=reverse)
|
||
sorted_files = sorted(my_files, key=lambda file: file[sort], reverse=reverse)
|
||
|
||
# 计算起始和结束位置
|
||
start = (int(get.p) - 1) * int(get.showRow)
|
||
end = start + int(get.showRow)
|
||
|
||
# 如果目录数大于等于结束位置,则按范围提取目录和文件
|
||
if len(sorted_dirs) >= end:
|
||
data['dir'] = sorted_dirs[start:end]
|
||
data['files'] = []
|
||
# 如果起始位置小于目录数但结束位置大于目录数,则提取部分目录和剩余文件
|
||
elif start < len(sorted_dirs) < end:
|
||
data['dir'] = sorted_dirs[start:len(sorted_dirs)]
|
||
data['files'] = sorted_files[:end - len(sorted_dirs)]
|
||
# 否则目录和文件都为空
|
||
else:
|
||
data['dir'] = []
|
||
data['files'] = sorted_files[start:end]
|
||
|
||
# data['dir'] = sorted(my_dirs, key=lambda file: file['nm'], reverse=False)
|
||
# data['files'] = sorted(my_files, key=lambda file: file['nm'], reverse=False)
|
||
data['path'] = str(get.path)
|
||
data['page'] = public.get_page(
|
||
len(my_dirs) + len(my_files), 1, max, 'GetFiles')['page']
|
||
data['store'] = self.get_files_store(None)
|
||
|
||
data['dir_history'] = public.get_dir_history('files', 'GetDirList')
|
||
data['search_history'] = public.get_search_history('files', 'get_list')
|
||
data['tamper_data'] = self._new_check_tamper(data)
|
||
data['file_recycle'] = os.path.exists('data/recycle_bin.pl')
|
||
data['is_max'] = is_max
|
||
data = self._get_bt_sync_status(data)
|
||
return data
|
||
|
||
# 用于GetDir 防篡改:获取文件是否在保护列表中
|
||
def _new_check_tamper(self, data):
|
||
try:
|
||
import PluginLoader
|
||
except:
|
||
return {}
|
||
args = public.dict_obj()
|
||
args.client_ip = public.GetClientIp()
|
||
args.fun = "check_dir_safe"
|
||
args.s = "check_dir_safe"
|
||
args.file_data = {
|
||
"base_path": data["path"],
|
||
"dirs": [i["sn"] for i in data["dir"]],
|
||
"files": [i["sn"] for i in data["files"]]
|
||
}
|
||
tamper_data = PluginLoader.plugin_run("tamper_core", "check_dir_safe", args)
|
||
return tamper_data
|
||
|
||
# 获取文件同步状态
|
||
@staticmethod
|
||
def _get_bt_sync_status(data):
|
||
config_file = "{}/plugin/rsync/config4.json".format(public.get_panel_path())
|
||
if not os.path.exists(config_file):
|
||
data["bt_sync"] = []
|
||
return data
|
||
try:
|
||
conf = json.loads(public.readFile(config_file))
|
||
except json.JSONDecodeError:
|
||
data["bt_sync"] = []
|
||
return data
|
||
|
||
dirs = []
|
||
for i in data["dir"]:
|
||
dirs.append(data['path'] + "/" + i["sn"])
|
||
|
||
res = [{} for _ in range(len(dirs))]
|
||
for idx, d in enumerate(dirs):
|
||
for value in conf.get("modules", []):
|
||
if value.get("path", "").rstrip("/") == d:
|
||
res[idx] = {
|
||
"type": "modules",
|
||
"name": value.get("name", ""),
|
||
"status": value.get("recv_status", True),
|
||
"path": d,
|
||
}
|
||
|
||
for idx, d in enumerate(dirs):
|
||
for value in conf.get("senders", []):
|
||
if value.get("source", "").rstrip("/") == d:
|
||
target = value.get("target_list", [{}])[0]
|
||
res[idx] = {
|
||
"type": "senders",
|
||
"name": target.get("name", ""),
|
||
"status": target.get("status", True),
|
||
"path": d,
|
||
}
|
||
|
||
data["bt_sync"] = res
|
||
return data
|
||
|
||
# ———————————————————
|
||
# 融合企业级防篡改 |
|
||
# ———————————————————
|
||
|
||
# 防篡改:获取文件是否在保护列表中
|
||
|
||
def _check_tamper(self, data):
|
||
try:
|
||
import PluginLoader
|
||
except:
|
||
return {}
|
||
args = public.dict_obj()
|
||
args.client_ip = public.GetClientIp()
|
||
args.fun = "check_dir_safe"
|
||
args.s = "check_dir_safe"
|
||
args.file_data = {
|
||
"base_path": data['PATH'],
|
||
"dirs": [i.split(";", 1)[0] for i in data["DIR"]],
|
||
"files": [i.split(";", 1)[0] for i in data["FILES"]]
|
||
}
|
||
data["tamper_data"] = PluginLoader.plugin_run("tamper_core", "check_dir_safe", args)
|
||
|
||
return data
|
||
|
||
|
||
|
||
# 获取文件同步状态
|
||
@staticmethod
|
||
def _get_bt_sync_status_old(data):
|
||
config_file = "{}/plugin/rsync/config4.json".format(public.get_panel_path())
|
||
if not os.path.exists(config_file):
|
||
data["bt_sync"] = {}
|
||
return data
|
||
try:
|
||
conf = json.loads(public.readFile(config_file))
|
||
except json.JSONDecodeError:
|
||
data["bt_sync"] = {}
|
||
return data
|
||
|
||
dirs = [data['PATH'] + "/" + i.split(";", 1)[0] for i in data["DIR"]]
|
||
res = [{} for _ in range(len(dirs))]
|
||
for idx, d in enumerate(dirs):
|
||
for value in conf.get("modules", []):
|
||
if value.get("path", "").rstrip("/") == d:
|
||
res[idx] = {
|
||
"type": "modules",
|
||
"name": value.get("name", ""),
|
||
"status": value.get("recv_status", True),
|
||
"path": d,
|
||
}
|
||
|
||
for idx, d in enumerate(dirs):
|
||
for value in conf.get("senders", []):
|
||
if value.get("source", "").rstrip("/") == d:
|
||
target = value.get("target_list", [{}])[0]
|
||
res[idx] = {
|
||
"type": "senders",
|
||
"name": target.get("name", ""),
|
||
"status": target.get("status", True),
|
||
"path": d,
|
||
}
|
||
|
||
data["bt_sync"] = res
|
||
return data
|
||
|
||
|
||
|
||
def get_file_ps(self, filename):
|
||
'''
|
||
@name 获取文件或目录备注
|
||
@author hwliang<2020-10-22>
|
||
@param filename<string> 文件或目录全路径
|
||
@return string
|
||
'''
|
||
|
||
ps_path = public.get_panel_path() + '/data/files_ps'
|
||
try:
|
||
f_key1 = '/'.join((ps_path, public.md5(filename)))
|
||
if os.path.exists(f_key1):
|
||
return public.readFile(f_key1)
|
||
|
||
f_key2 = '/'.join((ps_path, public.md5(os.path.basename(filename))))
|
||
if os.path.exists(f_key2):
|
||
return public.readFile(f_key2)
|
||
except:
|
||
pass
|
||
|
||
pss = {
|
||
'/www/server/data': 'MySQL data storage directory!',
|
||
'/www/server/mysql': 'MySQL program directory',
|
||
'/www/server/redis': 'Redis program directory',
|
||
'/www/server/mongodb': 'MongoDB program directory',
|
||
'/www/server/nvm': 'PM2/NVM/NPM program directory',
|
||
'/www/server/pass': 'Website Basic Auth authentication password storage directory',
|
||
'/www/server/speed': 'Website speed plugin directory',
|
||
'/www/server/docker': 'Docker and data directory',
|
||
'/www/server/total': 'Website Statistics Directory',
|
||
'/www/server/btwaf': 'WAF directory',
|
||
'/www/server/pure-ftpd': 'ftp program directory',
|
||
'/www/server/phpmyadmin': 'phpMyAdmin program directory',
|
||
'/www/server/rar': 'rar extension library directory, after deleting, it will lose support for RAR compressed files',
|
||
'/www/server/stop': 'Website disabled page directory, please do not delete!',
|
||
'/www/server/nginx': 'Nginx program directory',
|
||
'/www/server/apache': 'Apache program directory',
|
||
'/www/server/cron': 'Cron script and log directory',
|
||
'/www/server/php': 'All interpreters of PHP versions are in this directory',
|
||
'/www/server/tomcat': 'Tomcat program directory',
|
||
'/www/php_session': 'PHP-SESSION Quarantine directory',
|
||
'/proc': 'System process directory',
|
||
'/dev': 'System Device Catalog',
|
||
'/sys': 'System call directory',
|
||
'/tmp': 'System temporary file directory',
|
||
'/var/log': 'System log directory',
|
||
'/var/run': 'System operation log directory',
|
||
'/var/spool': 'System queue directory',
|
||
'/var/lock': 'System lock directory',
|
||
'/var/mail': 'System mail directory',
|
||
'/mnt': 'System mount directory',
|
||
'/media': 'Multimedia catalog',
|
||
'/dev/shm': 'Shared memory directory',
|
||
'/lib': 'System dynamic library directory',
|
||
'/lib64': 'System dynamic library directory',
|
||
'/lib32': 'System dynamic library directory',
|
||
'/usr/lib': 'System dynamic library directory',
|
||
'/usr/lib64': 'System dynamic library directory',
|
||
'/usr/local/lib': 'System dynamic library directory',
|
||
'/usr/local/lib64': 'System dynamic library directory',
|
||
'/usr/local/libexec': 'System dynamic library directory',
|
||
'/usr/local/sbin': 'System script directory',
|
||
'/usr/local/bin': 'System script directory'
|
||
}
|
||
|
||
if str(filename).endswith(".bt_split_json"):
|
||
return "PS: Split the recovery profile"
|
||
if str(filename).endswith(".bt_split"):
|
||
return "PS: Split unit file"
|
||
if filename in pss: return "PS:" + pss[filename]
|
||
try:
|
||
if not self.recycle_list: self.recycle_list = public.get_recycle_bin_list()
|
||
except:
|
||
pass
|
||
if filename + '/' in self.recycle_list:'PS: Recycle Bin Directory'
|
||
if filename in self.recycle_list: return 'PS: Recycle Bin Directory'
|
||
return ''
|
||
|
||
def set_file_ps(self, args):
|
||
'''
|
||
@name 设置文件或目录备注
|
||
@author hwliang<2020-10-22>
|
||
@param filename<string> 文件或目录全路径
|
||
@param ps_type<int> 备注类型 0.完整路径 1.文件名称
|
||
@param ps_body<string> 备注内容
|
||
@return dict
|
||
'''
|
||
filename = args.filename.strip()
|
||
ps_type = int(args.ps_type)
|
||
ps_body = public.xssencode2(args.ps_body)
|
||
ps_path = public.get_panel_path() + '/data/files_ps'
|
||
if not os.path.exists(ps_path):
|
||
os.makedirs(ps_path, 384, True)
|
||
if ps_type == 1:
|
||
f_name = os.path.basename(filename)
|
||
else:
|
||
f_name = filename
|
||
ps_key = public.md5(f_name)
|
||
|
||
f_key = '/'.join((ps_path, ps_key))
|
||
if ps_body:
|
||
public.writeFile(f_key, ps_body)
|
||
public.write_log_gettext('File manager', 'Set the file name [{}], notes: {}', (f_name, ps_body))
|
||
else:
|
||
if os.path.exists(f_key):
|
||
public.write_log_gettext('File manager', 'Clear file notes [{}]', (f_name))
|
||
os.remove(f_key)
|
||
return public.return_message(0, 0, public.lang("Setup successfully!"))
|
||
|
||
def check_file_sort(self, sort):
|
||
"""
|
||
@校验排序字段
|
||
"""
|
||
slist = ['name', 'size', 'mtime', 'accept', 'user']
|
||
if sort in slist: return sort
|
||
return 'name'
|
||
|
||
def __list_dir(self, path, my_sort='name', reverse=False):
|
||
'''
|
||
@name 获取文件列表,并排序
|
||
@author hwliang<2020-08-01>
|
||
@param path<string> 路径
|
||
@param my_sort<string> 排序字段
|
||
@param reverse<bool> 是否降序
|
||
@param list
|
||
'''
|
||
if not os.path.exists(path):
|
||
return []
|
||
py_v = sys.version_info[0]
|
||
tmp_files = []
|
||
|
||
for f_name in os.listdir(path):
|
||
try:
|
||
if py_v == 2:
|
||
f_name = f_name.encode('utf-8')
|
||
else:
|
||
f_name.encode('utf-8')
|
||
|
||
# 使用.join拼接效率更高
|
||
filename = "/".join((path, f_name))
|
||
sort_key = 1
|
||
sort_val = None
|
||
if not os.path.islink(filename):
|
||
# 此处直接做异常处理比先判断文件是否存在更高效
|
||
if my_sort == 'name':
|
||
sort_key = 0
|
||
elif my_sort == 'size':
|
||
sort_val = os.stat(filename).st_size
|
||
elif my_sort == 'mtime':
|
||
sort_val = os.stat(filename).st_mtime
|
||
elif my_sort == 'accept':
|
||
sort_val = os.stat(filename).st_mode
|
||
elif my_sort == 'user':
|
||
sort_val = os.stat(filename).st_uid
|
||
except Exception as err:
|
||
continue
|
||
# 使用list[tuple]排序效率更高
|
||
tmp_files.append((f_name, sort_val))
|
||
try:
|
||
tmp_files = sorted(tmp_files, key=lambda x: x[sort_key], reverse=reverse)
|
||
except:
|
||
pass
|
||
return tmp_files
|
||
|
||
def __format_stat_old(self, filename, path):
|
||
try:
|
||
stat = self.__get_stat_old(filename, path)
|
||
if not stat:
|
||
return None
|
||
tmp_stat = stat.split(';')
|
||
file_info = {'name': self.xssencode(tmp_stat[0].replace('/', '')), 'size': int(tmp_stat[1]), 'mtime': int(
|
||
tmp_stat[2]), 'accept': int(tmp_stat[3]), 'user': tmp_stat[4], 'link': tmp_stat[5]}
|
||
return file_info
|
||
except:
|
||
return None
|
||
def __format_stat(self, filename, path):
|
||
try:
|
||
stat = self.__get_stat(filename, path)
|
||
if not stat:
|
||
return None
|
||
tmp_stat = stat.split(';')
|
||
file_info = {
|
||
'name': self.xssencode(tmp_stat[0].replace('/', '')), 'size': int(tmp_stat[1]), 'mtime': int(
|
||
tmp_stat[2]), 'accept': tmp_stat[3], 'user': tmp_stat[4], 'link': tmp_stat[5]
|
||
}
|
||
return file_info
|
||
except:
|
||
return None
|
||
|
||
def SearchFiles(self, get):
|
||
if not hasattr(get, 'path'):
|
||
get.path = public.get_site_path()
|
||
if sys.version_info[0] == 2:
|
||
get.path = get.path.encode('utf-8')
|
||
if not os.path.exists(get.path):
|
||
get.path = '/www'
|
||
search = get.search.strip().lower()
|
||
my_dirs = []
|
||
my_files = []
|
||
count = 0
|
||
max = 3000
|
||
for d_list in os.walk(get.path):
|
||
if count >= max:
|
||
break
|
||
for d in d_list[1]:
|
||
if count >= max:
|
||
break
|
||
d = self.xssencode(d)
|
||
if d.lower().find(search) != -1:
|
||
filename = d_list[0] + '/' + d
|
||
if not os.path.exists(filename):
|
||
continue
|
||
my_dirs.append(self.__get_stat_old(filename, get.path))
|
||
count += 1
|
||
|
||
for f in d_list[2]:
|
||
if count >= max:
|
||
break
|
||
f = self.xssencode(f)
|
||
if f.lower().find(search) != -1:
|
||
filename = d_list[0] + '/' + f
|
||
if not os.path.exists(filename):
|
||
continue
|
||
my_files.append(self.__get_stat_old(filename, get.path))
|
||
count += 1
|
||
data = {}
|
||
data['DIR'] = sorted(my_dirs)
|
||
data['FILES'] = sorted(my_files)
|
||
data['PATH'] = str(get.path)
|
||
data['PAGE'] = public.get_page(
|
||
len(my_dirs) + len(my_files), 1, max, 'GetFiles')['page']
|
||
data['STORE'] = self.get_files_store(None)
|
||
return data
|
||
|
||
def __get_stat_old(self, filename, path=None, sn=None):
|
||
if os.path.islink(filename) and not os.path.exists(filename):
|
||
accept = "0"
|
||
mtime = "0"
|
||
user = "0"
|
||
size = "0"
|
||
else:
|
||
stat = os.stat(filename)
|
||
accept = str(oct(stat.st_mode)[-3:])
|
||
mtime = str(int(stat.st_mtime))
|
||
user = ''
|
||
try:
|
||
user = pwd.getpwuid(stat.st_uid).pw_name
|
||
except:
|
||
user = str(stat.st_uid)
|
||
size = str(stat.st_size)
|
||
link = ''
|
||
down_url = self.get_download_id(filename)
|
||
if os.path.islink(filename):
|
||
link = ' -> ' + os.readlink(filename)
|
||
tmp_path = (path + '/').replace('//', '/')
|
||
if path and tmp_path != '/':
|
||
filename = filename.replace(tmp_path, '', 1)
|
||
favorite = self.__check_favorite(filename, self.get_files_store(None))
|
||
return filename + ';' + size + ';' + mtime + ';' + accept + ';' + user + ';' + link + ';' + down_url + ';' + \
|
||
self.is_composer_json(filename) + ';' + favorite + ';' + self.__check_share(filename)
|
||
|
||
def __get_stat(self, filename, path=None, sn=None):
|
||
if os.path.islink(filename) and not os.path.exists(filename):
|
||
accept = "0"
|
||
mtime = "0"
|
||
user = "0"
|
||
size = "0"
|
||
else:
|
||
stat = os.stat(filename)
|
||
accept = str(oct(stat.st_mode)[-3:])
|
||
mtime = str(int(stat.st_mtime))
|
||
user = ''
|
||
try:
|
||
user = pwd.getpwuid(stat.st_uid).pw_name
|
||
except:
|
||
user = str(stat.st_uid)
|
||
size = str(stat.st_size)
|
||
link = ''
|
||
down_url = self.get_download_id(filename)
|
||
if os.path.islink(filename):
|
||
link = ' -> ' + os.readlink(filename)
|
||
tmp_path = (path + '/').replace('//', '/')
|
||
if path and tmp_path != '/':
|
||
filename = filename.replace(tmp_path, '', 1)
|
||
favorite = self.__check_favorite(filename, self.get_files_store(None))
|
||
|
||
file_info = {
|
||
'nm': filename, # 文件名
|
||
'sz': int(size), # 文件大小
|
||
'mt': int(mtime), # 修改时间
|
||
'acc': accept, # 权限
|
||
'user': user, # 用户
|
||
'lnk': link, # 链接
|
||
'durl': down_url, # 下载链接
|
||
'cmp': self.is_composer_json(filename), # composer.json
|
||
'fav': favorite, # 收藏
|
||
'share': self.__check_share(filename), # 共享
|
||
'sn': sn or ''
|
||
}
|
||
|
||
return file_info
|
||
|
||
|
||
# 获取指定目录下的所有视频或音频文件
|
||
def get_videos(self, args):
|
||
path = args.path.strip()
|
||
v_data = []
|
||
if not os.path.exists(path): return v_data
|
||
import mimetypes
|
||
for fname in os.listdir(path):
|
||
try:
|
||
filename = os.path.join(path, fname)
|
||
if not os.path.exists(filename): continue
|
||
if not os.path.isfile(filename): continue
|
||
v_tmp = {}
|
||
v_tmp['name'] = fname
|
||
v_tmp['type'] = mimetypes.guess_type(filename)[0]
|
||
v_tmp['size'] = os.path.getsize(filename)
|
||
if not v_tmp['type'].split('/')[0] in ['video']:
|
||
continue
|
||
v_data.append(v_tmp)
|
||
except:
|
||
continue
|
||
return sorted(v_data, key=lambda x: x['name'])
|
||
|
||
# 计算文件数量
|
||
def GetFilesCount(self, path, search):
|
||
if os.path.isfile(path):
|
||
return 1
|
||
if not os.path.exists(path):
|
||
return 0
|
||
i = 0
|
||
try:
|
||
for name in os.listdir(path):
|
||
if search:
|
||
if name.lower().find(search) == -1:
|
||
continue
|
||
i += 1
|
||
except:
|
||
return 0
|
||
return i
|
||
|
||
# 创建文件
|
||
def CreateFile(self, get):
|
||
# 校验磁盘大小
|
||
df_data = public.ExecShell("df -T | grep '/'")[0]
|
||
for data in str(df_data).split("\n"):
|
||
data_list = data.split()
|
||
if not data_list: continue
|
||
use_size = data_list[4]
|
||
size = data_list[5]
|
||
disk_path = data_list[6]
|
||
if int(use_size) < 1024 and str(size).rstrip("%") == "100" and disk_path in ["/", "/www"]:
|
||
return public.return_message(-1, 0, public.lang("File creation failed! The disk is full! please clear the space first!"))
|
||
|
||
if sys.version_info[0] == 2:
|
||
get.path = get.path.encode('utf-8').strip()
|
||
try:
|
||
fname = os.path.basename(get.path).strip()
|
||
fpath = os.path.dirname(get.path).strip()
|
||
get.path = os.path.join(fpath, fname)
|
||
if get.path[-1] == '.':
|
||
return public.return_message(-1, 0, public.lang("It is not recommended to use [ . ] at the end of the file because there may be security risks"))
|
||
if not self.CheckFileName(get.path):
|
||
return public.return_message(-1, 0, public.lang("File names can NOT contain special characters!"))
|
||
if os.path.exists(get.path):
|
||
return public.return_message(-1, 0, public.lang("Requested file exists!"))
|
||
path = os.path.dirname(get.path)
|
||
if not os.path.exists(path):
|
||
os.makedirs(path)
|
||
open(get.path, 'w+').close()
|
||
self.SetFileAccept(get.path)
|
||
public.write_log_gettext('File manager', 'Successfully created file [{}]!', (get.path,))
|
||
return public.return_message(0, 0, public.lang("Successfully created file!"))
|
||
except:
|
||
return public.return_message(-1, 0, public.lang("Failed to create file!"))
|
||
|
||
# 创建软链
|
||
def CreateLink(self, get):
|
||
'''
|
||
@name 创建软链接
|
||
@author hwliang<2021-03-23>
|
||
@param get<dict_obj{
|
||
sfile<string> 源文件
|
||
dfile<string> 软链文件名
|
||
}>
|
||
@return dict
|
||
'''
|
||
if not get.dfile or get.dfile[-1] == "/":
|
||
return public.return_message(-1, 0, public.lang("The specified soft link file name must contain the full path (full path)"))
|
||
if not 'sfile' in get: return public.return_message(-1, 0, public.lang("Parameter ERROR!"))
|
||
if not os.path.exists(get.sfile): return public.return_message(-1, 0, public.lang("Configuration file not exist"))
|
||
if os.path.exists(get.dfile): return public.return_message(-1, 0, public.lang("The specified soft link file name already exists"))
|
||
l_name = os.path.basename(get.dfile)
|
||
if re.match(r"^[\w\-\.]+$", l_name) == None: return public.return_message(-1, 0, public.lang("Link file name is illegal!"))
|
||
if get.dfile[0] != '/': return public.return_message(-1, 0, public.lang("The specified soft link file name must contain the full path (full path)"))
|
||
public.ExecShell("ln -sf {} {}".format(get.sfile, get.dfile))
|
||
if not os.path.exists(get.dfile): return public.return_message(-1, 0, public.lang("Softlink file creation failed"))
|
||
public.write_log_gettext('Firewall manager', 'Create softlink: {} -> {}', (get.dfile, get.sfile))
|
||
return public.return_message(0, 0, public.lang("The softlink file was created successfully"))
|
||
|
||
# 创建目录
|
||
def CreateDir(self, get):
|
||
if sys.version_info[0] == 2:
|
||
get.path = get.path.encode('utf-8').strip()
|
||
try:
|
||
if get.path[-1] == '.':
|
||
return public.return_message(-1, 0, public.lang("It is not recommended to use [ . ] at the end of the directory, because there may be safety risks"))
|
||
if not self.CheckFileName(get.path):
|
||
return public.return_message(-1, 0, public.lang("Directory names cannot contain special characters!"))
|
||
if os.path.exists(get.path):
|
||
return public.return_message(-1, 0, public.lang("Requested directory exists!"))
|
||
os.makedirs(get.path)
|
||
self.SetFileAccept(get.path)
|
||
public.write_log_gettext('File manager', 'Successfully created directory [ {} ]!', (get.path,))
|
||
return public.return_message(0, 0, public.lang("Successfully created directory!"))
|
||
except:
|
||
return public.return_message(-1, 0, public.lang("Failed to create directory!"))
|
||
def CheckDelete(self, path):
|
||
# 系统目录
|
||
system_dir = {
|
||
'/proc': 'System process directory',
|
||
'/dev': 'System Device Catalog',
|
||
'/sys': 'System call directory',
|
||
'/tmp': 'System temporary file directory',
|
||
'/var/log': 'System log directory',
|
||
'/var/run': 'System operation log directory',
|
||
'/var/spool': 'System queue directory',
|
||
'/var/lock': 'System lock directory',
|
||
'/var/mail': 'System mail directory',
|
||
'/mnt': 'System mount directory',
|
||
'/media': 'Multimedia catalog',
|
||
'/dev/shm': 'Shared memory directory',
|
||
'/lib': 'System dynamic library directory',
|
||
'/lib64': 'System dynamic library directory',
|
||
'/lib32': 'System dynamic library directory',
|
||
'/usr/lib': 'System dynamic library directory',
|
||
'/usr/lib64': 'System dynamic library directory',
|
||
'/usr/local/lib': 'System dynamic library directory',
|
||
'/usr/local/lib64': 'System dynamic library directory',
|
||
'/usr/local/libexec': 'System dynamic library directory',
|
||
'/usr/local/sbin': 'System script directory',
|
||
'/usr/local/bin': 'System script directory'
|
||
}
|
||
# 面板系统目录
|
||
bt_system_dir = {
|
||
public.get_panel_path(): 'BT main program directory',
|
||
'/www/server/data': 'MySQL database default data directory',
|
||
'/www/server/mysql': 'MySQL program directory',
|
||
'/www/server/redis': 'Redis program directory',
|
||
'/www/server/mongodb': 'MongoDB program directory',
|
||
'/www/server/nvm': 'PM2/NVM/NPM program directory',
|
||
'/www/server/pass': 'Website Basic Auth authentication password storage directory',
|
||
'/www/server/speed': 'Website acceleration data directory',
|
||
'/www/server/docker': 'Docker plugin program and data directory',
|
||
'/www/server/total': 'Website monitoring report data directory',
|
||
'/www/server/btwaf': 'WAF firewall data directory',
|
||
'/www/server/pure-ftpd': 'ftp program directory',
|
||
'/www/server/phpmyadmin': 'phpMyAdmin program directory',
|
||
'/www/server/rar': 'rar extension library directory, after deleting, it will lose support for RAR compressed files',
|
||
'/www/server/stop': 'Website disabled page directory, please do not delete!',
|
||
'/www/server/nginx': 'Nginx program directory',
|
||
'/www/server/apache': 'Apache program directory',
|
||
'/www/server/cron': 'Scheduled task script and log directory',
|
||
'/www/server/php': 'PHP directory, all PHP version interpreters are in this directory',
|
||
'/www/server/tomcat': 'Tomcat program directory',
|
||
'/www/php_session': 'PHP-SESSION isolation directory',
|
||
}
|
||
# 面板系统目录
|
||
# bt_system_file_type = {
|
||
# '.sh': 'shell 程序',
|
||
# '.py': 'python 程序',
|
||
# '.pl': 'pl',
|
||
# '.html': 'html',
|
||
# }
|
||
if system_dir.get(path):
|
||
return f"this is [{system_dir.get(path)}] do not delete!"
|
||
|
||
msg = bt_system_dir.get(path)
|
||
if msg:
|
||
return f"this is [{msg}] panel will be crash if you delete it, please uninstall it normally!"
|
||
return None
|
||
|
||
# 删除目录
|
||
def DeleteDir(self, get):
|
||
if sys.version_info[0] == 2:
|
||
get.path = get.path.encode('utf-8')
|
||
if os.path.basename(get.path) in ['Recycle_bin', '.Recycle_bin']:
|
||
return public.return_message(-1, 0, public.lang("Recovery failed!"))
|
||
if not os.path.exists(get.path) and not os.path.islink(get.path):
|
||
return public.return_message(-1, 0, public.lang("Requested directory does not exist"))
|
||
|
||
# 检查是否敏感目录
|
||
if not self.CheckDir(get.path):
|
||
return public.return_message(-1, 0, public.lang("Editing this directory may cause service exceptions!"))
|
||
# 检查关键目录
|
||
msg = self.CheckDelete(get.path)
|
||
if msg is not None:
|
||
return public.return_message(-1, 0, msg)
|
||
try:
|
||
# 检查是否存在.user.ini
|
||
# if os.path.exists(get.path+'/.user.ini'):
|
||
# public.ExecShell("chattr -i '"+get.path+"/.user.ini'")
|
||
public.ExecShell("chattr -R -i " + get.path)
|
||
if hasattr(get, 'empty'):
|
||
if not self.delete_empty(get.path):
|
||
return public.return_message(-1, 0, public.lang("Cannot delete non-empty directory!"))
|
||
|
||
if os.path.exists('data/recycle_bin.pl') and session.get('debug') != 1:
|
||
if self.Mv_Recycle_bin(get):
|
||
self.site_path_safe(get)
|
||
self.remove_file_ps(get)
|
||
public.add_security_logs("Del dir", "Delete directory: " + get.path)
|
||
return public.return_message(0, 0, public.lang("Directory moved to recycle bin!"))
|
||
public.add_security_logs("Del dir", "Delete directory: " + get.path)
|
||
if os.path.islink(get.path):
|
||
os.remove(get.path)
|
||
else:
|
||
import shutil
|
||
shutil.rmtree(get.path)
|
||
self.site_path_safe(get)
|
||
|
||
public.write_log_gettext('File manager', 'Successfully deleted directory [{}]!', (get.path,))
|
||
self.remove_file_ps(get)
|
||
return public.return_message(0, 0, public.lang(" Successfully deleted directory!"))
|
||
except:
|
||
return public.return_message(-1, 0, public.lang("Failed to delete directory!"))
|
||
|
||
# 删除 空目录
|
||
def delete_empty(self, path):
|
||
if sys.version_info[0] == 2:
|
||
path = path.encode('utf-8')
|
||
if len(os.listdir(path)) > 0:
|
||
return False
|
||
return True
|
||
|
||
# 删除文件
|
||
def DeleteFile(self, get):
|
||
# 校验参数
|
||
try:
|
||
get.validate([
|
||
Param('path').String(),
|
||
|
||
], [
|
||
public.validate.trim_filter(),
|
||
])
|
||
except Exception as ex:
|
||
public.print_log("error info: {}".format(ex))
|
||
return public.return_message(-1, 0, str(ex))
|
||
|
||
if sys.version_info[0] == 2:
|
||
get.path = get.path.encode('utf-8')
|
||
if not os.path.exists(get.path) and not os.path.islink(get.path):
|
||
try:
|
||
public.M("backup").where("filename=?", get.path).delete()
|
||
except Exception:
|
||
pass
|
||
return public.return_message(-1, 0, public.lang("Configuration file not exist"))
|
||
# 检查关键文件
|
||
msg = self.CheckDelete(get.path)
|
||
if msg is not None:
|
||
return public.return_message(-1, 0, msg)
|
||
# 检查是否为.user.ini
|
||
if get.path.find('.user.ini') != -1:
|
||
public.ExecShell("chattr -i '" + get.path + "'")
|
||
try:
|
||
if os.path.exists('data/recycle_bin.pl') and session.get('debug') != 1:
|
||
if self.Mv_Recycle_bin(get):
|
||
self.site_path_safe(get)
|
||
self.remove_file_ps(get)
|
||
# 删除数据库中的数据
|
||
try:
|
||
public.M("backup").where("filename=?", get.path).delete()
|
||
except Exception:
|
||
pass
|
||
public.add_security_logs("Del file", "Delete file: " + get.path)
|
||
return public.return_message(0, 0, public.lang("File moved to recycle bin"))
|
||
|
||
public.WriteLog('File manager', 'Successfully permanent deleted file: [{}]!'.format(get.path))
|
||
os.remove(get.path)
|
||
self.site_path_safe(get)
|
||
public.add_security_logs("Del file", "Delete file: " + get.path)
|
||
self.remove_file_ps(get)
|
||
return public.return_message(0, 0, public.lang("Successfully deleted file"))
|
||
except:
|
||
return public.return_message(-1, 0, public.lang("Failed to delete file!"))
|
||
|
||
def remove_file_ps(self, get):
|
||
'''
|
||
@name 删除文件或目录的备注信息
|
||
'''
|
||
get.filename = get.path
|
||
get.ps_body = ''
|
||
get.ps_type = '0'
|
||
self.set_file_ps(get)
|
||
|
||
# 移动到回收站
|
||
def Mv_Recycle_bin(self, get):
|
||
if not os.path.islink(get.path):
|
||
get.path = os.path.realpath(get.path)
|
||
rPath = public.get_recycle_bin_path(get.path)
|
||
rFile = os.path.join(rPath, get.path.replace('/', '_bt_') + '_t_' + str(time.time()))
|
||
try:
|
||
import shutil
|
||
shutil.move(get.path, rFile)
|
||
public.write_log_gettext('File manager', 'Successfully moved file [{}] to recycle bin!', (get.path,))
|
||
return True
|
||
except:
|
||
public.write_log_gettext(
|
||
'File manager', 'Failed to move file [{}] to recycle bin!', (get.path,))
|
||
return False
|
||
|
||
# 从回收站恢复
|
||
def Re_Recycle_bin(self, get):
|
||
if sys.version_info[0] == 2:
|
||
get.path = get.path.encode('utf-8')
|
||
get.path = public.html_decode(get.path).replace(';', '')
|
||
dFile = get.path.replace('_bt_', '/').split('_t_')[0]
|
||
|
||
# 检查所在回收站目录
|
||
recycle_bin_list = public.get_recycle_bin_list()
|
||
_ok = False
|
||
for r_path in recycle_bin_list:
|
||
for r_file in os.listdir(r_path):
|
||
if get.path == r_file:
|
||
_ok = True
|
||
rPath = r_path
|
||
get.path = os.path.join(rPath, get.path)
|
||
break
|
||
if _ok: break
|
||
|
||
if dFile.find('BTDB_') != -1:
|
||
import database_v2
|
||
return database_v2.database().RecycleDB(get.path)
|
||
try:
|
||
import shutil
|
||
if os.path.isdir(get.path) and os.path.exists(dFile):
|
||
shutil.move(dFile, dFile + "_{}.bak".format(public.format_date("%Y%m%d%H%M%S")))
|
||
shutil.move(get.path, dFile)
|
||
public.write_log_gettext('File manager', 'Successfully recovered [{}] from recycle bin!', (dFile,))
|
||
return public.return_message(0, 0, public.lang("Recovery succeeded!"))
|
||
except:
|
||
public.write_log_gettext('File manager', 'Failed to recover [{}] from recycle bin!', (dFile,))
|
||
return public.return_message(-1, 0, public.lang("Recovery failed!"))
|
||
|
||
# 获取回收站信息
|
||
def Get_Recycle_bin(self, get):
|
||
data = {}
|
||
data['dirs'] = []
|
||
data['files'] = []
|
||
data['status'] = os.path.exists('data/recycle_bin.pl')
|
||
data['status_db'] = os.path.exists('data/recycle_bin_db.pl')
|
||
recycle_bin_list = public.get_recycle_bin_list()
|
||
for rPath in recycle_bin_list:
|
||
if not os.path.exists(rPath): continue
|
||
for file in os.listdir(rPath):
|
||
try:
|
||
tmp = {}
|
||
fname = os.path.join(rPath, file)
|
||
if sys.version_info[0] == 2:
|
||
fname = fname.encode('utf-8')
|
||
else:
|
||
fname.encode('utf-8')
|
||
tmp1 = file.split('_bt_')
|
||
tmp2 = tmp1[len(tmp1) - 1].split('_t_')
|
||
file = self.xssencode(file)
|
||
tmp['rname'] = file
|
||
tmp['dname'] = file.replace('_bt_', '/').split('_t_')[0]
|
||
if tmp['dname'].find('@') != -1:
|
||
tmp['dname'] = "BTDB_" + tmp['dname'][5:].replace('@', "\\u").encode().decode("unicode_escape")
|
||
tmp['name'] = tmp2[0]
|
||
tmp['time'] = int(float(tmp2[1]))
|
||
if os.path.islink(fname):
|
||
filePath = os.readlink(fname)
|
||
if os.path.exists(filePath):
|
||
tmp['size'] = os.path.getsize(filePath)
|
||
else:
|
||
tmp['size'] = 0
|
||
else:
|
||
tmp['size'] = os.path.getsize(fname)
|
||
if os.path.isdir(fname):
|
||
if file[:5] == 'BTDB_':
|
||
tmp['size'] = public.get_path_size(fname)
|
||
data['dirs'].append(tmp)
|
||
else:
|
||
data['files'].append(tmp)
|
||
except:
|
||
continue
|
||
|
||
data['dirs'] = sorted(data['dirs'], key=lambda x: x['time'], reverse=True)
|
||
data['files'] = sorted(data['files'], key=lambda x: x['time'], reverse=True)
|
||
# return data
|
||
return public.return_message(0, 0, data)
|
||
|
||
# 彻底删除
|
||
def Del_Recycle_bin(self, get):
|
||
if sys.version_info[0] == 2:
|
||
get.path = get.path.encode('utf-8')
|
||
|
||
get.path = public.html_decode(get.path).replace(';', '')
|
||
|
||
dFile = get.path.split('_t_')[0]
|
||
# 检查所在回收站目录
|
||
recycle_bin_list = public.get_recycle_bin_list()
|
||
_ok = False
|
||
for r_path in recycle_bin_list:
|
||
for r_file in os.listdir(r_path):
|
||
if get.path == r_file:
|
||
_ok = True
|
||
rPath = r_path
|
||
filename = os.path.join(rPath, get.path)
|
||
break
|
||
if _ok:
|
||
break
|
||
|
||
tfile = get.path.replace('_bt_', '/').split('_t_')[0]
|
||
|
||
if not _ok:
|
||
return public.return_message(-1, 0, 'Error deleting file : {}', tfile)
|
||
|
||
if dFile.find('BTDB_') != -1:
|
||
import database_v2
|
||
return database_v2.database().DeleteTo(filename)
|
||
if not self.CheckDir(filename):
|
||
return public.return_message(-1, 0, public.lang("Never trouble troubles till troubles trouble you!"))
|
||
|
||
public.ExecShell('chattr -R -i ' + filename)
|
||
if os.path.isdir(filename):
|
||
import shutil
|
||
try:
|
||
shutil.rmtree(filename)
|
||
except:
|
||
public.ExecShell('chattr -R -a ' + filename)
|
||
public.ExecShell("rm -rf " + filename)
|
||
else:
|
||
try:
|
||
os.remove(filename)
|
||
except:
|
||
public.ExecShell("rm -f " + filename)
|
||
public.WriteLog('File manager', 'Delete the files {} in the Recycle Bin.'.format(tfile))
|
||
return public.return_message(0, 0, public.lang('Parmanently deleted {} from recycle bin!', tfile))
|
||
|
||
# 清空回收站
|
||
def Close_Recycle_bin(self, get):
|
||
|
||
import database
|
||
import shutil
|
||
|
||
recycle_bin_list = public.get_recycle_bin_list()
|
||
for rPath in recycle_bin_list:
|
||
public.ExecShell('chattr -R -i ' + rPath)
|
||
rlist = os.listdir(rPath)
|
||
i = 0
|
||
l = len(rlist)
|
||
for name in rlist:
|
||
i += 1
|
||
path = os.path.join(rPath, name)
|
||
public.writeSpeed(name, i, l)
|
||
if name.find('BTDB_') != -1:
|
||
database.database().DeleteTo(path)
|
||
continue
|
||
if os.path.isdir(path):
|
||
try:
|
||
shutil.rmtree(path)
|
||
except:
|
||
public.ExecShell('chattr -R -a ' + path)
|
||
public.ExecShell('rm -rf ' + path)
|
||
else:
|
||
try:
|
||
os.remove(path)
|
||
except:
|
||
public.ExecShell('rm -f ' + path)
|
||
|
||
public.writeSpeed(None, 0, 0)
|
||
public.write_log_gettext('File manager', 'Recycle bin emptied!')
|
||
return public.return_message(0, 0, public.lang("Recycle bin emptied!"))
|
||
|
||
# 回收站开关
|
||
def Recycle_bin(self, get):
|
||
c = 'data/recycle_bin.pl'
|
||
if hasattr(get, 'db'):
|
||
c = 'data/recycle_bin_db.pl'
|
||
if os.path.exists(c):
|
||
os.remove(c)
|
||
public.write_log_gettext('File manager', 'Recycle bin feature turned off!')
|
||
return public.return_message(0, 0, public.lang("Recycle bin feature turned off!"))
|
||
else:
|
||
public.writeFile(c, 'True')
|
||
public.write_log_gettext('File manager', 'Recycle bin feature turned on!')
|
||
return public.return_message(0, 0, public.lang("Recycle bin feature turned on!"))
|
||
|
||
# 复制文件
|
||
def CopyFile(self, get):
|
||
if sys.version_info[0] == 2:
|
||
get.sfile = get.sfile.encode('utf-8')
|
||
get.dfile = get.dfile.encode('utf-8')
|
||
if get.dfile[-1] == '.':
|
||
return public.return_message(-1, 0, public.lang("It is not recommended to use [.] at the end of the file because there may be security risks"))
|
||
if not os.path.exists(get.sfile):
|
||
return public.return_message(-1, 0, public.lang("Configuration file not exist"))
|
||
|
||
# if os.path.exists(get.dfile):
|
||
# return public.return_message(-1, 0, public.lang("Requested file exists!"))
|
||
|
||
if os.path.isdir(get.sfile):
|
||
return self.CopyDir(get)
|
||
|
||
import shutil
|
||
try:
|
||
shutil.copyfile(get.sfile, get.dfile)
|
||
public.write_log_gettext('File manager', 'Successfully copied file [{}] to [{}]!',
|
||
(get.sfile, get.dfile))
|
||
stat = os.stat(get.sfile)
|
||
os.chmod(get.dfile, stat.st_mode)
|
||
os.chown(get.dfile, stat.st_uid, stat.st_gid)
|
||
return public.return_message(0, 0, public.lang("Successfully copied file!"))
|
||
except shutil.SameFileError:
|
||
return public.return_message(0, 0, public.lang("Successfully copied file!"))
|
||
except PermissionError:
|
||
return public.return_message(-1, 0, public.lang("If the folder fails to be copied, check whether the directory is locked or tamper-proof is turned on"))
|
||
except OSError as e:
|
||
if 'Read-only' in str(e):
|
||
return public.return_message(-1, 0, public.lang("The current directory is read-only"))
|
||
else:
|
||
return public.return_message(-1, 0, public.lang("Folder copy failed:{}", str(e)))
|
||
except:
|
||
return public.return_message(-1, 0, public.lang("Failed to copy file!"))
|
||
|
||
# 复制文件夹
|
||
def CopyDir(self, get):
|
||
if sys.version_info[0] == 2:
|
||
get.sfile = get.sfile.encode('utf-8')
|
||
get.dfile = get.dfile.encode('utf-8')
|
||
if get.dfile[-1] == '.':
|
||
return public.return_message(-1, 0, public.lang("It is not recommended to use [.] at the end of the directory, because there may be safety risks"))
|
||
if not os.path.exists(get.sfile):
|
||
return public.return_message(-1, 0, public.lang("Requested directory does not exist"))
|
||
|
||
# if os.path.exists(get.dfile):
|
||
# return public.return_message(-1, 0, public.lang("Requested directory exists!"))
|
||
|
||
# if not self.CheckDir(get.dfile):
|
||
# return public.return_message(-1, 0, public.lang("Never trouble troubles till troubles trouble you!"))
|
||
|
||
try:
|
||
self.copytree(get.sfile, get.dfile)
|
||
stat = os.stat(get.sfile)
|
||
os.chmod(get.dfile, stat.st_mode)
|
||
os.chown(get.dfile, stat.st_uid, stat.st_gid)
|
||
public.write_log_gettext('File manager', 'Successfully copied directory!',
|
||
(get.sfile, get.dfile))
|
||
return public.return_message(0, 0, public.lang("Successfully copied directory!"))
|
||
except PermissionError:
|
||
return public.return_message(-1, 0, public.lang("If the folder fails to be copied, check whether the directory is locked or tamper-proof is turned on"))
|
||
except OSError as e:
|
||
if 'Read-only' in str(e):
|
||
return public.return_message(-1, 0, public.lang("The current directory is read-only"))
|
||
else:
|
||
return public.return_message(-1, 0, public.lang("Folder copy failed:{}", str(e)))
|
||
except:
|
||
return public.return_message(-1, 0, public.lang("Failed to copy directory!"))
|
||
|
||
# 移动文件或目录
|
||
def MvFile(self, get):
|
||
if sys.version_info[0] == 2:
|
||
get.sfile = get.sfile.encode('utf-8')
|
||
get.dfile = get.dfile.encode('utf-8')
|
||
if get.dfile[-1] == '.':
|
||
return public.return_message(-1, 0, public.lang("It is not recommended to use [.] at the end of the file because there may be security risks"))
|
||
if not self.CheckFileName(get.dfile):
|
||
return public.return_message(-1, 0, public.lang("File names can NOT contain special characters!"))
|
||
if os.path.basename(get.sfile) == '.Recycle_bin':
|
||
return public.return_message(-1, 0, public.lang("Recovery failed!"))
|
||
if not os.path.exists(get.sfile):
|
||
return public.return_message(-1, 0, public.lang("Configuration file not exist"))
|
||
|
||
if hasattr(get, 'rename'):
|
||
if os.path.exists(get.dfile):
|
||
return public.return_message(-1, 0, public.lang("The target file name already exists!"))
|
||
|
||
if get.dfile[-1] == '/':
|
||
get.dfile = get.dfile[:-1]
|
||
|
||
if get.dfile == get.sfile:
|
||
return public.return_message(-1, 0, public.lang("Meaningless operation"))
|
||
|
||
if not self.CheckDir(get.sfile):
|
||
return public.return_message(-1, 0, public.lang("Never trouble troubles till troubles trouble you!"))
|
||
try:
|
||
self.move(get.sfile, get.dfile)
|
||
self.site_path_safe(get)
|
||
if hasattr(get, 'rename'):
|
||
public.write_log_gettext('File manager', '[{}] renamed to [{}]', (get.sfile, get.dfile))
|
||
return public.return_message(0, 0, public.lang("Successfully renamed!"))
|
||
else:
|
||
public.write_log_gettext('File manager', 'Successfully moved [{}] to [{}]!',
|
||
(get.sfile, get.dfile))
|
||
return public.return_message(0, 0, public.lang("File moved!"))
|
||
except:
|
||
return public.return_message(-1, 0, public.lang("Failed to move file!"))
|
||
|
||
# 检查文件是否存在
|
||
def CheckExistsFiles(self, get):
|
||
if sys.version_info[0] == 2:
|
||
get.dfile = get.dfile.encode('utf-8')
|
||
data = []
|
||
filesx = []
|
||
if not hasattr(get, 'filename'):
|
||
if not 'selected' in session:
|
||
return []
|
||
filesx = json.loads(session['selected']['data'])
|
||
else:
|
||
filesx.append(get.filename)
|
||
|
||
for fn in filesx:
|
||
if fn == '.':
|
||
continue
|
||
filename = get.dfile + '/' + fn
|
||
if os.path.exists(filename):
|
||
tmp = {}
|
||
stat = os.stat(filename)
|
||
tmp['filename'] = fn
|
||
tmp['size'] = os.path.getsize(filename)
|
||
tmp['mtime'] = str(int(stat.st_mtime))
|
||
tmp['is_dir'] = os.path.isdir(filename)
|
||
data.append(tmp)
|
||
return data
|
||
|
||
# 取文件扩展名
|
||
def __get_ext(self, filename):
|
||
tmp = filename.split('.')
|
||
return tmp[-1]
|
||
|
||
# 获取文件内容
|
||
def GetFileBody(self, get):
|
||
if not hasattr(get, "path"):
|
||
return public.return_message(-1, 0, public.lang("Parameters are missing! path"))
|
||
from urllib.parse import unquote
|
||
if sys.version_info[0] == 2:
|
||
get.path = get.path.encode('utf-8')
|
||
get.path = urllib.parse.unquote(get.path)
|
||
get.path = html.escape(get.path)
|
||
get.path = self.xssdecode(get.path)
|
||
|
||
if get.path.find('/rewrite/null/') != -1:
|
||
webserver = public.get_webserver()
|
||
get.path = get.path.replace("/rewrite/null/", "/rewrite/{}/".format(webserver))
|
||
if get.path.find('/vhost/null/') != -1:
|
||
webserver = public.get_webserver()
|
||
get.path = get.path.replace("/vhost/null/", "/vhost/{}/".format(webserver))
|
||
# 普通文件才能访问编辑
|
||
try:
|
||
import stat
|
||
file_stat = os.lstat(get.path)
|
||
if not stat.S_ISREG(file_stat.st_mode):
|
||
return public.return_message(-1, 0, public.lang(
|
||
"Access to non-regular files (e.g., sockets, devices, directories) is not allowed."))
|
||
except (OSError, IOError):
|
||
# 文件不存在或无权限
|
||
pass
|
||
|
||
if not os.path.exists(get.path):
|
||
if get.path.find('rewrite') == -1:
|
||
return public.return_message(-1, 0, public.lang("Configuration file not exist"))
|
||
public.writeFile(get.path, '')
|
||
if self.__get_ext(get.path) in ['gz', 'zip', 'rar', 'exe', 'db', 'pdf', 'doc', 'xls', 'docx', 'xlsx', 'ppt',
|
||
'pptx', '7z', 'bz2', 'png', 'gif', 'jpg', 'jpeg', 'bmp', 'icon', 'ico', 'pyc',
|
||
'class', 'so', 'pyd']:
|
||
return public.return_message(-1, 0, public.lang("The file format does not support online editing!"))
|
||
# if os.path.getsize(get.path) > 3145928:
|
||
# return public.return_message(-1, 0, public.lang("Cannot edit files larger than 2MB online!"))
|
||
if os.path.isdir(get.path):
|
||
return public.return_message(-1, 0, public.lang("Writing verification file failed: {}", get.path))
|
||
if not os.path.exists(get.path):
|
||
return public.return_message(-1, 0, public.lang("The file does not exist: {}", get.path))
|
||
|
||
|
||
# 获取文件状态
|
||
f_stat = os.stat(get.path)
|
||
if stat.S_ISSOCK(f_stat.st_mode):
|
||
return public.return_message(-1, 0, public.lang("Socket files cannot be edited online"))
|
||
# return public.returnMsg(False, "Socket files cannot be edited online")
|
||
|
||
data = {}
|
||
data['status'] = True
|
||
data["only_read"] = False
|
||
data["size"] = os.path.getsize(get.path)
|
||
|
||
req_data = {
|
||
"PATH": os.path.dirname(get.path),
|
||
"DIR": [],
|
||
"FILES": [os.path.basename(get.path)],
|
||
}
|
||
resp = self._check_tamper(req_data)
|
||
|
||
tamper_data = resp.get("tamper_data", {})
|
||
tamper_status = tamper_data.get("files", [])
|
||
close_status = os.path.exists("{}/tamper/close_temp.pl".format(public.get_setup_path()))
|
||
if not close_status and len(tamper_status) != 0:
|
||
if str(tamper_status[0]).startswith("1") and self._check_tamper_white() is False:
|
||
data['msg'] = "The current file is tamper-proof, and editing is not supported!"
|
||
data["only_read"] = True
|
||
# 处理my.cnf为空的情况
|
||
myconf_file = '/etc/my.cnf'
|
||
if get.path == myconf_file:
|
||
if os.path.getsize(myconf_file) < 10:
|
||
mycnf_file_bak = '/etc/my.cnf.bak'
|
||
if os.path.exists(mycnf_file_bak):
|
||
public.writeFile(myconf_file, public.readFile(mycnf_file_bak))
|
||
|
||
data = {}
|
||
data['status'] = True
|
||
data["only_read"] = False
|
||
data["size"] = os.path.getsize(get.path)
|
||
|
||
if data["size"] > 3145928:
|
||
try:
|
||
data["next"] = True
|
||
info_data = ''
|
||
if "mode" in get and "p" in get:
|
||
if get.mode == "reverse":
|
||
info_data = public.GetNumLines(get.path, 1000, int(get.p)).split("\n")
|
||
info_data.reverse()
|
||
info_data = "\n".join(info_data)
|
||
|
||
if info_data == "":
|
||
data["next"] = False
|
||
else:
|
||
info_data = self.last_lines(get.path, 1000)
|
||
data["data"] = info_data
|
||
data["only_read"] = True
|
||
except:
|
||
return public.return_message(-1, 0, public.lang("File encoding is not compatible and cannot be read correctly!"))
|
||
|
||
else:
|
||
try:
|
||
fp = open(get.path, 'rb')
|
||
if fp:
|
||
data['encoding'] = 'utf-8'
|
||
if chardet:
|
||
chardet_data = fp.read(1024)
|
||
res = chardet.detect(chardet_data)
|
||
if res and res['confidence'] > 0.9:
|
||
data['encoding'] = res['encoding']
|
||
fp.seek(0)
|
||
srcBody = fp.read()
|
||
fp.close()
|
||
try:
|
||
data['data'] = srcBody.decode(data['encoding'])
|
||
except:
|
||
try:
|
||
data['encoding'] = 'utf-8'
|
||
data['data'] = srcBody.decode(data['encoding'])
|
||
except:
|
||
try:
|
||
data['encoding'] = 'GBK'
|
||
data['data'] = srcBody.decode(data['encoding'])
|
||
except:
|
||
try:
|
||
data['encoding'] = 'BIG5'
|
||
data['data'] = srcBody.decode(data['encoding'])
|
||
except:
|
||
return public.return_message(-1, 0, public.lang("File encoding is not compatible and cannot be read correctly!"))
|
||
except OSError as e:
|
||
return public.return_message(-1, 0, public.lang(
|
||
"If the file fails to open, the file may be occupied by other processes!"))
|
||
|
||
except Exception as e:
|
||
return public.return_message(-1, 0, public.lang("Failed to open the file: {}", str(e)))
|
||
|
||
|
||
if hasattr(get, 'filename'):
|
||
get.path = get.filename
|
||
|
||
if not os.path.exists(get.path):
|
||
return public.return_message(-1, 0, public.lang("The file does not exist"))
|
||
|
||
data['historys'] = self.get_history(get.path)
|
||
data['auto_save'] = self.get_auto_save(get.path)
|
||
data['st_mtime'] = str(int(os.stat(get.path).st_mtime))
|
||
return_status = -1
|
||
if data['status']:
|
||
return_status = 0
|
||
del data['status']
|
||
return public.return_message(return_status, 0, data)
|
||
|
||
|
||
def last_lines(self, filename, lines=1):
|
||
'''
|
||
@name 获取文件最后几行
|
||
@param filename str 文件名
|
||
@param lines int 行数
|
||
@return str
|
||
'''
|
||
max_len = 512 * lines
|
||
with open(filename, 'rb') as f:
|
||
f.seek(0, 2)
|
||
pos = f.tell()
|
||
max_size = min(pos, max_len)
|
||
f.seek(-max_size,1)
|
||
data = f.read(max_size)
|
||
|
||
return data.decode('utf-8', errors='ignore')
|
||
|
||
# 保存文件
|
||
def SaveFileBody(self, get):
|
||
if not 'path' in get:
|
||
return public.return_message(-1, 0, public.lang("[path] parameter cannot be empty!"))
|
||
if sys.version_info[0] == 2:
|
||
get.path = get.path.encode('utf-8')
|
||
|
||
if get.path.find('/rewrite/null/') != -1:
|
||
webserver = public.get_webserver()
|
||
get.path = get.path.replace("/rewrite/null/", "/rewrite/{}/".format(webserver))
|
||
if get.path.find('/vhost/null/') != -1:
|
||
webserver = public.get_webserver()
|
||
get.path = get.path.replace("/vhost/null/", "/vhost/{}/".format(webserver))
|
||
|
||
if not os.path.exists(get.path):
|
||
if get.path.find('.htaccess') == -1:
|
||
return public.return_message(-1, 0, public.lang("Configuration file not exist"))
|
||
elif os.path.getsize(get.path) > 3145928:
|
||
return public.return_message(-1, 0, public.lang("Files larger than 3MB cannot be edited online!"))
|
||
nginx_conf_path = public.get_vhost_path() + '/nginx/'
|
||
if get.path.find(nginx_conf_path) != -1:
|
||
if get.data.find('#SSL-START') != -1 and get.data.find('#SSL-END') != -1:
|
||
if get.data.find('#error_page 404/404.html;') == -1:
|
||
str1 = public.lang('Failed to save the configuration file')
|
||
str2 = public.lang('Do not modify the 404 rule commented in the SSL config')
|
||
str3 = public.lang('To modify the 404 config, find the following config location')
|
||
|
||
# return public.return_message(-1, 0, public.lang("Failed to save the configuration file:<p style="color:red;">Do not modify the 404 rule commented in the SSL config</p><p>To modify the 404 config, find the following config location:</p><pre>#ERROR-PAGE-START Error page configuration, allowed to be commented</pre>"))
|
||
return public.return_message(-1, 0,'{}:<p style="color:red;">{}</p><p>:</p><pre>#ERROR-PAGE-START Error page configuration, allowed to be commented</pre>'.format(
|
||
str1, str2, str3))
|
||
|
||
if 'st_mtime' in get:
|
||
if not 'force' in get or get['force'] != '1':
|
||
st_mtime = str(int(os.stat(get.path).st_mtime))
|
||
if st_mtime != get['st_mtime']:
|
||
data= {
|
||
"result": 'Failed to save, {} file has been changed, please refresh the content and modify it again.'.format(get.path),
|
||
"status": False }
|
||
return public.return_message(0, 0,data)
|
||
|
||
his_path = '/www/backup/file_history/'
|
||
if get.path.find(his_path) != -1:
|
||
return public.return_message(-1, 0, public.lang("Cannot modify history copy directly!"))
|
||
try:
|
||
if 'base64' in get:
|
||
import base64
|
||
get.data = base64.b64decode(get.data)
|
||
isConf = -1
|
||
skip_conf_check = False
|
||
if "skip_conf_check" in get and get.skip_conf_check in ("1", 1, "true", "True", True):
|
||
skip_conf_check = True
|
||
if not skip_conf_check and (os.path.exists('/etc/init.d/nginx') or os.path.exists('/etc/init.d/httpd')):
|
||
isConf = get.path.find('nginx')
|
||
if isConf == -1:
|
||
isConf = get.path.find('apache')
|
||
if isConf == -1:
|
||
isConf = get.path.find('rewrite')
|
||
if isConf != -1:
|
||
public.ExecShell('\\cp -a ' + get.path + ' /tmp/backup.conf')
|
||
|
||
data = get.data
|
||
if data == 'undefined': return public.return_message(-1, 0, public.lang("Wrong file content, please save again!"))
|
||
userini = False
|
||
if get.path.find('.user.ini') != -1:
|
||
userini = True
|
||
public.ExecShell('chattr -i ' + get.path)
|
||
|
||
if get.path.find('/www/server/cron') != -1:
|
||
try:
|
||
import crontab
|
||
data = crontab.crontab().CheckScript(data)
|
||
except:
|
||
pass
|
||
|
||
if get.encoding == 'ascii' or get.encoding == 'ansi':
|
||
get.encoding = 'utf-8'
|
||
self.save_history(get.path)
|
||
try:
|
||
if sys.version_info[0] == 2:
|
||
data = data.encode(get.encoding, errors='ignore')
|
||
fp = open(get.path, 'w+')
|
||
else:
|
||
|
||
data = data.encode(get.encoding, errors='ignore').decode(get.encoding)
|
||
fp = open(get.path, 'w+', encoding=get.encoding)
|
||
except:
|
||
fp = open(get.path, 'w+')
|
||
data = self.crlf_to_lf(data, get.path)
|
||
fp.write(data)
|
||
fp.close()
|
||
|
||
if isConf != -1:
|
||
isError = public.checkWebConfig(path=get.path)
|
||
if isError != True:
|
||
public.ExecShell('\\cp -a /tmp/backup.conf ' + get.path)
|
||
res = public.return_message(-1, 0, 'The save failed because an error was detected in the modified profile:<br><pre style="color:red;white-space: pre-line;">' + isError + '</pre>')
|
||
res["conf_check"] = 1
|
||
return res
|
||
public.serviceReload()
|
||
|
||
if userini:
|
||
public.ExecShell('chattr +i ' + get.path)
|
||
|
||
public.write_log_gettext('File manager', 'Successfully saved file [{}]!', (get.path,))
|
||
tmp_data = public.return_msg_gettext(True, public.lang('Saved!'))
|
||
data = {'msg': tmp_data['msg']}
|
||
data['historys'] = self.get_history(get.path) # 获取历史记录
|
||
data['st_mtime'] = str(int(os.stat(get.path).st_mtime))
|
||
data['status'] = True
|
||
return public.return_message(0, 0, data)
|
||
except Exception as ex:
|
||
return public.return_message(-1, 0, 'Save ERROR! {}' + str(ex))
|
||
|
||
def crlf_to_lf(self, data, filename):
|
||
'''
|
||
@name 将CRLF转换为LF
|
||
@author hwliang
|
||
@param data 要转换的数据
|
||
@param filename 文件名
|
||
@return string
|
||
'''
|
||
file_ext_name = os.path.splitext(filename)[-1]
|
||
if not file_ext_name:
|
||
if data.find('#!/bin/bash') == 0 or data.find('#!/bin/sh') == 0:
|
||
file_ext_name = '.sh'
|
||
elif data.find('#!/usr/bin/python') == 0 or data.find('import ') != -1:
|
||
file_ext_name = '.py'
|
||
elif data.find('#!/usr/bin/env node') == 0:
|
||
file_ext_name = '.js'
|
||
elif data.find('#!/usr/bin/env php') == 0 or data.find('<?php') != -1:
|
||
file_ext_name = '.php'
|
||
elif data.find('#!/usr/bin/env ruby') == 0:
|
||
file_ext_name = '.rb'
|
||
elif data.find('#!/usr/bin/env perl') == 0:
|
||
file_ext_name = '.pl'
|
||
elif data.find('#!/usr/bin/env lua') == 0 or data.find('require ') != -1:
|
||
file_ext_name = '.lua'
|
||
elif filename.find('/script/') != -1:
|
||
file_ext_name = '.sh'
|
||
elif filename.find('.') == -1:
|
||
file_ext_name = '.sh'
|
||
if not file_ext_name in ['.sh', '.py', '.pl', '.php', '.js', '.css', '.html', '.htm', '.shtml', '.shtm', '.jsp',
|
||
'.asp', '.aspx', '.txt']:
|
||
return data
|
||
|
||
if data.find('\r\n') == -1 or data.find('\r') == -1:
|
||
return data
|
||
return data.replace('\r\n', '\n').replace('\r', '\n')
|
||
|
||
# 保存历史副本
|
||
def save_history(self, filename):
|
||
if os.path.exists(public.get_panel_path() + '/data/not_file_history.pl'):
|
||
return True
|
||
try:
|
||
his_path = '/www/backup/file_history/'
|
||
if filename.find(his_path) != -1:
|
||
return
|
||
save_path = (his_path + filename).replace('//', '/')
|
||
if not os.path.exists(save_path):
|
||
os.makedirs(save_path, 384)
|
||
|
||
his_list = sorted(os.listdir(save_path), reverse=True)
|
||
num = public.readFile('data/history_num.pl')
|
||
if not num:
|
||
num = 100
|
||
else:
|
||
num = int(num)
|
||
d_num = len(his_list)
|
||
is_write = True
|
||
new_file_md5 = public.FileMd5(filename)
|
||
for i in range(d_num):
|
||
rm_file = save_path + '/' + his_list[i]
|
||
if i == 0: # 判断是否和上一份副本相同
|
||
old_file_md5 = public.FileMd5(rm_file)
|
||
if old_file_md5 == new_file_md5:
|
||
is_write = False
|
||
|
||
if i + 1 >= num: # 删除多余的副本
|
||
if os.path.exists(rm_file):
|
||
os.remove(rm_file)
|
||
continue
|
||
# 写入新的副本
|
||
if is_write:
|
||
public.writeFile(
|
||
save_path + '/' + str(int(time.time())), public.readFile(filename, 'rb'), 'wb')
|
||
except:
|
||
pass
|
||
|
||
# 取历史副本
|
||
def get_history(self, filename):
|
||
try:
|
||
save_path = ('/www/backup/file_history/' +
|
||
filename).replace('//', '/')
|
||
if not os.path.exists(save_path):
|
||
return []
|
||
return sorted(os.listdir(save_path), reverse=True)
|
||
except:
|
||
return []
|
||
|
||
# 删除指定副本
|
||
def del_history(self, args):
|
||
if not hasattr(args, "filename"):
|
||
return public.return_message(-1, 0, public.lang("The parameter filename is missing"))
|
||
|
||
if not hasattr(args, "history"):
|
||
return public.return_message(-1, 0, public.lang("Missing parameter history"))
|
||
|
||
if not os.path.exists(args.filename):
|
||
return public.return_message(-1, 0, public.lang("The file does not exist"))
|
||
save_path = ('/www/backup/file_history/' +
|
||
args.filename).replace('//', '/')
|
||
path = save_path + '/' + args.history
|
||
try:
|
||
os.remove(path)
|
||
except PermissionError as e:
|
||
if e.errno == 13:
|
||
return public.return_message(-1, 0, public.lang("There are not enough permissions to manipulate files or directories"))
|
||
return public.return_message(0, 0, public.lang("The old version [{}] was successfully deleted", args.history))
|
||
|
||
|
||
# 读取指定历史副本
|
||
def read_history(self, args):
|
||
if not hasattr(args, "filename"):
|
||
return public.return_message(-1, 0, public.lang("The parameter filename is missing"))
|
||
|
||
if not hasattr(args, "history"):
|
||
return public.return_message(-1, 0, public.lang("Missing parameter history"))
|
||
|
||
if not os.path.exists(args.filename):
|
||
return public.return_message(-1, 0, public.lang('The file does not exist!'))
|
||
save_path = ('/www/backup/file_history/' +
|
||
args.filename).replace('//', '/')
|
||
args.path = save_path + '/' + args.history
|
||
return self.GetFileBody(args)
|
||
|
||
# 恢复指定历史副本
|
||
def re_history(self, args):
|
||
save_path = ('/www/backup/file_history/' +
|
||
args.filename).replace('//', '/')
|
||
args.path = save_path + '/' + args.history
|
||
if not os.path.exists(args.path):
|
||
return public.return_message(-1, 0, public.lang("The specified historical copy does not exist!"))
|
||
import shutil
|
||
if not os.path.exists(args.filename):
|
||
return public.return_message(-1, 0, public.lang("The specified file does not exist!"))
|
||
try:
|
||
shutil.copyfile(args.path, args.filename)
|
||
except PermissionError as e:
|
||
if e.errno == 13:
|
||
return public.return_message(-1, 0, public.lang("Not having enough permissions to manipulate files or directories!"))
|
||
except:
|
||
return public.return_message(-1, 0, public.lang("Restore historical copy failed!"))
|
||
return self.GetFileBody(args)
|
||
|
||
# 自动保存配置
|
||
def auto_save_temp(self, args):
|
||
save_path = '/www/backup/file_auto_save/'
|
||
if not os.path.exists(save_path):
|
||
os.makedirs(save_path, 384)
|
||
filename = save_path + args.filename
|
||
if os.path.exists(filename):
|
||
f_md5 = public.FileMd5(filename)
|
||
s_md5 = public.md5(args.body)
|
||
if f_md5 == s_md5:
|
||
return public.return_message(0, 0, public.lang("Not Edit"))
|
||
public.writeFile(filename, args.body)
|
||
return public.return_message(0, 0, public.lang("Automatically saved successfully!"))
|
||
|
||
# 取上一次自动保存的结果
|
||
def get_auto_save_body(self, args):
|
||
save_path = '/www/backup/file_auto_save/'
|
||
args.path = save_path + args.filename
|
||
return self.GetFileBody(args)
|
||
|
||
# 取自动保存结果
|
||
def get_auto_save(self, filename):
|
||
try:
|
||
save_path = ('/www/backup/file_auto_save/' +
|
||
filename).replace('//', '/')
|
||
if not os.path.exists(save_path):
|
||
return None
|
||
return os.stat(save_path).st_mtime
|
||
except:
|
||
return None
|
||
|
||
def is_max_size(self, path, max_size, max_num=10000, total_size=0, total_num=0):
|
||
'''
|
||
@name 是否超过最大大小
|
||
@path 文件路径
|
||
@max_size 最大大小
|
||
@max_num 最大文件数量
|
||
@return bool
|
||
'''
|
||
if not os.path.exists(path) or not max_size:
|
||
return False, total_size, total_num
|
||
|
||
# 是否为文件?
|
||
if os.path.isfile(path):
|
||
total_size = os.path.getsize(path)
|
||
total_num = 1
|
||
if total_size > max_size:
|
||
return True, total_size, total_num
|
||
return False, total_size, total_num
|
||
|
||
# 是否为目录?
|
||
for root, dirs, files in os.walk(path, topdown=True):
|
||
total_num += len(files)
|
||
total_num += len(dirs)
|
||
# 判断是否超过最大文件数量
|
||
if total_num > max_num:
|
||
return True, total_size, total_num
|
||
|
||
for f in files:
|
||
filename = os.path.normcase(root + os.path.sep + f)
|
||
if not os.path.exists(filename): continue
|
||
if os.path.islink(filename): continue
|
||
total_size += os.path.getsize(filename)
|
||
|
||
# 判断是否超过最大大小
|
||
if total_size > max_size:
|
||
return True, total_size, total_num
|
||
|
||
return False, total_size, total_num
|
||
|
||
# 文件压缩
|
||
def Zip(self, get):
|
||
if not hasattr(get, 'dfile') or not get.dfile.strip():
|
||
return public.return_message(-1, 0, public.lang("The target compressed file cannot be empty!"))
|
||
dir_name = os.path.dirname(get.dfile)
|
||
if dir_name and not os.path.exists(dir_name):
|
||
os.makedirs(dir_name, exist_ok=True)
|
||
|
||
if not 'z_type' in get:
|
||
get.z_type = 'rar'
|
||
|
||
if get.z_type == 'rar':
|
||
if os.uname().machine != 'x86_64':
|
||
return public.return_message(-1, 0, public.lang("RAR component does not support aarch 64 platform"))
|
||
|
||
import panel_task_v2 as panelTask
|
||
task_obj = panelTask.bt_task()
|
||
max_size = 1024 * 1024 * 100
|
||
max_num = 10000
|
||
total_size = 0
|
||
total_num = 0
|
||
status = True
|
||
if not os.path.exists(os.path.dirname(get.dfile)):
|
||
os.makedirs(os.path.dirname(get.dfile))
|
||
for file_name in get.sfile.split(','):
|
||
path = os.path.join(get.path, file_name)
|
||
status, total_size, total_num = self.is_max_size(path, max_size, max_num, total_size, total_num)
|
||
if not status: break
|
||
|
||
# 如果被压缩目标小于100MB或文件数量少于1W个,则直接在主线程压缩
|
||
if not status:
|
||
return task_obj._zip(get.path, get.sfile, get.dfile, '/tmp/zip.log', get.z_type)
|
||
|
||
# 否则在后台线程压缩
|
||
task_obj.create_task('Compress files', 3, get.path, json.dumps(
|
||
{"sfile": get.sfile, "dfile": get.dfile, "z_type": get.z_type}))
|
||
public.WriteLog("TYPE_FILE", 'ZIP_SUCCESS', (get.sfile, get.dfile))
|
||
return public.return_message(0, 0, public.lang("The compression task has been added to the message queue!"))
|
||
|
||
# 文件解压
|
||
def UnZip(self, get):
|
||
if get.sfile[-4:] == '.rar':
|
||
if os.uname().machine != 'x86_64':
|
||
return public.return_message(-1, 0, public.lang("RAR component does not support aarch 64 platform"))
|
||
import panel_task_v2 as panelTask
|
||
if not 'password' in get:
|
||
get.password = ''
|
||
if not os.path.exists(get.sfile):
|
||
return public.return_message(-1, 0, public.lang("The specified archive does not exist!"))
|
||
if not os.path.exists(get.dfile):
|
||
os.makedirs(get.dfile)
|
||
zip_size = os.path.getsize(get.sfile)
|
||
task_obj = panelTask.bt_task()
|
||
if zip_size < 1024 * 1024 * 50:
|
||
return task_obj._unzip(get.sfile, get.dfile, get.password, "/tmp/unzip.log")
|
||
|
||
task_obj.create_task(public.get_msg_gettext('Decompress the file'), 2, get.sfile,
|
||
json.dumps({"dfile": get.dfile, "password": get.password}))
|
||
public.write_log_gettext("File manager", 'Successfully uncompressed file from [{}] to [{}]!',
|
||
(get.sfile, get.dfile))
|
||
return public.return_message(0, 0, public.lang("Decompression task added to the message queue!"))
|
||
|
||
# 获取文件/目录 权限信息
|
||
def GetFileAccess(self, get):
|
||
if sys.version_info[0] == 2:
|
||
get.filename = get.filename.encode('utf-8')
|
||
data = {}
|
||
try:
|
||
import pwd
|
||
stat = os.stat(get.filename)
|
||
data['chmod'] = str(oct(stat.st_mode)[-3:])
|
||
data['chown'] = pwd.getpwuid(stat.st_uid).pw_name
|
||
except:
|
||
data['chmod'] = 644
|
||
data['chown'] = 'www'
|
||
return data
|
||
|
||
# 设置文件权限和所有者
|
||
def SetFileAccess(self, get, all='-R'):
|
||
if sys.version_info[0] == 2:
|
||
get.filename = get.filename.encode('utf-8')
|
||
if 'all' in get:
|
||
if get.all == 'False':
|
||
all = ''
|
||
try:
|
||
if not self.CheckDir(get.filename):
|
||
return public.return_message(-1, 0, public.lang("Never trouble troubles till troubles trouble you!"))
|
||
if not os.path.exists(get.filename):
|
||
return public.return_message(-1, 0, public.lang("Configuration file not exist"))
|
||
public.ExecShell('chmod ' + all + ' ' + get.access + " '" + get.filename + "'")
|
||
public.ExecShell('chown ' + all + ' ' + get.user + ':' +
|
||
get.user + " '" + get.filename + "'")
|
||
public.write_log_gettext('File manager', "Set [{}]'s permission to [{}] and authorized user to [{}]",
|
||
(get.filename, get.access, get.user))
|
||
return public.return_message(0, 0, public.lang("Setup successfully!"))
|
||
except:
|
||
return public.return_message(-1, 0, public.lang("Failed to set"))
|
||
|
||
def SetFileAccept(self, filename):
|
||
public.ExecShell('chown -R www:www ' + filename)
|
||
if os.path.isfile(filename):
|
||
public.ExecShell('chmod -R 644 ' + filename)
|
||
else:
|
||
public.ExecShell('chmod -R 755 ' + filename)
|
||
|
||
# 取目录大小
|
||
|
||
def GetDirSize(self, get):
|
||
if not hasattr(get, "path"):
|
||
return public.return_message(-1, 0, public.lang("Parameters are missing! path"))
|
||
if sys.version_info[0] == 2:
|
||
get.path = get.path.encode('utf-8')
|
||
return public.to_size(public.get_path_size(get.path))
|
||
|
||
# 取目录大小2
|
||
def get_path_size(self, get):
|
||
if sys.version_info[0] == 2:
|
||
get.path = get.path.encode('utf-8')
|
||
data = {}
|
||
data['path'] = get.path
|
||
|
||
if os.path.exists(get.path):
|
||
size = public.get_path_size(get.path)
|
||
else:
|
||
size = 0
|
||
data['size'] = size
|
||
return data
|
||
|
||
def CloseLogs(self, get):
|
||
get.path = public.GetConfigValue('root_path')
|
||
public.ExecShell('rm -f ' + public.GetConfigValue('logs_path') + '/*')
|
||
public.ExecShell('rm -rf ' + public.GetConfigValue('logs_path') + '/history_backups/*')
|
||
public.ExecShell('rm -rf ' + public.GetConfigValue('logs_path') + '/request/*')
|
||
public.ExecShell('rm -f ' + public.GetConfigValue('logs_path') + '/pm2/*.log')
|
||
if public.get_webserver() == 'nginx':
|
||
public.ExecShell(
|
||
'kill -USR1 `cat ' + public.GetConfigValue('setup_path') + '/nginx/logs/nginx.pid`')
|
||
else:
|
||
public.ExecShell('/etc/init.d/httpd reload')
|
||
|
||
public.write_log_gettext('File manager', 'Site Logs emptied!')
|
||
get.path = public.GetConfigValue('logs_path')
|
||
return public.return_message(0, 0, self.GetDirSize(get))
|
||
|
||
# 批量操作
|
||
def SetBatchData(self, get: public.dict_obj):
|
||
if sys.version_info[0] == 2:
|
||
get.path = get.path.encode('utf-8')
|
||
if get.type == '1' or get.type == '2':
|
||
session['selected'] = get.get_items()
|
||
return public.return_message(0, 0, public.lang("Successfully marked, please click Paste All button in the target directory!"))
|
||
elif get.type == '3':
|
||
for key in json.loads(get.data):
|
||
key = html.unescape(key)
|
||
try:
|
||
if sys.version_info[0] == 2:
|
||
key = key.encode('utf-8')
|
||
filename = get.path + '/' + key
|
||
if not self.CheckDir(filename):
|
||
return public.return_message(-1, 0, public.lang("Never trouble troubles till troubles trouble you!"))
|
||
ret = ' -R '
|
||
if 'all' in get:
|
||
if get.all == 'False':
|
||
ret = ''
|
||
public.ExecShell('chmod ' + ret + get.access + " '" + filename + "'")
|
||
public.ExecShell('chown ' + ret + get.user +
|
||
':' + get.user + " '" + filename + "'")
|
||
except:
|
||
continue
|
||
public.write_log_gettext('File manager', 'Batch setting permission successful!')
|
||
return public.return_message(0, 0, public.lang("Batch setting permission successful!"))
|
||
else:
|
||
isRecyle = os.path.exists('data/recycle_bin.pl') and session.get('debug') != 1
|
||
path = get.path
|
||
get.data = json.loads(get.data)
|
||
l = len(get.data)
|
||
i = 0
|
||
args = public.dict_obj()
|
||
for key in get.data:
|
||
key = html.unescape(key)
|
||
try:
|
||
if sys.version_info[0] == 2:
|
||
key = key.encode('utf-8')
|
||
filename = path + '/' + key
|
||
get.path = filename
|
||
if not os.path.exists(filename):
|
||
# 软连接目标文件丢失会检测不到
|
||
os.remove(filename)
|
||
continue
|
||
i += 1
|
||
public.writeSpeed(key, i, l)
|
||
if os.path.isdir(filename):
|
||
if not self.CheckDir(filename):
|
||
return public.return_message(-1, 0, public.lang("Never trouble troubles till troubles trouble you!"))
|
||
public.ExecShell("chattr -R -i " + filename)
|
||
if isRecyle:
|
||
self.Mv_Recycle_bin(get)
|
||
elif os.path.islink(filename):
|
||
os.remove(filename)
|
||
else:
|
||
shutil.rmtree(filename)
|
||
elif os.path.islink(filename):
|
||
public.ExecShell('chattr -i ' + filename)
|
||
if isRecyle:
|
||
self.Mv_Recycle_bin(get)
|
||
else:
|
||
os.remove(filename)
|
||
else:
|
||
if key == '.user.ini':
|
||
if l > 1:
|
||
continue
|
||
public.ExecShell('chattr -i ' + filename)
|
||
if isRecyle:
|
||
|
||
self.Mv_Recycle_bin(get)
|
||
else:
|
||
os.remove(filename)
|
||
args.path = filename
|
||
self.remove_file_ps(args)
|
||
except:
|
||
continue
|
||
public.writeSpeed(None, 0, 0)
|
||
self.site_path_safe(get)
|
||
if not isRecyle:
|
||
public.write_log_gettext('File manager', 'Batch deleting successful!')
|
||
return public.return_message(0, 0, public.lang("Batch deleting successful!"))
|
||
else:
|
||
public.write_log_gettext('File manager',
|
||
'{} files or directories have been moved to the recycle bin in batches'.format(
|
||
i))
|
||
return public.return_message(0, 0, public.lang("{} files or directories have been moved to the recycle bin in batches", i))
|
||
|
||
# 批量粘贴
|
||
def BatchPaste(self, get):
|
||
import shutil
|
||
if sys.version_info[0] == 2:
|
||
get.path = get.path.encode('utf-8')
|
||
if not self.CheckDir(get.path):
|
||
return public.return_message(-1, 0, public.lang("Never trouble troubles till troubles trouble you!"))
|
||
if not 'selected' in session:
|
||
return public.return_message(-1, 0, public.lang("The operation failed, please re-copy the copy or cut process"))
|
||
i = 0
|
||
if not 'selected' in session:
|
||
return public.return_message(-1, 0, public.lang("The operation failed, please re-operate"))
|
||
myfiles = json.loads(session['selected']['data'])
|
||
l = len(myfiles)
|
||
if get.type == '1':
|
||
|
||
for key in myfiles:
|
||
if sys.version_info[0] == 2:
|
||
sfile = session['selected']['path'] + \
|
||
'/' + key.encode('utf-8')
|
||
dfile = get.path + '/' + key.encode('utf-8')
|
||
else:
|
||
sfile = session['selected']['path'] + '/' + key
|
||
dfile = get.path + '/' + key
|
||
|
||
if os.path.commonpath([dfile, sfile]) == sfile:
|
||
return public.return_message(-1, 0, public.lang("Wrong copy logic, from {} copy to {} has an inclusive relationship, there is an infinite loop copy risk!", sfile,dfile))
|
||
|
||
for key in myfiles:
|
||
i += 1
|
||
public.writeSpeed(key, i, l)
|
||
try:
|
||
if sys.version_info[0] == 2:
|
||
sfile = session['selected']['path'] + \
|
||
'/' + key.encode('utf-8')
|
||
dfile = get.path + '/' + key.encode('utf-8')
|
||
else:
|
||
sfile = session['selected']['path'] + '/' + key
|
||
dfile = get.path + '/' + key
|
||
|
||
if os.path.isdir(sfile):
|
||
self.copytree(sfile, dfile)
|
||
else:
|
||
shutil.copyfile(sfile, dfile)
|
||
stat = os.stat(sfile)
|
||
os.chown(dfile, stat.st_uid, stat.st_gid)
|
||
except:
|
||
continue
|
||
public.write_log_gettext('File manager', 'Batch copied from [{}] to [{}]',
|
||
(session['selected']['path'], get.path))
|
||
else:
|
||
for key in myfiles:
|
||
try:
|
||
i += 1
|
||
public.writeSpeed(key, i, l)
|
||
if sys.version_info[0] == 2:
|
||
sfile = session['selected']['path'] + '/' + key.encode('utf-8')
|
||
dfile = get.path + '/' + key.encode('utf-8')
|
||
else:
|
||
sfile = session['selected']['path'] + '/' + key
|
||
dfile = get.path + '/' + key
|
||
self.move(sfile, dfile)
|
||
except:
|
||
continue
|
||
self.site_path_safe(get)
|
||
public.write_log_gettext('File manager', 'Batch moved from [{}] to [{}]',
|
||
(session['selected']['path'], get.path))
|
||
public.writeSpeed(None, 0, 0);
|
||
errorCount = len(myfiles) - i
|
||
del (session['selected'])
|
||
return public.return_message(0, 0, public.lang('Batch operating succeeded [{}], failed [{}]', str(i), str(errorCount)))
|
||
|
||
# 移动和重命名
|
||
def move(self, sfile, dfile):
|
||
sfile = sfile.replace('//', '/')
|
||
dfile = dfile.replace('//', '/')
|
||
if sfile == dfile:
|
||
return False
|
||
if not os.path.exists(sfile):
|
||
return False
|
||
is_dir = os.path.isdir(sfile)
|
||
if not os.path.exists(dfile) or not is_dir:
|
||
if os.path.exists(dfile):
|
||
os.remove(dfile)
|
||
shutil.move(sfile, dfile)
|
||
else:
|
||
self.copytree(sfile, dfile)
|
||
if os.path.exists(sfile) and os.path.exists(dfile):
|
||
if is_dir:
|
||
shutil.rmtree(sfile)
|
||
else:
|
||
os.remove(sfile)
|
||
return True
|
||
|
||
# 复制目录
|
||
def copytree(self, sfile, dfile):
|
||
if sfile == dfile:
|
||
return False
|
||
if not os.path.exists(dfile):
|
||
os.makedirs(dfile)
|
||
for f_name in os.listdir(sfile):
|
||
if not f_name.strip(): continue
|
||
if f_name.find('./') != -1: continue
|
||
src_filename = (sfile + '/' + f_name).replace('//', '/')
|
||
dst_filename = (dfile + '/' + f_name).replace('//', '/')
|
||
mode_info = public.get_mode_and_user(src_filename)
|
||
if os.path.isdir(src_filename):
|
||
if not os.path.exists(dst_filename):
|
||
os.makedirs(dst_filename)
|
||
public.set_mode(dst_filename, mode_info['mode'])
|
||
public.set_own(dst_filename, mode_info['user'])
|
||
self.copytree(src_filename, dst_filename)
|
||
else:
|
||
try:
|
||
shutil.copy2(src_filename, dst_filename)
|
||
public.set_mode(dst_filename, mode_info['mode'])
|
||
public.set_own(dst_filename, mode_info['user'])
|
||
except:
|
||
pass
|
||
return True
|
||
|
||
# 下载文件
|
||
|
||
def DownloadFile(self, get):
|
||
import panelTask
|
||
task_obj = panelTask.bt_task()
|
||
get.filename = public.xsssec2(get.filename)
|
||
task_obj.create_task(public.get_msg_gettext('Download file'), 1, get.url, get.path + '/' + get.filename)
|
||
# if sys.version_info[0] == 2: get.path = get.path.encode('utf-8');
|
||
# import db,time
|
||
# isTask = '/tmp/panelTask.pl'
|
||
# execstr = get.url +'|bt|'+get.path+'/'+get.filename
|
||
# sql = db.Sql()
|
||
# sql.table('tasks').add('name,type,status,addtime,execstr',('下载文件['+get.filename+']','download','0',time.strftime('%Y-%m-%d %H:%M:%S'),execstr))
|
||
# public.writeFile(isTask,'True')
|
||
# self.SetFileAccept(get.path+'/'+get.filename)
|
||
public.write_log_gettext('File manager', 'Downloaded file [{}] to [{}]', (get.url, get.path))
|
||
return public.return_message(0, 0, public.lang("Download task added into the queue!"))
|
||
|
||
# 添加安装任务
|
||
def InstallSoft(self, get):
|
||
import db
|
||
import time
|
||
path = public.GetConfigValue('setup_path') + '/php'
|
||
if not os.path.exists(path):
|
||
public.ExecShell("mkdir -p " + path)
|
||
if session['server_os']['x'] != 'RHEL':
|
||
get.type = '3'
|
||
apacheVersion = 'false'
|
||
if public.get_webserver() == 'apache':
|
||
apacheVersion = public.readFile(
|
||
public.GetConfigValue('setup_path') + '/apache/version.pl')
|
||
public.writeFile('/var/bt_apacheVersion.pl', apacheVersion)
|
||
public.writeFile('/var/bt_setupPath.conf',
|
||
public.GetConfigValue('root_path'))
|
||
isTask = '/tmp/panelTask.pl'
|
||
execstr = "cd " + public.GetConfigValue('setup_path') + "/panel/install && /bin/bash install_soft.sh " + \
|
||
get.type + " install " + get.name + " " + get.version
|
||
if public.get_webserver() == "openlitespeed":
|
||
execstr = "cd " + public.GetConfigValue('setup_path') + "/panel/install && /bin/bash install_soft.sh " + \
|
||
get.type + " install " + get.name + "-ols " + get.version
|
||
sql = db.Sql()
|
||
if hasattr(get, 'id'):
|
||
id = get.id
|
||
else:
|
||
id = None
|
||
|
||
sql.table('tasks').add('id,name,type,status,addtime,execstr', (None,
|
||
'Install [' + get.name + '-' + get.version + ']',
|
||
'execshell', '0',
|
||
time.strftime('%Y-%m-%d %H:%M:%S'), execstr))
|
||
public.writeFile(isTask, 'True')
|
||
public.write_log_gettext('Installer', 'Download task added to queue!', (get.name, get.version))
|
||
time.sleep(0.1)
|
||
return public.return_message(0, 0, public.lang("Download task added to queue!"))
|
||
|
||
# 删除任务队列
|
||
def RemoveTask(self, get):
|
||
try:
|
||
name = public.M('tasks').where('id=?', (get.id,)).getField('name')
|
||
status = public.M('tasks').where(
|
||
'id=?', (get.id,)).getField('status')
|
||
public.M('tasks').delete(get.id)
|
||
if status == '-1':
|
||
public.ExecShell(
|
||
"kill `ps -ef |grep 'python panelSafe.pyc'|grep -v grep|grep -v panelExec|awk '{print $2}'`")
|
||
public.ExecShell(
|
||
"kill `ps -ef |grep 'install_soft.sh'|grep -v grep|grep -v panelExec|awk '{print $2}'`")
|
||
public.ExecShell(
|
||
"kill `ps aux | grep 'python task.pyc$'|awk '{print $2}'`")
|
||
public.ExecShell('''
|
||
pids=`ps aux | grep 'sh'|grep -v grep|grep install|awk '{print $2}'`
|
||
arr=($pids)
|
||
|
||
for p in ${arr[@]}
|
||
do
|
||
kill -9 $p
|
||
done
|
||
''')
|
||
|
||
public.ExecShell(
|
||
'rm -f ' + name.replace('Scan dir [', '').replace(']', '') + '/scan.pl')
|
||
isTask = '/tmp/panelTask.pl'
|
||
public.writeFile(isTask, 'True')
|
||
public.ExecShell('/etc/init.d/bt start')
|
||
except:
|
||
public.ExecShell('/etc/init.d/bt start')
|
||
return public.return_message(0, 0, public.lang("Task deleted"))
|
||
|
||
# 重新激活任务
|
||
def ActionTask(self, get):
|
||
isTask = '/tmp/panelTask.pl'
|
||
public.writeFile(isTask, 'True')
|
||
return public.return_message(0, 0, public.lang("Task queue activated"))
|
||
|
||
# 卸载软件
|
||
def UninstallSoft(self, get):
|
||
public.writeFile('/var/bt_setupPath.conf',
|
||
public.GetConfigValue('root_path'))
|
||
get.type = '0'
|
||
if session['server_os']['x'] != 'RHEL':
|
||
get.type = '3'
|
||
if public.get_webserver() == "openlitespeed":
|
||
default_ext = ["bz2", "calendar", "sysvmsg", "exif", "imap", "readline", "sysvshm", "xsl"]
|
||
if get.version == "73":
|
||
default_ext.append("opcache")
|
||
if not os.path.exists("/etc/redhat-release"):
|
||
default_ext.append("gmp")
|
||
default_ext.append("opcache")
|
||
if get.name.lower() in default_ext:
|
||
return public.return_message(-1, 0, public.lang("This extension is the default extension of OLS and cannot be uninstalled"))
|
||
execstr = "cd " + public.GetConfigValue('setup_path') + "/panel/install && /bin/bash install_soft.sh " + \
|
||
get.type + " uninstall " + get.name.lower() + " " + get.version.replace('.', '')
|
||
if public.get_webserver() == "openlitespeed":
|
||
execstr = "cd " + public.GetConfigValue('setup_path') + "/panel/install && /bin/bash install_soft.sh " + \
|
||
get.type + " uninstall " + get.name.lower() + "-ols " + get.version.replace('.', '')
|
||
public.ExecShell(execstr)
|
||
public.write_log_gettext('Installer', 'Uninstallaton succeeded',
|
||
(get.name, get.version))
|
||
return public.return_message(0, 0, public.lang("Uninstallaton succeeded"))
|
||
|
||
# 取任务队列进度
|
||
def GetTaskSpeed(self, get):
|
||
tempFile = '/tmp/panelExec.log'
|
||
# freshFile = '/tmp/panelFresh'
|
||
import db
|
||
find = db.Sql().table('tasks').where('status=? OR status=?', ('-1', '0')).field('id,type,name,execstr').find()
|
||
if (type(find) == str):
|
||
return public.return_message(-1, 0, public.lang('Query error, {}', find))
|
||
if not len(find):
|
||
return public.return_message(-1, 0, public.lang('No tasks in the LINEUP {}', "-2"))
|
||
isTask = '/tmp/panelTask.pl'
|
||
public.writeFile(isTask, 'True')
|
||
echoMsg = {}
|
||
echoMsg['name'] = find['name']
|
||
echoMsg['execstr'] = find['execstr']
|
||
if find['type'] == 'download':
|
||
try:
|
||
tmp = public.readFile(tempFile)
|
||
if len(tmp) < 10:
|
||
return public.return_message(-1, 0, public.lang('No tasks in the LINEUP {}', "-3"))
|
||
echoMsg['msg'] = json.loads(tmp)
|
||
echoMsg['isDownload'] = True
|
||
except:
|
||
db.Sql().table('tasks').where("id=?", (find['id'],)).save('status', ('0',))
|
||
return public.return_message(-1, 0, public.lang('No tasks in the LINEUP {}', "-4"))
|
||
else:
|
||
echoMsg['msg'] = self.GetLastLine(tempFile, 20)
|
||
echoMsg['isDownload'] = False
|
||
|
||
echoMsg['task'] = public.M('tasks').where("status!=?", ('1',)).field(
|
||
'id,status,name,type').order("id asc").select()
|
||
return public.return_message(0, 0, echoMsg)
|
||
|
||
# 取执行日志
|
||
def GetExecLog(self, get):
|
||
# return self.GetLastLine('/tmp/panelExec.log', 100)
|
||
return public.return_message(0, 0, self.GetLastLine('/tmp/panelExec.log', 100))
|
||
|
||
# 读文件指定倒数行数
|
||
def GetLastLine(self, inputfile, lineNum):
|
||
result = public.GetNumLines(inputfile, lineNum)
|
||
if len(result) < 1:
|
||
return public.lang("Loading...")
|
||
return result
|
||
|
||
# 执行SHELL命令
|
||
def ExecShell(self, get):
|
||
disabled = ['vi', 'vim', 'top', 'passwd', 'su']
|
||
get.shell = get.shell.strip()
|
||
tmp = get.shell.split(' ')
|
||
if tmp[0] in disabled:
|
||
return public.return_message(-1, 0, 'Sorry, [{}] command is NOT supported!', (tmp[0],))
|
||
shellStr = '''#!/bin/bash
|
||
PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin
|
||
export PATH
|
||
cd %s
|
||
%s
|
||
''' % (get.path, get.shell)
|
||
public.writeFile('/tmp/panelShell.sh', shellStr)
|
||
public.ExecShell(
|
||
'nohup bash /tmp/panelShell.sh > /tmp/panelShell.pl 2>&1 &')
|
||
return public.return_message(0, 0, public.lang("Command sent"))
|
||
|
||
# 取SHELL执行结果
|
||
def GetExecShellMsg(self, get):
|
||
fileName = '/tmp/panelShell.pl'
|
||
if not os.path.exists(fileName):
|
||
return 'FILE_SHELL_EMPTY'
|
||
status = not public.process_exists('bash', None, '/tmp/panelShell.sh')
|
||
return public.return_msg_gettext(status, public.GetNumLines(fileName, 200))
|
||
|
||
# 文件搜索
|
||
def GetSearch(self, get):
|
||
if not os.path.exists(get.path):
|
||
return public.return_message(-1, 0, public.lang("Requested directory does not exist"))
|
||
return public.ExecShell("find " + get.path + " -name '*" + get.search + "*'")
|
||
|
||
# 保存草稿
|
||
def SaveTmpFile(self, get):
|
||
save_path = public.get_panel_path() + '/temp'
|
||
if not os.path.exists(save_path):
|
||
os.makedirs(save_path)
|
||
get.path = os.path.join(save_path, public.Md5(get.path) + '.tmp')
|
||
public.writeFile(get.path, get.body)
|
||
return public.return_message(0, 0, public.lang("Saved"))
|
||
|
||
# 获取草稿
|
||
def GetTmpFile(self, get):
|
||
self.CleanOldTmpFile()
|
||
save_path = public.get_panel_path() + '/temp'
|
||
if not os.path.exists(save_path):
|
||
os.makedirs(save_path)
|
||
src_path = get.path
|
||
get.path = os.path.join(save_path, public.Md5(get.path) + '.tmp')
|
||
if not os.path.exists(get.path):
|
||
return public.return_message(-1, 0, public.lang("No drafts available!"))
|
||
data = self.GetFileInfo(get.path)
|
||
data['file'] = src_path
|
||
if 'rebody' in get:
|
||
data['body'] = public.readFile(get.path)
|
||
return data
|
||
|
||
# 清除过期草稿
|
||
def CleanOldTmpFile(self):
|
||
if 'clean_tmp_file' in session:
|
||
return True
|
||
save_path = public.get_panel_path() + '/temp'
|
||
max_time = 86400 * 30
|
||
now_time = time.time()
|
||
for tmpFile in os.listdir(save_path):
|
||
filename = os.path.join(save_path, tmpFile)
|
||
fileInfo = self.GetFileInfo(filename)
|
||
if now_time - fileInfo['modify_time'] > max_time:
|
||
os.remove(filename)
|
||
session['clean_tmp_file'] = True
|
||
return True
|
||
|
||
# 取指定文件信息
|
||
def GetFileInfo(self, path):
|
||
if not os.path.exists(path):
|
||
return False
|
||
stat = os.stat(path)
|
||
fileInfo = {}
|
||
fileInfo['modify_time'] = int(stat.st_mtime)
|
||
fileInfo['size'] = os.path.getsize(path)
|
||
return fileInfo
|
||
|
||
# 安装rar组件
|
||
def install_rar(self, get):
|
||
unrar_file = public.get_setup_path() + '/rar/unrar'
|
||
rar_file = public.get_setup_path() + '/rar/rar'
|
||
bin_unrar = '/usr/local/bin/unrar'
|
||
bin_rar = '/usr/local/bin/rar'
|
||
if os.path.exists(unrar_file) and os.path.exists(bin_unrar):
|
||
try:
|
||
import rarfile
|
||
except:
|
||
public.ExecShell("pip install rarfile")
|
||
return True
|
||
|
||
import platform
|
||
os_bit = ''
|
||
if platform.machine() == 'x86_64':
|
||
os_bit = '-x64'
|
||
download_url = public.get_url() + '/src/rarlinux' + os_bit + '-5.6.1.tar.gz'
|
||
|
||
tmp_file = '/tmp/bt_rar.tar.gz'
|
||
public.ExecShell('wget -O ' + tmp_file + ' ' + download_url)
|
||
if os.path.exists(unrar_file):
|
||
public.ExecShell("rm -rf {}".format(rar_file))
|
||
public.ExecShell("tar xvf " + tmp_file + ' -C {}'.format(public.get_setup_path()))
|
||
if os.path.exists(tmp_file):
|
||
os.remove(tmp_file)
|
||
if not os.path.exists(unrar_file):
|
||
return False
|
||
|
||
if os.path.exists(bin_unrar):
|
||
os.remove(bin_unrar)
|
||
if os.path.exists(bin_rar):
|
||
os.remove(bin_rar)
|
||
|
||
public.ExecShell('ln -sf ' + unrar_file + ' ' + bin_unrar)
|
||
public.ExecShell('ln -sf ' + rar_file + ' ' + bin_rar)
|
||
public.ExecShell("pip install rarfile")
|
||
# public.writeFile('data/restart.pl','True')
|
||
return True
|
||
|
||
def get_store_data(self):
|
||
data = []
|
||
path = 'data/file_store.json'
|
||
try:
|
||
if os.path.exists(path):
|
||
data = json.loads(public.readFile(path))
|
||
except:
|
||
data = []
|
||
if type(data) == dict:
|
||
result = []
|
||
for key in data:
|
||
for path in data[key]:
|
||
result.append(path)
|
||
self.set_store_data(result)
|
||
return result
|
||
return data
|
||
|
||
def set_store_data(self, data):
|
||
public.writeFile('data/file_store.json', json.dumps(data))
|
||
return True
|
||
|
||
# 获取收藏夹
|
||
def get_files_store(self, get):
|
||
data = self.get_store_data()
|
||
result = []
|
||
for path in data:
|
||
if type(path) == dict:
|
||
path = path['path']
|
||
info = {'path': path, 'name': os.path.basename(path)}
|
||
if os.path.isdir(path):
|
||
info['type'] = 'dir'
|
||
else:
|
||
info['type'] = 'file'
|
||
result.append(info)
|
||
return result
|
||
|
||
# 添加收藏夹
|
||
def add_files_store(self, get):
|
||
path = get.path
|
||
if not os.path.exists(path):
|
||
return public.return_message(-1, 0, public.lang("File or directory does not exist!"))
|
||
data = self.get_store_data()
|
||
if path in data:
|
||
return public.return_message(-1, 0, public.lang("Do not add it repeatedly!"))
|
||
data.append(path)
|
||
self.set_store_data(data)
|
||
return public.return_message(0, 0, public.lang("Successfully added"))
|
||
|
||
# 删除收藏夹
|
||
def del_files_store(self, get):
|
||
path = get.path
|
||
data = self.get_store_data()
|
||
if not path in data:
|
||
is_go = False
|
||
for info in data:
|
||
if type(info) == dict:
|
||
if info['path'] == path:
|
||
path = info
|
||
is_go = True
|
||
break
|
||
if not is_go:
|
||
return public.return_message(-1, 0, public.lang("This favorite object could not be found!"))
|
||
data.remove(path)
|
||
if len(data) <= 0:
|
||
data = []
|
||
self.set_store_data(data)
|
||
return public.return_message(0, 0, public.lang("Successfully deleted"))
|
||
|
||
# #单文件木马扫描
|
||
# def file_webshell_check(self,get):
|
||
# if not 'filename' in get: return public.return_message(0, 0,public.lang("file does not exist!"))
|
||
# import webshell_check
|
||
# if webshell_check.webshell_check().upload_file_url(get.filename.strip()):
|
||
# return public.return_message(-1, 0, public.lang("This file is webshell [ %s ]'%get.filename.strip().split('/"))[-1])
|
||
# else:
|
||
# return public.return_message(0, 0,public.lang("no risk"))
|
||
#
|
||
# #目录扫描木马
|
||
# def dir_webshell_check(self,get):
|
||
# if not 'path' in get: return public.return_message(-1, 0, public.lang("Please enter a valid directory!"))
|
||
# path=get.path.strip()
|
||
# if os.path.exists(path):
|
||
# #启动消息队列
|
||
# exec_shell = public.get_python_bin() + ' /www/server/panel/class/webshell_check.py dir %s mail'%path
|
||
# task_name = "Scan Trojan files for directory %s"%path
|
||
# import panelTask
|
||
# task_obj = panelTask.bt_task()
|
||
# task_obj.create_task(task_name, 0, exec_shell)
|
||
# return public.return_message(0, 0,public.lang("Starting Trojan killing process. Details will be in the panel security log"))
|
||
|
||
# 获取下载地址列表
|
||
def get_download_url_list(self, get):
|
||
my_table = 'download_token'
|
||
count = public.M(my_table).count()
|
||
|
||
if not 'p' in get:
|
||
get.p = 1
|
||
if not 'limit' in get:
|
||
get.limit = 12
|
||
if not 'collback' in get:
|
||
get.collback = ''
|
||
data = public.get_page(count, int(get.p), get.limit, get.collback)
|
||
data['data'] = public.M(my_table).order('id desc').field(
|
||
'id,filename,token,expire,ps,total,password,addtime').limit(data['shift'] + ',' + data['row']).select()
|
||
return public.return_message(0, 0, data)
|
||
|
||
# 获取短列表
|
||
def get_download_list(self):
|
||
if self.download_list != None: return self.download_list
|
||
my_table = 'download_token'
|
||
self.download_list = public.M(my_table).field('id,filename,expire').select()
|
||
if self.download_token_list == None: self.download_token_list = {}
|
||
m_time = time.time()
|
||
for d in self.download_list:
|
||
if not isinstance(d, dict):
|
||
continue
|
||
# 清理过期和无效
|
||
if self.download_is_rm: continue
|
||
|
||
filename = d.get('filename')
|
||
expire = d.get('expire')
|
||
if not filename or not isinstance(expire, (int, float)):
|
||
continue
|
||
if not os.path.exists(filename) or m_time > expire:
|
||
public.M(my_table).where('id=?', (d['id'],)).delete()
|
||
continue
|
||
self.download_token_list[d['filename']] = d['id']
|
||
|
||
# 标记清理
|
||
if not self.download_is_rm:
|
||
self.download_is_rm = True
|
||
|
||
# 获取id
|
||
def get_download_id(self, filename):
|
||
self.get_download_list()
|
||
return str(self.download_token_list.get(filename, '0'))
|
||
|
||
# 获取指定下载地址
|
||
def get_download_url_find(self, get):
|
||
if not 'id' in get: return public.return_message(-1, 0, public.lang("Wrong parameter"))
|
||
id = int(get.id)
|
||
my_table = 'download_token'
|
||
data = public.M(my_table).where('id=?', (id,)).find()
|
||
if not data: return public.return_message(-1, 0, public.lang("The specified address does not exist!"))
|
||
return public.return_message(0, 0, data)
|
||
|
||
# 删除下载地址
|
||
def remove_download_url(self, get):
|
||
if not 'id' in get: return public.return_message(-1, 0, public.lang("Wrong parameter"))
|
||
id = int(get.id)
|
||
my_table = 'download_token'
|
||
public.M(my_table).where('id=?', (id,)).delete()
|
||
return public.return_message(0, 0, public.lang("Successfully deleted!"))
|
||
|
||
# 修改下载地址
|
||
def modify_download_url(self, get):
|
||
if not 'id' in get: return public.return_message(-1, 0, public.lang("Wrong parameter"))
|
||
id = int(get.id)
|
||
my_table = 'download_token'
|
||
if not public.M(my_table).where('id=?', (id,)).count():
|
||
return public.return_message(-1, 0, public.lang("The specified address does not exist!"))
|
||
pdata = {}
|
||
if 'expire' in get: pdata['expire'] = get.expire
|
||
if 'password' in get:
|
||
pdata['password'] = get.password
|
||
if len(pdata['password']) < 4 and len(pdata['password']) > 0:
|
||
return public.return_message(-1, 0, public.lang("The length of the extracted password cannot be less than 4 digits"))
|
||
if not re.match(r'^\w+$', pdata['password']):
|
||
return public.return_message(-1, 0, public.lang("The password only supports a combination of uppercase and lowercase letters and numbers"))
|
||
|
||
if 'ps' in get: pdata['ps'] = get.ps
|
||
public.M(my_table).where('id=?', (id,)).update(pdata)
|
||
return public.return_message(0, 0, public.lang("Successfully modified"))
|
||
|
||
# 生成下载地址
|
||
def create_download_url(self, get):
|
||
if not os.path.exists(get.filename):
|
||
return public.return_message(-1, 0, public.lang("File or directory does not exist!"))
|
||
my_table = 'download_token'
|
||
mtime = int(time.time())
|
||
pdata = {
|
||
"filename": get.filename, # 文件名
|
||
"token": public.GetRandomString(12), # 12位随机密钥,用于URL
|
||
"expire": mtime + (int(get.expire) * 3600), # 过期时间
|
||
"ps": "ps:{}".format(get.ps), # 备注
|
||
"total": 0, # 下载计数
|
||
"password": str(get.password), # 提取密码
|
||
"addtime": mtime # 添加时间
|
||
}
|
||
exts = os.path.basename(get.filename).split('.')
|
||
if len(exts) > 1:
|
||
if str(get.filename).lower().endswith(".tar.gz"):
|
||
pdata['token'] += ".tar.gz"
|
||
else:
|
||
pdata['token'] += "." + exts[-1]
|
||
if len(pdata['password']) < 4 and len(pdata['password']) > 0:
|
||
return public.return_message(-1, 0, public.lang(" Please do not enter the following special characters [ ~ ` / = ]"))
|
||
if not re.match(r'^\w+$', pdata['password']) and pdata['password']:
|
||
return public.return_message(-1, 0, public.lang("The password only supports a combination of uppercase and lowercase letters and numbers"))
|
||
# 更新 or 插入
|
||
token = public.M(my_table).where('filename=?', (get.filename,)).getField('token')
|
||
if token:
|
||
return public.return_message(-1, 0, public.lang("Already shared!"))
|
||
# pdata['token'] = token
|
||
# del(pdata['total'])
|
||
# public.M(my_table).where('token=?',(token,)).update(pdata)
|
||
else:
|
||
id = public.M(my_table).insert(pdata)
|
||
pdata['id'] = id
|
||
# 添加关键数据统计
|
||
public.set_module_logs('linux_down', 'create_download_url', 1)
|
||
pdata["is_file"] = os.path.isfile(get.filename)
|
||
pdata['expire'] = public.format_date(times=pdata['expire'])
|
||
return public.return_message(0, 0, pdata)
|
||
|
||
# 取PHP-CLI执行命令
|
||
def __get_php_bin(self, php_version=None):
|
||
php_vs = public.get_php_versions(True)
|
||
if php_version:
|
||
if php_version != 'auto':
|
||
if not php_version in php_vs: return ''
|
||
else:
|
||
php_version = None
|
||
|
||
# 判段兼容的PHP版本是否安装
|
||
php_path = "/www/server/php/"
|
||
php_v = None
|
||
for pv in php_vs:
|
||
if php_version:
|
||
if php_version != pv: continue
|
||
php_bin = php_path + pv + "/bin/php"
|
||
if os.path.exists(php_bin):
|
||
php_v = pv
|
||
break
|
||
# 如果没安装直接返回False
|
||
if not php_v: return ''
|
||
# 处理PHP-CLI-INI配置文件
|
||
php_ini = '/www/server/panel/tmp/composer_php_cli_' + php_v + '.ini'
|
||
src_php_ini = php_path + php_v + '/etc/php.ini'
|
||
import shutil
|
||
shutil.copy(src_php_ini, php_ini)
|
||
# 解除所有禁用函数
|
||
php_ini_body = public.readFile(php_ini)
|
||
php_ini_body = re.sub(r"disable_functions\s*=.*", "disable_functions = ", php_ini_body)
|
||
public.writeFile(php_ini, php_ini_body)
|
||
return php_path + php_v + '/bin/php -c ' + php_ini
|
||
|
||
# 执行git
|
||
def exec_git(self, get):
|
||
if get.git_action == 'option':
|
||
public.ExecShell("nohup {} &> /tmp/panelExec.pl &".format(get.giturl))
|
||
else:
|
||
public.ExecShell("nohup git clone {} &> /tmp/panelExec.pl &".format(get.giturl))
|
||
return public.return_message(0, 0, public.lang("Command has been sent!"))
|
||
|
||
# 安装composer
|
||
def get_composer_bin(self):
|
||
composer_bin = '/usr/bin/composer'
|
||
download_addr = 'wget -O {} {}/install/src/composer.phper -T 5'.format(composer_bin, public.get_url())
|
||
if not os.path.exists(composer_bin):
|
||
public.ExecShell(download_addr)
|
||
elif os.path.getsize(composer_bin) < 100:
|
||
public.ExecShell(download_addr)
|
||
|
||
public.ExecShell('chmod +x {}'.format(composer_bin))
|
||
if not os.path.exists(composer_bin):
|
||
return False
|
||
return composer_bin
|
||
|
||
# 执行composer
|
||
def exec_composer(self, get):
|
||
# 校验参数
|
||
try:
|
||
get.validate([
|
||
Param('php_version').String(),
|
||
Param('composer_args').String(),
|
||
Param('composer_cmd').String(),
|
||
Param('repo').String(),
|
||
Param('path').String(),
|
||
Param('user').String(),
|
||
|
||
], [
|
||
public.validate.trim_filter(),
|
||
])
|
||
except Exception as ex:
|
||
public.print_log("error info: {}".format(ex))
|
||
return public.return_message(-1, 0, str(ex))
|
||
|
||
# 准备执行环境
|
||
composer_bin = self.get_composer_bin()
|
||
if not composer_bin:
|
||
return public.return_message(-1, 0, public.lang("No composer available!"))
|
||
|
||
# 取执行PHP版本
|
||
php_version = None
|
||
if 'php_version' in get:
|
||
php_version = get.php_version
|
||
php_bin = self.__get_php_bin(php_version)
|
||
if not php_bin:
|
||
return public.return_message(-1, 0, public.lang("No available PHP version was found, or the specified PHP version was not installed!"))
|
||
get.composer_cmd = get.composer_cmd.strip()
|
||
if get.composer_cmd == '':
|
||
if not os.path.exists(get.path + '/composer.json'):
|
||
return public.return_message(-1, 0, public.lang("The composer.json configuration file was not found in the specified directory!"))
|
||
log_file = '/tmp/composer.log'
|
||
user = ''
|
||
# del_cache = self._composer_user_home()
|
||
if 'user' in get:
|
||
user = 'sudo -u {} '.format(get.user)
|
||
if not os.path.exists('/usr/bin/sudo'):
|
||
if os.path.exists('/usr/bin/apt'):
|
||
public.ExecShell("apt install sudo -y > {}".format(log_file))
|
||
else:
|
||
public.ExecShell("yum install sudo -y > {}".format(log_file))
|
||
public.ExecShell("mkdir -p /home/www && chown -R www:www /home/www")
|
||
# del_cache = self._composer_user_home()
|
||
|
||
# 设置指定源
|
||
if 'repo' in get:
|
||
if get.repo != 'repos.packagist':
|
||
public.ExecShell(
|
||
'export COMPOSER_HOME=/tmp && {}{} {} config -g repo.packagist composer {}'.format(user, php_bin,
|
||
composer_bin,
|
||
get.repo))
|
||
else:
|
||
public.ExecShell(
|
||
'export COMPOSER_HOME=/tmp && {}{} {} config -g --unset repos.packagist'.format(user, php_bin,
|
||
composer_bin))
|
||
# 执行composer命令
|
||
if not get.composer_cmd:
|
||
composer_exec_str = '{} {} {} -vvv'.format(php_bin, composer_bin, get.composer_args)
|
||
else:
|
||
if get.composer_cmd.find('composer ') == 0 or get.composer_cmd.find('/usr/bin/composer ') == 0:
|
||
composer_cmd = get.composer_cmd.replace('composer ', '').replace('/usr/bin/composer ', '')
|
||
composer_exec_str = '{} {} {} -vvv'.format(php_bin, composer_bin, composer_cmd)
|
||
else:
|
||
composer_exec_str = '{} {} {} {} -vvv'.format(php_bin, composer_bin, get.composer_args,
|
||
get.composer_cmd)
|
||
|
||
if os.path.exists(log_file): os.remove(log_file)
|
||
public.ExecShell(
|
||
"cd {} && export COMPOSER_HOME=/tmp && {} nohup {} &> {} && echo 'BT-Exec-Completed' >> {} && rm -rf /home/www &".format(
|
||
get.path, user, composer_exec_str, log_file, log_file))
|
||
public.write_log_gettext('Composer', "Execute composer [{}] in the directory: [{}]",
|
||
(get.path, get.composer_args))
|
||
# del_cache()
|
||
return public.return_message(0, 0, public.lang("Command has been sent!"))
|
||
|
||
# 取composer版本
|
||
def get_composer_version(self, get):
|
||
composer_bin = self.get_composer_bin()
|
||
if not composer_bin:
|
||
return public.return_message(-1, 0, public.lang("No composer available!"))
|
||
|
||
try:
|
||
bs = str(public.readFile(composer_bin, 'rb'))
|
||
result = re.findall(r"const VERSION\s*=\s*.{0,2}'([\d\.]+)", bs)[0]
|
||
if not result: raise Exception('empty!')
|
||
except:
|
||
php_bin = self.__get_php_bin()
|
||
if not php_bin: return public.return_message(-1, 0, public.lang("No available PHP version found!"))
|
||
composer_exec_str = 'export COMPOSER_HOME=/tmp && ' + php_bin + ' ' + composer_bin + ' --version 2>/dev/null|grep \'Composer version\'|awk \'{print $3}\''
|
||
result = public.ExecShell(composer_exec_str)[0].strip()
|
||
data = public.return_message(0, 0, result)
|
||
if 'path' in get:
|
||
import panel_site_v2 as panelSite
|
||
data['message']['php_versions'] = panelSite.panelSite().GetPHPVersion(get, False)
|
||
data['message']['comp_json'] = True
|
||
data['message']['comp_lock'] = False
|
||
if not os.path.exists(get.path + '/composer.json'):
|
||
data['message']['comp_json'] = public.lang(
|
||
'[Composer.json] configuration file is not found in the specified directory!')
|
||
data['message']['file_path'] = ""
|
||
else:
|
||
data['message']['file_path'] = get.path+"/composer.json"
|
||
if os.path.exists(get.path + '/composer.lock'):
|
||
data['message']['comp_lock'] = public.lang(
|
||
'[Composer.lock] file exists in the specified directory, please delete it before executing')
|
||
return public.return_message(0, 0,data)
|
||
|
||
# 升级composer版本
|
||
def update_composer(self, get):
|
||
# 校验参数
|
||
try:
|
||
get.validate([
|
||
Param('repo').String(),
|
||
|
||
], [
|
||
public.validate.trim_filter(),
|
||
])
|
||
except Exception as ex:
|
||
public.print_log("error info: {}".format(ex))
|
||
return public.return_message(-1, 0, str(ex))
|
||
|
||
composer_bin = self.get_composer_bin()
|
||
if not composer_bin:
|
||
return public.return_message(-1, 0, public.lang("No composer available!"))
|
||
php_bin = self.__get_php_bin()
|
||
if not php_bin: return public.return_message(-1, 0, public.lang("No available PHP version found!"))
|
||
# 设置指定源
|
||
# if 'repo' in get:
|
||
# if get.repo:
|
||
# public.ExecShell('{} {} config -g repo.packagist composer {}'.format(php_bin,composer_bin,get.repo))
|
||
|
||
version1 = self.get_composer_version(get)['message']
|
||
composer_exec_str = 'export COMPOSER_HOME=/tmp && {} {} self-update -vvv'.format(php_bin, composer_bin)
|
||
public.ExecShell(composer_exec_str)
|
||
version2 = self.get_composer_version(get)['message']
|
||
if version1 == version2:
|
||
msg = public.lang('Currently the latest version, no upgrade required!')
|
||
else:
|
||
msg = public.lang('Upgrade composer from {} to {}', version1, version2)
|
||
public.write_log_gettext('Composer', msg)
|
||
return public.return_message(0, 0, msg)
|
||
|
||
# 计算文件HASH
|
||
def get_file_hash(self, args=None, filename=None):
|
||
if not filename: filename = args.filename
|
||
import hashlib
|
||
md5_obj = hashlib.md5()
|
||
sha1_obj = hashlib.sha1()
|
||
f = open(filename, 'rb')
|
||
while True:
|
||
b = f.read(8096)
|
||
if not b:
|
||
break
|
||
md5_obj.update(b)
|
||
sha1_obj.update(b)
|
||
f.close()
|
||
return {'md5': md5_obj.hexdigest(), 'sha1': sha1_obj.hexdigest()}
|
||
|
||
# 取历史副本
|
||
def get_history_info(self, filename):
|
||
try:
|
||
save_path = ('/www/backup/file_history/' +
|
||
filename).replace('//', '/')
|
||
if not os.path.exists(save_path):
|
||
return []
|
||
result = []
|
||
for f in sorted(os.listdir(save_path)):
|
||
f_name = (save_path + '/' + f).replace('//', '/')
|
||
pdata = {}
|
||
pdata['md5'] = public.FileMd5(f_name)
|
||
f_stat = os.stat(f_name)
|
||
pdata['st_mtime'] = int(f)
|
||
pdata['st_size'] = f_stat.st_size
|
||
pdata['history_file'] = f_name
|
||
result.insert(0, pdata)
|
||
return sorted(result, key=lambda x: x['st_mtime'], reverse=True)
|
||
except:
|
||
return []
|
||
|
||
# 获取文件扩展名
|
||
def get_file_ext(self, filename):
|
||
ss_exts = ['tar.gz', 'tar.bz2', 'tar.bz']
|
||
for s in ss_exts:
|
||
e_len = len(s)
|
||
f_len = len(filename)
|
||
if f_len < e_len: continue
|
||
if filename[-e_len:] == s:
|
||
return s
|
||
if filename.find('.') == -1: return ''
|
||
return filename.split('.')[-1]
|
||
|
||
# 取所属用户或组
|
||
def get_mode_user(self, uid):
|
||
import pwd
|
||
try:
|
||
return pwd.getpwuid(uid).pw_name
|
||
except:
|
||
return uid
|
||
|
||
# 取lsattr
|
||
def get_lsattr(self, filename):
|
||
if os.path.isfile(filename):
|
||
return public.ExecShell('lsattr {}'.format(filename))[0].split(' ')[0]
|
||
else:
|
||
s_name = os.path.basename(filename)
|
||
s_path = os.path.dirname(filename)
|
||
|
||
try:
|
||
res = public.ExecShell('lsattr {}'.format(s_path))[0].strip()
|
||
for s in res.split('\n'):
|
||
if not s: continue
|
||
lsattr_info = s.split()
|
||
if not lsattr_info: continue
|
||
if filename == lsattr_info[1]:
|
||
return lsattr_info[0]
|
||
except:
|
||
raise public.PanelError(lsattr_info)
|
||
|
||
return '--------------e----'
|
||
|
||
# 取指定文件属性
|
||
def get_file_attribute(self, args):
|
||
filename = args.filename.strip()
|
||
if not os.path.exists(filename):
|
||
return public.return_message(-1, 0, public.lang("File does not exist!"))
|
||
attribute = {}
|
||
attribute['name'] = os.path.basename(filename)
|
||
attribute['path'] = os.path.dirname(filename)
|
||
f_stat = os.stat(filename)
|
||
attribute['st_atime'] = int(f_stat.st_atime) # 最后访问时间
|
||
attribute['st_mtime'] = int(f_stat.st_mtime) # 最后修改时间
|
||
attribute['st_ctime'] = int(f_stat.st_ctime) # 元数据修改时间/权限或数据者变更时间
|
||
attribute['st_size'] = f_stat.st_size # 文件大小(bytes)
|
||
attribute['st_gid'] = f_stat.st_gid # 用户组id
|
||
attribute['st_uid'] = f_stat.st_uid # 用户id
|
||
attribute['st_nlink'] = f_stat.st_nlink # inode 的链接数
|
||
attribute['st_ino'] = f_stat.st_ino # inode 的节点号
|
||
attribute['st_mode'] = f_stat.st_mode # inode 保护模式
|
||
attribute['st_dev'] = f_stat.st_dev # inode 驻留设备
|
||
attribute['user'] = self.get_mode_user(f_stat.st_uid) # 所属用户
|
||
attribute['group'] = self.get_mode_user(f_stat.st_gid) # 所属组
|
||
attribute['mode'] = str(oct(f_stat.st_mode)[-3:]) # 文件权限号
|
||
attribute['md5'] = public.get_msg_gettext('Do not count files or directories larger than 100MB') # 文件MD5
|
||
attribute['sha1'] = public.get_msg_gettext('Do not count files or directories larger than 100MB') # 文件sha1
|
||
attribute['lsattr'] = self.get_lsattr(filename)
|
||
attribute['is_dir'] = os.path.isdir(filename) # 是否为目录
|
||
attribute['is_link'] = os.path.islink(filename) # 是否为链接文件
|
||
if attribute['is_link']:
|
||
attribute['st_type'] = 'Link file'
|
||
elif attribute['is_dir']:
|
||
attribute['st_type'] = 'Dir'
|
||
else:
|
||
attribute['st_type'] = self.get_file_ext(filename)
|
||
attribute['history'] = []
|
||
if f_stat.st_size < 104857600 and not attribute['is_dir']:
|
||
hash_info = self.get_file_hash(filename=filename)
|
||
attribute['md5'] = hash_info['md5']
|
||
attribute['sha1'] = hash_info['sha1']
|
||
attribute['history'] = self.get_history_info(filename) # 历史文件
|
||
return attribute
|
||
|
||
def files_search(self, args):
|
||
import panelSearchV2 as panelSearch
|
||
adad = panelSearch.panelSearch()
|
||
return adad.get_search(args)
|
||
|
||
def files_replace(self, args):
|
||
import panelSearch
|
||
adad = panelSearch.panelSearch()
|
||
return adad.get_replace(args)
|
||
|
||
def get_replace_logs(self, args):
|
||
import panelSearch
|
||
adad = panelSearch.panelSearch()
|
||
return adad.get_replace_logs(args)
|
||
|
||
def get_path_images(self, path):
|
||
'''
|
||
@name 获取目录的图片列表
|
||
@param path 目录路径
|
||
@return 图片列表
|
||
'''
|
||
image_list = []
|
||
for fname in os.listdir(path):
|
||
if fname.split('.')[-1] in ['png', 'jpeg', 'gif', 'jpg', 'bmp', 'ico']:
|
||
image_list.append(fname)
|
||
return ','.join(image_list)
|
||
|
||
def clear_thumbnail(self):
|
||
'''
|
||
@name 清除过期的缩略图缓存
|
||
@author hwliang
|
||
@return void
|
||
'''
|
||
try:
|
||
from YakPanel import cache
|
||
except:
|
||
return
|
||
ikey = 'thumbnail_cache'
|
||
if cache.get(ikey): return
|
||
|
||
cache_path = '{}/cache/thumbnail'.format(public.get_panel_path())
|
||
if not os.path.exists(cache_path): return
|
||
expire_time = time.time() - (30 * 86400) # 30天前的文件
|
||
for fname in os.listdir(cache_path):
|
||
filename = os.path.join(cache_path, fname)
|
||
if os.path.getctime(filename) < expire_time:
|
||
os.remove(filename)
|
||
|
||
# 标记,每天清理一次
|
||
cache.set(ikey, 1, 86400)
|
||
|
||
def get_images_resize(self, args):
|
||
'''
|
||
@name 获取指定图片的缩略图
|
||
@author hwliang<2022-03-02>
|
||
@param args<dict_obj>{
|
||
"path": "", 图片路径
|
||
"files": xx.png,aaa.jpg, 文件名称(不包含目录路径),如果files=*,则返回该目录下的所有图片
|
||
"width": 50, 宽
|
||
"heigth:50, 高
|
||
"return_type": "base64" // base64,file
|
||
}
|
||
@return base64编码的图片 or file
|
||
'''
|
||
from PIL import Image
|
||
from base64 import b64encode
|
||
from io import BytesIO
|
||
if args.files == '*':
|
||
args.files = self.get_path_images(args.path)
|
||
|
||
file_list = args.files.split(',')
|
||
|
||
width = int(args.width)
|
||
height = int(args.height)
|
||
|
||
cache_path = '{}/cache/thumbnail'.format(public.get_panel_path())
|
||
if not os.path.exists(cache_path): os.makedirs(cache_path, 384)
|
||
data = {}
|
||
_max_time = 3 # 最大处理时间
|
||
_stime = time.time()
|
||
|
||
# 清理过期的缩略图缓存
|
||
self.clear_thumbnail()
|
||
|
||
for fname in file_list:
|
||
try:
|
||
filename = os.path.join(args.path, fname)
|
||
f_size = os.path.getsize(filename)
|
||
cache_file = os.path.join(cache_path, public.md5("{}_{}_{}_{}".format(filename, width, height, f_size)))
|
||
if not os.path.exists(filename):
|
||
# 移除缓存文件
|
||
if os.path.exists(cache_file): os.remove(cache_file)
|
||
continue
|
||
|
||
# 有缩略图缓存的使用缓存
|
||
if os.path.exists(cache_file):
|
||
data[fname] = public.readFile(cache_file)
|
||
continue
|
||
|
||
# 超出最大处理时间直接跳过后续图片的处理,以免影响前端用户体验
|
||
if time.time() - _stime > _max_time:
|
||
data[fname] = ''
|
||
continue
|
||
|
||
im = Image.open(filename)
|
||
im.thumbnail((width, height))
|
||
out = BytesIO()
|
||
im.save(out, im.format)
|
||
out.seek(0)
|
||
image_type = im.format.lower()
|
||
mimetype = 'image/{}'.format(image_type)
|
||
if args.return_type == 'base64':
|
||
b64_data = "data:{};base64,".format(mimetype) + b64encode(out.read()).decode('utf-8')
|
||
data[fname] = b64_data
|
||
out.close()
|
||
# 写缩略图缓存
|
||
public.writeFile(cache_file, b64_data)
|
||
else:
|
||
from flask import send_file
|
||
return send_file(out, mimetype=mimetype, cache_timeout=0)
|
||
except:
|
||
data[fname] = ''
|
||
|
||
return public.return_data(True, data)
|
||
|
||
def set_rsync_data(self, data):
|
||
'''
|
||
@name 写入rsync配置数据
|
||
@author cjx
|
||
@param data<dict> 配置数据
|
||
@return bool
|
||
'''
|
||
public.writeFile('{}/data/file_rsync.json'.format(public.get_panel_path()), json.dumps(data))
|
||
return True
|
||
|
||
def get_rsync_data(self):
|
||
'''
|
||
@name 获取文件同步配置
|
||
@author cjx
|
||
@return dict
|
||
'''
|
||
data = {}
|
||
path = '{}/data/file_rsync.json'.format(public.get_panel_path())
|
||
try:
|
||
if os.path.exists(path):
|
||
data = json.loads(public.readFile(path))
|
||
except:
|
||
data = {}
|
||
return data
|
||
|
||
def add_files_rsync(self, get):
|
||
'''
|
||
@name 添加数据同步标记
|
||
@author cjx
|
||
'''
|
||
path = get.path
|
||
s_type = get.s_type
|
||
|
||
data = self.get_rsync_data()
|
||
if not path in data: data[path] = {}
|
||
|
||
data[path][s_type] = 1
|
||
|
||
self.set_rsync_data(data)
|
||
return public.return_message(0, 0, public.lang("Added successfully!"))
|
||
|
||
# 数据库对象
|
||
def _get_sqlite_connect(self):
|
||
try:
|
||
if not self.sqlite_connection:
|
||
self.sqlite_connection = sqlite3.connect('data/file_permissions.db')
|
||
except Exception as ex:
|
||
return "error: " + str(ex)
|
||
|
||
# 操作数据库
|
||
def _operate_db(self, q_sql, permissions_tb=None):
|
||
try:
|
||
self._get_sqlite_connect()
|
||
c = self.sqlite_connection.cursor()
|
||
table = "index_tb"
|
||
if permissions_tb:
|
||
table = permissions_tb
|
||
sql_data = q_sql.replace("TB_NAME", table)
|
||
return c.execute(sql_data)
|
||
except:
|
||
self._create_index_tb()
|
||
self._operate_db(q_sql, permissions_tb)
|
||
|
||
# 判断文件个数
|
||
def _get_file_total(self, path, num, date):
|
||
n = 0
|
||
for p in os.listdir(path):
|
||
full_path = path + "/" + p
|
||
if os.path.isfile(full_path):
|
||
if n == 0:
|
||
first_file = full_path
|
||
n += 1
|
||
if n >= num:
|
||
self.path_permission_exclude_list.append(path)
|
||
f_p = public.get_mode_and_user(path)
|
||
data = {'path': first_file, 'owner': f_p['user'], 'mode': f_p['mode'], 'type': 'first_file',
|
||
'date': date}
|
||
self.path_permission_list.append(data)
|
||
return n
|
||
|
||
# 创建权限表
|
||
def _create_permissions_tb(self, tb_name):
|
||
self._get_sqlite_connect()
|
||
sql = """
|
||
CREATE TABLE {}(
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
path CHAR ,
|
||
owner CHAR,
|
||
mode CHAR,
|
||
date CHAR,
|
||
type CHAR
|
||
);""".format(tb_name)
|
||
self.sqlite_connection.execute(sql)
|
||
|
||
def _create_index_tb(self):
|
||
self._get_sqlite_connect()
|
||
sql = """
|
||
CREATE TABLE index_tb(
|
||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||
permissions_tb CHAR ,
|
||
date CHAR,
|
||
remark CHAR,
|
||
first_path CHAR
|
||
);"""
|
||
self.sqlite_connection.execute(sql)
|
||
|
||
# 获取权限表名
|
||
def _get_permissions_tb_name(self, get_all_tb=None):
|
||
sql = 'select permissions_tb from TB_NAME'
|
||
data = self._operate_db(sql).fetchall()
|
||
exist_tb = [i[0] for i in data]
|
||
if get_all_tb:
|
||
return exist_tb
|
||
tb_names = ['p_tb' + str(x) for x in range(100)]
|
||
tb_name = []
|
||
if exist_tb:
|
||
for n_tb in tb_names:
|
||
if n_tb not in exist_tb:
|
||
tb_name.append(n_tb)
|
||
break
|
||
if not tb_name:
|
||
tb_name.append(tb_names[0])
|
||
self._create_permissions_tb(tb_name[0])
|
||
return tb_name[0]
|
||
|
||
# 写入索引表
|
||
def _write_index_tb(self, remark, date, tb_name, path):
|
||
ins_sql = "INSERT INTO TB_NAME (remark,date,permissions_tb,first_path) VALUES ('{}', '{}', '{}','{}')".format(
|
||
remark, date, tb_name, path)
|
||
self._operate_db(ins_sql)
|
||
self.sqlite_connection.commit()
|
||
|
||
# 写入权限表
|
||
def _write_permisssions_tb(self, tb_name):
|
||
p_p_l = self.path_permission_list
|
||
n = 0
|
||
for p_p in p_p_l:
|
||
ins_sql = "INSERT INTO TB_NAME (path,owner,mode,date,type) VALUES ('{}', '{}', '{}','{}','{}')".format(
|
||
p_p['path'], p_p['owner'], p_p['mode'], p_p['date'], p_p['type'])
|
||
self._operate_db(ins_sql, permissions_tb=tb_name)
|
||
n += 1
|
||
if n >= 1000:
|
||
self.sqlite_connection.commit()
|
||
n = 0
|
||
self.sqlite_connection.commit()
|
||
|
||
# 备份路径权限
|
||
def _back_path_permissions(self, path, date):
|
||
for p in os.listdir(path):
|
||
full_p = path + "/" + p
|
||
if os.path.isdir(full_p):
|
||
# 如果文件夹下数量大于500添加文件夹到排除列表,权限只记录第一个文件的权限
|
||
if self._get_file_total(full_p, 500, date):
|
||
permission_type = "exclude_dir"
|
||
else:
|
||
permission_type = "dir"
|
||
f_p = public.get_mode_and_user(full_p)
|
||
self.path_permission_list.append(
|
||
{'path': full_p, 'owner': f_p['user'], 'mode': f_p['mode'], 'type': permission_type, 'date': date})
|
||
self._back_path_permissions(full_p, date)
|
||
continue
|
||
if path in self.path_permission_exclude_list:
|
||
continue
|
||
f_p = public.get_mode_and_user(full_p)
|
||
data = {'path': full_p, 'owner': f_p['user'], 'mode': f_p['mode'], 'type': 'file', 'date': date}
|
||
self.path_permission_list.append(data)
|
||
|
||
# 备份目录权限
|
||
def back_dir_perm(self, path, back_sub_dir, date, remark, tb_name):
|
||
print("开始备份目录权限 {}".format(path))
|
||
self._write_index_tb(remark, date, tb_name, path)
|
||
f_p = public.get_mode_and_user(path)
|
||
data = {'path': path, 'owner': f_p["user"], 'mode': f_p['mode'], 'date': date, 'type': 'dir'}
|
||
self.path_permission_list.append(data)
|
||
if back_sub_dir == "0":
|
||
self._write_permisssions_tb(tb_name)
|
||
return True
|
||
self._back_path_permissions(path, date)
|
||
self._write_permisssions_tb(tb_name)
|
||
|
||
# 备份单个文件权限
|
||
def back_single_file_perm(self, path, date, remark, tb_name):
|
||
print("开始备份文件权限 {}".format(path))
|
||
self._write_index_tb(remark, date, tb_name, path)
|
||
f_p = public.get_mode_and_user(path)
|
||
data = {'path': path, 'owner': f_p["user"], 'mode': f_p['mode'], 'date': date, 'type': 'file'}
|
||
self.path_permission_list.append(data)
|
||
self._write_permisssions_tb(tb_name)
|
||
|
||
# 备份权限
|
||
def back_path_permissions(self, get):
|
||
back_limit = 100
|
||
if self._get_total_back() >= back_limit:
|
||
return public.return_message(-1, 0, public.lang("The number of backup versions has exceeded {} ,Please go to the upper right corner [Backup Permissions] to clean up the old backup before operating", back_limit))
|
||
if not os.path.exists(get.path):
|
||
return public.return_message(-1, 0, public.lang("Path is incorrect {}", get.path))
|
||
path = get.path
|
||
back_sub_dir = get.back_sub_dir
|
||
remark = get.remark
|
||
self.path_permission_list = list()
|
||
# self.file_permission_list = list()
|
||
self.path_permission_exclude_list = list()
|
||
date = int(time.time())
|
||
tb_name = self._get_permissions_tb_name()
|
||
try:
|
||
if os.path.isdir(path):
|
||
self.back_dir_perm(path, back_sub_dir, date, remark, tb_name)
|
||
else:
|
||
self.back_single_file_perm(path, date, remark, tb_name)
|
||
except Exception as e:
|
||
return public.return_message(-1, 0, public.lang("Backup error {} ", e))
|
||
finally:
|
||
self.sqlite_connection.commit()
|
||
self.sqlite_connection.close()
|
||
self.sqlite_connection = None
|
||
return public.return_message(0, 0, public.lang("Backup succeeded!"))
|
||
|
||
# 获取所有需要还原文件和文件夹
|
||
def _get_restore_file(self, path):
|
||
for p in os.listdir(path):
|
||
full_p = path + "/" + p
|
||
if os.path.isdir(full_p):
|
||
self.file_permission_list.append(full_p)
|
||
self._get_restore_file(full_p)
|
||
continue
|
||
self.file_permission_list.append(full_p)
|
||
|
||
# 直接递归还原目录下的文件权限
|
||
def _recursive_restore_file_perm(self, path, p_i):
|
||
file_permissions = self._operate_db(
|
||
"SELECT owner,mode from TB_NAME where pid='{}' and type='{}'".format(p_i[0], 'first_file'),
|
||
'file').fetchall()
|
||
f_p = file_permissions[0]
|
||
for i in os.listdir(path):
|
||
i = "{}/{}".format(path, i)
|
||
if os.path.isfile(i):
|
||
public.set_mode(i, f_p[1])
|
||
public.set_own(i, f_p[0])
|
||
|
||
# 还原子目录权限
|
||
def _restore_subdir_perm(self, path, date):
|
||
path_info = self._operate_db(
|
||
"SELECT id,owner,mode,type from TB_NAME where path='{}' and date='{}'".format(path, date),
|
||
'path').fetchall()
|
||
p_i = path_info[0]
|
||
public.set_mode(path, p_i[2])
|
||
public.set_own(path, p_i[1])
|
||
if p_i[3] == "exclude_dir":
|
||
self._recursive_restore_file_perm(path, p_i)
|
||
file_permissions = self._operate_db(
|
||
"SELECT path,owner,mode from TB_NAME where pid='{}' and date='{}'".format(p_i[0], date),
|
||
'file').fetchall()
|
||
if file_permissions:
|
||
for f in file_permissions:
|
||
if f[0] == ".user.ini":
|
||
continue
|
||
file_path = "{}/{}".format(path, f[0])
|
||
public.set_mode(file_path, f[2])
|
||
public.set_own(file_path, f[1])
|
||
|
||
# 还原目录权限
|
||
def _restore_dir_perm(self, path_full, restore_sub_dir, date):
|
||
tb_name = self._operate_db("select permissions_tb from TB_NAME where date='{}'".format(date)).fetchall()
|
||
main_dir_data = self._operate_db("select path,owner,mode from TB_NAME where path='{}'".format(path_full),
|
||
permissions_tb=tb_name[0][0]).fetchall()
|
||
if main_dir_data:
|
||
public.set_mode(main_dir_data[0][0], main_dir_data[0][2])
|
||
public.set_own(main_dir_data[0][0], main_dir_data[0][1])
|
||
if restore_sub_dir == "0":
|
||
public.return_msg_gettext(True, "Permission restored successfully")
|
||
self._get_restore_file(path_full)
|
||
if tb_name:
|
||
data = self._operate_db("select path,owner,mode from TB_NAME", permissions_tb=tb_name[0][0]).fetchall()
|
||
for d in data:
|
||
if '.user.ini' in d[0]:
|
||
continue
|
||
if d[0] in self.file_permission_list:
|
||
public.set_mode(d[0], d[2])
|
||
public.set_own(d[0], d[1])
|
||
return public.return_message(0, 0, public.lang("Permission restored successfully"))
|
||
|
||
# 还原单个文件权限
|
||
def restore_single_file_perm(self, path_full, date):
|
||
tb_name = self._operate_db("select permissions_tb from TB_NAME where date='{}'".format(date)).fetchall()
|
||
main_dir_data = self._operate_db("select path,owner,mode from TB_NAME where path='{}'".format(path_full),
|
||
permissions_tb=tb_name[0][0]).fetchall()
|
||
if main_dir_data:
|
||
public.set_mode(main_dir_data[0][0], main_dir_data[0][2])
|
||
public.set_own(main_dir_data[0][0], main_dir_data[0][1])
|
||
return public.return_message(0, 0, public.lang("Permission restored successfully"))
|
||
return public.return_message(-1, 0, public.lang("The file does not have backup permissions"))
|
||
|
||
# 还原权限
|
||
def restore_path_permissions(self, get):
|
||
self.file_permission_list = list()
|
||
path_full = get.path
|
||
restore_sub_dir = get.restore_sub_dir
|
||
date = get.date
|
||
try:
|
||
if os.path.isdir(path_full):
|
||
result = self._restore_dir_perm(path_full, restore_sub_dir, date)
|
||
else:
|
||
result = self.restore_single_file_perm(path_full, date)
|
||
return result
|
||
finally:
|
||
self.sqlite_connection.close()
|
||
self.sqlite_connection = None
|
||
|
||
def get_path_premissions(self, get):
|
||
path_full = get.path
|
||
result = []
|
||
exist_tbs = self._get_permissions_tb_name(get_all_tb=True)
|
||
for tb_name in exist_tbs:
|
||
data = self._operate_db("select path,owner,mode,date from TB_NAME where path='{}'".format(path_full),
|
||
permissions_tb=tb_name).fetchall()
|
||
if not data and os.path.isdir(path_full) and path_full[-1] != "/":
|
||
path_full += "/"
|
||
data = self._operate_db(
|
||
"select path,owner,mode,date from TB_NAME where path='{}'".format(path_full),
|
||
permissions_tb=tb_name).fetchall()
|
||
if data:
|
||
index_data = self._operate_db(
|
||
"select id,remark from index_tb where permissions_tb='{}'".format(tb_name)).fetchall()
|
||
d_l = []
|
||
for i in data[0]:
|
||
d_l.append(i)
|
||
if index_data:
|
||
d_l.append(index_data[0][1])
|
||
d_l.append(index_data[0][0])
|
||
result.append(d_l)
|
||
return sorted(result, key=lambda x: x[3], reverse=True)
|
||
|
||
def del_path_premissions(self, get):
|
||
p_tb = self._operate_db("select permissions_tb from index_tb where id='{}'".format(get.id)).fetchall()
|
||
# 删除引导行
|
||
self._operate_db("delete from index_tb where id='{}'".format(get.id)).fetchall()
|
||
if p_tb:
|
||
self._operate_db("drop table '{}'".format(p_tb[0][0]))
|
||
self.sqlite_connection.commit()
|
||
self.sqlite_connection.close()
|
||
return public.return_message(0, 0, public.lang("Successfully deleted!"))
|
||
|
||
# 获取所有备份
|
||
def get_all_back(self, get):
|
||
data = self._operate_db('select id,remark,date,first_path from index_tb').fetchall()
|
||
return public.return_message(0, 0, sorted(data, key=lambda x: x[2], reverse=True))
|
||
|
||
def _get_total_back(self):
|
||
data = self._operate_db('select id from index_tb').fetchall()
|
||
return len(data)
|
||
|
||
# 一键恢复默认权限
|
||
def fix_permissions(self, get):
|
||
if not hasattr(get, "uid"):
|
||
import pwd
|
||
get.uid = pwd.getpwnam('www').pw_uid
|
||
get.gid = pwd.getpwnam('www').pw_gid
|
||
path = get.path
|
||
if os.path.isfile(path):
|
||
os.chown(path, get.uid, get.gid)
|
||
os.chmod(path, 0o644)
|
||
return public.return_message(0, 0, public.lang("Permission repair succeeded"))
|
||
os.chown(path, get.uid, get.gid)
|
||
os.chmod(path, 0o755)
|
||
for file in os.listdir(path):
|
||
try:
|
||
filename = os.path.join(path, file)
|
||
os.chown(filename, get.uid, get.gid)
|
||
if os.path.isdir(filename):
|
||
os.chmod(filename, 0o755)
|
||
get.path = filename
|
||
self.fix_permissions(get)
|
||
continue
|
||
os.chmod(filename, 0o644)
|
||
except:
|
||
print(public.get_error_info())
|
||
return public.return_message(0, 0, public.lang("Permission repair succeeded"))
|
||
|
||
def restore_website(self, args):
|
||
"""
|
||
@name 恢复站点文件
|
||
@author zhwen<zhw@yakpanel.com>
|
||
@parma file_name 备份得文件名
|
||
@parma site_id 网站id
|
||
"""
|
||
import panel_restore_v2 as panel_restore
|
||
pr = panel_restore.panel_restore()
|
||
return pr.restore_website_backup(args)
|
||
|
||
def get_progress(self, args):
|
||
"""
|
||
@name 获取进度日志
|
||
@author zhwen<zhw@yakpanel.com>
|
||
"""
|
||
import panel_restore_v2 as panel_restore
|
||
pr = panel_restore.panel_restore()
|
||
return pr.get_progress(args)
|
||
|
||
# 获取各类型数据库的SQL备份文件list
|
||
def get_sql_backup(self, get):
|
||
"""
|
||
@name 获取各类型数据库的SQL备份文件
|
||
# 替换原获取接口
|
||
@param p 页码
|
||
@param limit 每页条数
|
||
@param search 文件名称过滤
|
||
@param type_sql 数据库类型,参数示例:mysql、mongodb、pgsql
|
||
"""
|
||
p = getattr(get, "p", 1)
|
||
limit = getattr(get, "limit", 10)
|
||
search = getattr(get, "search", None)
|
||
type_sql = getattr(get, "type_sql", "")
|
||
return_js = getattr(get, "return_js", "")
|
||
search_lower = search.lower() if search else None
|
||
|
||
# 参数验证优化
|
||
if not str(p).isdigit():
|
||
return public.return_message(-1, 0, "ParameterError! p")
|
||
if not str(limit).isdigit():
|
||
return public.return_message(-1, 0, "ParameterError!limit")
|
||
if type_sql not in ['mysql', 'mongodb', 'pgsql']:
|
||
return public.return_message(-1, 0, "ParameterError!type_sql")
|
||
|
||
# 从设置中获取备份路径,用于获取手动上次文件
|
||
backup_path = public.M('config').where("id=?", ('1',)).getField('backup_path')
|
||
path_root_directory = os.path.join(backup_path, 'database')
|
||
|
||
if type_sql == 'mysql':
|
||
type_link = '%_mysql_%'
|
||
elif type_sql == 'mongodb':
|
||
type_link = '%_mongodb_%'
|
||
path_root_directory = os.path.join(path_root_directory, 'mongodb')
|
||
elif type_sql == 'pgsql':
|
||
type_link = '%_pgsql_%'
|
||
path_root_directory = os.path.join(path_root_directory, 'pgsql')
|
||
|
||
p = int(p)
|
||
limit = int(limit)
|
||
|
||
# 从数据库中查询备份文件
|
||
if search_lower:
|
||
search_lower_sql = f"%{search_lower}%"
|
||
backup_list = public.M('backup').field('name,filename,size,addtime').where("type=1 and name LIKE ? and name LIKE ?",(type_link,search_lower_sql,)).order("addtime DESC").select()
|
||
else:
|
||
backup_list = public.M('backup').field('name,filename,size,addtime').where("type=1 and name LIKE ?", (type_link,)).order("addtime DESC").select()
|
||
|
||
# 获取手动上传的备份文件
|
||
upload_list = self.get_dir_backup(path_root_directory, False, search_lower)
|
||
backup_list.extend(upload_list)
|
||
|
||
# 分页处理优化
|
||
total = len(backup_list)
|
||
start_idx = (p - 1) * limit
|
||
end_idx = start_idx + limit
|
||
|
||
if total > 0:
|
||
backup_list.sort(key=lambda x: x["addtime"], reverse=True)
|
||
backup_list = backup_list[start_idx:end_idx]
|
||
|
||
for i in backup_list:
|
||
try:
|
||
if 'filename' in i:
|
||
i['path'] = i['filename']
|
||
del i['filename']
|
||
i['ctime'] = datetime.strptime(i['addtime'], "%Y-%m-%d %H:%M:%S").timestamp()
|
||
del i['addtime']
|
||
except:
|
||
pass
|
||
|
||
# 分页信息处理
|
||
try:
|
||
from flask import request
|
||
uri = public.url_encode(request.full_path)
|
||
except:
|
||
uri = ''
|
||
|
||
import page
|
||
page_obj = page.Page()
|
||
page_info = page_obj.GetPage({
|
||
"p": p,
|
||
"count": total,
|
||
"row": limit,
|
||
"return_js": return_js,
|
||
"uri": uri,
|
||
})
|
||
return public.return_message(0, 0, {"data": backup_list, "page": page_info})
|
||
|
||
# 数据库上传文件扫描函数
|
||
def get_dir_backup(self, backup_dir: str, is_recursion: bool,search_lower=None) -> list:
|
||
"""
|
||
param backup_dir: 备份目录路径
|
||
param is_recursion: 是否递归查找子目录
|
||
param search_lower: 搜索过滤条件,默认为None
|
||
return: 返回备份文件列表
|
||
"""
|
||
ext_set = {"sql", "tar.gz", "gz", "zip"}
|
||
backup_list = []
|
||
if not os.path.exists(backup_dir):
|
||
return backup_list
|
||
with os.scandir(backup_dir) as entries:
|
||
for entry in entries:
|
||
try:
|
||
if entry.is_dir(follow_symlinks=False):
|
||
if entry.name == "all_backup":
|
||
continue
|
||
if is_recursion:
|
||
self.get_dir_backup(entry.path, is_recursion)
|
||
elif entry.is_file(follow_symlinks=False):
|
||
# 提取并验证扩展名
|
||
name = entry.name
|
||
ext = name.split(".")[-1].lower() if "." in name else ""
|
||
if ext not in ext_set:
|
||
continue
|
||
|
||
# 搜索过滤
|
||
if search_lower and search_lower not in name.lower():
|
||
continue
|
||
|
||
# 获取文件属性
|
||
stat = entry.stat()
|
||
backup_list.append({
|
||
"name": name,
|
||
"path": entry.path,
|
||
"size": stat.st_size,
|
||
"addtime": datetime.fromtimestamp(stat.st_ctime).strftime("%Y-%m-%d %H:%M:%S"),
|
||
})
|
||
except OSError:
|
||
continue # 忽略无法访问的文件/目录
|
||
return backup_list
|
||
|
||
@staticmethod
|
||
def test_path(get=None):
|
||
try:
|
||
path = get.path.strip()
|
||
except AttributeError:
|
||
return public.return_message(-1, 0, "Parameter error")
|
||
|
||
|
||
if not os.path.exists(path):
|
||
return public.return_message(0, 0, {
|
||
"path": "",
|
||
"is_dir": None,
|
||
"exists": False
|
||
})
|
||
|
||
if os.path.islink(path):
|
||
real_path = os.path.abspath(os.readlink(path))
|
||
if not os.path.exists(real_path):
|
||
return public.return_message(0, 0, {
|
||
"path": "",
|
||
"is_dir": None,
|
||
"exists": False
|
||
})
|
||
else:
|
||
real_path = path
|
||
|
||
return public.return_message(0, 0, {
|
||
"path": real_path,
|
||
"is_dir": os.path.isdir(real_path),
|
||
"exists": True,
|
||
})
|
||
|
||
|
||
# 添加文件历史记录
|
||
def file_history(self, args):
|
||
file_history_data = os.path.join(public.get_panel_path(), "data/file_history_data.json")
|
||
path = args.path.strip()
|
||
name = args.name.strip()
|
||
|
||
if not os.path.exists(file_history_data):
|
||
public.writeFile(file_history_data, '[]')
|
||
|
||
if not path:
|
||
return public.return_message(-1, 0, public.lang("Please select the file"))
|
||
|
||
|
||
try:
|
||
filedata = json.loads(public.readFile(file_history_data))
|
||
except json.decoder.JSONDecodeError:
|
||
filedata = []
|
||
|
||
# 判断是否已经存在
|
||
for data in filedata:
|
||
if data["filename"] == name and data["filepath"] == path:
|
||
data["time"] = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())
|
||
filedata = sorted(filedata, key=lambda x: x['time'], reverse=True)
|
||
public.writeFile(file_history_data, json.dumps(filedata))
|
||
return public.return_message(0, 0, public.lang("Add successfully"))
|
||
|
||
content = {
|
||
"filename": name,
|
||
"filepath": path,
|
||
"time": time.strftime('%Y-%m-%d %H:%M:%S', time.localtime()),
|
||
"id": public.GetRandomString(12)
|
||
}
|
||
|
||
filedata.append(content)
|
||
|
||
filedata = sorted(filedata, key=lambda x: x['time'], reverse=True)
|
||
|
||
if len(filedata) > 10:
|
||
filedata = filedata[:10]
|
||
|
||
public.writeFile(file_history_data, json.dumps(filedata))
|
||
|
||
return public.return_message(0, 0, public.lang("Add successfully"))
|
||
|
||
# 获取文件历史记录列表 最新10条
|
||
def file_history_list(self, args):
|
||
file_history_data = os.path.join(public.get_panel_path(), "data/file_history_data.json")
|
||
|
||
if not os.path.exists(file_history_data):
|
||
public.writeFile(file_history_data, '[]')
|
||
data = public.readFile(file_history_data)
|
||
if not data:
|
||
data = '[]'
|
||
data = json.loads(data)
|
||
|
||
for item in data:
|
||
item['historys'] = self.get_history(item["filepath"])
|
||
|
||
return data
|
||
# 删除文件历史记录
|
||
def del_file_history(self, args):
|
||
id = args.id
|
||
file_history_data = os.path.join(public.get_panel_path(), "data/file_history_data.json")
|
||
filedata = json.loads(public.readFile(file_history_data))
|
||
|
||
for index, data in enumerate(filedata):
|
||
if data["id"] == id:
|
||
del filedata[index]
|
||
break
|
||
|
||
public.writeFile(file_history_data, json.dumps(filedata))
|
||
return public.return_message(0, 0, public.lang("Delete successfully"))
|
||
# 防篡改:检查进程白名单,是否允许面板编辑
|
||
def _check_tamper_white(self) -> bool:
|
||
tamper = "/www/server/tamper/tamper.conf"
|
||
if not os.path.isfile(tamper):
|
||
return False
|
||
try:
|
||
tamper_info = json.loads(public.readFile(tamper))
|
||
except:
|
||
return False
|
||
if "Yak-Panel" in tamper_info["process_names"]:
|
||
return True
|
||
return False
|