Web

Как заменить URL-адреса в тексте ссылками HTML

Как заставить PHP определить, что это ссылка http://, и вывести ее как:

print "<a href='http://www.example.com'>http://www.example.com</a>";

 Я помню, как делал нечто подобное раньше, однако это не было надежным решением, так как сложные ссылки постоянно ломались. Другой хорошей идеей будет, если у вас есть ссылка типа:

http://example.com/test.php?val1=bla&val2blablabla%20bla%20bla.bl

 

 В итоге:

print "<a href='http://example.com/test.php?val1=bla&val2=bla%20bla%20bla.bla'>";

print "http://example.com/test.php";

print "</a>";

 

Любые варианты на этот счет?

 

Ответ 1

Давайте рассмотрим возможные варианты. У вас есть некоторый простой текст, предоставленный пользователем, который вы хотите отобразить с гиперссылками URL.

  1. Префикс протокола "http://" должен быть необязательным.

  2. Должны приниматься как домены, так и IP-адреса.

  3. Должен приниматься любой допустимый домен верхнего уровня, например, .aero и .xn--jxalpdlp.

  4. Номера портов должны быть разрешены.

  5. URL должны быть разрешены в обычном контексте предложения. Например, во фразе «Посетите codernet.ru» завершающая точка не является частью URL.

  6. Возможно, вы захотите разрешить URL "https://", а также другие URL.

  7. Как всегда при отображении пользовательского текста в HTML, необходимо предотвратить межсайтовый скриптинг (XSS). Также необходимо, чтобы амперсанды в URL корректно экранировались как &amp;.

  8. Поддержка адресов IPv6, вероятно, не нужна.

  9. Поддержка адресов электронной почты, безусловно, является плюсом.

  10. Поддерживается только ввод обычного текста HTML-теги во вводе не должны восприниматься (версия для Bitbucket поддерживает ввод HTML).

  11. Проверка на последнюю версию GitHub, с поддержкой адресов электронной почты, аутентифицированных URL, URL в кавычках и круглых скобках, HTML-ввода, а также обновленного списка TLD.

Вот мое мнение:

<?php

$text = <<<EOD

<script>alert('Remember kids: Say no to XSS-attacks! Always HTML escape untrusted input!');</script>

EOD;

$rexProtocol = '(https?://)?';

$rexDomain   = '((?:[-a-zA-Z0-9]{1,63}\.)+[-a-zA-Z0-9]{2,63}|(?:[0-9]{1,3}\.){3}[0-9]{1,3})';

$rexPort     = '(:[0-9]{1,5})?';

$rexPath     = '(/[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]*?)?';

$rexQuery    = '(\?[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]+?)?';

$rexFragment = '(#[!$-/0-9:;=@_\':;!a-zA-Z\x7f-\xff]+?)?';

//Решение 1:

function callback($match) {

    // Добавьте http://, если протокол не указан

    $completeUrl = $match[1] ? $match[0] : "http://{$match[0]}";

    return '<a href="' . $completeUrl . '">'

        . $match[2] . $match[3] . $match[4] . '</a>';

}

print "<pre>";

print preg_replace_callback("&\\b$rexProtocol$rexDomain$rexPort$rexPath$rexQuery$rexFragment(?=[?.!,;:\"]?(\s|$))&",

    'callback', htmlspecialchars($text));

print "</pre>";

 

Чтобы правильно экранировать символы «<» и «&», я прогоняю весь текст через htmlspecialchars перед обработкой. Это не идеально, поскольку экранирование html может привести к неправильному определению границ URL. Следующий код исправляет две вышеупомянутые проблемы, но является более многословным, поскольку я более или менее повторно реализую preg_replace_callback, используя preg_match.

// Решение 2:

 

$validTlds = array_fill_keys(explode(" ", ".aero .asia .biz .cat .com .coop .edu .gov .info .int .jobs .mil .mobi .museum .name .net .org .pro .tel .travel .ac .ad .ae .af .ag .ai .al .am .an .ao .aq .ar .as .at .au .aw .ax .az .ba .bb .bd .be .bf .bg .bh .bi .bj .bm .bn .bo .br .bs .bt .bv .bw .by .bz .ca .cc .cd .cf .cg .ch .ci .ck .cl .cm .cn .co .cr .cu .cv .cx .cy .cz .de .dj .dk .dm .do .dz .ec .ee .eg .er .es .et .eu .fi .fj .fk .fm .fo .fr .ga .gb .gd .ge .gf .gg .gh .gi .gl .gm .gn .gp .gq .gr .gs .gt .gu .gw .gy .hk .hm .hn .hr .ht .hu .id .ie .il .im .in .io .iq .ir .is .it .je .jm .jo .jp .ke .kg .kh .ki .km .kn .kp .kr .kw .ky .kz .la .lb .lc .li .lk .lr .ls .lt .lu .lv .ly .ma .mc .md .me .mg .mh .mk .ml .mm .mn .mo .mp .mq .mr .ms .mt .mu .mv .mw .mx .my .mz .na .nc .ne .nf .ng .ni .nl .no .np .nr .nu .nz .om .pa .pe .pf .pg .ph .pk .pl .pm .pn .pr .ps .pt .pw .py .qa .re .ro .rs .ru .rw .sa .sb .sc .sd .se .sg .sh .si .sj .sk .sl .sm .sn .so .sr .st .su .sv .sy .sz .tc .td .tf .tg .th .tj .tk .tl .tm .tn .to .tp .tr .tt .tv .tw .tz .ua .ug .uk .us .uy .uz .va .vc .ve .vg .vi .vn .vu .wf .ws .ye .yt .yu .za .zm .zw .xn--0zwm56d .xn--11b5bs3a9aj6g .xn--80akhbyknj4f .xn--9t4b11yi5a .xn--deba0ad .xn--g6w251d .xn--hgbk6aj7f53bba .xn--hlcj6aya9esc7a .xn--jxalpdlp .xn--kgbechtv .xn--zckzah .arpa"), true);

$position = 0;

while (preg_match("{\\b$rexProtocol$rexDomain$rexPort$rexPath$rexQuery$rexFragment(?=[?.!,;:\"]?(\s|$))}", $text, &$match, PREG_OFFSET_CAPTURE, $position)) {

    list($url, $urlPosition) = $match[0];

    // Выведите текст, ведущий к URL.

    print(htmlspecialchars(substr($text, $position, $urlPosition - $position)));

    $domain = $match[2][0];

    $port   = $match[3][0];

    $path   = $match[4][0];

    // Проверьте, действителен ли TLD или что $domain является IP-адресом.

    $tld = strtolower(strrchr($domain, '.'));

    if (preg_match('{\.[0-9]{1,3}}', $tld) || isset($validTlds[$tld])) {

        // Добавьте http://, если протокол не указан

        $completeUrl = $match[1][0] ? $url : "http://$url";

        // Выведите гиперссылку.

        printf('<a href="%s">%s</a>', htmlspecialchars($completeUrl), htmlspecialchars("$domain$port$path"));

    } else {

        // Не является действительным URL.

        print(htmlspecialchars($url));

    }

    // Продолжаем разбор текста после URL.

    $position = $urlPosition + strlen($url);

}

// Выведите оставшуюся часть текста.

print(htmlspecialchars(substr($text, $position)));

 

Ответ 2

Вот то, что я нашел, проверенное и испытанное.

function make_links_blank($text) {

  return  preg_replace(

     array(

       '/(?(?=<a[^>]*>.+<\/a>)

             (?:<a[^>]*>.+<\/a>)

             |

             ([^="\']?)((?:https?|ftp|bf2|):\/\/[^<> \n\r]+)

         )/iex',

       '/<a([^>]*)target="?[^"\']+"?/i',

       '/<a([^>]+)>/i',

       '/(^|\s)(www.[^<> \n\r]+)/iex',

       '/(([_A-Za-z0-9-]+)(\\.[_A-Za-z0-9-]+)*@([A-Za-z0-9-]+)

       (\\.[A-Za-z0-9-]+)*)/iex'

       ),

     array(

       "stripslashes((strlen('\\2')>0?'\\1<a href=\"\\2\">\\2</a>\\3':'\\0'))",

       '<a\\1',

       '<a\\1 target="_blank">',

       "stripslashes((strlen('\\2')>0?'\\1<a href=\"http://\\2\">\\2</a>\\3':'\\0'))",

       "stripslashes((strlen('\\2')>0?'<a href=\"mailto:\\0\">\\0</a>':'\\0'))"

       ),

       $text

   );

}

 

Ответ 3

Я использую эту функцию, она работает у меня:

function AutoLinkUrls($str,$popup = FALSE){

    if (preg_match_all("#(^|\s|\()((http(s?)://)|(www\.))(\w+[^\s\)\<]+)#i", $str, $matches)){

        $pop = ($popup == TRUE) ? " target=\"_blank\" " : "";

        for ($i = 0; $i < count($matches['0']); $i++){

            $period = '';

            if (preg_match("|\.$|", $matches['6'][$i])){

                $period = '.';

                $matches['6'][$i] = substr($matches['6'][$i], 0, -1);

            }

            $str = str_replace($matches['0'][$i],

                    $matches['1'][$i].'<a href="http'.

                    $matches['4'][$i].'://'.

                    $matches['5'][$i].

                    $matches['6'][$i].'"'.$pop.'>http'.

                    $matches['4'][$i].'://'.

                    $matches['5'][$i].

                    $matches['6'][$i].'</a>'.

                    $period, $str);

        }//end for

    }//end if

    return $str;

}//end AutoLinkUrls

 

Ответ 4

Этот RegEx должен работать для любой ссылки, кроме новых доменов с 3+ символами...

{

  \\b

  # Соответствие ведущей части (proto://hostname, или просто hostname)

  (

    # http://, or https:// leading part

    (https?)://[-\\w]+(\\.\\w[-\\w]*)+

  |

    # или попытайтесь найти имя хоста с более конкретным подвыражением

    (?i: [a-z0-9] (?:[-a-z0-9]*[a-z0-9])? \\. )+ # sub domains

    # Теперь окончание .com и т. д. Для них требуется строчный регистр

    (?-i: com\\b

        | edu\\b

        | biz\\b

        | gov\\b

        | in(?:t|fo)\\b # .int or .info

        | mil\\b

        | net\\b

        | org\\b

        | [a-z][a-z]\\.[a-z][a-z]\\b # two-letter country code

    )

  )

  # Разрешить необязательный номер порта

  ( : \\d+ )?

  # Остальная часть URL является необязательной и начинается с /

  (

    /

    # Остальное эвристика для того, что, похоже, работает хорошо.

    [^.!,?;"\\'()\[\]\{\}\s\x7F-\\xFF]*

    (

      [.!,?]+ [^.!,?;"\\'()\\[\\]\{\\}\s\\x7F-\\xFF]+

    )*

  )?

}ix

 

Схожие статьи

Web

Проверка на сертификат в HTTPS и SSL3_GET_SERVER_CERTIFICATE

Безопасный и простой в управлении браузер для платежей
Web

Безопасный и простой в управлении браузер для платежей

Клиент-серверная архитектура: что это такое и для чего ее используют?
Web

Клиент-серверная архитектура: что это такое и для чего ее используют?

Как можно взломать почту gmail и как можно ее защитить от взлома
Web

Как можно взломать почту gmail и как можно ее защитить от взлома