Edit File by line
/home/barbar84/public_h.../wp-conte.../plugins/sujqvwi/AnonR/anonr.TX.../opt/sharedra.../cms_tool...
File: helpers.py
#! /opt/imh-python/bin/python3
[0] Fix | Delete
""" Helper functions for CMS manipulation """
[1] Fix | Delete
[2] Fix | Delete
# Author: Daniel K
[3] Fix | Delete
[4] Fix | Delete
import sys
[5] Fix | Delete
import os
[6] Fix | Delete
import re
[7] Fix | Delete
import logging
[8] Fix | Delete
import pymysql
[9] Fix | Delete
import subprocess
[10] Fix | Delete
import warnings
[11] Fix | Delete
import datetime
[12] Fix | Delete
import glob
[13] Fix | Delete
import shutil
[14] Fix | Delete
from cpapis import cpapi2, whmapi1
[15] Fix | Delete
[16] Fix | Delete
[17] Fix | Delete
LOGGER = logging.getLogger(__name__)
[18] Fix | Delete
# Temporary functions. Should be removed when added to py rads
[19] Fix | Delete
[20] Fix | Delete
# from rads import common
[21] Fix | Delete
[22] Fix | Delete
[23] Fix | Delete
def common_get_string(
[24] Fix | Delete
prompt, string_filter=r'[a-zA-Z0-9._/-]+$', hint='regex', default=None
[25] Fix | Delete
):
[26] Fix | Delete
"""
[27] Fix | Delete
Prompt to request a string, and require it to match a regex.
[28] Fix | Delete
If string fails to match, give a hint, which by default is just
[29] Fix | Delete
the regex. If no matching string is obtained, return None.
[30] Fix | Delete
If empty string is entered, return default if any exists.
[31] Fix | Delete
[32] Fix | Delete
Defined filters: alpha, digits, email, cpuser, database, url
[33] Fix | Delete
"""
[34] Fix | Delete
[35] Fix | Delete
# Predefined filters
[36] Fix | Delete
if string_filter is None:
[37] Fix | Delete
string_filter = '.*'
[38] Fix | Delete
hint = 'Sorry, that should have matched.'
[39] Fix | Delete
elif 'alpha' in string_filter:
[40] Fix | Delete
string_filter = '[a-zA-Z0-9]+$'
[41] Fix | Delete
if hint == 'regex':
[42] Fix | Delete
hint = 'Must be only alphanumeric characters.'
[43] Fix | Delete
elif 'digits' in string_filter:
[44] Fix | Delete
string_filter = '[0-9.]+'
[45] Fix | Delete
if hint == 'regex':
[46] Fix | Delete
hint = 'Must be only digits.'
[47] Fix | Delete
elif 'email' in string_filter:
[48] Fix | Delete
string_filter = (
[49] Fix | Delete
r'[a-z0-9._-]+@[a-z0-9._-]+'
[50] Fix | Delete
+ r'\.([a-z]{2,15}|xn--[a-z0-9]{2,30})$'
[51] Fix | Delete
)
[52] Fix | Delete
if hint == 'regex':
[53] Fix | Delete
hint = 'Must be a valid email address.'
[54] Fix | Delete
elif 'cpuser' in string_filter:
[55] Fix | Delete
string_filter = '[a-z0-9]{1,14}$'
[56] Fix | Delete
if hint == 'regex':
[57] Fix | Delete
hint = (
[58] Fix | Delete
'Must be a valid cPanel user: '
[59] Fix | Delete
+ 'letters and numbers, under 14 characters.'
[60] Fix | Delete
)
[61] Fix | Delete
elif 'database' in string_filter:
[62] Fix | Delete
# This one is not precise, but provided for convenience.
[63] Fix | Delete
string_filter = '[a-z0-9]{1,8}_[a-z0-9]{1,12}$'
[64] Fix | Delete
if hint == 'regex':
[65] Fix | Delete
hint = (
[66] Fix | Delete
'Must be a valid database user: '
[67] Fix | Delete
+ 'letters and numbers, single underscore.'
[68] Fix | Delete
)
[69] Fix | Delete
elif 'url' in string_filter:
[70] Fix | Delete
string_filter = (
[71] Fix | Delete
r'([a-z]{3,}://)?'
[72] Fix | Delete
r'([a-z0-9_-]+.){1,}([a-z]{2,15}|xn--[a-z0-9]'
[73] Fix | Delete
r'{2,30})(:[0-9]+)?'
[74] Fix | Delete
r'((/[a-zA-Z0-9/.%_-]*)(\?[a-zA-Z0-9/.%=;_-]+)?)?$'
[75] Fix | Delete
)
[76] Fix | Delete
if hint == 'regex':
[77] Fix | Delete
hint = 'Must be a valid URL.'
[78] Fix | Delete
[79] Fix | Delete
while True:
[80] Fix | Delete
[81] Fix | Delete
print("%s\n" % prompt)
[82] Fix | Delete
[83] Fix | Delete
try:
[84] Fix | Delete
choice = input()
[85] Fix | Delete
except KeyboardInterrupt:
[86] Fix | Delete
print("\nCancelled")
[87] Fix | Delete
return None
[88] Fix | Delete
[89] Fix | Delete
if default is not None and choice == '':
[90] Fix | Delete
return default
[91] Fix | Delete
if re.match(string_filter, choice) is not None:
[92] Fix | Delete
return choice
[93] Fix | Delete
print('\nInvalid answer. ', end=' ')
[94] Fix | Delete
[95] Fix | Delete
if hint == 'regex':
[96] Fix | Delete
print('\nString must match the patter: /%s/' % string_filter)
[97] Fix | Delete
elif hint is None:
[98] Fix | Delete
print(' ', end=' ')
[99] Fix | Delete
else:
[100] Fix | Delete
print(hint)
[101] Fix | Delete
print('Try again.\n')
[102] Fix | Delete
[103] Fix | Delete
[104] Fix | Delete
# ==== Globals ====
[105] Fix | Delete
[106] Fix | Delete
# Dictionary of dbs we have converted. This is to try to prevent
[107] Fix | Delete
# the use of duplicate db and db user names.
[108] Fix | Delete
DB_CONVERSIONS = {}
[109] Fix | Delete
DB_USER_CONVERSIONS = {}
[110] Fix | Delete
[111] Fix | Delete
# ==== Functions ====
[112] Fix | Delete
[113] Fix | Delete
[114] Fix | Delete
def get_cp_home(cpuser):
[115] Fix | Delete
'''Return home directory of cPanel user'''
[116] Fix | Delete
[117] Fix | Delete
if len(glob.glob("/home*/")) > 1:
[118] Fix | Delete
[119] Fix | Delete
result = whmapi1('accountsummary', {'user': cpuser})
[120] Fix | Delete
[121] Fix | Delete
if 0 == result['metadata']['result']:
[122] Fix | Delete
LOGGER.error(
[123] Fix | Delete
"WHM API could not find home directory for %s: %s",
[124] Fix | Delete
cpuser,
[125] Fix | Delete
result['metadata']['reason'],
[126] Fix | Delete
)
[127] Fix | Delete
return None
[128] Fix | Delete
partition = result['data']['acct'][0]['partition']
[129] Fix | Delete
else:
[130] Fix | Delete
if len(glob.glob("/var/cpanel/users/%s" % cpuser)) == 0:
[131] Fix | Delete
LOGGER.error(
[132] Fix | Delete
"%s does not appear to be a valid cPanel user.", cpuser
[133] Fix | Delete
)
[134] Fix | Delete
return None
[135] Fix | Delete
partition = "home"
[136] Fix | Delete
[137] Fix | Delete
docroot = f"/{partition}/{cpuser}"
[138] Fix | Delete
[139] Fix | Delete
return docroot
[140] Fix | Delete
[141] Fix | Delete
[142] Fix | Delete
def find_start_path(user_path):
[143] Fix | Delete
'''
[144] Fix | Delete
Find start path from for user or path given.
[145] Fix | Delete
'''
[146] Fix | Delete
[147] Fix | Delete
if user_path is None:
[148] Fix | Delete
LOGGER.error("No user or path specified")
[149] Fix | Delete
sys.exit(1)
[150] Fix | Delete
[151] Fix | Delete
if '/' in user_path:
[152] Fix | Delete
requested_path = user_path
[153] Fix | Delete
match = re.search(r"/home[^/]*/([^/]+)", user_path)
[154] Fix | Delete
if match is None:
[155] Fix | Delete
LOGGER.error(
[156] Fix | Delete
"Could not find a username in path '%s'", requested_path
[157] Fix | Delete
)
[158] Fix | Delete
sys.exit(1)
[159] Fix | Delete
[160] Fix | Delete
username = match.group(1)
[161] Fix | Delete
else:
[162] Fix | Delete
username = user_path
[163] Fix | Delete
requested_path = ''
[164] Fix | Delete
[165] Fix | Delete
docroot = get_cp_home(username)
[166] Fix | Delete
if docroot is None:
[167] Fix | Delete
return None
[168] Fix | Delete
[169] Fix | Delete
if requested_path == '':
[170] Fix | Delete
return docroot
[171] Fix | Delete
[172] Fix | Delete
if re.match(docroot, requested_path) is None:
[173] Fix | Delete
LOGGER.error(
[174] Fix | Delete
"Path given (%s) is not part of %s's document root (%s)",
[175] Fix | Delete
requested_path,
[176] Fix | Delete
username,
[177] Fix | Delete
docroot,
[178] Fix | Delete
)
[179] Fix | Delete
return None
[180] Fix | Delete
[181] Fix | Delete
if os.path.isdir(requested_path):
[182] Fix | Delete
return requested_path
[183] Fix | Delete
[184] Fix | Delete
print("Path given does not exist: '%s'" % requested_path)
[185] Fix | Delete
return None
[186] Fix | Delete
[187] Fix | Delete
[188] Fix | Delete
def backup_file(filename):
[189] Fix | Delete
'''
[190] Fix | Delete
Find an unused filename and make a backup of a file
[191] Fix | Delete
'''
[192] Fix | Delete
[193] Fix | Delete
if not os.path.isfile(filename):
[194] Fix | Delete
LOGGER.info("File %s does not exist to backup", filename)
[195] Fix | Delete
return None
[196] Fix | Delete
[197] Fix | Delete
date_today = datetime.datetime.utcnow().strftime("%Y-%m-%d")
[198] Fix | Delete
[199] Fix | Delete
new_file = f"{filename}.cms_tools.file.bak.{date_today}"
[200] Fix | Delete
[201] Fix | Delete
if not os.path.exists(new_file):
[202] Fix | Delete
LOGGER.info("Copying %s -> %s", filename, new_file)
[203] Fix | Delete
shutil.copy2(filename, new_file)
[204] Fix | Delete
return new_file
[205] Fix | Delete
[206] Fix | Delete
for num in range(1, 3):
[207] Fix | Delete
[208] Fix | Delete
new_file = "{}.cms_tools.file.bak.{}.{}".format(
[209] Fix | Delete
filename, date_today, num
[210] Fix | Delete
)
[211] Fix | Delete
[212] Fix | Delete
if not os.path.exists(new_file):
[213] Fix | Delete
LOGGER.info("Copying %s -> %s", filename, new_file)
[214] Fix | Delete
try:
[215] Fix | Delete
shutil.copyfile(filename, new_file)
[216] Fix | Delete
except OSError as error:
[217] Fix | Delete
LOGGER.error(
[218] Fix | Delete
"File copy failed. Could not copy %s to %s: %s",
[219] Fix | Delete
filename,
[220] Fix | Delete
new_file,
[221] Fix | Delete
error,
[222] Fix | Delete
)
[223] Fix | Delete
return False
[224] Fix | Delete
[225] Fix | Delete
return new_file
[226] Fix | Delete
[227] Fix | Delete
LOGGER.warning("There are already too many backup files for %s", filename)
[228] Fix | Delete
[229] Fix | Delete
return False
[230] Fix | Delete
[231] Fix | Delete
[232] Fix | Delete
def restore_file(source_file, destination_file):
[233] Fix | Delete
'''
[234] Fix | Delete
Replace destination file with source file, removing source file
[235] Fix | Delete
'''
[236] Fix | Delete
[237] Fix | Delete
if not os.path.isfile(source_file):
[238] Fix | Delete
LOGGER.warning("File %s does not exist. Cannot restore.", source_file)
[239] Fix | Delete
return False
[240] Fix | Delete
[241] Fix | Delete
try:
[242] Fix | Delete
shutil.move(source_file, destination_file)
[243] Fix | Delete
LOGGER.info("Restored %s from %s", destination_file, source_file)
[244] Fix | Delete
except OSError as error:
[245] Fix | Delete
LOGGER.error(
[246] Fix | Delete
"File restore failed. Could not restore %s to %s: %s",
[247] Fix | Delete
source_file,
[248] Fix | Delete
destination_file,
[249] Fix | Delete
error,
[250] Fix | Delete
)
[251] Fix | Delete
[252] Fix | Delete
return True
[253] Fix | Delete
[254] Fix | Delete
[255] Fix | Delete
def readfile(filename):
[256] Fix | Delete
'''
[257] Fix | Delete
Read data from a file or report error and return None
[258] Fix | Delete
'''
[259] Fix | Delete
[260] Fix | Delete
LOGGER.debug("Reading from %s", filename)
[261] Fix | Delete
if os.path.exists(filename):
[262] Fix | Delete
with open(filename, encoding='utf-8') as file_handle:
[263] Fix | Delete
try:
[264] Fix | Delete
file_data = file_handle.read()
[265] Fix | Delete
return file_data
[266] Fix | Delete
except OSError:
[267] Fix | Delete
LOGGER.error("Error reading file")
[268] Fix | Delete
return None
[269] Fix | Delete
return None
[270] Fix | Delete
[271] Fix | Delete
[272] Fix | Delete
# End readfile()
[273] Fix | Delete
[274] Fix | Delete
[275] Fix | Delete
def lastmatch(regex, data):
[276] Fix | Delete
'''
[277] Fix | Delete
Return the last regex match in a set of data or None
[278] Fix | Delete
'''
[279] Fix | Delete
[280] Fix | Delete
if None is data:
[281] Fix | Delete
return None
[282] Fix | Delete
if None is regex:
[283] Fix | Delete
return None
[284] Fix | Delete
[285] Fix | Delete
# Create the regex as a multiline
[286] Fix | Delete
regex_object = re.compile(regex, re.M)
[287] Fix | Delete
[288] Fix | Delete
result = regex_object.findall(data)
[289] Fix | Delete
[290] Fix | Delete
if 0 == len(result):
[291] Fix | Delete
return None
[292] Fix | Delete
[293] Fix | Delete
return result[-1]
[294] Fix | Delete
[295] Fix | Delete
[296] Fix | Delete
# End lastmatch()
[297] Fix | Delete
[298] Fix | Delete
[299] Fix | Delete
def strip_php_comments(data):
[300] Fix | Delete
'''
[301] Fix | Delete
Return data minus any PHP style comments
[302] Fix | Delete
'''
[303] Fix | Delete
[304] Fix | Delete
# Remove C++ style comments
[305] Fix | Delete
data = re.sub(r"\s+//.*", "", data)
[306] Fix | Delete
[307] Fix | Delete
# Remove C style comments
[308] Fix | Delete
data = re.sub(r"/\*(.*\n)*?.*?\*/", "", data)
[309] Fix | Delete
[310] Fix | Delete
return data
[311] Fix | Delete
[312] Fix | Delete
[313] Fix | Delete
# End strip_php_comments
[314] Fix | Delete
[315] Fix | Delete
[316] Fix | Delete
def find_php_define(const_name, data):
[317] Fix | Delete
'''
[318] Fix | Delete
Find the last instance of const_name being defined in php data
[319] Fix | Delete
'''
[320] Fix | Delete
return lastmatch(
[321] Fix | Delete
r'define\( *["\']%s["\']\s*,\s*["\']([^"\']+)["\']' % const_name, data
[322] Fix | Delete
)
[323] Fix | Delete
[324] Fix | Delete
[325] Fix | Delete
# End find_php_define
[326] Fix | Delete
[327] Fix | Delete
[328] Fix | Delete
def find_php_var(var_name, data):
[329] Fix | Delete
'''
[330] Fix | Delete
Find the last instance of var_name being assigned in php data
[331] Fix | Delete
'''
[332] Fix | Delete
return lastmatch(r'\$%s\s*=\s*["\']([^"\']+)["\']' % var_name, data)
[333] Fix | Delete
[334] Fix | Delete
[335] Fix | Delete
# End find_php_var
[336] Fix | Delete
[337] Fix | Delete
[338] Fix | Delete
def php_re_define(const_name, value, filename):
[339] Fix | Delete
'''
[340] Fix | Delete
Change all instances in filename where const_name is defined,
[341] Fix | Delete
and redefine it to value
[342] Fix | Delete
'''
[343] Fix | Delete
[344] Fix | Delete
with open(filename, encoding='utf-8') as sources:
[345] Fix | Delete
lines = sources.readlines()
[346] Fix | Delete
with open(filename, "w", encoding='utf-8') as sources:
[347] Fix | Delete
for line in lines:
[348] Fix | Delete
sources.write(
[349] Fix | Delete
re.sub(
[350] Fix | Delete
r'define\( *["\']%s["\'] *,'
[351] Fix | Delete
r' *["\']([^"\']+)["\'] *\)' % const_name,
[352] Fix | Delete
f"define('{const_name}','{value}')",
[353] Fix | Delete
line,
[354] Fix | Delete
)
[355] Fix | Delete
)
[356] Fix | Delete
[357] Fix | Delete
return True
[358] Fix | Delete
[359] Fix | Delete
[360] Fix | Delete
# End php_re_define
[361] Fix | Delete
[362] Fix | Delete
[363] Fix | Delete
def php_re_assign(var_name, value, filename):
[364] Fix | Delete
'''
[365] Fix | Delete
Change all instances in filename where var_name is assigned,
[366] Fix | Delete
and reassign it to value
[367] Fix | Delete
'''
[368] Fix | Delete
[369] Fix | Delete
with open(filename, encoding='utf-8') as sources:
[370] Fix | Delete
lines = sources.readlines()
[371] Fix | Delete
with open(filename, "w", encoding='utf-8') as sources:
[372] Fix | Delete
for line in lines:
[373] Fix | Delete
sources.write(
[374] Fix | Delete
re.sub(
[375] Fix | Delete
r'\$%s *= *["\']([^"\']+)["\']' % var_name,
[376] Fix | Delete
f"${var_name} = '{value}'",
[377] Fix | Delete
line,
[378] Fix | Delete
)
[379] Fix | Delete
)
[380] Fix | Delete
[381] Fix | Delete
[382] Fix | Delete
# End php_re_define
[383] Fix | Delete
[384] Fix | Delete
[385] Fix | Delete
def make_valid_db_name(cpuser, prefix, current_name, name_type="database"):
[386] Fix | Delete
'''
[387] Fix | Delete
Find a valid replacement database name. Typce can be database or user
[388] Fix | Delete
'''
[389] Fix | Delete
[390] Fix | Delete
used_names = ()
[391] Fix | Delete
[392] Fix | Delete
LOGGER.debug("Finding new name for: %s", current_name)
[393] Fix | Delete
[394] Fix | Delete
# If we've already made this one, just return it
[395] Fix | Delete
if name_type == "database":
[396] Fix | Delete
if current_name in DB_CONVERSIONS:
[397] Fix | Delete
return DB_CONVERSIONS[current_name]
[398] Fix | Delete
used_names = list(DB_CONVERSIONS.values())
[399] Fix | Delete
[400] Fix | Delete
result = cpapi2('MysqlFE::listdbs', user=cpuser)
[401] Fix | Delete
[402] Fix | Delete
if 'result' in result['cpanelresult']['data']:
[403] Fix | Delete
LOGGER.error(
[404] Fix | Delete
"cPanel API could not list databases: %s",
[405] Fix | Delete
result['cpanelresult']['data']['reason'],
[406] Fix | Delete
)
[407] Fix | Delete
sys.exit(1)
[408] Fix | Delete
[409] Fix | Delete
for i in result['cpanelresult']['data']:
[410] Fix | Delete
used_names += i['db']
[411] Fix | Delete
[412] Fix | Delete
else:
[413] Fix | Delete
# if name_type == "user"
[414] Fix | Delete
if current_name in DB_USER_CONVERSIONS:
[415] Fix | Delete
return DB_USER_CONVERSIONS[current_name]
[416] Fix | Delete
used_names = list(DB_USER_CONVERSIONS.values())
[417] Fix | Delete
[418] Fix | Delete
result = cpapi2('MysqlFE::listdbs', user=cpuser)
[419] Fix | Delete
if 'result' in result['cpanelresult']['data']:
[420] Fix | Delete
LOGGER.error(
[421] Fix | Delete
"cPanel API could not list users: %s",
[422] Fix | Delete
result['cpanelresult']['data']['reason'],
[423] Fix | Delete
)
[424] Fix | Delete
sys.exit(1)
[425] Fix | Delete
[426] Fix | Delete
for i in result['cpanelresult']['data']:
[427] Fix | Delete
used_names += i['user']
[428] Fix | Delete
[429] Fix | Delete
# Add cp name only so that empty additions will fail
[430] Fix | Delete
used_names = used_names + ["%s_" % prefix]
[431] Fix | Delete
[432] Fix | Delete
is_set = False
[433] Fix | Delete
[434] Fix | Delete
# For reference, this is how easily it is done with bash:
[435] Fix | Delete
# "${cp}_$(echo "$1"|grep -Po '^([^_]*_)?\K[a-z0-9]{1,7}')"
[436] Fix | Delete
# Sadly, Python can't handle variable-length lookbehinds
[437] Fix | Delete
[438] Fix | Delete
# Remove unacceptable characters
[439] Fix | Delete
name_base = re.sub('[^a-z0-9_]', '', current_name.lower())
[440] Fix | Delete
[441] Fix | Delete
# Get the group after the last _
[442] Fix | Delete
last_section = re.search('[^_]*$', name_base).group()
[443] Fix | Delete
[444] Fix | Delete
if len(last_section) > 4:
[445] Fix | Delete
name_base = last_section
[446] Fix | Delete
else:
[447] Fix | Delete
first_section = re.search('^[^_]*', name_base).group()
[448] Fix | Delete
if len(first_section) > 4:
[449] Fix | Delete
name_base = first_section
[450] Fix | Delete
else:
[451] Fix | Delete
name_base = re.sub('[^a-z0-9]', '', name_base)
[452] Fix | Delete
name_base = name_base[:8]
[453] Fix | Delete
[454] Fix | Delete
# Simply try the base name itself
[455] Fix | Delete
new_name = "{}_{}".format(
[456] Fix | Delete
prefix,
[457] Fix | Delete
re.search('^[a-z0-9]{1,%d}' % (15 - len(prefix)), name_base).group(0),
[458] Fix | Delete
)
[459] Fix | Delete
[460] Fix | Delete
if new_name not in used_names:
[461] Fix | Delete
is_set = True
[462] Fix | Delete
[463] Fix | Delete
if not is_set:
[464] Fix | Delete
if 14 > (len(prefix) + len(name_base)):
[465] Fix | Delete
for i in range(1, (10 ** (15 - len(prefix) - len(name_base)))):
[466] Fix | Delete
print("name base: %s" % name_base)
[467] Fix | Delete
new_name = "{}_{}{}".format(
[468] Fix | Delete
prefix,
[469] Fix | Delete
re.search(
[470] Fix | Delete
'^[a-z0-9]{1,%d}' % (15 - len(prefix)), name_base
[471] Fix | Delete
).group(0),
[472] Fix | Delete
i,
[473] Fix | Delete
)
[474] Fix | Delete
[475] Fix | Delete
if new_name not in used_names:
[476] Fix | Delete
is_set = True
[477] Fix | Delete
break
[478] Fix | Delete
[479] Fix | Delete
# If it isn't set yet, try replacing characters on the end with numbers
[480] Fix | Delete
if not is_set:
[481] Fix | Delete
for i in range(len(name_base[: (15 - len(prefix) - 1)]), 1, -1):
[482] Fix | Delete
tmp_base = name_base[:i]
[483] Fix | Delete
for i in range(1, (10 ** (15 - len(prefix) - len(tmp_base)) - 1)):
[484] Fix | Delete
new_name = "{}_{}{}".format(
[485] Fix | Delete
prefix,
[486] Fix | Delete
re.search(
[487] Fix | Delete
'^[a-z0-9]{1,%d}' % (15 - len(prefix)), tmp_base
[488] Fix | Delete
).group(0),
[489] Fix | Delete
i,
[490] Fix | Delete
)
[491] Fix | Delete
[492] Fix | Delete
if new_name not in used_names:
[493] Fix | Delete
is_set = True
[494] Fix | Delete
break
[495] Fix | Delete
if is_set:
[496] Fix | Delete
break
[497] Fix | Delete
[498] Fix | Delete
if is_set:
[499] Fix | Delete
12
It is recommended that you Edit text format, this type of Fix handles quite a lot in one request
Function