fork download
  1. <?php
  2.  
  3. function escape_and_linkify($text) {
  4. $regex =
  5. '`https?+:(?://(?:(?:[-.0-9_a-z~]|%[0-9a-f][0-9a-f]' .
  6. '|[!$&-,:;=])*+@)?+(?:\[(?:(?:[0-9a-f]{1,4}:){6}(?:' .
  7. '[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:\d|[1-9]\d|1\d{2}|2' .
  8. '[0-4]\d|25[0-5])\.(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25' .
  9. '[0-5])\.(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.(?' .
  10. ':\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]))|::(?:[0-9a-f' .
  11. ']{1,4}:){5}(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:\d|[1' .
  12. '-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.(?:\d|[1-9]\d|1\d{' .
  13. '2}|2[0-4]\d|25[0-5])\.(?:\d|[1-9]\d|1\d{2}|2[0-4]\\' .
  14. 'd|25[0-5])\.(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])' .
  15. ')|(?:[0-9a-f]{1,4})?+::(?:[0-9a-f]{1,4}:){4}(?:[0-' .
  16. '9a-f]{1,4}:[0-9a-f]{1,4}|(?:\d|[1-9]\d|1\d{2}|2[0-' .
  17. '4]\d|25[0-5])\.(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-' .
  18. '5])\.(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.(?:\d' .
  19. '|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]))|(?:(?:[0-9a-f]{' .
  20. '1,4}:)?+[0-9a-f]{1,4})?+::(?:[0-9a-f]{1,4}:){3}(?:' .
  21. '[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:\d|[1-9]\d|1\d{2}|2' .
  22. '[0-4]\d|25[0-5])\.(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25' .
  23. '[0-5])\.(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.(?' .
  24. ':\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]))|(?:(?:[0-9a-' .
  25. 'f]{1,4}:){0,2}[0-9a-f]{1,4})?+::(?:[0-9a-f]{1,4}:)' .
  26. '{2}(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:\d|[1-9]\d|1\\' .
  27. 'd{2}|2[0-4]\d|25[0-5])\.(?:\d|[1-9]\d|1\d{2}|2[0-4' .
  28. ']\d|25[0-5])\.(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5' .
  29. '])\.(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]))|(?:(?:' .
  30. '[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?+::[0-9a-f]{1,4' .
  31. '}:(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:\d|[1-9]\d|1\d' .
  32. '{2}|2[0-4]\d|25[0-5])\.(?:\d|[1-9]\d|1\d{2}|2[0-4]' .
  33. '\d|25[0-5])\.(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]' .
  34. ')\.(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5]))|(?:(?:[' .
  35. '0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?+::(?:[0-9a-f]{1' .
  36. ',4}:[0-9a-f]{1,4}|(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25' .
  37. '[0-5])\.(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.(?' .
  38. ':\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.(?:\d|[1-9]\\' .
  39. 'd|1\d{2}|2[0-4]\d|25[0-5]))|(?:(?:[0-9a-f]{1,4}:){' .
  40. '0,5}[0-9a-f]{1,4})?+::[0-9a-f]{1,4}|(?:(?:[0-9a-f]' .
  41. '{1,4}:){0,6}[0-9a-f]{1,4})?+::|v[0-9a-f]++\.[!$&-.' .
  42. '0-;=_a-z~]++)\]|(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0' .
  43. '-5])\.(?:\d|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.(?:\\' .
  44. 'd|[1-9]\d|1\d{2}|2[0-4]\d|25[0-5])\.(?:\d|[1-9]\d|' .
  45. '1\d{2}|2[0-4]\d|25[0-5])|(?:[-.0-9_a-z~]|%[0-9a-f]' .
  46. '[0-9a-f]|[!$&-,;=])*+)(?::\d*+)?+(?:/(?:[-.0-9_a-z' .
  47. '~]|%[0-9a-f][0-9a-f]|[!$&-,:;=@])*+)*+|/(?:(?:[-.0' .
  48. '-9_a-z~]|%[0-9a-f][0-9a-f]|[!$&-,:;=@])++(?:/(?:[-' .
  49. '.0-9_a-z~]|%[0-9a-f][0-9a-f]|[!$&-,:;=@])*+)*+)?+|' .
  50. '(?:[-.0-9_a-z~]|%[0-9a-f][0-9a-f]|[!$&-,:;=@])++(?' .
  51. ':/(?:[-.0-9_a-z~]|%[0-9a-f][0-9a-f]|[!$&-,:;=@])*+' .
  52. ')*+)?+(?:\?+(?:[-.0-9_a-z~]|%[0-9a-f][0-9a-f]|[!$&' .
  53. '-,/:;=?+@])*+)?+(?:#(?:[-.0-9_a-z~]|%[0-9a-f][0-9a' .
  54. '-f]|[!$&-,/:;=?+@])*+)?+`i'
  55. ;
  56. $regex,
  57. function ($m) {
  58. $p = parse_url(htmlspecialchars_decode($m[0], ENT_QUOTES));
  59. if (!isset($p['host'])) {
  60. return false;
  61. }
  62. $p += array_fill_keys(array('path', 'query'), '');
  63. parse_str($p['query'], $q);
  64. switch ($p['host']) {
  65. case 'www.nicovideo.jp':
  66. return
  67. preg_match('@^/watch/([sn]m\d++)/*+$@', $p['path'], $n) ?
  68. '<script type="text/javascript" src="http://e...content-available-to-author-only...o.jp/thumb_watch/' . $n[1] . '"></script>' :
  69. htmlspecialchars($m[0], ENT_QUOTES, 'UTF-8')
  70. ;
  71. case 'nico.ms':
  72. return
  73. preg_match('@^/([sn]m\d++)/*+$@', $p['path'], $n) ?
  74. '<script type="text/javascript" src="http://e...content-available-to-author-only...o.jp/thumb_watch/' . $n[1] . '"></script>' :
  75. htmlspecialchars($m[0], ENT_QUOTES, 'UTF-8')
  76. ;
  77. case 'youtu.be':
  78. return
  79. preg_match('@^/(\w++)/*+$@', $p['path'], $n) ?
  80. '<iframe width="420" height="315" src="//www.youtube.com/embed/' . $n[1] .'" frameborder="0" allowfullscreen></iframe>' :
  81. htmlspecialchars($m[0], ENT_QUOTES, 'UTF-8')
  82. ;
  83. case 'www.youtube.com':
  84. case 'youtube.com':
  85. case 'jp.youtube.com':
  86. if (!isset($q['v']) || !is_string($q['v'])) {
  87. return false;
  88. }
  89. return
  90. preg_match('@^/(\w++)/*+$@', $p['path'], $n) ?
  91. '<iframe width="420" height="315" src="//www.youtube.com/embed/' . $q['v'] .'" frameborder="0" allowfullscreen></iframe>' :
  92. htmlspecialchars($m[0], ENT_QUOTES, 'UTF-8')
  93. ;
  94. default:
  95. return htmlspecialchars($m[0], ENT_QUOTES, 'UTF-8');
  96. }
  97. },
  98. htmlspecialchars($text, ENT_QUOTES, 'UTF-8')
  99. );
  100. }
  101.  
  102. $text = <<<EOD
  103. Youtubeにはhttp://w...content-available-to-author-only...e.com/watch?v=s0SE8GMgeEIとか
  104. http://y...content-available-to-author-only...u.be/s0SE8GMgeEIのようなURLフォーマットがあります。
  105. http://w...content-available-to-author-only...e.com/watch?foo=hoge&v=s0SE8GMgeEI&bar=hogeのように
  106. 余分なクエリがついていてもちゃんと処理すべきでしょう。
  107. 日本に限定するならば
  108. http://j...content-available-to-author-only...e.com/watch?v=s0SE8GMgeEIもちゃんと処理すべきですね。
  109.  
  110. 一方ニコニコ動画には
  111. http://w...content-available-to-author-only...o.jp/watch/sm1136355だけではなく
  112. http://w...content-available-to-author-only...o.jp/watch/nm1136355のように、
  113. 「s」と「m」両方が来る可能性があります。また、
  114. http://n...content-available-to-author-only...o.ms/sm1136355のような短縮URLもあります。
  115. これらにはちゃんと対応すべきです。
  116.  
  117. とはいっても、無効なURLもちゃんと判別しなければなりません。
  118. 例えばhttp://y...content-available-to-author-only...u.be/s0SE8GMgeEI/hogehoge&a=bとかは明らかにおかしいし、
  119. http://w...content-available-to-author-only...o.jp/watch/nm11363abcもニコニコ動画の仕様としてはおかしいです。
  120. こういったものは無視する必要があります。当然、関係ないものはエスケープして表示させましょう。
  121. 「&」「<」「>」といった文字がちゃんとエスケープされるか確認してください。
  122. EOD;
  123.  
  124. echo escape_and_linkify($text);
Success #stdin #stdout 0.01s 20520KB
stdin
Standard input is empty
stdout
Youtubeには<iframe width="420" height="315" src="//www.youtube.com/embed/s0SE8GMgeEI" frameborder="0" allowfullscreen></iframe>とか
<iframe width="420" height="315" src="//www.youtube.com/embed/s0SE8GMgeEI" frameborder="0" allowfullscreen></iframe>のようなURLフォーマットがあります。
<iframe width="420" height="315" src="//www.youtube.com/embed/s0SE8GMgeEI" frameborder="0" allowfullscreen></iframe>のように
余分なクエリがついていてもちゃんと処理すべきでしょう。
日本に限定するならば
<iframe width="420" height="315" src="//www.youtube.com/embed/s0SE8GMgeEI" frameborder="0" allowfullscreen></iframe>もちゃんと処理すべきですね。

一方ニコニコ動画には
<script type="text/javascript" src="http://e...content-available-to-author-only...o.jp/thumb_watch/sm1136355"></script>だけではなく
<script type="text/javascript" src="http://e...content-available-to-author-only...o.jp/thumb_watch/nm1136355"></script>のように、
「s」と「m」両方が来る可能性があります。また、
<script type="text/javascript" src="http://e...content-available-to-author-only...o.jp/thumb_watch/sm1136355"></script>のような短縮URLもあります。
これらにはちゃんと対応すべきです。

とはいっても、無効なURLもちゃんと判別しなければなりません。
例えばhttp://y...content-available-to-author-only...u.be/s0SE8GMgeEI/hogehoge&amp;amp;a=bとかは明らかにおかしいし、
http://w...content-available-to-author-only...o.jp/watch/nm11363abcもニコニコ動画の仕様としてはおかしいです。
こういったものは無視する必要があります。当然、関係ないものはエスケープして表示させましょう。
「&amp;」「&lt;」「&gt;」といった文字がちゃんとエスケープされるか確認してください。