diff --git a/core/lib/Drupal/Core/Url.php b/core/lib/Drupal/Core/Url.php index 5bfba84..7525374 100644 --- a/core/lib/Drupal/Core/Url.php +++ b/core/lib/Drupal/Core/Url.php @@ -312,10 +312,12 @@ public static function fromRouteMatch(RouteMatchInterface $route_match) { * another application placed within the Drupal site installation. * * Do not use this method for URLs that have a Drupal route (pages generated - * by Drupal). See the documentation of Url::SCHEME_BASE for details. + * by Drupal) or for host-relative paths. See the documentation of + * Url::SCHEME_BASE for details. * * @param string $path - * The base-relative path, for example 'robots.txt'. + * The base-relative path, for example 'robots.txt'. Leading slashes are + * discarded. * @param array $options * (optional) An array of options. See Url::fromUri() for details. * @@ -329,6 +331,8 @@ public static function fromRouteMatch(RouteMatchInterface $route_match) { * @see \Drupal\Core\Url::fromRoute() */ public static function fromBaseRelativePath($path, $options = array()) { + // Trim leading slashes to ensure a base-relative path is used. + $path = static::trimLeadingSlashes($path); return static::fromUri(static::SCHEME_BASE . ':' . $path, $options); } @@ -336,15 +340,17 @@ public static function fromBaseRelativePath($path, $options = array()) { * Creates a new Url object from an unrouted, host-relative path. * * This method should only be used for paths within the same host as the - * site installation. + * site installation that do not have a Drupal route and are not within the + * Drupal installation directory. * * Do not use this method for URLs that have a Drupal route (pages generated - * by Drupal). See the documentation of Url::SCHEME_BASE for details. + * by Drupal) or for base-relative paths. See the documentation of + * Url::SCHEME_BASE for details. * * @param string $path * The host-relative path. For example, if Drupal is installed in the * subdirectory http://example.com/drupal/, use 'index.html' for a URL to - * http://example.com/index.html. + * http://example.com/index.html. Leading slashes are discarded. * @param array $options * (optional) An array of options. See Url::fromUri() for details. * @@ -358,18 +364,65 @@ public static function fromBaseRelativePath($path, $options = array()) { * @see \Drupal\Core\Url::fromRoute() */ public static function fromHostRelativePath($path, $options = array()) { + // Trim leading slashes, then add exactly one, to ensure a host-relative + // path is used. + $path = static::trimLeadingSlashes($path); return static::fromUri(static::SCHEME_BASE . ':/' . $path, $options); } /** + * Trims leading slashes from a path. + * + * @param string $path + * A potential base-relative or host-relative path. + * + * @return string + * The path, with leading slashes trimmed. + */ + protected static function trimLeadingSlashes($path) { + return preg_replace('/^(\/)+/', '', $path); + } + + /** + * Creates a Url object for a path from user input. + * + * Use this method to create a URL for paths from user input that may or may + * not correspond to a valid Drupal route. + * + * @param string $path + * A path string from user input. Only three types of paths are allowed: + * - Site paths, beginning with a '/'. + * - Query strings, beginning with a '?'. + * - Fragments, beginning with a '#'. + * @param array $options + * (optional) An array of options. See Url::fromUri() for details. + * + * @return \Drupal\Core\Url + * A new Url object based on user input. + * + * @todo The intent here is to support a user interaction pattern where + * entering one of the three allowed characters at the beginning of a + * field denotes that the user wants to link to a specific path on the + * site, with all other input interpreted as an attempt to autocomplete the + * label of a specific resource. How do we document that, and how do we + * establish the expectation for other APIs and for UIs? + */ + public static function fromUserInput($path, $options = []) { + if ((strpos($path, '/') !== 0) && (strpos($path, '/') !== 0) && (strpos($path, '/') !== 0)) { + throw new \InvalidArgumentException(String::format('The path @path is not valid as user input. User-input paths must begin with one of the following characters: / # ?', ['@path' => $path])); + } + return static::fromUri(static::SCHEME_USER_PATH . ':' . $path, $options); + } + + /** * Creates a new Url object from a URI. * * This method is used to create URLs for internal and external URIs with a * unified API and storage format. * - * - For URIs that have Drupal routes (that is, most pages generated by - * Drupal), use Url::fromRoute() whenever possible, to avoid circumventing - * the routing system. + * - For URIs that have Drupal routes (pages generated by Drupal), use + * Url::fromRoute() whenever possible, to avoid circumventing the routing + * system. * - For paths from user input that may or may not correspond to a valid * Drupal route, use Url::fromUserInput(). * - For base-relative and host-relative paths that are not created by Drupal,