Skip to content
Permalink
Browse files

- Patch #284899 by c960657, voxpelli, mattconnolly: fixed Drupal url …

…problem with clean urls.
  • Loading branch information...
dbuytaert committed Jan 29, 2010
1 parent 139f437 commit 1df3cfffefefc93ed2d29041d148938d08bb9d4e
@@ -88,11 +88,12 @@ DirectoryIndex index.php index.html index.htm
# uncomment the following line:
# RewriteBase /
# Rewrite URLs of the form 'x' to the form 'index.php?q=x'.
# Pass all requests not referring directly to files in the filesystem to
# index.php. Clean URLs are handled in drupal_environment_initialize().
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_URI} !=/favicon.ico
RewriteRule ^(.*)$ index.php?q=$1 [L,QSA]
RewriteRule ^ index.php [L]
</IfModule>
# $Id$
@@ -492,6 +492,13 @@ function drupal_environment_initialize() {
$_SERVER['HTTP_HOST'] = '';
}
// When clean URLs are enabled, emulate ?q=foo/bar using REQUEST_URI. It is
// not possible to append the query string using mod_rewrite without the B
// flag (this was added in Apache 2.2.8), because mod_rewrite unescapes the
// path before passing it on to PHP. This is a problem when the path contains
// e.g. "&" or "%" that have special meanings in URLs and must be encoded.
$_GET['q'] = request_path();
// Enforce E_ALL, but allow users to set levels not part of E_ALL.
error_reporting(E_ALL | error_reporting());
@@ -559,8 +566,8 @@ function drupal_settings_initialize() {
// $_SERVER['SCRIPT_NAME'] can, in contrast to $_SERVER['PHP_SELF'], not
// be modified by a visitor.
if ($dir = trim(dirname($_SERVER['SCRIPT_NAME']), '\,/')) {
$base_path = "/$dir";
if ($dir = rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/')) {
$base_path = $dir;
$base_url .= $base_path;
$base_path .= '/';
}
@@ -1858,6 +1865,50 @@ function language_default($property = NULL) {
return $property ? $language->$property : $language;
}
/**
* Returns the requested URL path of the page being viewed.
*
* Examples:
* - http://example.com/node/306 returns "node/306".
* - http://example.com/drupalfolder/node/306 returns "node/306" while
* base_path() returns "/drupalfolder/".
* - http://example.com/path/alias (which is a path alias for node/306) returns
* "path/alias" as opposed to the internal path.
*
* @return
* The requested Drupal URL path.
*
* @see current_path()
*/
function request_path() {
static $path;
if (isset($path)) {
return $path;
}
if (isset($_GET['q'])) {
// This is a request with a ?q=foo/bar query string. $_GET['q'] is
// overwritten in drupal_path_initialize(), but request_path() is called
// very early in the bootstrap process, so the original value is saved in
// $path and returned in later calls.
$path = $_GET['q'];
}
elseif (isset($_SERVER['REQUEST_URI'])) {
// This is a request using a clean URL. Extract the path from REQUEST_URI.
$request_path = strtok($_SERVER['REQUEST_URI'], '?');
$base_path_len = strlen(rtrim(dirname($_SERVER['SCRIPT_NAME']), '\/'));
// Unescape and strip $base_path prefix, leaving q without a leading slash.
$path = substr(urldecode($request_path), $base_path_len + 1);
}
else {
// This is the front page.
$path = '';
}
return $path;
}
/**
* If Drupal is behind a reverse proxy, we use the X-Forwarded-For header
* instead of $_SERVER['REMOTE_ADDR'], which would be the IP address of
@@ -501,7 +501,6 @@ function drupal_http_build_query(array $query, $parent = '') {
}
else {
// For better readability of paths in query strings, we decode slashes.
// @see drupal_encode_path()
$params[] = $key . '=' . str_replace('%2F', '/', rawurlencode($value));
}
}
@@ -623,38 +622,18 @@ function drupal_parse_url($url) {
}
/**
* Encode a path for usage in a URL.
* Encodes a Drupal path for use in a URL.
*
* Wrapper around rawurlencode() which avoids Apache quirks. Should be used when
* placing arbitrary data into the path component of an URL.
* For aesthetic reasons slashes are not escaped.
*
* Do not use this function to pass a path to url(). url() properly handles
* and encodes paths internally.
* This function should only be used on paths, not on query string arguments.
* Otherwise, unwanted double encoding will occur.
*
* Notes:
* - For aesthetic reasons, we do not escape slashes. This also avoids a 'feature'
* in Apache where it 404s on any path containing '%2F'.
* - mod_rewrite unescapes %-encoded ampersands, hashes, and slashes when clean
* URLs are used, which are interpreted as delimiters by PHP. These
* characters are double escaped so PHP will still see the encoded version.
* - With clean URLs, Apache changes '//' to '/', so every second slash is
* double escaped.
* Note that url() takes care of calling this function, so a path passed to that
* function should not be encoded in advance.
*
* @param $path
* The URL path component to encode.
* The Drupal path to encode.
*/
function drupal_encode_path($path) {
if (!empty($GLOBALS['conf']['clean_url'])) {
return str_replace(array('%2F', '%26', '%23', '//'),
array('/', '%2526', '%2523', '/%252F'),
rawurlencode($path)
);
}
else {
return str_replace('%2F', '/', rawurlencode($path));
}
return str_replace('%2F', '/', rawurlencode($path));
}
/**
@@ -901,6 +901,10 @@ function file_unmunge_filename($filename) {
* of $basename.
*/
function file_create_filename($basename, $directory) {
// 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);
// A URI or path may already have a trailing slash or look like "public://".
if (substr($directory, -1) == '/') {
$separator = '';
@@ -384,6 +384,8 @@ function drupal_match_path($path, $patterns) {
*
* @return
* The current Drupal URL path.
*
* @see request_path()
*/
function current_path() {
return $_GET['q'];
@@ -639,7 +639,7 @@ class DrupalPublicStreamWrapper extends DrupalLocalStreamWrapper {
*/
function getExternalUrl() {
$path = str_replace('\\', '/', file_uri_target($this->uri));
return $GLOBALS['base_url'] . '/' . self::getDirectoryPath() . '/' . $path;
return $GLOBALS['base_url'] . '/' . self::getDirectoryPath() . '/' . drupal_encode_path($path);
}
}
@@ -276,7 +276,7 @@ Drupal.ACDB.prototype.search = function (searchString) {
// Ajax GET request for autocompletion.
$.ajax({
type: 'GET',
url: db.uri + '/' + Drupal.encodePath(searchString),
url: db.uri + '/' + encodeURIComponent(searchString),
dataType: 'json',
success: function (matches) {
if (typeof matches.status == 'undefined' || matches.status != 0) {
@@ -282,14 +282,13 @@ Drupal.unfreezeHeight = function () {
};

/**
* Wrapper around encodeURIComponent() which avoids Apache quirks (equivalent of
* drupal_encode_path() in PHP). This function should only be used on paths, not
* on query string arguments.
* Encodes a Drupal path for use in a URL.
*
* For aesthetic reasons slashes are not escaped.
*/
Drupal.encodePath = function (item, uri) {
uri = uri || location.href;
item = encodeURIComponent(item).replace(/%2F/g, '/');
return (uri.indexOf('?q=') != -1) ? item : item.replace(/%26/g, '%2526').replace(/%23/g, '%2523').replace(/\/\//g, '/%252F');
return encodeURIComponent(item).replace(/%2F/g, '/');
};

/**
@@ -66,11 +66,13 @@ class PathTestCase extends DrupalWebTestCase {
$this->assertText($node1->title, 'Alias works.');
$this->assertResponse(200);

// Change alias.
// Change alias to one containing "exotic" characters.
$pid = $this->getPID($edit['alias']);

$previous = $edit['alias'];
$edit['alias'] = $this->randomName(8);
$edit['alias'] = "- ._~!$'\"()*@[]?&+%#,;=:" . // "Special" ASCII characters.
"%23%25%26%2B%2F%3F" . // Characters that look like a percent-escaped string.
"éøïвβ中國書۞"; // Characters from various non-ASCII alphabets.
$this->drupalPost('admin/config/search/path/edit/' . $pid, $edit, t('Save'));

// Confirm that the alias works.
@@ -122,9 +124,11 @@ class PathTestCase extends DrupalWebTestCase {
$this->assertText($node1->title, 'Alias works.');
$this->assertResponse(200);

// Change alias.
// Change alias to one containing "exotic" characters.
$previous = $edit['path[alias]'];
$edit['path[alias]'] = $this->randomName(8);
$edit['path[alias]'] = "- ._~!$'\"()*@[]?&+%#,;=:" . // "Special" ASCII characters.
"%23%25%26%2B%2F%3F" . // Characters that look like a percent-escaped string.
"éøïвβ中國書۞"; // Characters from various non-ASCII alphabets.
$this->drupalPost('node/' . $node1->nid . '/edit', $edit, t('Save'));

// Confirm that the alias works.
@@ -1888,6 +1888,8 @@ class FileDownloadTest extends FileTestCase {

function setUp() {
parent::setUp('file_test');
// Clear out any hook calls.
file_test_reset();
}

/**
@@ -1937,6 +1939,70 @@ class FileDownloadTest extends FileTestCase {
$this->drupalHead($url);
$this->assertResponse(404, t('Correctly returned 404 response for a non-existent file.'));
}

/**
* Test file_create_url().
*/
function testFileCreateUrl() {
global $base_url;

$basename = " -._~!$'\"()*@[]?&+%#,;=:\n\x00" . // "Special" ASCII characters.
"%23%25%26%2B%2F%3F" . // Characters that look like a percent-escaped string.
"éøïвβ中國書۞"; // Characters from various non-ASCII alphabets.
$basename_encoded = '%20-._%7E%21%24%27%22%28%29%2A%40%5B%5D%3F%26%2B%25%23%2C%3B%3D%3A__' .
'%2523%2525%2526%252B%252F%253F' .
'%C3%A9%C3%B8%C3%AF%D0%B2%CE%B2%E4%B8%AD%E5%9C%8B%E6%9B%B8%DB%9E';

$this->checkUrl('public', '', $basename, $base_url . '/' . file_directory_path() . '/' . $basename_encoded);
$this->checkUrl('private', '', $basename, $base_url . '/system/files/' . $basename_encoded);
$this->checkUrl('private', '', $basename, $base_url . '/?q=system/files/' . $basename_encoded, '0');
}

/**
* Download a file from the URL generated by file_create_url().
*
* Create a file with the specified scheme, directory and filename; check that
* the URL generated by file_create_url() for the specified file equals the
* specified URL; fetch the URL and then compare the contents to the file.
*
* @param $scheme
* A scheme, e.g. "public"
* @param $directory
* A directory, possibly ""
* @param $filename
* A filename
* @param $expected_url
* The expected URL
* @param $clean_url
* The value of the clean_url setting
*/
private function checkUrl($scheme, $directory, $filename, $expected_url, $clean_url = '1') {
variable_set('clean_url', $clean_url);

// Convert $filename to a valid filename, i.e. strip characters not
// supported by the filesystem, and create the file in the specified
// directory.
$filepath = file_create_filename($filename, $directory);
$directory_uri = $scheme . '://' . dirname($filepath);
file_prepare_directory($directory_uri, FILE_CREATE_DIRECTORY);
$file = $this->createFile($filepath, NULL, $scheme);

$url = file_create_url($file->uri);
$this->assertEqual($url, $expected_url, t('Generated URL matches expected URL.'));

if ($scheme == 'private') {
// Tell the implementation of hook_file_download() in file_test.module
// that this file may be downloaded.
file_test_set_return('download', array('x-foo' => 'Bar'));
}

$this->drupalGet($url);
if ($this->assertResponse(200) == 'pass') {
$this->assertRaw(file_get_contents($file->uri), t('Contents of the file are correct.'));
}

file_delete($file);
}
}

/**
@@ -51,6 +51,17 @@ class MenuRouterTestCase extends DrupalWebTestCase {
$this->assertRaw('seven/style.css', t("The administrative theme's CSS appears on the page."));
}

/**
* Test path containing "exotic" characters.
*/
function testExoticPath() {
$path = "menu-test/ -._~!$'\"()*@[]?&+%#,;=:" . // "Special" ASCII characters.
"%23%25%26%2B%2F%3F" . // Characters that look like a percent-escaped string.
"éøïвβ中國書۞"; // Characters from various non-ASCII alphabets.
$this->drupalGet($path);
$this->assertRaw('This is menu_test_callback().');
}

/**
* Test the theme callback when the site is in maintenance mode.
*/
@@ -58,6 +58,15 @@ function menu_test_menu() {
'page arguments' => array(TRUE),
'access arguments' => array('access content'),
);
// Path containing "exotic" characters.
$path = "menu-test/ -._~!$'\"()*@[]?&+%#,;=:" . // "Special" ASCII characters.
"%23%25%26%2B%2F%3F" . // Characters that look like a percent-escaped string.
"éøïвβ中國書۞"; // Characters from various non-ASCII alphabets.
$items[$path] = array(
'title' => '"Exotic" path',
'page callback' => 'menu_test_callback',
'access arguments' => array('access content'),
);

// Hidden tests; base parents.
// Same structure as in Menu and Block modules. Since those structures can
@@ -174,7 +183,7 @@ function menu_test_menu() {
* A random string.
*/
function menu_test_callback() {
return $this->randomName();
return 'This is menu_test_callback().';
}

/**
@@ -200,6 +200,15 @@ class UrlAlterFunctionalTest extends DrupalWebTestCase {
$this->assertUrlOutboundAlter("taxonomy/term/$tid", "taxonomy/term/$tid");
}

/**
* Test current_path() and request_path().
*/
function testCurrentUrlRequestedPath() {
$this->drupalGet('url-alter-test/bar');
$this->assertRaw('request_path=url-alter-test/bar', t('request_path() returns the requested path.'));
$this->assertRaw('current_path=url-alter-test/foo', t('current_path() returns the internal path.'));
}

/**
* Assert that an outbound path is altered to an expected value.
*
Oops, something went wrong.

0 comments on commit 1df3cff

Please sign in to comment.
You can’t perform that action at this time.