Как сделать эту функцию разбора PHP PHP практически идеальной? - PullRequest
0 голосов
/ 19 июля 2010

Эта функция великолепна, но ее основной недостаток в том, что она не обрабатывает домены, заканчивающиеся на .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 ]

9 голосов
/ 19 июля 2010

Что не так со встроенным parse_url ?

4 голосов
/ 19 июля 2010

Это может быть или не быть интересным, но вот регулярное выражение, которое я написал, что в основном соответствует 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;
    }
}
1 голос
/ 19 июля 2010

Заменить этот бит:

(?P<extension>\w+)

На:

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

Где (?: ... ) часть - группа без захвата, с ? сделать его необязательным.

Вероятно, я бы пошел еще дальше и изменил бы этот бит следующим образом:

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

Поскольку расширение не содержит цифр или подчеркиваний, а обычно состоит из 2/3 букв (я думаю, что .museum самый длинный, при 6 ... так что 10, вероятно, является безопасным максимумом).

Если вы сделаете это, вы можете захотеть добавить флаг без учета регистра (или также ввести AZ).

Исходя из вашего комментария, вы хотите сделать субдомен частью совпадения «ленивым» (сопоставлять только в случае необходимости) и, таким образом, разрешить расширению захватывать обе части.

Для этого просто добавьте? до конца квантифера, изменяя:

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

на

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

И (теоретически - PHP не пригоден для тестирования), который только сделаетсубдомен длиннее, если это необходимо, поэтому следует позволить группе расширений соответствовать соответствующим образом.

Обновление:
Хорошо, если вы уже извлекли полное имя хоста (используя parse_url, как предложено в других комментариях Q /), попробуйте это для сопоставления частей субдомена, домена и расширения:

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

Это оставит . в конце субдомена (и в начале extensio) n, но вы можете использовать substr($string,0,-1) или аналогичный для его удаления.

Расширенная форма для удобства чтения:

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

(можете добавить комментарии, чтобы объяснить что-либо из этого, если необходимо?)

0 голосов
/ 04 октября 2016

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...