updated core to 7.66

This commit is contained in:
2019-04-23 16:10:56 +02:00
parent dc21b82664
commit 200a1093e8
186 changed files with 2603 additions and 691 deletions
+56 -1
View File
@@ -1,7 +1,62 @@
Drupal 7.xx, xxxx-xx-xx (development version)
-----------------------
Drupal 7.66, 2019-04-17
-----------------------
- Fixed security issues:
- SA-CORE-2019-006
Drupal 7.65, 2019-03-20
-----------------------
- Fixed security issues:
- SA-CORE-2019-004
Drupal 7.64, 2019-02-06
-----------------------
- [regression] Unset the 'host' header in drupal_http_request() during redirect
- Fixed: 7.x does not have Phar protection and Phar tests are failing on Drupal 7
- Fixed: Notice: Undefined index: display_field in file_field_widget_value() (line 582 of /module/file/file.field.inc)
- Performance improvement: Registry rebuild should not parse the same file twice in the same request
- Fixed _registry_update() to clear caches after transaction is committed
Drupal 7.63, 2019-01-16
-----------------------
- Fixed a fatal error for some Drush users introduced by SA-CORE-2019-002.
Drupal 7.62, 2019-01-15
-----------------------
- Fixed security issues:
- SA-CORE-2019-001
- SA-CORE-2019-002
Drupal 7.61, 2018-11-07
-----------------------
- File upload validation functions and hook_file_validate() implementations are
now always passed the correct file URI.
- The default form cache expiration of 6 hours is now configurable (API
addition: https://www.drupal.org/node/2857751).
- Allowed callers of drupal_http_request() to optionally specify an explicit
Host header.
- Allowed the + character to appear in usernames.
- PHP 7.2: Fixed Archive_Tar incompatibility.
- PHP 7.2: Removed deprecated function each().
- PHP 7.2: Avoid count() calls on uncountable variables.
- PHP 7.2: Removed deprecated create_function() call.
- PHP 7.2: Make sure variables are arrays in theme_links().
- Fixed theme-settings.php not being loaded on cached forms
- Fixed problem with IE11 & Chrome(PointerEvents enabled) & some Firefox scroll to the top of the page after dragging the bottom item with jquery 1.5 <-> 1.11
Drupal 7.60, 2018-10-18
------------------------
- Fixed security issues. See SA-CORE-2018-006.
Drupal 7.59, 2018-04-25
-----------------------
- Fixed security issues (remote code execution). See SA-CORE-2018-004.
Drupal 7.58, 2018-03-28
-----------------------
- Fixed security issues (multiple vulnerabilities). See SA-CORE-2018-002.
- Fixed security issues (remote code execution). See SA-CORE-2018-002.
Drupal 7.57, 2018-02-21
-----------------------
+2 -2
View File
@@ -15,6 +15,7 @@ The branch maintainers for Drupal 7 are:
- Fabian Franz 'Fabianx' https://www.drupal.org/u/fabianx
- David Rothstein 'David_Rothstein' https://www.drupal.org/u/david_rothstein
- Stefan Ruijsenaars 'stefan.r' https://www.drupal.org/u/stefanr-0
- (provisional) Pol Dellaiera 'Pol' https://www.drupal.org/u/pol
Component maintainers
@@ -44,10 +45,9 @@ Cron system
- Derek Wright 'dww' https://www.drupal.org/u/dww
Database system
- Larry Garfield 'Crell' https://www.drupal.org/u/crell
- ?
- MySQL driver
- Larry Garfield 'Crell' https://www.drupal.org/u/crell
- David Strauss 'David Strauss' https://www.drupal.org/u/david-strauss
- PostgreSQL driver
+24 -2
View File
@@ -8,7 +8,7 @@
/**
* The current system version.
*/
define('VERSION', '7.58');
define('VERSION', '7.66');
/**
* Core API compatibility.
@@ -704,6 +704,19 @@ function drupal_environment_initialize() {
// Set sane locale settings, to ensure consistent string, dates, times and
// numbers handling.
setlocale(LC_ALL, 'C');
// PHP's built-in phar:// stream wrapper is not sufficiently secure. Override
// it with a more secure one, which requires PHP 5.3.3. For lower versions,
// unregister the built-in one without replacing it. Sites needing phar
// support for lower PHP versions must implement hook_stream_wrappers() to
// register their desired implementation.
if (in_array('phar', stream_get_wrappers(), TRUE)) {
stream_wrapper_unregister('phar');
if (version_compare(PHP_VERSION, '5.3.3', '>=')) {
include_once DRUPAL_ROOT . '/includes/file.phar.inc';
file_register_phar_wrapper();
}
}
}
/**
@@ -2778,6 +2791,11 @@ function _drupal_bootstrap_variables() {
unset($_GET['destination']);
unset($_REQUEST['destination']);
}
// Use the DrupalRequestSanitizer to ensure that the destination's query
// parameters are not dangerous.
if (isset($_GET['destination'])) {
DrupalRequestSanitizer::cleanDestination();
}
// If there's still something in $_REQUEST['destination'] that didn't come
// from $_GET, check it too.
if (isset($_REQUEST['destination']) && (!isset($_GET['destination']) || $_REQUEST['destination'] != $_GET['destination']) && url_is_external($_REQUEST['destination'])) {
@@ -3780,8 +3798,12 @@ function _drupal_shutdown_function() {
chdir(DRUPAL_ROOT);
try {
while (list($key, $callback) = each($callbacks)) {
// Manually iterate over the array instead of using a foreach loop.
// A foreach operates on a copy of the array, so any shutdown functions that
// were added from other shutdown functions would never be called.
while ($callback = current($callbacks)) {
call_user_func_array($callback['callback'], $callback['arguments']);
next($callbacks);
}
}
catch (Exception $exception) {
+22 -7
View File
@@ -611,8 +611,9 @@ function drupal_parse_url($url) {
}
// The 'q' parameter contains the path of the current page if clean URLs are
// disabled. It overrides the 'path' of the URL when present, even if clean
// URLs are enabled, due to how Apache rewriting rules work.
if (isset($options['query']['q'])) {
// URLs are enabled, due to how Apache rewriting rules work. The path
// parameter must be a string.
if (isset($options['query']['q']) && is_string($options['query']['q'])) {
$options['path'] = $options['query']['q'];
unset($options['query']['q']);
}
@@ -866,8 +867,10 @@ function drupal_http_request($url, array $options = array()) {
// Make the socket connection to a proxy server.
$socket = 'tcp://' . $proxy_server . ':' . variable_get('proxy_port', 8080);
// The Host header still needs to match the real request.
$options['headers']['Host'] = $uri['host'];
$options['headers']['Host'] .= isset($uri['port']) && $uri['port'] != 80 ? ':' . $uri['port'] : '';
if (!isset($options['headers']['Host'])) {
$options['headers']['Host'] = $uri['host'];
$options['headers']['Host'] .= isset($uri['port']) && $uri['port'] != 80 ? ':' . $uri['port'] : '';
}
break;
case 'http':
@@ -877,14 +880,18 @@ function drupal_http_request($url, array $options = array()) {
// RFC 2616: "non-standard ports MUST, default ports MAY be included".
// We don't add the standard port to prevent from breaking rewrite rules
// checking the host that do not take into account the port number.
$options['headers']['Host'] = $uri['host'] . ($port != 80 ? ':' . $port : '');
if (!isset($options['headers']['Host'])) {
$options['headers']['Host'] = $uri['host'] . ($port != 80 ? ':' . $port : '');
}
break;
case 'https':
// Note: Only works when PHP is compiled with OpenSSL support.
$port = isset($uri['port']) ? $uri['port'] : 443;
$socket = 'ssl://' . $uri['host'] . ':' . $port;
$options['headers']['Host'] = $uri['host'] . ($port != 443 ? ':' . $port : '');
if (!isset($options['headers']['Host'])) {
$options['headers']['Host'] = $uri['host'] . ($port != 443 ? ':' . $port : '');
}
break;
default:
@@ -1087,6 +1094,11 @@ function drupal_http_request($url, array $options = array()) {
elseif ($options['max_redirects']) {
// Redirect to the new location.
$options['max_redirects']--;
// We need to unset the 'Host' header
// as we are redirecting to a new location.
unset($options['headers']['Host']);
$result = drupal_http_request($location, $options);
$result->redirect_code = $code;
}
@@ -2310,7 +2322,10 @@ function url($path = NULL, array $options = array()) {
$language = isset($options['language']) && isset($options['language']->language) ? $options['language']->language : '';
$alias = drupal_get_path_alias($original_path, $language);
if ($alias != $original_path) {
$path = $alias;
// Strip leading slashes from internal path aliases to prevent them
// becoming external URLs without protocol. /example.com should not be
// turned into //example.com.
$path = ltrim($alias, '/');
}
}
+50 -5
View File
@@ -993,8 +993,15 @@ function file_build_uri($path) {
* @return
* The destination filepath, or FALSE if the file already exists
* and FILE_EXISTS_ERROR is specified.
*
* @throws RuntimeException
* Thrown if the filename contains invalid UTF-8.
*/
function file_destination($destination, $replace) {
$basename = drupal_basename($destination);
if (!drupal_validate_utf8($basename)) {
throw new RuntimeException(sprintf("Invalid filename '%s'", $basename));
}
if (file_exists($destination)) {
switch ($replace) {
case FILE_EXISTS_REPLACE:
@@ -1002,7 +1009,6 @@ function file_destination($destination, $replace) {
break;
case FILE_EXISTS_RENAME:
$basename = drupal_basename($destination);
$directory = drupal_dirname($destination);
$destination = file_create_filename($basename, $directory);
break;
@@ -1218,11 +1224,20 @@ function file_unmunge_filename($filename) {
* @return
* File path consisting of $directory and a unique filename based off
* of $basename.
*
* @throws RuntimeException
* Thrown if the $basename is not valid UTF-8 or another error occurs
* stripping control characters.
*/
function file_create_filename($basename, $directory) {
$original = $basename;
// Strip control characters (ASCII value < 32). Though these are allowed in
// some filesystems, not many applications handle them well.
$basename = preg_replace('/[\x00-\x1F]/u', '_', $basename);
if (preg_last_error() !== PREG_NO_ERROR) {
throw new RuntimeException(sprintf("Invalid filename '%s'", $original));
}
if (substr(PHP_OS, 0, 3) == 'WIN') {
// These characters are not allowed in Windows filenames
$basename = str_replace(array(':', '*', '?', '"', '<', '>', '|'), '_', $basename);
@@ -1534,9 +1549,9 @@ function file_save_upload($form_field_name, $validators = array(), $destination
// rename filename.php.foo and filename.php to filename.php.foo.txt and
// filename.php.txt, respectively). Don't rename if 'allow_insecure_uploads'
// evaluates to TRUE.
if (!variable_get('allow_insecure_uploads', 0) && preg_match('/\.(php|pl|py|cgi|asp|js)(\.|$)/i', $file->filename) && (substr($file->filename, -4) != '.txt')) {
if (!variable_get('allow_insecure_uploads', 0) && preg_match('/\.(php|phar|pl|py|cgi|asp|js)(\.|$)/i', $file->filename) && (substr($file->filename, -4) != '.txt')) {
$file->filemime = 'text/plain';
$file->uri .= '.txt';
// The destination filename will also later be used to create the URI.
$file->filename .= '.txt';
// The .txt extension may not be in the allowed list of extensions. We have
// to add it here or else the file upload will fail.
@@ -1563,7 +1578,13 @@ function file_save_upload($form_field_name, $validators = array(), $destination
if (substr($destination, -1) != '/') {
$destination .= '/';
}
$file->destination = file_destination($destination . $file->filename, $replace);
try {
$file->destination = file_destination($destination . $file->filename, $replace);
}
catch (RuntimeException $e) {
drupal_set_message(t('The file %source could not be uploaded because the name is invalid.', array('%source' => $form_field_name)), 'error');
return FALSE;
}
// If file_destination() returns FALSE then $replace == FILE_EXISTS_ERROR and
// there's an existing file so we need to bail.
if ($file->destination === FALSE) {
@@ -2130,9 +2151,33 @@ function file_download_access($uri) {
* 'filename', and 'name' members corresponding to the matching files.
*/
function file_scan_directory($dir, $mask, $options = array(), $depth = 0) {
// Default nomask option.
$nomask = '/(\.\.?|CVS)$/';
// Overrides the $nomask variable accordingly if $options['nomask'] is set.
//
// Allow directories specified in settings.php to be ignored. You can use this
// to not check for files in common special-purpose directories. For example,
// node_modules and bower_components. Ignoring irrelevant directories is a
// performance boost.
if (!isset($options['nomask'])) {
$ignore_directories = variable_get(
'file_scan_ignore_directories',
array()
);
foreach ($ignore_directories as $index => $ignore_directory) {
$ignore_directories[$index] = preg_quote($ignore_directory, '/');
}
if (!empty($ignore_directories)) {
$nomask = '/^(\.\.?)|CVS|' . implode('|', $ignore_directories) . '$/';
}
}
// Merge in defaults.
$options += array(
'nomask' => '/(\.\.?|CVS)$/',
'nomask' => $nomask,
'callback' => 0,
'recurse' => TRUE,
'key' => 'uri',
+41
View File
@@ -0,0 +1,41 @@
<?php
use Drupal\Core\Security\PharExtensionInterceptor;
use TYPO3\PharStreamWrapper\Manager as PharStreamWrapperManager;
use TYPO3\PharStreamWrapper\Behavior as PharStreamWrapperBehavior;
use TYPO3\PharStreamWrapper\PharStreamWrapper;
/**
* Registers a phar stream wrapper that is more secure than PHP's built-in one.
*
* @see file_get_stream_wrappers()
*/
function file_register_phar_wrapper() {
$directory = DRUPAL_ROOT . '/misc/typo3/phar-stream-wrapper/src';
include_once $directory . '/Assertable.php';
include_once $directory . '/Behavior.php';
include_once $directory . '/Exception.php';
include_once $directory . '/Helper.php';
include_once $directory . '/Manager.php';
include_once $directory . '/PharStreamWrapper.php';
include_once DRUPAL_ROOT . '/misc/typo3/drupal-security/PharExtensionInterceptor.php';
// Set up a stream wrapper to handle insecurities due to PHP's built-in
// phar stream wrapper.
try {
$behavior = new PharStreamWrapperBehavior();
PharStreamWrapperManager::initialize(
$behavior->withAssertion(new PharExtensionInterceptor())
);
}
catch (\LogicException $e) {
// Continue if the PharStreamWrapperManager is already initialized.
// For example, this occurs following a drupal_static_reset(), such
// as during tests.
};
// To prevent file_stream_wrapper_valid_scheme() treating "phar" as a valid
// scheme, this is registered with PHP only, not with hook_stream_wrappers()
// or the internal storage of file_get_stream_wrappers().
stream_wrapper_register('phar', '\\TYPO3\\PharStreamWrapper\\PharStreamWrapper');
}
+8 -4
View File
@@ -555,8 +555,10 @@ function form_get_cache($form_build_id, &$form_state) {
* Stores a form in the cache.
*/
function form_set_cache($form_build_id, $form, $form_state) {
// 6 hours cache life time for forms should be plenty.
$expire = 21600;
// The default cache_form expiration is 6 hours. On busy sites, the cache_form
// table can become very large. A shorter cache lifetime can help to keep the
// table's size under control.
$expire = variable_get('form_cache_expiration', 21600);
// Ensure that the form build_id embedded in the form structure is the same as
// the one passed in as a parameter. This is an additional safety measure to
@@ -1438,10 +1440,12 @@ function _form_validate(&$elements, &$form_state, $form_id = NULL) {
// length if it's a string, and the item count if it's an array.
// An unchecked checkbox has a #value of integer 0, different than string
// '0', which could be a valid value.
$is_empty_multiple = (!count($elements['#value']));
$is_countable = is_array($elements['#value']) || $elements['#value'] instanceof Countable;
$is_empty_multiple = $is_countable && count($elements['#value']) == 0;
$is_empty_string = (is_string($elements['#value']) && drupal_strlen(trim($elements['#value'])) == 0);
$is_empty_value = ($elements['#value'] === 0);
if ($is_empty_multiple || $is_empty_string || $is_empty_value) {
$is_empty_null = is_null($elements['#value']);
if ($is_empty_multiple || $is_empty_string || $is_empty_value || $is_empty_null) {
// Although discouraged, a #title is not mandatory for form elements. In
// case there is no #title, we cannot set a form error message.
// Instead of setting no #title, form constructors are encouraged to set
+1 -1
View File
@@ -779,7 +779,7 @@ function drupal_uninstall_modules($module_list = array(), $uninstall_dependents
$module_list = array_flip(array_values($module_list));
$profile = drupal_get_profile();
while (list($module) = each($module_list)) {
foreach (array_keys($module_list) as $module) {
if (!isset($module_data[$module]) || drupal_get_installed_schema_version($module) == SCHEMA_UNINSTALLED) {
// This module doesn't exist or is already uninstalled. Skip it.
unset($module_list[$module]);
+6 -3
View File
@@ -576,7 +576,8 @@ function _menu_load_objects(&$item, &$map) {
// 'load arguments' in the hook_menu() entry, but they need
// some processing. In this case the $function is the key to the
// load_function array, and the value is the list of arguments.
list($function, $args) = each($function);
$args = current($function);
$function = key($function);
$load_functions[$index] = $function;
// Some arguments are placeholders for dynamic items to process.
@@ -2402,7 +2403,8 @@ function menu_set_active_trail($new_trail = NULL) {
// a stripped down menu tree containing the active trail only, in case
// the given menu has not been built in this request yet.
$tree = menu_tree_page_data($preferred_link['menu_name'], NULL, TRUE);
list($key, $curr) = each($tree);
$curr = current($tree);
next($tree);
}
// There is no link for the current path.
else {
@@ -2432,7 +2434,8 @@ function menu_set_active_trail($new_trail = NULL) {
}
$tree = $curr['below'] ? $curr['below'] : array();
}
list($key, $curr) = each($tree);
$curr = current($tree);
next($tree);
}
// Make sure the current page is in the trail to build the page title, by
// appending either the preferred link or the menu router item for the
+10 -2
View File
@@ -404,7 +404,11 @@ function module_enable($module_list, $enable_dependencies = TRUE) {
// Create an associative array with weights as values.
$module_list = array_flip(array_values($module_list));
while (list($module) = each($module_list)) {
// The array is iterated over manually (instead of using a foreach) because
// modules may be added to the list within the loop and we need to process
// them.
while ($module = key($module_list)) {
next($module_list);
if (!isset($module_data[$module])) {
// This module is not found in the filesystem, abort.
return FALSE;
@@ -540,7 +544,11 @@ function module_disable($module_list, $disable_dependents = TRUE) {
$module_list = array_flip(array_values($module_list));
$profile = drupal_get_profile();
while (list($module) = each($module_list)) {
// The array is iterated over manually (instead of using a foreach) because
// modules may be added to the list within the loop and we need to process
// them.
while ($module = key($module_list)) {
next($module_list);
if (!isset($module_data[$module]) || !$module_data[$module]->status) {
// This module doesn't exist or is already disabled, skip it.
unset($module_list[$module]);
+30 -5
View File
@@ -19,7 +19,6 @@
* Does the work for registry_update().
*/
function _registry_update() {
// The registry serves as a central autoloader for all classes, including
// the database query builders. However, the registry rebuild process
// requires write ability to the database, which means having access to the
@@ -33,6 +32,11 @@ function _registry_update() {
require_once DRUPAL_ROOT . '/includes/database/select.inc';
require_once DRUPAL_ROOT . '/includes/database/' . $driver . '/query.inc';
// During the first registry rebuild in a request, we check all the files.
// During subsequent rebuilds, we only add new files. It makes the rebuilding
// process faster during installation of modules.
static $check_existing_files = TRUE;
// Get current list of modules and their files.
$modules = db_query("SELECT * FROM {system} WHERE type = 'module'")->fetchAll();
// Get the list of files we are going to parse.
@@ -55,6 +59,9 @@ function _registry_update() {
$files["$filename"] = array('module' => '', 'weight' => 0);
}
// Initialize an empty array for the unchanged files.
$unchanged_files = array();
$transaction = db_transaction();
try {
// Allow modules to manually modify the list of files before the registry
@@ -63,10 +70,19 @@ function _registry_update() {
// list can then be added to the list of files that the registry will parse,
// or modify attributes of a file.
drupal_alter('registry_files', $files, $modules);
foreach (registry_get_parsed_files() as $filename => $file) {
// Add the hash for those files we have already parsed.
if (isset($files[$filename])) {
$files[$filename]['hash'] = $file['hash'];
if ($check_existing_files === TRUE) {
$files[$filename]['hash'] = $file['hash'];
}
else {
// Ignore that file for this request, it has been parsed previously
// and it is unlikely it has changed.
unset($files[$filename]);
$unchanged_files[$filename] = $file;
}
}
else {
// Flush the registry of resources in files that are no longer on disc
@@ -79,8 +95,12 @@ function _registry_update() {
->execute();
}
}
$parsed_files = _registry_parse_files($files);
// Add unchanged files to the files.
$files += $unchanged_files;
$unchanged_resources = array();
$lookup_cache = array();
if ($cache = cache_get('lookup_cache', 'cache_bootstrap')) {
@@ -89,12 +109,10 @@ function _registry_update() {
foreach ($lookup_cache as $key => $file) {
// If the file for this cached resource is carried over unchanged from
// the last registry build, then we can safely re-cache it.
if ($file && in_array($file, array_keys($files)) && !in_array($file, $parsed_files)) {
if ($file && isset($files[$file]) && !in_array($file, $parsed_files, TRUE)) {
$unchanged_resources[$key] = $file;
}
}
module_implements('', FALSE, TRUE);
_registry_check_code(REGISTRY_RESET_LOOKUP_CACHE);
}
catch (Exception $e) {
$transaction->rollback();
@@ -102,6 +120,13 @@ function _registry_update() {
throw $e;
}
module_implements('', FALSE, TRUE);
_registry_check_code(REGISTRY_RESET_LOOKUP_CACHE);
// During the next run in this request, don't bother re-checking existing
// files.
$check_existing_files = FALSE;
// We have some unchanged resources, warm up the cache - no need to pay
// for looking them up again.
if (count($unchanged_resources) > 0) {
+32
View File
@@ -51,6 +51,38 @@ class DrupalRequestSanitizer {
}
}
/**
* Removes the destination if it is dangerous.
*
* Note this can only be called after common.inc has been included.
*
* @return bool
* TRUE if the destination has been removed from $_GET, FALSE if not.
*/
public static function cleanDestination() {
$dangerous_keys = array();
$log_sanitized_keys = variable_get('sanitize_input_logging', FALSE);
$parts = drupal_parse_url($_GET['destination']);
// If there is a query string, check its query parameters.
if (!empty($parts['query'])) {
$whitelist = variable_get('sanitize_input_whitelist', array());
self::stripDangerousValues($parts['query'], $whitelist, $dangerous_keys);
if (!empty($dangerous_keys)) {
// The destination is removed rather than sanitized to mirror the
// handling of external destinations.
unset($_GET['destination']);
unset($_REQUEST['destination']);
if ($log_sanitized_keys) {
trigger_error(format_string('Potentially unsafe destination removed from query string parameters (GET) because it contained the following keys: @keys', array('@keys' => implode(', ', $dangerous_keys))));
}
return TRUE;
}
}
return FALSE;
}
/**
* Strips dangerous keys from the provided input.
*
+19 -17
View File
@@ -1776,13 +1776,13 @@ function theme_link($variables) {
* http://www.w3.org/TR/WCAG-TECHS/H42.html for more information.
*/
function theme_links($variables) {
$links = $variables['links'];
$attributes = $variables['attributes'];
$links = (array) $variables['links'];
$attributes = (array) $variables['attributes'];
$heading = $variables['heading'];
global $language_url;
$output = '';
if (count($links) > 0) {
if (!empty($links)) {
// Treat the heading first if it is present to prepend it to the
// list of links.
if (!empty($heading)) {
@@ -1995,7 +1995,7 @@ function theme_table($variables) {
$empty = $variables['empty'];
// Add sticky headers, if applicable.
if (count($header) && $sticky) {
if (!empty($header) && $sticky) {
drupal_add_js('misc/tableheader.js');
// Add 'sticky-enabled' class to the table to identify it for JS.
// This is needed to target tables constructed by this function.
@@ -2009,7 +2009,7 @@ function theme_table($variables) {
}
// Format the table columns:
if (count($colgroups)) {
if (!empty($colgroups)) {
foreach ($colgroups as $number => $colgroup) {
$attributes = array();
@@ -2044,38 +2044,40 @@ function theme_table($variables) {
}
// Add the 'empty' row message if available.
if (!count($rows) && $empty) {
if (empty($rows) && $empty) {
$header_count = 0;
foreach ($header as $header_cell) {
if (is_array($header_cell)) {
$header_count += isset($header_cell['colspan']) ? $header_cell['colspan'] : 1;
}
else {
$header_count++;
if (!empty($header)) {
foreach ($header as $header_cell) {
if (is_array($header_cell)) {
$header_count += isset($header_cell['colspan']) ? $header_cell['colspan'] : 1;
}
else {
$header_count++;
}
}
}
$rows[] = array(array('data' => $empty, 'colspan' => $header_count, 'class' => array('empty', 'message')));
}
// Format the table header:
if (count($header)) {
if (!empty($header)) {
$ts = tablesort_init($header);
// HTML requires that the thead tag has tr tags in it followed by tbody
// tags. Using ternary operator to check and see if we have any rows.
$output .= (count($rows) ? ' <thead><tr>' : ' <tr>');
$output .= (!empty($rows) ? ' <thead><tr>' : ' <tr>');
foreach ($header as $cell) {
$cell = tablesort_header($cell, $header, $ts);
$output .= _theme_table_cell($cell, TRUE);
}
// Using ternary operator to close the tags based on whether or not there are rows
$output .= (count($rows) ? " </tr></thead>\n" : "</tr>\n");
$output .= (!empty($rows) ? " </tr></thead>\n" : "</tr>\n");
}
else {
$ts = array();
}
// Format the table rows:
if (count($rows)) {
if (!empty($rows)) {
$output .= "<tbody>\n";
$flip = array('even' => 'odd', 'odd' => 'even');
$class = 'even';
@@ -2095,7 +2097,7 @@ function theme_table($variables) {
$attributes = array();
$no_striping = FALSE;
}
if (count($cells)) {
if (!empty($cells)) {
// Add odd/even class
if (!$no_striping) {
$class = $flip[$class];
+112
View File
@@ -0,0 +1,112 @@
/**
* For jQuery versions less than 3.4.0, this replaces the jQuery.extend
* function with the one from jQuery 3.4.0, slightly modified (documented
* below) to be compatible with older jQuery versions and browsers.
*
* This provides the Object.prototype pollution vulnerability fix to Drupal
* installations running older jQuery versions, including the versions shipped
* with Drupal core and https://www.drupal.org/project/jquery_update.
*
* @see https://github.com/jquery/jquery/pull/4333
*/
(function (jQuery) {
// Do not override jQuery.extend() if the jQuery version is already >=3.4.0.
var versionParts = jQuery.fn.jquery.split('.');
var majorVersion = parseInt(versionParts[0]);
var minorVersion = parseInt(versionParts[1]);
var patchVersion = parseInt(versionParts[2]);
var isPreReleaseVersion = (patchVersion.toString() !== versionParts[2]);
if (
(majorVersion > 3) ||
(majorVersion === 3 && minorVersion > 4) ||
(majorVersion === 3 && minorVersion === 4 && patchVersion > 0) ||
(majorVersion === 3 && minorVersion === 4 && patchVersion === 0 && !isPreReleaseVersion)
) {
return;
}
/**
* This is almost verbatim copied from jQuery 3.4.0.
*
* Only two minor changes have been made:
* - The call to isFunction() is changed to jQuery.isFunction().
* - The two calls to Array.isArray() is changed to jQuery.isArray().
*
* The above two changes ensure compatibility with all older jQuery versions
* (1.4.4 - 3.3.1) and older browser versions (e.g., IE8).
*/
jQuery.extend = jQuery.fn.extend = function() {
var options, name, src, copy, copyIsArray, clone,
target = arguments[ 0 ] || {},
i = 1,
length = arguments.length,
deep = false;
// Handle a deep copy situation
if ( typeof target === "boolean" ) {
deep = target;
// Skip the boolean and the target
target = arguments[ i ] || {};
i++;
}
// Handle case when target is a string or something (possible in deep copy)
if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {
target = {};
}
// Extend jQuery itself if only one argument is passed
if ( i === length ) {
target = this;
i--;
}
for ( ; i < length; i++ ) {
// Only deal with non-null/undefined values
if ( ( options = arguments[ i ] ) != null ) {
// Extend the base object
for ( name in options ) {
copy = options[ name ];
// Prevent Object.prototype pollution
// Prevent never-ending loop
if ( name === "__proto__" || target === copy ) {
continue;
}
// Recurse if we're merging plain objects or arrays
if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
( copyIsArray = jQuery.isArray( copy ) ) ) ) {
src = target[ name ];
// Ensure proper type for the source value
if ( copyIsArray && !jQuery.isArray( src ) ) {
clone = [];
} else if ( !copyIsArray && !jQuery.isPlainObject( src ) ) {
clone = {};
} else {
clone = src;
}
copyIsArray = false;
// Never move original objects, clone them
target[ name ] = jQuery.extend( deep, clone, copy );
// Don't bring in undefined values
} else if ( copy !== undefined ) {
target[ name ] = copy;
}
}
}
}
// Return the modified object
return target;
};
})(jQuery);
+34 -12
View File
@@ -580,21 +580,43 @@ Drupal.tableDrag.prototype.dropRow = function (event, self) {
* Get the mouse coordinates from the event (allowing for browser differences).
*/
Drupal.tableDrag.prototype.mouseCoords = function (event) {
// Complete support for pointer events was only introduced to jQuery in
// version 1.11.1; between versions 1.7 and 1.11.0 pointer events have the
// clientX and clientY properties undefined. In those cases, the properties
// must be retrieved from the event.originalEvent object instead.
var clientX = event.clientX || event.originalEvent.clientX;
var clientY = event.clientY || event.originalEvent.clientY;
if (event.pageX || event.pageY) {
return { x: event.pageX, y: event.pageY };
// Match both null and undefined, but not zero, by using != null.
// See https://stackoverflow.com/questions/2647867/how-to-determine-if-variable-is-undefined-or-null
if (event.pageX != null && event.pageY != null) {
return {x: event.pageX, y: event.pageY};
}
return {
x: clientX + document.body.scrollLeft - document.body.clientLeft,
y: clientY + document.body.scrollTop - document.body.clientTop
};
// Complete support for pointer events was only introduced to jQuery in
// version 1.11.1; between versions 1.7 and 1.11.0 pointer events have the
// pageX and pageY properties undefined. In those cases, the properties must
// be retrieved from the event.originalEvent object instead.
if (event.originalEvent && event.originalEvent.pageX != null && event.originalEvent.pageY != null) {
return {x: event.originalEvent.pageX, y: event.originalEvent.pageY};
}
// Some old browsers do not support MouseEvent.pageX and *.pageY at all.
// See https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageY
// For those, we look at event.clientX and event.clientY instead.
if (event.clientX == null || event.clientY == null) {
// In some jQuery versions, some events created by jQuery do not have
// clientX and clientY. But the original event might have.
if (!event.originalEvent) {
throw new Error("The event has no coordinates, and no event.originalEvent.");
}
event = event.originalEvent;
if (event.clientX == null || event.clientY == null) {
throw new Error("The original event has no coordinates.");
}
}
// Copied from jQuery.event.fix() in jQuery 1.4.1.
// In newer jQuery versions, this code is in jQuery.event.mouseHooks.filter().
var doc = document.documentElement, body = document.body;
var pageX = event.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
var pageY = event.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
return {x: pageX, y: pageY};
};
/**
@@ -0,0 +1,79 @@
<?php
namespace Drupal\Core\Security;
use TYPO3\PharStreamWrapper\Assertable;
use TYPO3\PharStreamWrapper\Helper;
use TYPO3\PharStreamWrapper\Exception;
/**
* An alternate PharExtensionInterceptor to support phar-based CLI tools.
*
* @see \TYPO3\PharStreamWrapper\Interceptor\PharExtensionInterceptor
*/
class PharExtensionInterceptor implements Assertable {
/**
* Determines whether phar file is allowed to execute.
*
* The phar file is allowed to execute if:
* - the base file name has a ".phar" suffix.
* - it is the CLI tool that has invoked the interceptor.
*
* @param string $path
* The path of the phar file to check.
* @param string $command
* The command being carried out.
*
* @return bool
* TRUE if the phar file is allowed to execute.
*
* @throws Exception
* Thrown when the file is not allowed to execute.
*/
public function assert($path, $command) {
if ($this->baseFileContainsPharExtension($path)) {
return TRUE;
}
throw new Exception(
sprintf(
'Unexpected file extension in "%s"',
$path
),
1535198703
);
}
/**
* Determines if a path has a .phar extension or invoked execution.
*
* @param string $path
* The path of the phar file to check.
*
* @return bool
* TRUE if the file has a .phar extension or if the execution has been
* invoked by the phar file.
*/
private function baseFileContainsPharExtension($path) {
$baseFile = Helper::determineBaseFile($path);
if ($baseFile === NULL) {
return FALSE;
}
// If the stream wrapper is registered by invoking a phar file that does
// not not have .phar extension then this should be allowed. For
// example, some CLI tools recommend removing the extension.
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
// Find the last entry in the backtrace containing a 'file' key as
// sometimes the last caller is executed outside the scope of a file. For
// example, this occurs with shutdown functions.
do {
$caller = array_pop($backtrace);
} while (empty($caller['file']) && !empty($backtrace));
if (isset($caller['file']) && $baseFile === Helper::determineBaseFile($caller['file'])) {
return TRUE;
}
$fileExtension = pathinfo($baseFile, PATHINFO_EXTENSION);
return strtolower($fileExtension) === 'phar';
}
}
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 TYPO3 project - https://typo3.org/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+155
View File
@@ -0,0 +1,155 @@
[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/TYPO3/phar-stream-wrapper/badges/quality-score.png?b=v2)](https://scrutinizer-ci.com/g/TYPO3/phar-stream-wrapper/?branch=v2)
[![Travis CI Build Status](https://travis-ci.org/TYPO3/phar-stream-wrapper.svg?branch=v2)](https://travis-ci.org/TYPO3/phar-stream-wrapper)
# PHP Phar Stream Wrapper
## Abstract & History
Based on Sam Thomas' findings concerning
[insecure deserialization in combination with obfuscation strategies](https://blog.secarma.co.uk/labs/near-phar-dangerous-unserialization-wherever-you-are)
allowing to hide Phar files inside valid image resources, the TYPO3 project
decided back then to introduce a `PharStreamWrapper` to intercept invocations
of the `phar://` stream in PHP and only allow usage for defined locations in
the file system.
Since the TYPO3 mission statement is **inspiring people to share**, we thought
it would be helpful for others to release our `PharStreamWrapper` as standalone
package to the PHP community.
The mentioned security issue was reported to TYPO3 on 10th June 2018 by Sam Thomas
and has been addressed concerning the specific attack vector and for this generic
`PharStreamWrapper` in TYPO3 versions 7.6.30 LTS, 8.7.17 LTS and 9.3.1 on 12th
July 2018.
* https://typo3.org/security/advisory/typo3-core-sa-2018-002/
* https://blog.secarma.co.uk/labs/near-phar-dangerous-unserialization-wherever-you-are
* https://youtu.be/GePBmsNJw6Y
## License
In general the TYPO3 core is released under the GNU General Public License version
2 or any later version (`GPL-2.0-or-later`). In order to avoid licensing issues and
incompatibilities this `PharStreamWrapper` is licenced under the MIT License. In case
you duplicate or modify source code, credits are not required but really appreciated.
## Credits
Thanks to [Alex Pott](https://github.com/alexpott), Drupal for creating
back-ports of all sources in order to provide compatibility with PHP v5.3.
## Installation
The `PharStreamWrapper` is provided as composer package `typo3/phar-stream-wrapper`
and has minimum requirements of PHP v5.3 ([`v2`](https://github.com/TYPO3/phar-stream-wrapper/tree/v2) branch) and PHP v7.0 ([`master`](https://github.com/TYPO3/phar-stream-wrapper) branch).
### Installation for PHP v7.0
```
composer require typo3/phar-stream-wrapper ^3.0
```
### Installation for PHP v5.3
```
composer require typo3/phar-stream-wrapper ^2.0
```
## Example
The following example is bundled within this package, the shown
`PharExtensionInterceptor` denies all stream wrapper invocations files
not having the `.phar` suffix. Interceptor logic has to be individual and
adjusted to according requirements.
```
$behavior = new \TYPO3\PharStreamWrapper\Behavior();
Manager::initialize(
$behavior->withAssertion(new PharExtensionInterceptor())
);
if (in_array('phar', stream_get_wrappers())) {
stream_wrapper_unregister('phar');
stream_wrapper_register('phar', 'TYPO3\\PharStreamWrapper\\PharStreamWrapper');
}
```
* `PharStreamWrapper` defined as class reference will be instantiated each time
`phar://` streams shall be processed.
* `Manager` as singleton pattern being called by `PharStreamWrapper` instances
in order to retrieve individual behavior and settings.
* `Behavior` holds reference to interceptor(s) that shall assert correct/allowed
invocation of a given `$path` for a given `$command`. Interceptors implement
the interface `Assertable`. Interceptors can act individually on following
commands or handle all of them in case not defined specifically:
+ `COMMAND_DIR_OPENDIR`
+ `COMMAND_MKDIR`
+ `COMMAND_RENAME`
+ `COMMAND_RMDIR`
+ `COMMAND_STEAM_METADATA`
+ `COMMAND_STREAM_OPEN`
+ `COMMAND_UNLINK`
+ `COMMAND_URL_STAT`
## Interceptor
The following interceptor is shipped with the package and ready to use in order
to block any Phar invocation of files not having a `.phar` suffix. Besides that
individual interceptors are possible of course.
```
class PharExtensionInterceptor implements Assertable
{
/**
* Determines whether the base file name has a ".phar" suffix.
*
* @param string $path
* @param string $command
* @return bool
* @throws Exception
*/
public function assert($path, $command)
{
if ($this->baseFileContainsPharExtension($path)) {
return true;
}
throw new Exception(
sprintf(
'Unexpected file extension in "%s"',
$path
),
1535198703
);
}
/**
* @param string $path
* @return bool
*/
private function baseFileContainsPharExtension($path)
{
$baseFile = Helper::determineBaseFile($path);
if ($baseFile === null) {
return false;
}
$fileExtension = pathinfo($baseFile, PATHINFO_EXTENSION);
return strtolower($fileExtension) === 'phar';
}
}
```
## Helper
* `Helper::determineBaseFile(string $path)`: Determines base file that can be
accessed using the regular file system. For instance the following path
`phar:///home/user/bundle.phar/content.txt` would be resolved to
`/home/user/bundle.phar`.
* `Helper::resetOpCache()`: Resets PHP's OPcache if enabled as work-around for
issues in `include()` or `require()` calls and OPcache delivering wrong
results. More details can be found in PHP's bug tracker, for instance like
https://bugs.php.net/bug.php?id=66569
## Security Contact
In case of finding additional security issues in the TYPO3 project or in this
`PharStreamWrapper` package in particular, please get in touch with the
[TYPO3 Security Team](mailto:security@typo3.org).
@@ -0,0 +1,24 @@
{
"name": "typo3/phar-stream-wrapper",
"description": "Interceptors for PHP's native phar:// stream handling",
"type": "library",
"license": "MIT",
"homepage": "https://typo3.org/",
"keywords": ["php", "phar", "stream-wrapper", "security"],
"require": {
"php": "^5.3.3|^7.0"
},
"require-dev": {
"phpunit/phpunit": "^4.8.36"
},
"autoload": {
"psr-4": {
"TYPO3\\PharStreamWrapper\\": "src/"
}
},
"autoload-dev": {
"psr-4": {
"TYPO3\\PharStreamWrapper\\Tests\\": "tests/"
}
}
}
@@ -0,0 +1,22 @@
<?php
namespace TYPO3\PharStreamWrapper;
/*
* This file is part of the TYPO3 project.
*
* It is free software; you can redistribute it and/or modify it under the terms
* of the MIT License (MIT). For the full copyright and license information,
* please read the LICENSE file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
interface Assertable
{
/**
* @param string $path
* @param string $command
* @return bool
*/
public function assert($path, $command);
}
@@ -0,0 +1,124 @@
<?php
namespace TYPO3\PharStreamWrapper;
/*
* This file is part of the TYPO3 project.
*
* It is free software; you can redistribute it and/or modify it under the terms
* of the MIT License (MIT). For the full copyright and license information,
* please read the LICENSE file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
class Behavior implements Assertable
{
const COMMAND_DIR_OPENDIR = 'dir_opendir';
const COMMAND_MKDIR = 'mkdir';
const COMMAND_RENAME = 'rename';
const COMMAND_RMDIR = 'rmdir';
const COMMAND_STEAM_METADATA = 'stream_metadata';
const COMMAND_STREAM_OPEN = 'stream_open';
const COMMAND_UNLINK = 'unlink';
const COMMAND_URL_STAT = 'url_stat';
/**
* @var string[]
*/
private $availableCommands = array(
self::COMMAND_DIR_OPENDIR,
self::COMMAND_MKDIR,
self::COMMAND_RENAME,
self::COMMAND_RMDIR,
self::COMMAND_STEAM_METADATA,
self::COMMAND_STREAM_OPEN,
self::COMMAND_UNLINK,
self::COMMAND_URL_STAT,
);
/**
* @var Assertable[]
*/
private $assertions;
/**
* @param Assertable $assertable
* @return static
*/
public function withAssertion(Assertable $assertable)
{
$commands = func_get_args();
array_shift($commands);
$this->assertCommands($commands);
$commands = $commands ?: $this->availableCommands;
$target = clone $this;
foreach ($commands as $command) {
$target->assertions[$command] = $assertable;
}
return $target;
}
/**
* @param string $path
* @param string $command
* @return bool
*/
public function assert($path, $command)
{
$this->assertCommand($command);
$this->assertAssertionCompleteness();
return $this->assertions[$command]->assert($path, $command);
}
/**
* @param array $commands
*/
private function assertCommands(array $commands)
{
$unknownCommands = array_diff($commands, $this->availableCommands);
if (empty($unknownCommands)) {
return;
}
throw new \LogicException(
sprintf(
'Unknown commands: %s',
implode(', ', $unknownCommands)
),
1535189881
);
}
private function assertCommand($command)
{
if (in_array($command, $this->availableCommands, true)) {
return;
}
throw new \LogicException(
sprintf(
'Unknown command "%s"',
$command
),
1535189882
);
}
private function assertAssertionCompleteness()
{
$undefinedAssertions = array_diff(
$this->availableCommands,
array_keys($this->assertions)
);
if (empty($undefinedAssertions)) {
return;
}
throw new \LogicException(
sprintf(
'Missing assertions for commands: %s',
implode(', ', $undefinedAssertions)
),
1535189883
);
}
}
@@ -0,0 +1,16 @@
<?php
namespace TYPO3\PharStreamWrapper;
/*
* This file is part of the TYPO3 project.
*
* It is free software; you can redistribute it and/or modify it under the terms
* of the MIT License (MIT). For the full copyright and license information,
* please read the LICENSE file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
class Exception extends \RuntimeException
{
}
@@ -0,0 +1,183 @@
<?php
namespace TYPO3\PharStreamWrapper;
/*
* This file is part of the TYPO3 project.
*
* It is free software; you can redistribute it and/or modify it under the terms
* of the MIT License (MIT). For the full copyright and license information,
* please read the LICENSE file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
class Helper
{
/*
* Resets PHP's OPcache if enabled as work-around for issues in `include()`
* or `require()` calls and OPcache delivering wrong results.
*
* @see https://bugs.php.net/bug.php?id=66569
*/
public static function resetOpCache()
{
if (function_exists('opcache_reset')
&& function_exists('opcache_get_status')
) {
$status = opcache_get_status();
if (!empty($status['opcache_enabled'])) {
opcache_reset();
}
}
}
/**
* Determines base file that can be accessed using the regular file system.
* For e.g. "phar:///home/user/bundle.phar/content.txt" that would result
* into "/home/user/bundle.phar".
*
* @param string $path
* @return string|null
*/
public static function determineBaseFile($path)
{
$parts = explode('/', static::normalizePath($path));
while (count($parts)) {
$currentPath = implode('/', $parts);
if (@is_file($currentPath)) {
return $currentPath;
}
array_pop($parts);
}
return null;
}
/**
* @param string $path
* @return string
*/
public static function removePharPrefix($path)
{
$path = trim($path);
if (stripos($path, 'phar://') !== 0) {
return $path;
}
return substr($path, 7);
}
/**
* Normalizes a path, removes phar:// prefix, fixes Windows directory
* separators. Result is without trailing slash.
*
* @param string $path
* @return string
*/
public static function normalizePath($path)
{
return rtrim(
static::getCanonicalPath(
static::removePharPrefix($path)
),
'/'
);
}
/**
* Fixes a path for windows-backslashes and reduces double-slashes to single slashes
*
* @param string $path File path to process
* @return string
*/
private static function normalizeWindowsPath($path)
{
return str_replace('\\', '/', $path);
}
/**
* Resolves all dots, slashes and removes spaces after or before a path...
*
* @param string $path Input string
* @return string Canonical path, always without trailing slash
*/
private static function getCanonicalPath($path)
{
$path = static::normalizeWindowsPath($path);
$absolutePathPrefix = '';
if (static::isAbsolutePath($path)) {
if (static::isWindows() && strpos($path, ':/') === 1) {
$absolutePathPrefix = substr($path, 0, 3);
$path = substr($path, 3);
} else {
$path = ltrim($path, '/');
$absolutePathPrefix = '/';
}
}
$pathParts = explode('/', $path);
$pathPartsLength = count($pathParts);
for ($partCount = 0; $partCount < $pathPartsLength; $partCount++) {
// double-slashes in path: remove element
if ($pathParts[$partCount] === '') {
array_splice($pathParts, $partCount, 1);
$partCount--;
$pathPartsLength--;
}
// "." in path: remove element
if ((isset($pathParts[$partCount]) ? $pathParts[$partCount] : '') === '.') {
array_splice($pathParts, $partCount, 1);
$partCount--;
$pathPartsLength--;
}
// ".." in path:
if ((isset($pathParts[$partCount]) ? $pathParts[$partCount] : '') === '..') {
if ($partCount === 0) {
array_splice($pathParts, $partCount, 1);
$partCount--;
$pathPartsLength--;
} elseif ($partCount >= 1) {
// Rremove this and previous element
array_splice($pathParts, $partCount - 1, 2);
$partCount -= 2;
$pathPartsLength -= 2;
} elseif ($absolutePathPrefix) {
// can't go higher than root dir
// simply remove this part and continue
array_splice($pathParts, $partCount, 1);
$partCount--;
$pathPartsLength--;
}
}
}
return $absolutePathPrefix . implode('/', $pathParts);
}
/**
* Checks if the $path is absolute or relative (detecting either '/' or
* 'x:/' as first part of string) and returns TRUE if so.
*
* @param string $path File path to evaluate
* @return bool
*/
private static function isAbsolutePath($path)
{
// Path starting with a / is always absolute, on every system
// On Windows also a path starting with a drive letter is absolute: X:/
return (isset($path[0]) ? $path[0] : null) === '/'
|| static::isWindows() && (
strpos($path, ':/') === 1
|| strpos($path, ':\\') === 1
);
}
/**
* @return bool
*/
private static function isWindows()
{
return stripos(PHP_OS, 'WIN') === 0;
}
}
@@ -0,0 +1,55 @@
<?php
namespace TYPO3\PharStreamWrapper\Interceptor;
/*
* This file is part of the TYPO3 project.
*
* It is free software; you can redistribute it and/or modify it under the terms
* of the MIT License (MIT). For the full copyright and license information,
* please read the LICENSE file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use TYPO3\PharStreamWrapper\Assertable;
use TYPO3\PharStreamWrapper\Helper;
use TYPO3\PharStreamWrapper\Exception;
class PharExtensionInterceptor implements Assertable
{
/**
* Determines whether the base file name has a ".phar" suffix.
*
* @param string $path
* @param string $command
* @return bool
* @throws Exception
*/
public function assert($path, $command)
{
if ($this->baseFileContainsPharExtension($path)) {
return true;
}
throw new Exception(
sprintf(
'Unexpected file extension in "%s"',
$path
),
1535198703
);
}
/**
* @param string $path
* @return bool
*/
private function baseFileContainsPharExtension($path)
{
$baseFile = Helper::determineBaseFile($path);
if ($baseFile === null) {
return false;
}
$fileExtension = pathinfo($baseFile, PATHINFO_EXTENSION);
return strtolower($fileExtension) === 'phar';
}
}
@@ -0,0 +1,85 @@
<?php
namespace TYPO3\PharStreamWrapper;
/*
* This file is part of the TYPO3 project.
*
* It is free software; you can redistribute it and/or modify it under the terms
* of the MIT License (MIT). For the full copyright and license information,
* please read the LICENSE file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
class Manager implements Assertable
{
/**
* @var self
*/
private static $instance;
/**
* @var Behavior
*/
private $behavior;
/**
* @param Behavior $behaviour
* @return self
*/
public static function initialize(Behavior $behaviour)
{
if (self::$instance === null) {
self::$instance = new self($behaviour);
return self::$instance;
}
throw new \LogicException(
'Manager can only be initialized once',
1535189871
);
}
/**
* @return self
*/
public static function instance()
{
if (self::$instance !== null) {
return self::$instance;
}
throw new \LogicException(
'Manager needs to be initialized first',
1535189872
);
}
/**
* @return bool
*/
public static function destroy()
{
if (self::$instance === null) {
return false;
}
self::$instance = null;
return true;
}
/**
* @param Behavior $behaviour
*/
private function __construct(Behavior $behaviour)
{
$this->behavior = $behaviour;
}
/**
* @param string $path
* @param string $command
* @return bool
*/
public function assert($path, $command)
{
return $this->behavior->assert($path, $command);
}
}
@@ -0,0 +1,477 @@
<?php
namespace TYPO3\PharStreamWrapper;
/*
* This file is part of the TYPO3 project.
*
* It is free software; you can redistribute it and/or modify it under the terms
* of the MIT License (MIT). For the full copyright and license information,
* please read the LICENSE file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
class PharStreamWrapper
{
/**
* Internal stream constants that are not exposed to PHP, but used...
* @see https://github.com/php/php-src/blob/e17fc0d73c611ad0207cac8a4a01ded38251a7dc/main/php_streams.h
*/
const STREAM_OPEN_FOR_INCLUDE = 128;
/**
* @var resource
*/
public $context;
/**
* @var resource
*/
protected $internalResource;
/**
* @return bool
*/
public function dir_closedir()
{
if (!is_resource($this->internalResource)) {
return false;
}
$this->invokeInternalStreamWrapper(
'closedir',
$this->internalResource
);
return !is_resource($this->internalResource);
}
/**
* @param string $path
* @param int $options
* @return bool
*/
public function dir_opendir($path, $options)
{
$this->assert($path, Behavior::COMMAND_DIR_OPENDIR);
$this->internalResource = $this->invokeInternalStreamWrapper(
'opendir',
$path,
$this->context
);
return is_resource($this->internalResource);
}
/**
* @return string|false
*/
public function dir_readdir()
{
return $this->invokeInternalStreamWrapper(
'readdir',
$this->internalResource
);
}
/**
* @return bool
*/
public function dir_rewinddir()
{
if (!is_resource($this->internalResource)) {
return false;
}
$this->invokeInternalStreamWrapper(
'rewinddir',
$this->internalResource
);
return is_resource($this->internalResource);
}
/**
* @param string $path
* @param int $mode
* @param int $options
* @return bool
*/
public function mkdir($path, $mode, $options)
{
$this->assert($path, Behavior::COMMAND_MKDIR);
return $this->invokeInternalStreamWrapper(
'mkdir',
$path,
$mode,
(bool) ($options & STREAM_MKDIR_RECURSIVE),
$this->context
);
}
/**
* @param string $path_from
* @param string $path_to
* @return bool
*/
public function rename($path_from, $path_to)
{
$this->assert($path_from, Behavior::COMMAND_RENAME);
$this->assert($path_to, Behavior::COMMAND_RENAME);
return $this->invokeInternalStreamWrapper(
'rename',
$path_from,
$path_to,
$this->context
);
}
/**
* @param string $path
* @param int $options
* @return bool
*/
public function rmdir($path, $options)
{
$this->assert($path, Behavior::COMMAND_RMDIR);
return $this->invokeInternalStreamWrapper(
'rmdir',
$path,
$this->context
);
}
/**
* @param int $cast_as
*/
public function stream_cast($cast_as)
{
throw new Exception(
'Method stream_select() cannot be used',
1530103999
);
}
public function stream_close()
{
$this->invokeInternalStreamWrapper(
'fclose',
$this->internalResource
);
}
/**
* @return bool
*/
public function stream_eof()
{
return $this->invokeInternalStreamWrapper(
'feof',
$this->internalResource
);
}
/**
* @return bool
*/
public function stream_flush()
{
return $this->invokeInternalStreamWrapper(
'fflush',
$this->internalResource
);
}
/**
* @param int $operation
* @return bool
*/
public function stream_lock($operation)
{
return $this->invokeInternalStreamWrapper(
'flock',
$this->internalResource,
$operation
);
}
/**
* @param string $path
* @param int $option
* @param string|int $value
* @return bool
*/
public function stream_metadata($path, $option, $value)
{
$this->assert($path, Behavior::COMMAND_STEAM_METADATA);
if ($option === STREAM_META_TOUCH) {
return call_user_func_array(
array($this, 'invokeInternalStreamWrapper'),
array_merge(array('touch', $path), (array) $value)
);
}
if ($option === STREAM_META_OWNER_NAME || $option === STREAM_META_OWNER) {
return $this->invokeInternalStreamWrapper(
'chown',
$path,
$value
);
}
if ($option === STREAM_META_GROUP_NAME || $option === STREAM_META_GROUP) {
return $this->invokeInternalStreamWrapper(
'chgrp',
$path,
$value
);
}
if ($option === STREAM_META_ACCESS) {
return $this->invokeInternalStreamWrapper(
'chmod',
$path,
$value
);
}
return false;
}
/**
* @param string $path
* @param string $mode
* @param int $options
* @param string|null $opened_path
* @return bool
*/
public function stream_open(
$path,
$mode,
$options,
&$opened_path = null
) {
$this->assert($path, Behavior::COMMAND_STREAM_OPEN);
$arguments = array($path, $mode, (bool) ($options & STREAM_USE_PATH));
// only add stream context for non include/require calls
if (!($options & static::STREAM_OPEN_FOR_INCLUDE)) {
$arguments[] = $this->context;
// work around https://bugs.php.net/bug.php?id=66569
// for including files from Phar stream with OPcache enabled
} else {
Helper::resetOpCache();
}
$this->internalResource = call_user_func_array(
array($this, 'invokeInternalStreamWrapper'),
array_merge(array('fopen'), $arguments)
);
if (!is_resource($this->internalResource)) {
return false;
}
if ($opened_path !== null) {
$metaData = stream_get_meta_data($this->internalResource);
$opened_path = $metaData['uri'];
}
return true;
}
/**
* @param int $count
* @return string
*/
public function stream_read($count)
{
return $this->invokeInternalStreamWrapper(
'fread',
$this->internalResource,
$count
);
}
/**
* @param int $offset
* @param int $whence
* @return bool
*/
public function stream_seek($offset, $whence = SEEK_SET)
{
return $this->invokeInternalStreamWrapper(
'fseek',
$this->internalResource,
$offset,
$whence
) !== -1;
}
/**
* @param int $option
* @param int $arg1
* @param int $arg2
* @return bool
*/
public function stream_set_option($option, $arg1, $arg2)
{
if ($option === STREAM_OPTION_BLOCKING) {
return $this->invokeInternalStreamWrapper(
'stream_set_blocking',
$this->internalResource,
$arg1
);
}
if ($option === STREAM_OPTION_READ_TIMEOUT) {
return $this->invokeInternalStreamWrapper(
'stream_set_timeout',
$this->internalResource,
$arg1,
$arg2
);
}
if ($option === STREAM_OPTION_WRITE_BUFFER) {
return $this->invokeInternalStreamWrapper(
'stream_set_write_buffer',
$this->internalResource,
$arg2
) === 0;
}
return false;
}
/**
* @return array
*/
public function stream_stat()
{
return $this->invokeInternalStreamWrapper(
'fstat',
$this->internalResource
);
}
/**
* @return int
*/
public function stream_tell()
{
return $this->invokeInternalStreamWrapper(
'ftell',
$this->internalResource
);
}
/**
* @param int $new_size
* @return bool
*/
public function stream_truncate($new_size)
{
return $this->invokeInternalStreamWrapper(
'ftruncate',
$this->internalResource,
$new_size
);
}
/**
* @param string $data
* @return int
*/
public function stream_write($data)
{
return $this->invokeInternalStreamWrapper(
'fwrite',
$this->internalResource,
$data
);
}
/**
* @param string $path
* @return bool
*/
public function unlink($path)
{
$this->assert($path, Behavior::COMMAND_UNLINK);
return $this->invokeInternalStreamWrapper(
'unlink',
$path,
$this->context
);
}
/**
* @param string $path
* @param int $flags
* @return array|false
*/
public function url_stat($path, $flags)
{
$this->assert($path, Behavior::COMMAND_URL_STAT);
$functionName = $flags & STREAM_URL_STAT_QUIET ? '@stat' : 'stat';
return $this->invokeInternalStreamWrapper($functionName, $path);
}
/**
* @param string $path
* @param string $command
*/
protected function assert($path, $command)
{
if ($this->resolveAssertable()->assert($path, $command) === true) {
return;
}
throw new Exception(
sprintf(
'Denied invocation of "%s" for command "%s"',
$path,
$command
),
1535189880
);
}
/**
* @return Assertable
*/
protected function resolveAssertable()
{
return Manager::instance();
}
/**
* Invokes commands on the native PHP Phar stream wrapper.
*
* @param string $functionName
* @param mixed ...$arguments
* @return mixed
*/
private function invokeInternalStreamWrapper($functionName)
{
$arguments = func_get_args();
array_shift($arguments);
$silentExecution = $functionName{0} === '@';
$functionName = ltrim($functionName, '@');
$this->restoreInternalSteamWrapper();
try {
if ($silentExecution) {
$result = @call_user_func_array($functionName, $arguments);
} else {
$result = call_user_func_array($functionName, $arguments);
}
} catch (\Exception $exception) {
$this->registerStreamWrapper();
throw $exception;
} catch (\Throwable $throwable) {
$this->registerStreamWrapper();
throw $throwable;
}
$this->registerStreamWrapper();
return $result;
}
private function restoreInternalSteamWrapper()
{
stream_wrapper_restore('phar');
}
private function registerStreamWrapper()
{
stream_wrapper_unregister('phar');
stream_wrapper_register('phar', get_class($this));
}
}
+3 -4
View File
@@ -7,8 +7,7 @@ files[] = aggregator.test
configure = admin/config/services/aggregator/settings
stylesheets[all][] = aggregator.css
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
@@ -5,8 +5,7 @@ version = VERSION
core = 7.x
hidden = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -6,8 +6,7 @@ core = 7.x
files[] = block.test
configure = admin/structure/block
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -5,8 +5,7 @@ version = VERSION
core = 7.x
hidden = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
@@ -13,8 +13,7 @@ regions[footer] = Footer
regions[highlighted] = Highlighted
regions[help] = Help
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -5,8 +5,7 @@ version = VERSION
core = 7.x
files[] = blog.test
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -7,8 +7,7 @@ files[] = book.test
configure = admin/content/book/settings
stylesheets[all][] = book.css
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+7 -4
View File
@@ -768,11 +768,13 @@ function book_prev($book_link) {
return NULL;
}
$flat = book_get_flat_menu($book_link);
// Assigning the array to $flat resets the array pointer for use with each().
reset($flat);
$curr = NULL;
do {
$prev = $curr;
list($key, $curr) = each($flat);
$curr = current($flat);
$key = key($flat);
next($flat);
} while ($key && $key != $book_link['mlid']);
if ($key == $book_link['mlid']) {
@@ -806,9 +808,10 @@ function book_prev($book_link) {
*/
function book_next($book_link) {
$flat = book_get_flat_menu($book_link);
// Assigning the array to $flat resets the array pointer for use with each().
reset($flat);
do {
list($key, $curr) = each($flat);
$key = key($flat);
next($flat);
}
while ($key && $key != $book_link['mlid']);
+3 -4
View File
@@ -5,8 +5,7 @@ version = VERSION
core = 7.x
files[] = color.test
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -9,8 +9,7 @@ files[] = comment.test
configure = admin/content/comment
stylesheets[all][] = comment.css
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -6,8 +6,7 @@ core = 7.x
files[] = contact.test
configure = admin/structure/contact
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -5,8 +5,7 @@ version = VERSION
core = 7.x
files[] = contextual.test
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -7,8 +7,7 @@ files[] = dashboard.test
dependencies[] = block
configure = admin/dashboard/customize
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -5,8 +5,7 @@ version = VERSION
core = 7.x
files[] = dblog.test
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -11,8 +11,7 @@ dependencies[] = field_sql_storage
required = TRUE
stylesheets[all][] = theme/field.css
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
@@ -7,8 +7,7 @@ dependencies[] = field
files[] = field_sql_storage.test
required = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -7,8 +7,7 @@ dependencies[] = field
dependencies[] = options
files[] = tests/list.test
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+8 -1
View File
@@ -61,7 +61,7 @@ function list_update_7001() {
// Additionally, float keys need to be disambiguated ('.5' is '0.5').
if ($field['type'] == 'list_number' && !empty($allowed_values)) {
$keys = array_map(create_function('$a', 'return (string) (float) $a;'), array_keys($allowed_values));
$keys = array_map('_list_update_7001_float_string_cast', array_keys($allowed_values));
$allowed_values = array_combine($keys, array_values($allowed_values));
}
@@ -88,6 +88,13 @@ function list_update_7001() {
}
}
/**
* Helper callback function to cast the array element.
*/
function _list_update_7001_float_string_cast($element) {
return (string) (float) $element;
}
/**
* Helper function for list_update_7001: extract allowed values from a string.
*
@@ -5,8 +5,7 @@ package = Testing
version = VERSION
hidden = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -6,8 +6,7 @@ core = 7.x
dependencies[] = field
files[] = number.test
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+1 -1
View File
@@ -69,7 +69,7 @@ class NumberFieldTestCase extends DrupalWebTestCase {
preg_match('|test-entity/manage/(\d+)/edit|', $this->url, $match);
$id = $match[1];
$this->assertRaw(t('test_entity @id has been created.', array('@id' => $id)), 'Entity was created');
$this->assertRaw(round($value, 2), 'Value is displayed.');
$this->assertRaw($value, 'Value is displayed.');
// Try to create entries with more than one decimal separator; assert fail.
$wrong_entries = array(
+3 -4
View File
@@ -6,8 +6,7 @@ core = 7.x
dependencies[] = field
files[] = options.test
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -7,8 +7,7 @@ dependencies[] = field
files[] = text.test
required = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -6,8 +6,7 @@ files[] = field_test.entity.inc
version = VERSION
hidden = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -6,8 +6,7 @@ core = 7.x
dependencies[] = field
files[] = field_ui.test
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+1 -1
View File
@@ -599,7 +599,7 @@ function file_field_widget_value($element, $input = FALSE, $form_state) {
// If the display field is present make sure its unchecked value is saved.
$field = field_widget_field($element, $form_state);
if (empty($input['display'])) {
$input['display'] = $field['settings']['display_field'] ? 0 : 1;
$input['display'] = !empty($field['settings']['display_field']) ? 0 : 1;
}
}
+3 -4
View File
@@ -6,8 +6,7 @@ core = 7.x
dependencies[] = field
files[] = tests/file.test
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3
View File
@@ -239,6 +239,9 @@ function file_ajax_upload() {
$form_parents = func_get_args();
$form_build_id = (string) array_pop($form_parents);
// Sanitize form parents before using them.
$form_parents = array_filter($form_parents, 'element_child');
if (empty($_POST['form_build_id']) || $form_build_id != $_POST['form_build_id']) {
// Invalid request.
drupal_set_message(t('An unrecoverable error occurred. The uploaded file likely exceeded the maximum file size (@size) that this server supports.', array('@size' => format_size(file_upload_max_size()))), 'error');
+57
View File
@@ -1875,3 +1875,60 @@ class FileFieldAnonymousSubmission extends FileFieldTestCase {
}
}
/**
* Tests the file_scan_directory() function.
*/
class FileScanDirectory extends FileFieldTestCase {
/**
* @var string
*/
protected $path;
/**
* {@inheritdoc}
*/
public static function getInfo() {
return array(
'name' => 'File ScanDirectory',
'description' => 'Tests the file_scan_directory() function.',
'group' => 'File',
);
}
/**
* {@inheritdoc}
*/
function setUp() {
parent::setUp();
$this->path = 'modules/file/tests/fixtures/file_scan_ignore';
}
/**
* Tests file_scan_directory() obeys 'file_scan_ignore_directories' setting.
* If nomask is not passed as argument, it should use the default settings.
* If nomask is passed as argument, it should obey this rule.
*/
public function testNoMask() {
$files = file_scan_directory($this->path, '/\.txt$/');
$this->assertEqual(3, count($files), '3 text files found when not ignoring directories.');
global $conf;
$conf['file_scan_ignore_directories'] = array('frontend_framework');
$files = file_scan_directory($this->path, '/\.txt$/');
$this->assertEqual(1, count($files), '1 text files found when ignoring directories called "frontend_framework".');
// Make that directories specified by default still work when a new nomask is provided.
$files = file_scan_directory($this->path, '/\.txt$/', array('nomask' => '/^c.txt/'));
$this->assertEqual(2, count($files), '2 text files found when an "nomask" option is passed in.');
// Ensure that the directories in file_scan_ignore_directories are escaped using preg_quote.
$conf['file_scan_ignore_directories'] = array('frontend.*');
$files = file_scan_directory($this->path, '/\.txt$/');
$this->assertEqual(3, count($files), '2 text files found when ignoring a directory that is not there.');
}
}
+3 -4
View File
@@ -5,8 +5,7 @@ version = VERSION
core = 7.x
hidden = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -7,8 +7,7 @@ files[] = filter.test
required = TRUE
configure = admin/config/content/formats
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -9,8 +9,7 @@ files[] = forum.test
configure = admin/structure/forum
stylesheets[all][] = forum.css
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -5,8 +5,7 @@ version = VERSION
core = 7.x
files[] = help.test
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+2 -1
View File
@@ -736,7 +736,8 @@ function theme_image_style_effects($variables) {
if (!isset($form[$key]['#access']) || $form[$key]['#access']) {
$rows[] = array(
'data' => $row,
'class' => !empty($form[$key]['weight']['#access']) || $key == 'new' ? array('draggable') : array(),
// Use a strict (===) comparison since $key can be 0.
'class' => !empty($form[$key]['weight']['#access']) || $key === 'new' ? array('draggable') : array(),
);
}
}
+3 -4
View File
@@ -7,8 +7,7 @@ dependencies[] = file
files[] = image.test
configure = admin/config/media/image-styles
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -6,8 +6,7 @@ core = 7.x
files[] = image_module_test.module
hidden = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -6,8 +6,7 @@ core = 7.x
files[] = locale.test
configure = admin/config/regional/language
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+1 -5
View File
@@ -3188,11 +3188,7 @@ class LocaleLanguageNegotiationInfoFunctionalTest extends DrupalWebTestCase {
foreach (language_types_info() as $type => $info) {
if (isset($info['fixed'])) {
$negotiation = variable_get("language_negotiation_$type", array());
$equal = count($info['fixed']) == count($negotiation);
while ($equal && list($id) = each($negotiation)) {
list(, $info_id) = each($info['fixed']);
$equal = $info_id == $id;
}
$equal = array_keys($negotiation) === array_values($info['fixed']);
$this->assertTrue($equal, format_string('language negotiation for %type is properly set up', array('%type' => $type)));
}
}
+3 -4
View File
@@ -5,8 +5,7 @@ package = Testing
version = VERSION
hidden = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -6,8 +6,7 @@ core = 7.x
files[] = menu.test
configure = admin/structure/menu
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -9,8 +9,7 @@ required = TRUE
configure = admin/structure/types
stylesheets[all][] = node.css
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -5,8 +5,7 @@ version = VERSION
core = 7.x
hidden = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -5,8 +5,7 @@ version = VERSION
core = 7.x
hidden = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -5,8 +5,7 @@ version = VERSION
core = 7.x
hidden = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -5,8 +5,7 @@ package = Core
core = 7.x
files[] = openid.test
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -6,8 +6,7 @@ core = 7.x
dependencies[] = openid
hidden = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -4,8 +4,7 @@ package = Core
version = VERSION
core = 7.x
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -6,8 +6,7 @@ core = 7.x
files[] = path.test
configure = admin/config/search/path
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+29 -1
View File
@@ -21,7 +21,7 @@ class PathTestCase extends DrupalWebTestCase {
parent::setUp('path');
// Create test user and login.
$web_user = $this->drupalCreateUser(array('create page content', 'edit own page content', 'administer url aliases', 'create url aliases'));
$web_user = $this->drupalCreateUser(array('create page content', 'edit own page content', 'administer url aliases', 'create url aliases', 'access content overview'));
$this->drupalLogin($web_user);
}
@@ -160,6 +160,34 @@ class PathTestCase extends DrupalWebTestCase {
$this->drupalGet($edit['path[alias]']);
$this->assertNoText($node1->title, 'Alias was successfully deleted.');
$this->assertResponse(404);
// Create third test node.
$node3 = $this->drupalCreateNode();
// Create an invalid alias with a leading slash and verify that the slash
// is removed when the link is generated. This ensures that URL aliases
// cannot be used to inject external URLs.
// @todo The user interface should either display an error message or
// automatically trim these invalid aliases, rather than allowing them to
// be silently created, at which point the functional aspects of this
// test will need to be moved elsewhere and switch to using a
// programmatically-created alias instead.
$alias = $this->randomName(8);
$edit = array('path[alias]' => '/' . $alias);
$this->drupalPost('node/' . $node3->nid . '/edit', $edit, t('Save'));
$this->drupalGet('admin/content');
// This checks the link href before clicking it, rather than using
// DrupalWebTestCase::assertUrl() after clicking it, because the test
// browser does not always preserve the correct number of slashes in the
// URL when it visits internal links; using DrupalWebTestCase::assertUrl()
// would actually make the test pass unconditionally on the testbot (or
// anywhere else where Drupal is installed in a subdirectory).
$link_xpath = $this->xpath('//a[normalize-space(text())=:label]', array(':label' => $node3->title));
$link_href = (string) $link_xpath[0]['href'];
$link_prefix = base_path() . (variable_get('clean_url', 0) ? '' : '?q=');
$this->assertEqual($link_href, $link_prefix . $alias);
$this->clickLink($node3->title);
$this->assertResponse(404);
}
/**
+3 -4
View File
@@ -5,8 +5,7 @@ version = VERSION
core = 7.x
files[] = php.test
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -6,8 +6,7 @@ core = 7.x
files[] = poll.test
stylesheets[all][] = poll.css
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -11,8 +11,7 @@ configure = admin/config/people/profile
; See user_system_info_alter().
hidden = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -5,8 +5,7 @@ version = VERSION
core = 7.x
files[] = rdf.test
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -6,8 +6,7 @@ core = 7.x
hidden = TRUE
dependencies[] = blog
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -8,8 +8,7 @@ files[] = search.test
configure = admin/config/search/settings
stylesheets[all][] = search.css
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
@@ -5,8 +5,7 @@ version = VERSION
core = 7.x
hidden = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -5,8 +5,7 @@ version = VERSION
core = 7.x
hidden = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -5,8 +5,7 @@ version = VERSION
core = 7.x
hidden = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -6,8 +6,7 @@ core = 7.x
files[] = shortcut.test
configure = admin/config/user-interface/shortcut
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+2 -2
View File
@@ -3012,7 +3012,7 @@ class DrupalWebTestCase extends DrupalTestCase {
if (!$message) {
$message = t('Raw "@raw" found', array('@raw' => $raw));
}
return $this->assert(strpos($this->drupalGetContent(), $raw) !== FALSE, $message, $group);
return $this->assert(strpos($this->drupalGetContent(), (string) $raw) !== FALSE, $message, $group);
}
/**
@@ -3032,7 +3032,7 @@ class DrupalWebTestCase extends DrupalTestCase {
if (!$message) {
$message = t('Raw "@raw" not found', array('@raw' => $raw));
}
return $this->assert(strpos($this->drupalGetContent(), $raw) === FALSE, $message, $group);
return $this->assert(strpos($this->drupalGetContent(), (string) $raw) === FALSE, $message, $group);
}
/**
Binary file not shown.
+3 -4
View File
@@ -57,8 +57,7 @@ files[] = tests/upgrade/update.trigger.test
files[] = tests/upgrade/update.field.test
files[] = tests/upgrade/update.user.test
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
@@ -5,8 +5,7 @@ version = VERSION
core = 7.x
hidden = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
@@ -5,8 +5,7 @@ package = Testing
version = VERSION
hidden = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -5,8 +5,7 @@ version = VERSION
core = 7.x
hidden = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -5,8 +5,7 @@ version = VERSION
core = 7.x
hidden = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -5,8 +5,7 @@ package = Testing
version = VERSION
hidden = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+3 -4
View File
@@ -5,8 +5,7 @@ package = Testing
version = VERSION
hidden = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
+2 -6
View File
@@ -729,16 +729,12 @@ class BootstrapMiscTestCase extends DrupalUnitTestCase {
* Tests that the drupal_check_memory_limit() function works as expected.
*/
function testCheckMemoryLimit() {
$memory_limit = ini_get('memory_limit');
// Test that a very reasonable amount of memory is available.
$this->assertTrue(drupal_check_memory_limit('30MB'), '30MB of memory tested available.');
// Get the available memory and multiply it by two to make it unreasonably
// high.
$twice_avail_memory = ($memory_limit * 2) . 'MB';
// Test an unlimited memory limit.
// The function should always return true if the memory limit is set to -1.
$this->assertTrue(drupal_check_memory_limit($twice_avail_memory, -1), 'drupal_check_memory_limit() returns TRUE when a limit of -1 (none) is supplied');
$this->assertTrue(drupal_check_memory_limit('9999999999YB', -1), 'drupal_check_memory_limit() returns TRUE when a limit of -1 (none) is supplied');
// Test that even though we have 30MB of memory available - the function
// returns FALSE when given an upper limit for how much memory can be used.
+3 -4
View File
@@ -7,8 +7,7 @@ stylesheets[all][] = common_test.css
stylesheets[print][] = common_test.print.css
hidden = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"
@@ -5,8 +5,7 @@ version = VERSION
core = 7.x
hidden = TRUE
; Information added by Drupal.org packaging script on 2018-03-28
version = "7.58"
; Information added by Drupal.org packaging script on 2019-04-17
version = "7.66"
project = "drupal"
datestamp = "1522264019"
datestamp = "1555533576"

Some files were not shown because too many files have changed in this diff Show More