440 lines
14 KiB
Plaintext
440 lines
14 KiB
Plaintext
|
|
<?php
|
||
|
|
/*
|
||
|
|
* Gallery - a web based photo album viewer and editor
|
||
|
|
* Copyright (C) 2000-2007 Bharat Mediratta
|
||
|
|
*
|
||
|
|
* This program is free software; you can redistribute it and/or modify
|
||
|
|
* it under the terms of the GNU General Public License as published by
|
||
|
|
* the Free Software Foundation; either version 2 of the License, or (at
|
||
|
|
* your option) any later version.
|
||
|
|
*
|
||
|
|
* This program is distributed in the hope that it will be useful, but
|
||
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
|
* General Public License for more details.
|
||
|
|
*
|
||
|
|
* You should have received a copy of the GNU General Public License
|
||
|
|
* along with this program; if not, write to the Free Software
|
||
|
|
* Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.
|
||
|
|
*/
|
||
|
|
/**
|
||
|
|
* URL rewrite helper.
|
||
|
|
* @package Rewrite
|
||
|
|
* @subpackage Classes
|
||
|
|
* @author Douglas Cau <douglas@cau.se>
|
||
|
|
* @version $Revision: 15865 $
|
||
|
|
* @static
|
||
|
|
*/
|
||
|
|
|
||
|
|
/* General status codes */
|
||
|
|
define('REWRITE_STATUS_OK', 0);
|
||
|
|
define('REWRITE_STATUS_BAD_KEYWORD', 1);
|
||
|
|
define('REWRITE_STATUS_MULTISITE', 2);
|
||
|
|
define('REWRITE_STATUS_DUPE_SHORT_URL', 3); /* Deprecated */
|
||
|
|
define('REWRITE_STATUS_INVALID_PATTERN', 4);
|
||
|
|
define('REWRITE_STATUS_EMPTY_VALUE', 5);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* URL rewrite helper.
|
||
|
|
* @static
|
||
|
|
*/
|
||
|
|
class RewriteHelper {
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Load and initialize the rewrite plugin. If no plugin has been configured yet it returns a
|
||
|
|
* GalleryStatus ERROR_MISSING_VALUE.
|
||
|
|
* @param boolean $new (optional) true if we need a new instance
|
||
|
|
* @return array object GalleryStatus a status code
|
||
|
|
* object RewritePlugin a loaded parser
|
||
|
|
*/
|
||
|
|
function getRewriteParser($new=false) {
|
||
|
|
global $gallery;
|
||
|
|
static $rewriteParser;
|
||
|
|
$platform =& $gallery->getPlatform();
|
||
|
|
|
||
|
|
if (!isset($rewriteParser) || $new) {
|
||
|
|
list ($ret, $rewriteParserId) = GalleryCoreApi::getPluginParameter(
|
||
|
|
'module', 'rewrite', 'parserId');
|
||
|
|
if ($ret) {
|
||
|
|
return array($ret, null);
|
||
|
|
}
|
||
|
|
if (empty($rewriteParserId)) {
|
||
|
|
return array(GalleryCoreApi::error(ERROR_MISSING_VALUE), null);
|
||
|
|
}
|
||
|
|
|
||
|
|
$path = 'modules/rewrite/classes/parsers/' . $rewriteParserId . '/parser.inc';
|
||
|
|
if ($platform->file_exists(
|
||
|
|
GalleryCoreApi::getPluginBaseDir('module', 'rewrite') . $path)) {
|
||
|
|
GalleryCoreApi::requireOnce($path);
|
||
|
|
}
|
||
|
|
$class = $rewriteParserId . 'Parser';
|
||
|
|
$rewriteParser = new $class();
|
||
|
|
}
|
||
|
|
|
||
|
|
return array(null, $rewriteParser);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Parse active rules into an array of regular expressions for parsing URLs during page requests
|
||
|
|
* and build an array for the URL generator to use when generating short URLs.
|
||
|
|
* @param array $activeRules array array ('pattern' => string pattern) active rules by reference
|
||
|
|
* @param object RewriteParser $rewriteParser
|
||
|
|
* @param object GalleryModule $upgradeModule (optional) passed in during activate / upgrade
|
||
|
|
* @param array $flags optional definition of default and mandatory flags
|
||
|
|
* @return array object GalleryStatus a status code
|
||
|
|
* int rewrite status code (REWRITE_STATUS_OK on success)
|
||
|
|
* array parsed patterns translated to regular expressions
|
||
|
|
* array short URLs which will used by to generate URLs
|
||
|
|
* array string module (on error)
|
||
|
|
* int rule id (on error)
|
||
|
|
*/
|
||
|
|
function parseActiveRules(&$activeRules, $rewriteParser, $upgradeModule=null, $flags=null) {
|
||
|
|
global $gallery;
|
||
|
|
$urlGenerator =& $gallery->getUrlGenerator();
|
||
|
|
|
||
|
|
/* Get access list information */
|
||
|
|
list ($ret, $accessList) = GalleryCoreApi::getPluginParameter(
|
||
|
|
'module', 'rewrite', 'accessList');
|
||
|
|
if ($ret) {
|
||
|
|
return array($ret, null, null, null, null);
|
||
|
|
}
|
||
|
|
$accessList = unserialize($accessList);
|
||
|
|
$ourHostNames[$urlGenerator->getHostName()] = true;
|
||
|
|
$ourHostNames[$urlGenerator->getHostName(true)] = true;
|
||
|
|
$accessList = array_merge($accessList, array_keys($ourHostNames));
|
||
|
|
|
||
|
|
/* Get allow empty referer */
|
||
|
|
list ($ret, $allowEmptyReferer) = GalleryCoreApi::getPluginParameter(
|
||
|
|
'module', 'rewrite', 'allowEmptyReferer');
|
||
|
|
if ($ret) {
|
||
|
|
return array($ret, null, null, null, null);
|
||
|
|
}
|
||
|
|
|
||
|
|
$regexRules = array();
|
||
|
|
$shortUrls = array();
|
||
|
|
foreach (array_keys($activeRules) as $moduleId) {
|
||
|
|
if (isset($upgradeModule) && $upgradeModule->getId() == $moduleId) {
|
||
|
|
/* Avoid PLUGIN_VERSION_MISMATCH during upgrade by passing in module */
|
||
|
|
$module = $upgradeModule;
|
||
|
|
} else {
|
||
|
|
list ($ret, $module) = GalleryCoreApi::loadPlugin('module', $moduleId);
|
||
|
|
if ($ret) {
|
||
|
|
if ($ret->getErrorCode() & ERROR_PLUGIN_VERSION_MISMATCH) {
|
||
|
|
/*
|
||
|
|
* Add CONFIGURATION_REQUIRED code to more gracefully abort upgrade if a
|
||
|
|
* dependent module for one of our rules also needs upgrading.
|
||
|
|
*/
|
||
|
|
$ret->addErrorCode(ERROR_CONFIGURATION_REQUIRED);
|
||
|
|
}
|
||
|
|
return array($ret, null, null, null, null);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$rules = $module->getRewriteRules();
|
||
|
|
foreach ($activeRules[$moduleId] as $ruleId => $activeRule) {
|
||
|
|
/* Make sure this rule still exists, if not silently continue */
|
||
|
|
if (!isset($rules[$ruleId])) {
|
||
|
|
unset($activeRules[$moduleId][$ruleId]);
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Make sure this parser supports this kind of rule */
|
||
|
|
if (!$rewriteParser->isValidRule($rules[$ruleId], $activeRule)) {
|
||
|
|
return array(null, REWRITE_STATUS_INVALID_PATTERN,
|
||
|
|
null, null, array($moduleId, $ruleId));
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Save the pattern for the URL generator to use */
|
||
|
|
if (isset($rules[$ruleId]['match']) && isset($activeRule['pattern'])) {
|
||
|
|
$shortUrl = array('match' => $rules[$ruleId]['match'],
|
||
|
|
'pattern' => $activeRule['pattern']);
|
||
|
|
|
||
|
|
/* Get custom function information */
|
||
|
|
if (!empty($rules[$ruleId]['keywords'])) {
|
||
|
|
foreach ($rules[$ruleId]['keywords'] as $key => $value) {
|
||
|
|
if (isset($value['function'])) {
|
||
|
|
$shortUrl['functions'][$key] = $value['function'];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Save the onLoad handler for this rule */
|
||
|
|
if (isset($rules[$ruleId]['onLoad'])) {
|
||
|
|
$shortUrl['onLoad'] = $rules[$ruleId]['onLoad'];
|
||
|
|
}
|
||
|
|
|
||
|
|
$shortUrls[] = $shortUrl;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Parse the pattern and create regular expressions with conditions */
|
||
|
|
if (empty($rules[$ruleId]['keywords'])) {
|
||
|
|
$rules[$ruleId]['keywords'] = array();
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Add custom keywords to the list of allowed keywords */
|
||
|
|
if (!isset($rules[$ruleId]['keywords']['itemId'])) {
|
||
|
|
$rules[$ruleId]['keywords']['itemId'] = array('pattern' => '([0-9]+)');
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Restrict this rule to given query string conditions */
|
||
|
|
if (!empty($rules[$ruleId]['restrict'])) {
|
||
|
|
foreach ($rules[$ruleId]['restrict'] as $key => $value) {
|
||
|
|
$rules[$ruleId]['conditions'][] = array(
|
||
|
|
'test' => 'QUERY_STRING',
|
||
|
|
'pattern' => $key . '=' . $value);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Exempt requests from the following hosts */
|
||
|
|
if (!empty($rules[$ruleId]['exemptReferer'])) {
|
||
|
|
foreach ($accessList as $host) {
|
||
|
|
$rules[$ruleId]['conditions'][] = array(
|
||
|
|
'test' => 'HTTP:Referer',
|
||
|
|
'pattern' => '!^[a-zA-Z0-9\\+\\.\\-]+://' . $host . '/',
|
||
|
|
'flags' => array('NC'));
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Exempt requests with empty referer */
|
||
|
|
if (!empty($allowEmptyReferer)) {
|
||
|
|
$rules[$ruleId]['conditions'][] = array(
|
||
|
|
'test' => 'HTTP:Referer',
|
||
|
|
'pattern' => '!^$');
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Build the query string to map the request on to */
|
||
|
|
if (empty($rules[$ruleId]['queryString'])) {
|
||
|
|
$rules[$ruleId]['queryString'] = array();
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!empty($rules[$ruleId]['match'])) {
|
||
|
|
$rules[$ruleId]['queryString'] = array_merge(
|
||
|
|
$rules[$ruleId]['match'], $rules[$ruleId]['queryString']);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Options */
|
||
|
|
if (empty($rules[$ruleId]['options'])) {
|
||
|
|
$rules[$ruleId]['options'] = array();
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Merge with default options. Later values override defaults. */
|
||
|
|
$rules[$ruleId]['options'] = array_merge(
|
||
|
|
array('forceDirect' => isset($rules[$ruleId]['queryString']['view'])
|
||
|
|
&& $rules[$ruleId]['queryString']['view'] == 'watermark.DownloadItem',
|
||
|
|
'forceServerRelativeUrl' => true,
|
||
|
|
'forceSessionId' => false,
|
||
|
|
'htmlEntities' => false,
|
||
|
|
'urlEncode' => false,
|
||
|
|
'useAuthToken' => false), $rules[$ruleId]['options']);
|
||
|
|
|
||
|
|
/* Build the list of flags to apply to this rule */
|
||
|
|
if (isset($rules[$ruleId]['flags']) && isset($flags)) {
|
||
|
|
$rules[$ruleId]['flags'] = array_merge(
|
||
|
|
$rules[$ruleId]['flags'], $flags['mandatory']);
|
||
|
|
} else if (isset($flags)){
|
||
|
|
$rules[$ruleId]['flags'] = $flags['default'];
|
||
|
|
} else {
|
||
|
|
$rules[$ruleId]['flags'] = array();
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Make sure that there's no subrequest made when we match by the query string */
|
||
|
|
if (!empty($rules[$ruleId]['restrict'])) {
|
||
|
|
$rules[$ruleId]['flags'][] = 'L';
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Ignore duplicate flags */
|
||
|
|
$rules[$ruleId]['flags'] = array_unique($rules[$ruleId]['flags']);
|
||
|
|
|
||
|
|
/* Parse the rule */
|
||
|
|
list ($ret, $code) = RewriteHelper::_parseRule(
|
||
|
|
$regexRules, $rules[$ruleId], $activeRule);
|
||
|
|
if ($ret) {
|
||
|
|
return array($ret, null, null, null, null);
|
||
|
|
}
|
||
|
|
if ($code != REWRITE_STATUS_OK) {
|
||
|
|
return array(null, $code, null, null, array($moduleId, $ruleId));
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
usort($regexRules, array('RewriteHelper', '_sortRules'));
|
||
|
|
return array(null, REWRITE_STATUS_OK, $regexRules, $shortUrls, null);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Replace keywords with appropriate pattern and append to $regexRules.
|
||
|
|
* @param array $regexRules of parsed rules
|
||
|
|
* @param array $rule rewrite rule
|
||
|
|
* @param array $activeRule array ('pattern' => string pattern)
|
||
|
|
* @return array object GalleryStatus a status code
|
||
|
|
* int rewrite status code (REWRITE_STATUS_OK on success)
|
||
|
|
* @access private
|
||
|
|
*/
|
||
|
|
function _parseRule(&$regexRules, $rule, $activeRule) {
|
||
|
|
$regexRule = array();
|
||
|
|
$regexRule['keywords'] = array();
|
||
|
|
$regexRule['queryString'] = $rule['queryString'];
|
||
|
|
$regexRule['options'] = $rule['options'];
|
||
|
|
$regexRule['flags'] = $rule['flags'];
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Backreference 0 contains the text that matched the full pattern, backreference 1
|
||
|
|
* contains the text that matched the first parenthesized subpattern, and so on
|
||
|
|
*/
|
||
|
|
$regexRule['keywords'][] = null;
|
||
|
|
|
||
|
|
if (!empty($rule['conditions'])) {
|
||
|
|
$regexRule['conditions'] = array();
|
||
|
|
foreach ($rule['conditions'] as $condition) {
|
||
|
|
$code = RewriteHelper::_parseKeywords(
|
||
|
|
$regexRule, $condition['pattern'], $rule['keywords']);
|
||
|
|
if ($code != REWRITE_STATUS_OK) {
|
||
|
|
return array(null, $code);
|
||
|
|
}
|
||
|
|
|
||
|
|
$regexRule['conditions'][] = $condition;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!empty($activeRule['pattern'])) {
|
||
|
|
$regexRule['pattern'] = preg_quote($activeRule['pattern']);
|
||
|
|
$code = RewriteHelper::_parseKeywords(
|
||
|
|
$regexRule, $regexRule['pattern'], $rule['keywords']);
|
||
|
|
if ($code != REWRITE_STATUS_OK) {
|
||
|
|
return array(null, $code);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
$regexRules[] = $regexRule;
|
||
|
|
|
||
|
|
return array(null, REWRITE_STATUS_OK);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Replace keywords with appropriate pattern and add backreference to $regexRule['keywords'].
|
||
|
|
* @param array $regexRule parsed rule
|
||
|
|
* @param array $pattern
|
||
|
|
* @param array $keywords of keywords => regular expresion for the htaccess file
|
||
|
|
* @return int rewrite status code (REWRITE_STATUS_OK on success)
|
||
|
|
* @access private
|
||
|
|
*/
|
||
|
|
function _parseKeywords(&$regexRule, &$pattern, $keywords) {
|
||
|
|
preg_match_all('/\%([^%]+)\%/', $pattern, $matches);
|
||
|
|
foreach ($matches[1] as $keyword) {
|
||
|
|
if (!isset($keywords[$keyword]['pattern'])) {
|
||
|
|
return REWRITE_STATUS_BAD_KEYWORD;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* TODO: What if $pattern contains more than one instance of "%$keyword%"? */
|
||
|
|
$pattern = str_replace('%' . $keyword . '%', $keywords[$keyword]['pattern'], $pattern);
|
||
|
|
|
||
|
|
if (empty($keywords[$keyword]['ignore'])) {
|
||
|
|
$regexRule['keywords'][] = $keyword;
|
||
|
|
} else {
|
||
|
|
$regexRule['keywords'][] = null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
return REWRITE_STATUS_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Comparison function used to order regex rules. Order rules from most specific to least
|
||
|
|
* specific.
|
||
|
|
* @param array $a first rule to compare
|
||
|
|
* @param array $b second rule to compare
|
||
|
|
* @return int an integer less than, equal to, or greater than zero if the first rule is
|
||
|
|
* considered to be respectively less than, equal to, or greater than the second
|
||
|
|
* @access private
|
||
|
|
*/
|
||
|
|
function _sortRules($a, $b) {
|
||
|
|
/* Flags: rules which don't allow subrequests come last */
|
||
|
|
if (in_array('L', $a['flags']) xor in_array('L', $b['flags'])) {
|
||
|
|
if (in_array('L', $b['flags'])) {
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
/* Conditions */
|
||
|
|
if (!empty($a['conditions']) || !empty($b['conditions'])) {
|
||
|
|
if (!empty($a['conditions']) && !empty($b['conditions'])) {
|
||
|
|
return count($a['conditions']) - count($b['conditions']);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!empty($a['conditions'])) {
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
* Pattern: static patterns come before regex patterns, long patterns come before short
|
||
|
|
* patterns, empty patterns come last.
|
||
|
|
*/
|
||
|
|
if (!empty($a['pattern']) || !empty($b['pattern'])) {
|
||
|
|
if (!empty($a['pattern']) && !empty($b['pattern'])) {
|
||
|
|
if (count($a['keywords']) < 2 xor count($b['keywords']) < 2) {
|
||
|
|
if (count($a['keywords']) < 2) {
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
return strlen($b['pattern']) - strlen($a['pattern']);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!empty($a['pattern'])) {
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
return 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Get the rewrite rule history for a specific module.
|
||
|
|
* @param string $moduleId id of the module
|
||
|
|
* @return array object GalleryStatus a status code
|
||
|
|
* array (mixed ruleId => array ('pattern' => string pattern))
|
||
|
|
*/
|
||
|
|
function getHistoryForModule($moduleId) {
|
||
|
|
list ($ret, $history) = GalleryCoreApi::getPluginParameter(
|
||
|
|
'module', 'rewrite', 'history.' . $moduleId);
|
||
|
|
if ($ret) {
|
||
|
|
return array($ret, null);
|
||
|
|
}
|
||
|
|
$history = empty($history) ? array() : unserialize($history);
|
||
|
|
|
||
|
|
return array(null, $history);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Store the rewrite rule history for a specific module.
|
||
|
|
* @param string $moduleId id of the module
|
||
|
|
* @param array $history array (mixed ruleId => array ('pattern' => string pattern))
|
||
|
|
* @return object GalleryStatus a status code
|
||
|
|
*/
|
||
|
|
function setHistoryForModule($moduleId, $history) {
|
||
|
|
if (!empty($history)) {
|
||
|
|
$ret = GalleryCoreApi::setPluginParameter(
|
||
|
|
'module', 'rewrite', 'history.' . $moduleId, serialize($history));
|
||
|
|
} else {
|
||
|
|
$ret = GalleryCoreApi::removePluginParameter(
|
||
|
|
'module', 'rewrite', 'history.' . $moduleId);
|
||
|
|
}
|
||
|
|
if ($ret) {
|
||
|
|
return $ret;
|
||
|
|
}
|
||
|
|
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
?>
|