如何检测给定的 URL 是否是当前 URL


How can I detect if a given URL is the current one?

我需要检测提供的URL是否与当前导航到的URL匹配。请注意,以下是所有有效但语义等效的 URL:

https://www.example.com/path/to/page/index.php?parameter=value
https://www.example.com/path/to/page/index.php
https://www.example.com/path/to/page/
https://www.example.com/path/to/page
http://www.example.com/path/to/page
//www.example.com/path/to/page
//www/path/to/page
../../../path/to/page
../../to/page
../page
./
如果给定的 URL 指向当前页面,

则最后一个函数必须返回 true,如果没有,则必须返回false我没有预期的 URL 列表; 这将用于只希望在链接到当前页面时禁用链接的客户端。请注意,我希望忽略参数,因为这些参数不表示此站点上的当前页面。我使用了以下正则表达式:

/^((https?:)?'/'/www('.example'.com)'/path'/to'/page'/?(index.php)?('?.+=.*('&.+=.*)*)?)|('.'/)$/i

其中https?www'.example'.com'/path'/to'/pageindex.php 是用$_SERVER["PHP_SELF"]动态检测并制成正则表达式形式,但这与 ../../to/page 等相对 URL 不匹配。

编辑:我对正则表达式更进一步:refiddle.co/gv8现在我只需要 PHP 为任何给定页面动态创建正则表达式。

首先,无法预测将导致当前页面显示的有效URL的总列表,因为您无法预测(或控制(可能链接回页面的外部链接。 如果有人使用TinyURL或 bit.ly 怎么办? 正则表达式不会削减芥末。

如果您需要确保链接不会产生相同的页面,那么您需要对其进行测试。 这是一个基本概念:

  1. 每个页面都有一个唯一的 ID。 称之为序列号。 它应该是持久的。 序列号应嵌入页面中可预测的(尽管可能是不可见的(位置。

  2. 创建页面时,PHP 需要遍历每个页面的所有链接,访问每个链接,并确定链接是否解析为序列号与调用页面序列号匹配的页面。

  3. 如果序列号不匹配,则将链接显示为链接。 否则,显示其他内容。

显然,对于页面制作来说,这将是一个艰巨的、资源密集型的过程。 你真的不想以这种方式解决你的问题。

考虑到你的"最终目标"评论,我怀疑你最好的方法是近似的。 以下是一些策略...

第一个选项也是最简单的。如果您正在构建一个通常以一种格式创建链接的内容管理系统,只需支持该格式即可。 维基百科的方法之所以有效,是因为[[link]]是他们生成的东西,所以他们知道它是如何格式化的。

其次是你问题的方向。 URL 的元素是"协议"、"主机"、"路径"和"查询字符串"。 您可以将它们分解为正则表达式,并可能使其正确。 您已经声明打算忽略查询字符串。 所以。。。从'((https?:)?//(www'.)?example'.com)?' . $_SERVER['SCRIPT_NAME']开始,并添加适合的结局。 其他答案已经在帮助您解决这个问题。

第三个选项要复杂得多,但可以让您对测试进行更精细的控制。 与最后一个选项一样,您拥有各种 URL 元素。 您可以在不使用正则表达式的情况下测试每个的有效性。 例如:

$a = array();                                 // init array for valid URLs
// Step through each variation of our path...
foreach([$_SERVER['SCRIPT_NAME'], $_SERVER['REQUEST_URI']] as $path) {
  // Step through each variation of our host...
  foreach ([$_SERVER['HTTP_HOST'], explode(".", $_SERVER['HTTP_HOST'])[0]] as $server) {
    // Step through each variation of our protocol...
    foreach (['https://','http://','//'] as $protocol) {
      // Set the URL as a key.
      $a[ $protocol . $server . $path ] = 1;
    }
  }
  // Also for each path, step through directories and parents...
  $apath=explode('/', $path);                 // turn the path into an array
  unset($apath[0]);                           // strip the leading slash
  for( $i = 1; $i <= count($apath); $i++ ) {
    if (strlen($apath[$i])) {
      $a[ str_repeat("../", 1+count($apath)-$i) . implode("/", $apath) ] = 1;
                                              // add relative paths
    }
    unset($apath[$i]);
  }
  $a[ "./" . implode("/", $apath) ] = 1;      // add current directory
}

然后只需测试链接(减去其查询字符串(是否是数组中的索引。 或调整以适应;我相信你明白了。

我最喜欢这第三种解决方案。

实际上不需要正则表达式来去除所有查询参数。您可以使用strok()

$url = strtok($url, '?');

并且,要检查 URL 数组的输出,请执行以下操作:

$url_list = <<<URL
https://www.example.com/path/to/page/index.php?parameter=value
https://www.example.com/path/to/page/index.php    
...    
./?parameter=value
./
URL;
$urls = explode("'n", $url_list);
foreach ($urls as $url) {
    $url = strtok($url, '?'); // remove everything after ?
    echo $url."'n";
}

作为一个功能(可以改进(:

function checkURLMatch($url, $url_array) {
    $url = strtok($url, '?'); // remove everything after ?
    if( in_array($url, $url_array)) {
        // url exists array
        return True;
    } else {
        // url not in array
        return False;
    }
}

现场观看!

您可以使用此方法:

function checkURL($me, $s) {
   $dir = dirname($me) . '/';
   // you may need to refine this
   $s = preg_filter(array('~^//~', '~/$~', '~'?.*$~', '~'.'./~'),
                    array('', '', '', $dir), $s);
   // parse resulting URL
   $url = parse_url($s);
   var_dump($url);
   // match parsed URL's path with self
   return ($url['path'] === $me);
}
// your page's URL with stripped out .php    
$me = str_replace('.php', '', $_SERVER['PHP_SELF']);
// assume this is the URL you are matching against
$s = '../page/';
// compare $me with $s
$ret = checkURL($me, $s);
var_dump($ret);

现场演示:http://ideone.com/OZZM53

由于过去几天我得到了报酬,所以我不只是坐在那里等待答案。我想出了一个在我的测试平台上工作的方法;其他人怎么看?感觉有点臃肿,但也感觉防弹。

调试留下的回声,以防您想要回显一些内容。

global $debug;$debug = false; // toggle debug echoes and var_dumps

/**
 * Returns a boolean indicating whether the given URL is the current one.
 * 
 * @param $otherURL the other URL, as a string. Can be any URL, relative or canonical. Invalid URLs will not match.
 * 
 * @return true iff the given URL points to the same place as the current one
 */
function isCurrentURL($otherURL)
{global $debug;
    if($debug)echo"<!--'r'nisCurrentURL($otherURL)'r'n{'r'n";
    if ($thisURL == $otherURL) // unlikely, but possible. Might as well check.
        return true;
    // BEGIN Parse other URL
    $otherProtocol = parse_url($otherURL);
    $otherHost = $otherProtocol["host"] or null; // if $otherProtocol["host"] is set and is not null, use it. Else, use null.
    $otherDomain = explode(".", $otherHost) or $otherDomain;
    $otherSubdomain = array_shift($otherDomain); // subdom only
    $otherDomain = implode(".", $otherDomain); // domain only
    $otherFilepath = $otherProtocol["path"] or null;
    $otherProtocol = $otherProtocol["scheme"] or null;
    // END Parse other URL
    // BEGIN Get current URL
    #if($debug){echo '$_SERVER == '; var_dump($_SERVER);}
    $thisProtocol = $_SERVER["HTTP_X_FORWARDED_PROTO"]; // http or https
    $thisHost = $_SERVER["HTTP_HOST"]; // subdom or subdom.domain.tld
    $thisDomain = explode(".", $thisHost);
    $thisSubdomain = array_shift($thisDomain); // subdom only
    $thisDomain = implode(".", $thisDomain); // domain only
    if ($thisDomain == "")
        $thisDomain = $otherDomain;
    $thisFilepath = $_SERVER["PHP_SELF"]; // /path/to/file.php
    $thisURL = "$thisProtocol://$thisHost$thisFilepath";
    // END Get current URL
    if($debug)echo"Current URL is $thisURL ($thisProtocol, $thisSubdomain, $thisDomain, $thisFilepath).'r'n";
    if($debug)echo"Other URL is $otherURL ($otherProtocol, $otherHost, $otherFilepath).'r'n";
    $thisDomainRegexed = isset($thisDomain) && $thisDomain != null && $thisDomain != "" ? "('." . str_replace(".","'.",$thisDomain) . ")?" : ""; // prepare domain for insertion into regex
    //                                                                                                      v this makes the last slash before index.php optional
    $regex = "/^(($thisProtocol:)?'/'/$thisSubdomain$thisDomainRegexed)?" . preg_replace('/index'''..+$/i','?(index'..+)?', str_replace(array(".", "/"), array("'.", "'/"), $thisFilepath)) . '$/i';
    if($debug)echo "'r'nregex is $regex'r'nComparing regex against $otherURL";
    if (preg_match($regex, $otherURL))
    {
        if($debug)echo"'r'n'tIt's a match! Returning true...'r'n}'r'n-->";
        return true;
    }
    else
    {
        if($debug)echo"'r'n'tOther URL is NOT a fully-qualified URL in this subdomain. Checking if it is relative...";
        if($otherURL == $thisFilepath) // somewhat likely
        {
            if($debug)echo"'r'n't'tOhter URL and this filepath are an exact match! Returning true...'r'n}'r'n-->";
            return true;
        }
        else
        {
            if($debug)echo"'r'n't'tFilepath is not an exact match. Testing against regex...";
            $regex = regexFilepath($thisFilepath);
            if($debug)echo"'r'n't'tNew Regex is $regex";
            if($debug)echo"'r'n't'tComparing regex against $otherFilepath...";
            if (preg_match($regex, $otherFilepath))
            {
                if($debug)echo"'r'n't't'tIt's a match! Returning true...'r'n}'r'n-->";
                return true;
            }
        }
    }
    if($debug)echo"'r'nI tried my hardest, but couldn't match $otherURL to $thisURL. Returning false...'r'n}'r'n-->";
    return false;
}
/**
 * Uses the given filepath to create a regex that will match it in any of its relative representations.
 * 
 * @param $path the filepath to be converted
 * 
 * @return a regex that matches a all relative forms of the given filepath
 */
function regexFilepath($path)
{global $debug;
    if($debug)echo"'r'nregexFilepath($path)'r'n{'r'n";
    $filepathArray = explode("/", $path);
    if (count($filepathArray) == 0)
        throw new Exception("given parameter not a filepath: $path");
    if ($filepathArray[0] == "") // this can happen if the path starts with a "/"
        array_shift($filepathArray); // strip the first element off the array
    $isIndex = preg_match("/^index'..+$/i", end($filepathArray));
    $filename = array_pop($filepathArray);
    if($debug){var_dump($filepathArray);}
$ret = '';
foreach($filepathArray as $i)
    $ret = "('.'.'/$ret$i'/)?"; // make a pseudo-recursive relative filepath
if($debug)echo "'r'n$ret";
$ret = preg_replace('/')'?$/', '?)', $ret); // remove the last '?' and add one before the last ''/'
if($debug)echo "'r'n$ret";
$ret = '/^' . ($ret == '' ? ''.'/' : "(('.'/)|$ret)") . ($isIndex ? '(index'..+)?' : str_replace('.', ''.', $filename)) . '$/i'; // if this filepath leads to an index.php (etc.), then that filename is implied and irrelevant.
if($debug)echo''r'n}'r'n';
}

这似乎与我需要它匹配的所有内容相匹配,而不是我不需要它匹配的内容。