如何使这个PHP URL解析功能近乎完美?

时间:2010-07-18 22:08:34

标签: php regex url parsing

这个功能很棒,但它的主要缺点是它不处理以.co.uk或.com.au结尾的域。如何修改它来处理这个?

function parseUrl($url) {
    $r  = "^(?:(?P<scheme>\w+)://)?";
    $r .= "(?:(?P<login>\w+):(?P<pass>\w+)@)?";
    $r .= "(?P<host>(?:(?P<subdomain>[-\w\.]+)\.)?" . "(?P<domain>[-\w]+\.(?P<extension>\w+)))";
    $r .= "(?::(?P<port>\d+))?";
    $r .= "(?P<path>[\w/-]*/(?P<file>[\w-]+(?:\.\w+)?)?)?";
    $r .= "(?:\?(?P<arg>[\w=&]+))?";
    $r .= "(?:#(?P<anchor>\w+))?";
    $r = "!$r!";

    preg_match ( $r, $url, $out );

    return $out;
}

澄清我寻找除parse_url()以外的东西的原因是我想剥离(可能是多个)子域名。

print_r(parse_url('sub1.sub2.test.co.uk'));

结果:

Array(
[scheme] => http
[host] => sub1.sub2.test.co.uk
)

我想要提取的是“test.co.uk”(没有子域名),所以首先使用parse_url是一个毫无意义的额外步骤,其中输出与输入相同。

4 个答案:

答案 0 :(得分:9)

内置parse_url有什么问题?

答案 1 :(得分:4)

这可能是也可能没有意义,但这里是一个正则表达式我写的主要是符合RFC3986(它实际上稍微严格一些,因为它不允许一些更不寻常的URI语法):

~^(?:(?:(?P<scheme>[a-z][0-9a-z.+-]*?)://)?(?P<authority>(?:(?P<userinfo>(?P<username>(?:[\w.\~-]|(?:%[\da-f]{2})|[!$&'()*+,;=])*)?:(?P<password>(?:[\w.\~-]|(?:%[\da-f]{2})|[!$&'()*+,;=])*)?|(?:[\w.\~-]|(?:%[\da-f]{2})|[!$&'()*+,;=]|:)*?)@)?(?P<host>(?P<domain>(?:[a-z](?:[0-9a-z-]*(?:[0-9a-z]))?\.)+(?:[a-z](?:[0-9a-z-]*(?:[0-9a-z]))?))|(?P<ip>(?:25[0-5]|2[0-4]\d|[01]\d\d|\d?\d).(?:25[0-5]|2[0-4]\d|[01]\d\d|\d?\d).(?:25[0-5]|2[0-4]\d|[01]\d\d|\d?\d).(?:25[0-5]|2[0-4]\d|[01]\d\d|\d?\d)))(?::(?P<port>\d+))?(?=/|$)))?(?P<path>/?(?:(?:[\w.\~-]|(?:%[\da-f]{2})|[!$&'()*+,;=]|:|@)+/)*(?:(?:[\w.\~-]|(?:%[\da-f]{2})|[!$&'()*+,;=]|:|@)+/?)?)(?:\?(?P<query>(?:(?:[\w.\~-]|(?:%[\da-f]{2})|[!$&'()*+,;=]|:|@)|/|\?)*?))?(?:#(?P<fragment>(?:(?:[\w.\~-]|(?:%[\da-f]{2})|[!$&'()*+,;=]|:|@)|/|\?)*))?$~i

命名的组件是:

scheme
authority
  userinfo
    username
    password
  domain
  ip
path
query
fragment

以下是生成它的代码(以及某些选项定义的变体):

public static function validateUri($uri, &$components = false, $flags = 0)
{
    if (func_num_args() > 3)
    {
        $flags = array_slice(func_get_args(), 2);
    }

    if (is_array($flags))
    {
        $flagsArray = $flags;
        $flags = array();
        foreach ($flagsArray as $flag)
        {
            if (is_int($flag))
            {
                $flags |= $flag;
            }
        }
    }

    // Set options.
    $requireScheme = !($flags & self::URI_ALLOW_NO_SCHEME);
    $requireAuthority = !($flags & self::URI_ALLOW_NO_AUTHORITY);
    $isRelative = (bool)($flags & self::URI_IS_RELATIVE);
    $requireMultiPartDomain = (bool)($flags & self::URI_REQUIRE_MULTI_PART_DOMAIN);

    // And we're away…

    // Some character types (taken from RFC 3986: http://tools.ietf.org/html/rfc3986).
    $hex = '[\da-f]'; // Hexadecimal digit.
    $pct = "(?:%$hex{2})"; // "Percent-encoded" value.
    $gen = '[\[\]:/?#@]'; // Generic delimiters.
    $sub = '[!$&\'()*+,;=]'; // Sub-delimiters.
    $reserved = "(?:$gen|$sub)"; // Reserved characters.
    $unreserved = '[\w.\~-]'; // Unreserved characters.
    $pChar = "(?:$unreserved|$pct|$sub|:|@)"; // Path characters.
    $qfChar = "(?:$pChar|/|\?)"; // Query/fragment characters.

    // Other entities.
    $octet = '(?:25[0-5]|2[0-4]\d|[01]\d\d|\d?\d)';
    $label = '[a-z](?:[0-9a-z-]*(?:[0-9a-z]))?';

    $scheme = '(?:(?P<scheme>[a-z][0-9a-z.+-]*?)://)';

    // Authority components.
    $userInfo = "(?:(?P<userinfo>(?P<username>(?:$unreserved|$pct|$sub)*)?:(?P<password>(?:$unreserved|$pct|$sub)*)?|(?:$unreserved|$pct|$sub|:)*?)@)?";
    $ip = "(?P<ip>$octet.$octet.$octet.$octet)";
    if ($requireMultiPartDomain)
    {
        $domain = "(?P<domain>(?:$label\.)+(?:$label))";
    }
    else
    {
        $domain = "(?P<domain>(?:$label\.)*(?:$label))";
    }
    $host = "(?P<host>$domain|$ip)";
    $port = '(?::(?P<port>\d+))?';

    // Primary hierarchical URI components.
    $authority = "(?P<authority>$userInfo$host$port(?=/|$))";
    $path = "(?P<path>/?(?:$pChar+/)*(?:$pChar+/?)?)";

    // Final bits.
    $query = "(?:\?(?P<query>$qfChar*?))?";
    $fragment = "(?:#(?P<fragment>$qfChar*))?";

    // Construct the final pattern.
    $pattern = '~^';

    // Only include scheme and authority if the path is not relative.
    if (!$isRelative)
    {
        if ($requireScheme)
        {
            // If the scheme is required, then the authority must also be there.
            $pattern .= $scheme . $authority;
        }
        else if ($requireAuthority)
        {
            $pattern .= "$scheme?$authority";
        }
        else
        {
            $pattern .= "(?:$scheme?$authority)?";
        }
    }
    else
    {
        // Disallow that optional slash we put in $path.
        $pattern .= '(?!/)';
    }

    // Now add standard elements and terminate the pattern.
    $pattern .= $path . $query . $fragment . '$~i';

    // Finally, validate that sucker!
    $components = array();
    $result = (bool)preg_match($pattern, $uri, $matches);
    if ($result)
    {
        // Filter out all of the useless numerical matches.
        foreach ($matches as $key => $value)
        {
            if (!is_int($key))
            {
                $components[$key] = $value;
            }
        }

        return true;
    }
    else
    {
        return false;
    }
}

答案 2 :(得分:1)

替换此位:

(?P<extension>\w+)

使用:

(?P<extension>\w+(?:\.\w+)?)

(?: ... )部分是非捕获组,?使其成为可选组件。


我可能会更进一步,改变这一点:

(?P<extension>[a-z]{2,10}(?:\.[a-z]{2,10})?)

由于扩展名不包含数字或下划线,并且通常只有2/3个字母(我认为。博物馆是最长的,在6 ...所以10可能是最安全的。)

如果你这样做,你可能想要添加一个不区分大小写的标志,(或者也放入A-Z)。


根据你的评论,你想让匹配'懒惰'的子域成为一部分(只有在必要时才匹配),从而允许扩展捕获这两个部分。

要做到这一点,只需在量化器的末尾添加?,即可更改:

(?P<subdomain>[-\w\.]+)

(?P<subdomain>[-\w\.]+?)

并且(理论上 - 没有PHP方便测试)只会在必要时使子域更长,所以应该允许扩展组适当匹配。


<强>更新
好的,假设您已经提取了完整的主机名(使用其他Q /注释中建议的parse_url),请尝试使用此匹配子域,域和扩展部分:

^(?P<subdomains>(?:[\w-]+\.)*?)(?P<domain>[\w-]+(?P<extension>(?:\.[a-z]{2,10}){1,2}))$

这将在子域的末尾(以及在扩展的开头)留下. n,但是您可以使用substr($string,0,-1)或类似内容删除它。

扩展表格以便于阅读:

^
(?P<subdomains>
  (?:[\w-]+\.)*?
)
(?P<domain>
  [\w-]+
  (?P<extension>
     (?:\.[a-z]{2,10}){1,2}
   )
)$

(如果需要,可以添加注释来解释其中的任何内容吗?)

答案 3 :(得分:0)

parse_url()无法提取子域名和域名扩展名。你必须在这里发明自己的解决方案。

我认为正确的实现必须包含所有域名扩展的库,并定期更新。

https://en.wikipedia.org/wiki/List_of_Internet_top-level_domains

http://www.iana.org/domains/root/db

https://publicsuffix.org/list/public_suffix_list.dat

https://raw.githubusercontent.com/publicsuffix/list/master/public_suffix_list.dat