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