diff --git a/core/modules/path/path.module b/core/modules/path/path.module index 67fc536..5ee92e1 100644 --- a/core/modules/path/path.module +++ b/core/modules/path/path.module @@ -178,11 +178,58 @@ function path_form_element_validate($element, &$form_state, $complete_form) { $query->range(0, 1); if ($query->execute()->fetchField()) { form_error($element, t('The alias is already in use.')); + } else { + // If it isn't a duplicate, it still might be invalid. + $valid = path_alias_is_valid($path['alias']); + if ($valid !== TRUE) { + form_error($element, $valid); + } } } } /** + * Validates that a path alias is valid. + * + * @param $alias + * A string containing the alias in question. + * + * @return + * TRUE if the alias is valid, otherwise the error message. + */ +function path_alias_is_valid($alias) { + $alias = strtolower(trim($alias, '/ ')); + + // We could create an alias that would conflict with another node/user/taxonomy + // later on, so we need to take account of that. (e.g. Create node 7 + // with the alias 'node/9/delete'. It will make it impossible to delete + // node 9 later on without modifying node 7 first.) + $parts = explode('/', $alias, 2); + $restricted_paths = array('node', 'taxonomy', 'user'); + if (in_array($parts[0], $restricted_paths)) { + return t('The alias cannot start with "@part"', array('@part' => $parts[0])); + } + + // Verify that no other module has reserved this address + $router_item = menu_get_item($alias); + if (!empty($router_item)) { + return t('The alias is reserved by the system'); + } + + // Before invoking Drupal, the webserver will first check the local + // filesystem for files/folders that match the path, so Drupal cannot + // match any path that would match an item on the filesystem. + // Additionally, .htaccess redirects some D7 paths to their D8 counterparts + // (e.g. cron.php). Those have to be enumerated and checked seperately. + $restricted_files = array('install.php', 'cron.php', 'update.php'); + if (file_exists($alias) || in_array($alias, $restricted_files)) { + return t('The alias conflicts with an item on the filesystem'); + } + + return TRUE; +} + +/** * Implements hook_node_insert(). */ function path_node_insert($node) { diff --git a/core/modules/path/path.test b/core/modules/path/path.test index d652654..7b37554 100644 --- a/core/modules/path/path.test +++ b/core/modules/path/path.test @@ -212,6 +212,27 @@ class PathAliasTestCase extends PathTestCase { $this->assertText(t('The alias is already in use.')); $this->assertFieldByXPath("//input[@name='path[alias]' and contains(@class, 'error')]", $edit['path[alias]'], 'Textfield exists and has the error class.'); } + + /** + * Tests that only valid aliases pass path_alias_is_valid(). + */ + function testPathAliasIsValid() { + $tests = array( + 'admin' => 'The alias is reserved by the system', + '/Admin/content/' => 'The alias is reserved by the system', + 'Update.php' => 'The alias conflicts with an item on the filesystem', + 'core/modules' => 'The alias conflicts with an item on the filesystem', + 'node/10000/edit' => 'The alias cannot start with "node"', + 'User' => 'The alias cannot start with "user"', + 'user/login' => 'The alias cannot start with "user"', + 'users_are_people_too' => TRUE, + 'dries-sings-the-drupal-song' => TRUE, + ); + foreach ($tests AS $alias => $expected) { + $result = path_alias_is_valid($alias); + $this->assertIdentical($expected, $result); + } + } } /**