Commit b0c26b36 authored by heddn's avatar heddn Committed by heddn
Browse files

Issue #3063133 by heddn, tatarbj: Backport ModifiedFiles

parent 7b8e8038
<?php
/**
* Modified files service.
*/
class ModifiedFilesService {
/**
* {@inheritdoc}
*/
public static function getModifiedFiles(array $extensions = []) {
$modified_files = [];
foreach (static::getHashUrls($extensions) as $url) {
$response = drupal_http_request($url);
if (isset($response->code) && ($response->code == 200)) {
static::processHashes($response->data, $modified_files);
}
}
return $modified_files;
}
/**
* Process checking hashes of files from external URL.
*
* @param string $data
* The response data.
* @param array $modified_files
* The list of modified files.
*/
protected static function processHashes($data, array &$modified_files) {
$separator = "\r\n";
$line = strtok($data, $separator);
while ($line !== FALSE) {
$line = strtok($separator);
list($hash, $file) = preg_split('/\s+/', $line, 2);
$file = trim($file);
// If the line is empty, proceed to the next line.
if (empty($hash) && empty($file)) {
continue;
}
// If one of the values is invalid, log and continue.
if (!$hash || !$file) {
watchdog('automatic_updaters', '@hash or @file is empty; the hash file is malformed for this line.', ['@hash' => $hash, '@file' => $file], WATCHDOG_ERROR);
continue;
}
if (static::isIgnoredPath($file)) {
continue;
}
$file_path = DRUPAL_ROOT . DIRECTORY_SEPARATOR . $file;
if (!file_exists($file_path) || hash_file('sha512', $file_path) !== $hash) {
$modified_files[] = $file_path;
}
}
}
/**
* Get an iterator of urls.
*
* @param array $extensions
* The list of extensions, keyed by extension name and value the info array.
*
* @codingStandardsIgnoreStart
*
* @return \Generator
*
* @@codingStandardsIgnoreEnd
*/
protected static function getHashUrls(array $extensions) {
foreach ($extensions as $extension_name => $info) {
yield static::buildUrl($extension_name, $info);
}
}
/**
* Build an extension's hash file URL.
*
* @param string $extension_name
* The extension name.
* @param \stdClass $info
* The extension's info.
*
* @return string
* The URL endpoint with for an extension.
*/
protected static function buildUrl($extension_name, $info) {
$version = static::getExtensionVersion($info);
$project_name = static::getProjectName($extension_name, $info);
$hash_name = static::getHashName($info);
$uri = variable_get('automatic_updates_download_url', 'https://ftp.drupal.org/files/projects');
return url($uri . "/$project_name/$version/$hash_name");
}
/**
* Get the extension version.
* @param \stdClass $info
* The extension's info.
*
* @return string|null
* The version or NULL if undefined.
*/
protected static function getExtensionVersion($info) {
if (isset($info->version) && strpos($info->version, '-dev') === FALSE) {
return $info->version;
}
}
/**
* Get the extension's project name.
*
* @param string $extension_name
* The extension name.
* @param \stdClass $info
* The extension's info.
*
* @return string
* The project name or fallback to extension name if project is undefined.
*/
protected static function getProjectName($extension_name, $info) {
$project_name = $extension_name;
if (isset($info->project)) {
$project_name = $info->project;
}
return $project_name;
}
/**
* Get the hash file name.
*
* @param \stdClass $info
* The extension's info.
*
* @return string|null
* The hash name.
*/
protected static function getHashName($info) {
$hash_name = 'SHA512SUMS';
if (isset($info->project)) {
$hash_name .= '-package';
}
return $hash_name;
}
/**
* Check if the file path is ignored.
*
* @param string $file_path
* The file path.
*
* @return bool
* TRUE if file path is ignored, else FALSE.
*/
protected static function isIgnoredPath($file_path) {
$paths = variable_get('automatic_updates_ignored_paths', "sites/all/modules/custom/*\nsites/all/themes/custom/*");
if (drupal_match_path($file_path, $paths)) {
return TRUE;
}
}
}
<?php
/**
* Modified code checker.
*/
class ModifiedFiles implements ReadinessCheckerInterface {
/**
* {@inheritdoc}
*/
public static function run() {
return static::modifiedFilesCheck();
}
/**
* Check if the site contains any modified code.
*
* @return array
* An array of translatable strings if any checks fail.
*/
protected static function modifiedFilesCheck() {
$messages = [];
foreach (ModifiedFilesService::getModifiedFiles(static::getInfos()) as $file) {
$messages[] = static::t('The hash for @file does not match its original. Updates that include that file will fail and require manual intervention.', ['@file' => $file]);
}
return $messages;
}
/**
* Returns an array of info files information of available extensions.
*
* @return array
* An associative array of extension information arrays, keyed by extension
* name.
*/
protected static function getInfos() {
$extensions = drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info$/', 'modules', $key = 'name', $min_depth = 1);
return array_merge($extensions, drupal_system_listing('/^' . DRUPAL_PHP_FUNCTION_PATTERN . '\.info$/', 'themes', $key = 'name', $min_depth = 1));
}
}
......@@ -23,6 +23,7 @@ class ReadinessCheckerManager {
*/
protected static function getCheckers() {
static::$checkers['warning'][0][] = 'FileOwnership';
static::$checkers['warning'][0][] = 'ModifiedFiles';
static::$checkers['warning'][0][] = 'PhpSapi';
static::$checkers['error'][0][] = 'PhpSapi';
......
......@@ -6,7 +6,9 @@ package = Security
files[] = tests/automatic_updates.test
files[] = AutomaticUpdatesPsa.php
files[] = ModifiedFilesService.php
files[] = ReadinessCheckers/ReadinessCheckerManager.php
files[] = ReadinessCheckers/ReadinessCheckerInterface.php
files[] = ReadinessCheckers/FileOwnership.php
files[] = ReadinessCheckers/ModifiedFiles.php
files[] = ReadinessCheckers/PhpSapi.php
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment