Trouble with regular expression for comments code
I am currently making a homepage where logged in users can write comments. The comment string is first run through a function that str_replaces emoticons. After that I want it to exchange
[url=www.whatever.com]linktext[/url]
with:
<a href='www.whatever.com'&开发者_运维技巧gt;linktext</a>
The reason for this is that I want to strip the text for all the html code that isn't controlled by my comment code, in case some users decide to get creative-
and thought it would be best to use preg replace but the code I ended up with (Partially from reading about reg exp from my trusty "O reilly Sql and Php"-book and partially from the web) Is pretty bonkers, and most importantly, doesn't work.
Any help would be appreciated, thanks.
It's probably possible to exchange the entire code, not in 2 segments like I have done. Just decided on that getting 2 smaller parts to work first would be easier, and then merge them afterwards.
code:
function text_format($string)
{
$pattern="/([url=)+[a-zA-Z0-9]+(])+/";
$string=preg_replace($pattern, "/(<a href=\')+[a-zA-Z0-9]+(\'>)+/", $string);
$pattern="/([\/url])+/";
$string=preg_replace($pattern, "/(<\/a>)+/", $string);
return $string;
}
It looks like you're using something similar to BBCode. Why not use a BBCode parser, such as this one?
http://nbbc.sourceforge.net/
It also handles smilies, replacing them with images. If you use their test page, you will still see the text though, because they don't host the images and they set the alt-text to the smily.
I experimented a bit with the following:
function text_format($string)
{
return preg_replace('#\[url=([^\]]+)\]([^\[]*)\[/url\]#', '<a href="$1">$2</a>', $string);
}
However, one immediate fault with this is that if linktext
is empty, there will be nothing between <a>
and </a>
. One way around it would be to do another pass with something like this:
preg_replace('#<a href="([^"]+)"></a>#', '<a href="$1">$1</a>', $string);
Another option would be to use preg_replace_callback and put this logic inside your callback function.
Finally, this is obviously a common "problem" and has been solved many times by others, and if using a more mature open sourced solution is an option, I'd recommend looking for one.
@Lauri Lehtinen's answer is good for learning the idea behind the technique, but you shouldn't use it in practice because it would make your site extremely vulnerable to XSS attacks. Also, link spammers would appreciate the lack of rel="nofollow"
on the generated links.
Instead, use something like:
<?php
// \author Daniel Trebbien
// \date 2010-06-22
// \par License
// Public Domain
$allowed_uri_schemes = array('http', 'https', 'ftp', 'ftps', 'irc', 'mailto');
/**
* Encodes a string in RFC 3986
*
* \see http://tools.ietf.org/html/rfc3986
*/
function encode_uri($str)
{
$str = urlencode('' . $str);
$search = array('%3A', '%2F', '%3F', '%23', '%5B', '%5D', '%40', '%21', '%24', '%26', '%27', '%28', '%29', '%2A', '%2B', '%2C', '%3B', '%3D', '%2E', '%7E');
$replace = array(':', '/', '?', '#', '[', ']', '@', '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=', '.', '~'); // gen-delims / sub-delims / unreserved
return str_ireplace($search, $replace, $str);
}
function url_preg_replace_callback($matches)
{
global $allowed_uri_schemes;
if (empty($matches[1]))
return $matches[0];
$href = trim($matches[1]);
if (($i = strpos($href, ':')) !== FALSE) {
if (strrpos($href, '/', $i) === FALSE) {
if (!in_array(strtolower(substr($href, 0, $i)), $allowed_uri_schemes))
return $matches[0];
}
}
// unescape `\]`, `\\\]`, `\\\\\]`, etc.
for ($j = strpos($href, '\\]'); $j !== FALSE; $j = strpos($href, '\\]', $j)) {
for ($i = $j - 2; $i >= 0 && $href[$i] == '\\' && $href[$i + 1] == '\\'; $i -= 2)
/* empty */;
$i += 2;
$h = '';
if ($i > 0)
$h = substr($href, 0, $i);
for ($numBackslashes = floor(($j - $i)/2); $numBackslashes > 0; --$numBackslashes)
$h .= '\\';
$h .= ']';
if (($j + 2) < strlen($href))
$h .= substr($href, $j + 2);
$href = $h;
$j = $i + floor(($j - $i)/2) + 1;
}
if (!empty($matches[2]))
$href .= str_replace('\\\\', '\\', $matches[2]);
if (empty($matches[3]))
$linkText = $href;
else {
$linkText = trim($matches[3]);
if (empty($linkText))
$linkText = $href;
}
$href = htmlspecialchars(encode_uri(htmlspecialchars_decode($href)));
return "<a href=\"$href\" rel=\"nofollow\">$linkText</a>";
}
function render($input)
{
$input = htmlspecialchars(strip_tags('' . $input));
$input = preg_replace_callback('~\[url=((?:[^\]]|(?<!\\\\)(?:\\\\\\\\)*\\\\\])*)((?<!\\\\)(?:\\\\\\\\)*)\]' . '((?:[^[]|\[(?!/)|\[/(?!u)|\[/u(?!r)|\[/ur(?!l)|\[/url(?!\]))*)' . '\[/url\]~i', 'url_preg_replace_callback', $input);
return $input;
}
which I believe is safe against XSS. This version has the added benefit that it is possible to write out links to URLs that contain ']'
.
Evaluate this code with the following "test suite":
echo render('[url=http://www.bing.com/][[/[/u[/ur[/urlBing[/url]') . "\n";
echo render('[url=][/url]') . "\n";
echo render('[url=http://www.bing.com/][[/url]') . "\n";
echo render('[url=http://www.bing.com/][/[/url]') . "\n";
echo render('[url=http://www.bing.com/][/u[/url]') . "\n";
echo render('[url=http://www.bing.com/][/ur[/url]') . "\n";
echo render('[url=http://www.bing.com/][/url[/url]') . "\n";
echo render('[url=http://www.bing.com/][/url][/url]') . "\n";
echo render('[url= javascript: window.alert("hi")]click me[/url]') . "\n";
echo render('[url=#" onclick="window.alert(\'hi\')"]click me[/url]') . "\n";
echo render('[url=http://www.bing.com/] [/url]') . "\n";
echo render('[url=/?#[\\]@!$&\'()*+,;=.~] [/url]') . "\n"; // link text should be `/?#[]@!$&'()*+,;=.~`
echo render('[url=http://localhost/\\\\]d]abc[/url]') . "\n"; // href should be `http://localhost/%5C`, link text should be `d]abc`
echo render('[url=\\]][/url]') . "\n"; // link text should be `]`
echo render('[url=\\\\\\]][/url]') . "\n"; // link text should be `\]`
echo render('[url=\\\\\\\\\\]][/url]') . "\n"; // link text should be `\\]`
echo render('[url=a\\\\\\\\\\]bcde\\]fgh\\\\\\]ijklm][/url]') . "\n"; // link text should be `a\\]bcde]fgh\]ijklm`
Or, just look at the Codepad results.
As you can see, it works.
精彩评论