Как кодировать URL-адреса только не-ASCII-символы URL-адреса в PHP, но оставить зарезервированные символы незакодированными?

У меня есть URL-адрес, который выглядит так (обратите внимание на символы «„):

http://tinklarastis.omnitel.lt/kokius-aptarnavimo-kanalus-klientui-siulo-„omnitel“-1494

Я получаю его от парсера SimplePie, если это имеет значение. Теперь, если вы попытаетесь перейти по этому конкретному URL-адресу в своем браузере и скопировать его из адресной строки, вы получите URL-адрес с символами, отличными от ASCII, закодировано в процентах:

http://tinklarastis.omnitel.lt/kokius-aptarnavimo-kanalus-klientui-siulo-%E2%80%9Eomnitel%E2%80%9C-1494

Я пытаюсь понять, как я могу имитировать одно и то же преобразование в PHP. Я не могу просто использовать urlencode() или urlrawencode(), поскольку они кодируют как символы, отличные от ASCII, и зарезервированные символы, в то время как в в моем случае зарезервированные символы (/?& и т. д.) должны оставаться такими, какие они есть.

До сих пор я видел только решения, которые включают разделение URL-адрес на части между зарезервированными символами, а затем с использованием urlencode(), но мне это кажется хакерским, и я надеюсь, что есть более элегантное решение. Я пробовал различные варианты iconv(), mb_convert_encoding(), но пока безуспешно.


person Aurimas    schedule 22.03.2012    source источник
comment
Что такого хакерского в решении, на которое вы ссылаетесь? Что это за элегантный способ тривиальной манипуляции со строками, который вы ищете?   -  person Your Common Sense    schedule 23.03.2012
comment
@YourCommonSense Возможно, я ошибаюсь, но мне кажется, что моя ситуация не является произвольным упражнением по манипулированию строками. Скорее, это довольно общая задача кодирования - тот факт, что браузеры делают это при копировании URL-адресов из адресной строки, указывает на то, что за этим должен стоять какой-то стандарт/значение.   -  person Aurimas    schedule 23.03.2012
comment
Да, это общая задача кодирования. Что само по себе является тривиальной строковой операцией. И у вас уже есть решение. Не вижу смысла задавать еще один вопрос, если вы уже нашли ответ.   -  person Your Common Sense    schedule 23.03.2012
comment
Я ищу определенный способ решения этой задачи. Чтобы избежать HTML, можно использовать htmlspecialchars() или просто написать пользовательскую функцию с кодами символов и str_replace(). Вы правы, я знаю собственный способ, но я ищу решение, которое использовало бы встроенные функции манипуляции со строками (независимо от того, насколько они тривиальны).   -  person Aurimas    schedule 23.03.2012
comment
У тебя есть это. принятый ответ на вопрос, на который вы ссылаетесь, соответствует описанию - он использует встроенные функции манипулирования строками. В чем проблема?   -  person Your Common Sense    schedule 23.03.2012


Ответы (5)


У меня есть простой однострочный код, который я использую для кодирования на месте только для символов, отличных от ASCII, используя preg_match_callback:

preg_replace_callback('/[^\x20-\x7f]/', function($match) {
    return urlencode($match[0]);
}, $url);

Обратите внимание, что анонимная функция поддерживается только в PHP 5.3+.

person alexandru.asandei    schedule 25.11.2014
comment
Это должен быть принятый ответ. Он обрабатывает символы, отличные от ASCII, в любом месте URL-адреса (путь и строка запроса) и не требует выполнения таких проверок, как предотвращение двойного кодирования. как в ответе ОП. - person BenMorel; 25.05.2019
comment
Хорошее решение, спасибо! Если вы также хотите, чтобы стандартные пробелы были закодированы в +, используйте вместо этого '/[^\x21-\x7f]/'. И если вы хотите, чтобы они были закодированы в %20 для соответствия RFC 3986, вызовите rawurlencode() вместо urlencode(). - person spackmat; 19.05.2020

Немного покопавшись, я пришел к выводу, что в PHP нет способа добиться хороших результатов (однако в других языках, таких как python/perl, похоже, есть функции именно для этого варианта использования). Это функция, которую я придумал (обеспечивает кодирование фрагмента пути URL):

function url_path_encode($url) {
    $path = parse_url($url, PHP_URL_PATH);
    if (strpos($path,'%') !== false) return $url; //avoid double encoding
    else {
        $encoded_path = array_map('urlencode', explode('/', $path));
        return str_replace($path, implode('/', $encoded_path), $url);
    }   
}
person Aurimas    schedule 05.06.2012

Эта функция может помочь:

function sanitizeUrl($url)
{
    $chars = '$-_.+!*\'(),{}|\\^~[]`<>#%";/?:@&=';
    $pattern = '~[^a-z0-9' . preg_quote($chars, '~') . ']+~iu';

    $callback = create_function('$matches', 'return urlencode($matches[0]);');

    return preg_replace_callback($pattern, $callback, $url);
}
person urmaul    schedule 20.03.2013
comment
Разрешено только в символах URL: '-._~:/?#[]@!$&\'()*+,;=' - person vladnev; 16.08.2017

Я думаю, это сделает то, что вы хотите.

<?php

$string = 'http://tinklarastis.omnitel.lt/kokius-aptarnavimo-kanalus-klientui-siulo-„omnitel“-1494/?foo=bar&fizz=buzz';

var_dump(filter_var($string, FILTER_SANITIZE_STRING, FILTER_FLAG_ENCODE_HIGH));

Это даст вам:

$ php test.php
string(140) "http://tinklarastis.omnitel.lt/kokius-aptarnavimo-kanalus-klientui-siulo-&#226;&#128;&#158;omnitel&#226;&#128;&#156;-1494/?foo=bar&fizz=buzz"
person SamHennessy    schedule 28.03.2012
comment
Хорошее предложение, но оно не будет соответствовать RFC 3986. - person vladnev; 16.08.2017

function cyrillicaToUrlencode($text){
return $line = preg_replace_callback('/([а-яё])/ui',
                            function ($matches) {
                                return urlencode($matches[0]);
                            }, 
                            $text); 
}

echo cyrillicaToUrlencode("https://test.com/Москваёtext1Воронежtext2Москваёtext3yМоскваё___-Москваё");

Вернется - https://test.com/%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0%D1%91text1%D0%92%D0%BE%D1%80%D0%BE%D0%BD%D0%B5%D0%B6text2%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0%D1%91text3y%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0%D1%91___-%D0%9C%D0%BE%D1%81%D0%BA%D0%B2%D0%B0%D1%91

person Юрий Светлов    schedule 26.05.2018