Update for Joomla 3.7.2

This commit is contained in:
Christoph Haas 2017-05-27 12:19:40 +02:00
parent 2bd7573770
commit a405edc8ce
1 changed files with 125 additions and 80 deletions

View File

@ -1,21 +1,22 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from pathlib import Path import argparse
import re import hashlib
import urllib.request
from urllib.request import urlretrieve
import zipfile
import shutil
import stat
import os import os
import os.path import os.path
import hashlib import re
import shutil
import stat
import sys import sys
import argparse import socket
import urllib.request
import urllib.error
import zipfile
from distutils.version import StrictVersion from distutils.version import StrictVersion
from pathlib import Path
############################################################## ##############################################################
## CONFIG # CONFIG
############################################################## ##############################################################
# BASE BASE # BASE BASE
@ -27,52 +28,57 @@ csv_file_name = 'joomla_status.csv'
# TMP DOWNLOAD DIR # TMP DOWNLOAD DIR
tmp_dl_dir = '/tmp' tmp_dl_dir = '/tmp'
############################################################# #############################################################
def get_joomla_version(filepath): def get_joomla_version(version_file_path):
fobj = open(filepath, "r", -1, None, 'replace') version_file = open(version_file_path, "r", -1, None, 'replace')
version = -1 main_version = -1
dev_version = -1 dev_version = -1
for line in fobj: for line in version_file:
if version != -1 and dev_version != -1: if main_version != -1 and dev_version != -1:
break break
match = re.search("""RELEASE[\s]*=[\s]*'?"?([0-9\.]+)'?"?""", line) version_match = re.search("""RELEASE[\s]*=[\s]*'?"?([0-9.]+)'?"?""", line)
if match is not None: if version_match is not None:
#print(match.group(1)) # print(version_match.group(1))
version = match.group(1) main_version = version_match.group(1)
match = re.search("""DEV_LEVEL[\s]*=[\s]*'?"?([0-9]+)'?"?""", line)
if match is not None:
#print(match.group(1))
dev_version = match.group(1)
fobj.close()
if version != -1 and dev_version != -1: version_match = re.search("""DEV_LEVEL[\s]*=[\s]*'?"?([0-9]+)'?"?""", line)
return str(version + "." + dev_version) if version_match is not None:
# print(version_match.group(1))
dev_version = version_match.group(1)
version_file.close()
if main_version != -1 and dev_version != -1:
return str(main_version + "." + dev_version)
else: else:
return "" return ""
def check_version(version, newest_version):
if StrictVersion(newest_version) > StrictVersion(version): def check_version(current_version, latest_version):
if StrictVersion(latest_version) > StrictVersion(current_version):
return False return False
else: else:
return True return True
def get_newest_version(): def get_newest_version():
response = urllib.request.urlopen("http://update.joomla.org/core/list.xml") response = urllib.request.urlopen("http://update.joomla.org/core/list.xml")
highest_version = "0.0" highest_version = "0.0"
for line in response: for line in response:
match = re.search("""[\s]version="?'?([0-9\.]+)"?'?""", str(line)) version_match = re.search("""[\s]version="?'?([0-9.]+)"?'?""", str(line))
if match is not None: if version_match is not None:
if StrictVersion(match.group(1)) > StrictVersion(highest_version): if StrictVersion(version_match.group(1)) > StrictVersion(highest_version):
highest_version = match.group(1) highest_version = version_match.group(1)
return highest_version return highest_version
def download_joomla_version(version):
version_match = re.search("""(\d+)\.(\d+)(\.(\d+))?([ab](\d+))?""", version) def download_joomla_version(joomla_version):
version_match = re.search("""(\d+)\.(\d+)(\.(\d+))?([ab](\d+))?""", joomla_version)
dst_path = "" dst_path = ""
if version_match is not None: if version_match is not None:
if version_match.group(1) == "2": if version_match.group(1) == "2":
@ -82,40 +88,56 @@ def download_joomla_version(version):
version_string = version_match.group(1) + "-" + version_match.group(2) + "-" + version_match.group(4) version_string = version_match.group(1) + "-" + version_match.group(2) + "-" + version_match.group(4)
# check if file has already been downloaded... # check if file has already been downloaded...
dst_path = tmp_dl_dir + "/orig_joomla_" + version + ".zip" dst_path = tmp_dl_dir + "/orig_joomla_" + joomla_version + ".zip"
dst_file = Path(dst_path) dst_file = Path(dst_path)
if not dst_file.is_file(): if not dst_file.is_file():
url = "https://downloads.joomla.org/cms/" + version_path + "/" + version_string + "/joomla_" + version_string + "-stable-full_package-zip?format=zip" if StrictVersion(joomla_version) > StrictVersion('3.7.1'):
version_string2 = ".".join(version_string.rsplit('-', 1))
url = "https://downloads.joomla.org/cms/" + \
version_path + "/" + version_string + \
"/Joomla_" + version_string2 + \
"-Stable-Full_Package.zip?format=zip"
else:
url = "https://downloads.joomla.org/cms/" + \
version_path + "/" + version_string + \
"/joomla_" + version_string + \
"-stable-full_package-zip?format=zip"
try: try:
urllib.request.urlretrieve (url, dst_path) urllib.request.urlretrieve(url, dst_path)
except: except (urllib.error.URLError, urllib.error.HTTPError, socket.error):
# print("Download of", url, "failed!")
dst_path = "" dst_path = ""
return dst_path return dst_path
def extract_downloaded_joomla_version(version, path):
dst_path = tmp_dl_dir + "/orig_joomla_" + version def extract_downloaded_joomla_version(joomla_version, zip_file_path):
dst_path = tmp_dl_dir + "/orig_joomla_" + joomla_version
# extract a fresh copy... # extract a fresh copy...
shutil.rmtree(dst_path, onerror=remove_readonly) shutil.rmtree(dst_path, onerror=remove_readonly)
try: try:
with zipfile.ZipFile(path, "r") as zip_ref: with zipfile.ZipFile(zip_file_path, "r") as zip_ref:
zip_ref.extractall(dst_path) zip_ref.extractall(dst_path)
except: except (urllib.error.URLError, urllib.error.HTTPError, socket.error):
dst_path = "" dst_path = ""
return dst_path return dst_path
def remove_readonly(func, path, excinfo): def remove_readonly(func, path, excinfo):
if os.path.isdir(path): if os.path.isdir(path):
os.chmod(path, stat.S_IWRITE) os.chmod(path, stat.S_IWRITE)
func(path) func(path)
def get_dir_md5(dir_root): def get_dir_md5(dir_root):
exclude_dirs = {"installation", "tmp"} exclude_dirs = {"installation", "tmp"}
hash = hashlib.md5() md5_hash = hashlib.md5()
for dirpath, dirnames, filenames in os.walk(dir_root, topdown=True): for dirpath, dirnames, filenames in os.walk(dir_root, topdown=True):
dirnames.sort(key=os.path.normcase) dirnames.sort(key=os.path.normcase)
@ -140,57 +162,69 @@ def get_dir_md5(dir_root):
f = open(filepath, 'rb') f = open(filepath, 'rb')
for chunk in iter(lambda: f.read(65536), b''): for chunk in iter(lambda: f.read(65536), b''):
hash.update(chunk) md5_hash.update(chunk)
return md5_hash.hexdigest()
return hash.hexdigest()
def cmp_joomla_directories(original_root, installation_root): def cmp_joomla_directories(original_root, installation_root):
exclude_dirs = {"installation", "tmp", "logs"} exclude_dirs = {"installation", "tmp", "logs"}
check_failures = [] check_failures = []
for dirpath, dirnames, filenames in os.walk(original_root, topdown=True):
dirnames.sort(key=os.path.normcase) for dir_path, dir_names, file_names in os.walk(original_root, topdown=True):
filenames.sort(key=os.path.normcase)
dirnames[:] = [d for d in dirnames if d not in exclude_dirs] dir_names.sort(key=os.path.normcase)
file_names.sort(key=os.path.normcase)
for filename in filenames: dir_names[:] = [d for d in dir_names if d not in exclude_dirs]
relative_path = os.path.relpath(dirpath, original_root)
for filename in file_names:
relative_path = os.path.relpath(dir_path, original_root)
if relative_path == ".": if relative_path == ".":
relative_path = "" relative_path = ""
orig_filepath = os.path.join(dirpath, filename) orig_file_path = os.path.join(dir_path, filename)
inst_filepath = os.path.join(installation_root, os.path.join(relative_path, filename)) inst_file_path = os.path.join(installation_root, os.path.join(relative_path, filename))
if os.path.isfile(inst_filepath): if os.path.isfile(inst_file_path):
hash_orig = hashlib.md5() hash_orig = hashlib.md5()
f = open(orig_filepath, 'rb') f = open(orig_file_path, 'rb')
for chunk in iter(lambda: f.read(65536), b''): for chunk in iter(lambda: f.read(65536), b''):
hash_orig.update(chunk) hash_orig.update(chunk)
f.close() f.close()
hash_inst = hashlib.md5() hash_inst = hashlib.md5()
f = open(inst_filepath, 'rb') f = open(inst_file_path, 'rb')
for chunk in iter(lambda: f.read(65536), b''): for chunk in iter(lambda: f.read(65536), b''):
hash_inst.update(chunk) hash_inst.update(chunk)
f.close() f.close()
if hash_orig.hexdigest() == hash_inst.hexdigest(): if hash_orig.hexdigest() == hash_inst.hexdigest():
#print("file ok", os.path.join(relative_path, filename), hash_orig.hexdigest(), hash_inst.hexdigest()) """
print("file ok",
os.path.join(relative_path, filename),
hash_orig.hexdigest(),
hash_inst.hexdigest())
"""
pass pass
else: else:
#print("file NOT OK!!!!!!!!!!!!!!!!!!!!!!!", os.path.join(relative_path, filename), hash_orig.hexdigest(), hash_inst.hexdigest()) """
print("file NOT OK!",
os.path.join(relative_path, filename),
hash_orig.hexdigest(),
hash_inst.hexdigest())
"""
check_failures.append(os.path.join(relative_path, filename)) check_failures.append(os.path.join(relative_path, filename))
else: else:
#print("File", os.path.join(relative_path, filename), "is missing!") # print("File", os.path.join(relative_path, filename), "is missing!")
pass pass
return check_failures return check_failures
class bcolors:
class ConsoleColors:
HEADER = '\033[95m' HEADER = '\033[95m'
OKBLUE = '\033[94m' OKBLUE = '\033[94m'
OKGREEN = '\033[92m' OKGREEN = '\033[92m'
@ -200,6 +234,7 @@ class bcolors:
BOLD = '\033[1m' BOLD = '\033[1m'
UNDERLINE = '\033[4m' UNDERLINE = '\033[4m'
# MAIN # MAIN
########### ###########
@ -211,7 +246,7 @@ args = parser.parse_args()
newest_version = get_newest_version() newest_version = get_newest_version()
print(bcolors.HEADER + "Newest Joomla Version: ", bcolors.OKBLUE, newest_version, bcolors.ENDC, "\n") print(ConsoleColors.HEADER + "Newest Joomla Version: ", ConsoleColors.OKBLUE, newest_version, ConsoleColors.ENDC, "\n")
fobj = open(csv_file_name, "w") fobj = open(csv_file_name, "w")
fobj.write("Status;Integrity;Actual Version;Newest Version;Domain;Path\n") fobj.write("Status;Integrity;Actual Version;Newest Version;Domain;Path\n")
@ -229,30 +264,37 @@ for file_path in Path(base_path).glob('**/version.php'):
version_status = "UNKN" version_status = "UNKN"
integrity_status = "UNKN" integrity_status = "UNKN"
if not check_version(version, newest_version): if not check_version(version, newest_version):
print(bcolors.FAIL, "[WARNING]", bcolors.ENDC, "Outdated Joomla version found!\t[", bcolors.FAIL + version + bcolors.ENDC, "] [", bcolors.WARNING + domain + bcolors.ENDC, "] \tin ", file_path) print(ConsoleColors.FAIL, "[WARNING]", ConsoleColors.ENDC, "Outdated Joomla version found!\t[",
ConsoleColors.FAIL + version + ConsoleColors.ENDC, "] [",
ConsoleColors.WARNING + domain + ConsoleColors.ENDC, "] \tin ",
file_path)
version_status = "WARN" version_status = "WARN"
else: else:
print(bcolors.OKGREEN, "[OK] ", bcolors.ENDC, "Up to date Joomla version found!\t[", bcolors.OKGREEN + version + bcolors.ENDC, "] [", bcolors.WARNING + domain + bcolors.ENDC, "] \tin ", file_path) print(ConsoleColors.OKGREEN, "[OK] ", ConsoleColors.ENDC, "Up to date Joomla version found!\t[",
ConsoleColors.OKGREEN + version + ConsoleColors.ENDC, "] [",
ConsoleColors.WARNING + domain + ConsoleColors.ENDC, "] \tin ",
file_path)
version_status = "OKOK" version_status = "OKOK"
if not args.nointegrity: if not args.nointegrity:
print(bcolors.HEADER, " -> Checking file integrity: ", bcolors.ENDC, end=" ") print(ConsoleColors.HEADER, " -> Checking file integrity: ", ConsoleColors.ENDC, end=" ")
sys.stdout.flush() sys.stdout.flush()
dl_path = download_joomla_version(version) dl_path = download_joomla_version(version)
if not dl_path: if not dl_path:
print(bcolors.FAIL, "Failed to download joomla source!", bcolors.ENDC) print(ConsoleColors.FAIL, "Failed to download joomla source!", ConsoleColors.ENDC)
else: else:
orig_root = extract_downloaded_joomla_version(version, dl_path) orig_root = extract_downloaded_joomla_version(version, dl_path)
cms_root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(str(file_path))))) # strip "libraries/cms/version/version.php" from filename cms_root = os.path.dirname(os.path.dirname(os.path.dirname(
os.path.dirname(str(file_path))))) # strip "libraries/cms/version/version.php" from filename
if not orig_root: if not orig_root:
print(bcolors.FAIL, "Failed to extract joomla source!", bcolors.ENDC) print(ConsoleColors.FAIL, "Failed to extract joomla source!", ConsoleColors.ENDC)
else: else:
result_list = cmp_joomla_directories(orig_root, cms_root) result_list = cmp_joomla_directories(orig_root, cms_root)
if len(result_list) == 0: if len(result_list) == 0:
print(bcolors.OKGREEN, "OK", bcolors.ENDC) print(ConsoleColors.OKGREEN, "OK", ConsoleColors.ENDC)
integrity_status = "OKOK" integrity_status = "OKOK"
else: else:
# check if only image files differ... if so ignore it. # check if only image files differ... if so ignore it.
@ -265,18 +307,21 @@ for file_path in Path(base_path).glob('**/version.php'):
real_fail_count = real_fail_count + 1 real_fail_count = real_fail_count + 1
if real_fail_count == 0: if real_fail_count == 0:
print(bcolors.WARNING, "OK", bcolors.ENDC, "Use -v to get details!") print(ConsoleColors.WARNING, "OK", ConsoleColors.ENDC, "Use -v to get details!")
integrity_status = "WARN" integrity_status = "WARN"
else: else:
print(bcolors.FAIL, "FAIL", bcolors.ENDC, "Use -v to get details!") print(ConsoleColors.FAIL, "FAIL", ConsoleColors.ENDC, "Use -v to get details!")
integrity_status = "FAIL" integrity_status = "FAIL"
if args.verbose: if args.verbose:
if len(result_list) > 0: if len(result_list) > 0:
print('\tMissmatch: %s' % '\n\tMissmatch: '.join(map(str, result_list))) print('\tMissmatch: %s' % '\n\tMissmatch: '.join(map(str, result_list)))
fobj.write(version_status + ";" + integrity_status + ";" + version + ";" + newest_version + ";" + domain + ";" + str(file_path) + "\n") fobj.write(
print("") # empty last line version_status + ";" + integrity_status + ";" + version + ";" + newest_version + ";" + domain + ";" + str(
file_path) + "\n")
print("") # empty last line
fobj.close() fobj.close()
print("\n" + bcolors.HEADER + "All versions written to: ", bcolors.OKBLUE, csv_file_name, bcolors.ENDC) print("\n" + ConsoleColors.HEADER + "All versions written to: ",
ConsoleColors.OKBLUE, csv_file_name, ConsoleColors.ENDC)