Browse Source

Fix autoLinkUrls() failing on long string in parens

When a URL contained a long string in parenthesis, the old pattern hit
its backtrace limit and failed to do anything. This new pattern doesn't
have nearly as much backtracking. I had to use some additional
replacement logic to correctly handle URLs wrapped in punctuation.

Refs #9916
Mark Story 9 years ago
parent
commit
38f5433b98
2 changed files with 41 additions and 10 deletions
  1. 37 10
      src/View/Helper/TextHelper.php
  2. 4 0
      tests/TestCase/View/Helper/TextHelperTest.php

+ 37 - 10
src/View/Helper/TextHelper.php

@@ -117,9 +117,21 @@ class TextHelper extends Helper
         $this->_placeholders = [];
         $options += ['escape' => true];
 
-        $pattern = '#(?<!href="|src="|">)((?:https?|ftp|nntp)://[\p{L}0-9.\-_:]+' .
-            '(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+' .
-            '(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:\'".,<>?«»“”‘’]))#i';
+        $pattern = '/(?:(?<!href="|src="|">)
+            (?>
+                (
+                    (?<left>[\[<(]) # left paren,brace
+                    (?>
+                        # Lax match URL
+                        (?<url>(?:https?|ftp|nntp):\/\/[\p{L}0-9.\-_:]+(?:[\/?][\p{L}0-9.\-_:\/?=&>\[\]()#@]+)?)
+                        (?<right>[\])>]) # right paren,brace
+                    )
+                )
+                |
+                (?<url_bare>(?P>url)) # A bare URL. Use subroutine
+            )
+            )/ixu';
+
         $text = preg_replace_callback(
             $pattern,
             [&$this, '_insertPlaceHolder'],
@@ -146,8 +158,20 @@ class TextHelper extends Helper
      */
     protected function _insertPlaceHolder($matches)
     {
-        $key = md5($matches[0]);
-        $this->_placeholders[$key] = $matches[0];
+        $match = $matches[0];
+        $envelope = ['', ''];
+        if (isset($matches['url'])) {
+            $match = $matches['url'];
+            $envelope = [$matches['left'], $matches['right']];
+        }
+        if (isset($matches['url_bare'])) {
+            $match = $matches['url_bare'];
+        }
+        $key = md5($match);
+        $this->_placeholders[$key] = [
+            'content' => $match,
+            'envelope' => $envelope
+        ];
 
         return $key;
     }
@@ -162,12 +186,13 @@ class TextHelper extends Helper
     protected function _linkUrls($text, $htmlOptions)
     {
         $replace = [];
-        foreach ($this->_placeholders as $hash => $url) {
-            $link = $url;
+        foreach ($this->_placeholders as $hash => $content) {
+            $link = $url = $content['content'];
+            $envelope = $content['envelope'];
             if (!preg_match('#^[a-z]+\://#i', $url)) {
                 $url = 'http://' . $url;
             }
-            $replace[$hash] = $this->Html->link($link, $url, $htmlOptions);
+            $replace[$hash] = $envelope[0] . $this->Html->link($link, $url, $htmlOptions) . $envelope[1];
         }
 
         return strtr($text, $replace);
@@ -184,8 +209,10 @@ class TextHelper extends Helper
     protected function _linkEmails($text, $options)
     {
         $replace = [];
-        foreach ($this->_placeholders as $hash => $url) {
-            $replace[$hash] = $this->Html->link($url, 'mailto:' . $url, $options);
+        foreach ($this->_placeholders as $hash => $content) {
+            $url = $content['content'];
+            $envelope = $content['envelope'];
+            $replace[$hash] = $envelope[0] . $this->Html->link($url, 'mailto:' . $url, $options) . $envelope[1];
         }
 
         return strtr($text, $replace);

+ 4 - 0
tests/TestCase/View/Helper/TextHelperTest.php

@@ -347,6 +347,10 @@ class TextHelperTest extends TestCase
             [
                 "Text with partial www.cakephp.org\r\nwww.cakephp.org urls and CRLF",
                 "Text with partial <a href=\"http://www.cakephp.org\">www.cakephp.org</a>\r\n<a href=\"http://www.cakephp.org\">www.cakephp.org</a> urls and CRLF"
+            ],
+            [
+                'https://nl.wikipedia.org/wiki/Exploit_(computerbeveiliging)',
+                '<a href="https://nl.wikipedia.org/wiki/Exploit_(computerbeveiliging)">https://nl.wikipedia.org/wiki/Exploit_(computerbeveiliging)</a>'
             ]
         ];
     }