Reputation: 377
I use preg_replace and I want to include a url inside the replacement string. How do I quote that string? It appears preg_quote is only for the search pattern.
$replace = '\1'.addslashes($url).'\3'.addslashes($title).'\4';
Upvotes: 6
Views: 2044
Reputation: 2973
You should use Prepared Patterns. It works like Prepared Statements in SQL:
Pattern::inject("\1@url\2@url\3", ['url' => $input]);
Upvotes: 0
Reputation: 9278
To make it obvious one is escaping any potential back-references in the $replacement
parameter of preg_replace()
, use a function:
function preg_quote_replacement($input) {
return addcslashes($input, '\\$');
}
In OP's case:
$subject = preg_replace(
$pattern,
'\1'.preg_quote_replacement($url).'\3'.preg_quote_replacement($title).'\4',
$subject
);
Upvotes: 0
Reputation: 33908
addslashes
is not sufficientpreg_quote
escapes too muchSee this demo.
As Mario commented you can use addcslashes($str, "\\$")
.
Upvotes: 5
Reputation: 197659
You did not provide an example, so I compiled one on my own. The working solution I came up with is using a simple callback function:
$url = 'http://example.com/';
$title = 'Make it Complex \4';
$subject = 'Call \\4 " $me an url';
$pattern = '/(.*)an()( )(url)/';
$replace = function($m) use ($url, $title)
{
return "$m[1]$url$m[3]$title$m[4]";
};
$result = preg_replace_callback($pattern, $replace, $subject);
Result:
Call \4 " $me http://example.com/ Make it Complex \4url
The callback function is a so called anonymous function Docs which makes it easy to edit the code in place.
In case you need this more often you can put that into a function of your own, probably to make it more re-useable. You can even go to that far and create yourself your own pattern to replace subgroup matches and variables. For examle {\1}
stands for the subpattern 1 match, {$2}
for the second variable. Wrapping this into a function of it's own:
$patternf = function()
{
$values = func_get_args();
$mask = $values ? array_shift($values) : NULL;
return function($matches) use ($mask, $values)
{
$parts = preg_split('/({[\\\\\\$][0-9]{1,3}})/', $mask, 0, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
foreach($parts as &$part)
if (preg_match('/^{([\\\\\\$])([0-9]{1,3})}$/', $part, $p))
$part = $p[1] == '\\' ? $matches[(int)$p[2]] : $values[$p[2]-1];
return implode('', $parts);
};
};
Would allow you to make the replacement more handy:
$replace = $patternf('{\1}{$1}{\3}{$2}{\4}', $url, $title);
$result = preg_replace_callback($pattern, $replace, $subject);
Demo. Wrapping this into a function of it's own:
function preg_replace_subst($pattern, $replace, $subject)
{
$values = func_get_args();
$pattern = array_shift($values);
$mask = array_shift($values);
$subject = array_shift($values);
$callback = function($matches) use ($mask, $values)
{
$parts = preg_split('/({[\\\\\\$][0-9]{1,3}})/', $mask, 0, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
foreach($parts as &$part)
if (preg_match('/^{([\\\\\\$])([0-9]{1,3})}$/', $part, $p))
$part = $p[1] == '\\' ? $matches[(int)$p[2]] : $values[$p[2]-1];
return implode('', $parts);
};
return preg_replace_callback($pattern, $callback, $subject);
}
Would give it an easy interface:
$url = 'http://example.com/';
$title = 'Make it Complex \4';
$subject = 'Call \\4 " $me an url';
$pattern = '/(.*)an()( )(url)/';
$replace = '{\1}{$1}{\3}{$2}{\4}';
$result = preg_replace_subst($pattern, $replace, $subject, $url, $title);
But with many substitution variables it should be possible to pass them as an array probably otherwise it gets a bit lengthy.
e
modifier with preg_replace
(and why it does not work)When using the e
modifier, the matches get replaced in the replace string and it then get's evaluated. As other variables do not get escaped, the matches do interfere with PHP variable substitution, which is dangerous:
$url = 'http://example.com/';
$title = 'Make it Complex \4';
$subject = 'Call me an url.';
$pattern = '/(.*)an()( )(url)/e';
$replace = '"$1{$url}$3{$title}$4"';
$result = preg_replace($pattern, $replace, $subject);
Outputs:
Call me http://example.com/ Make it Complex \4url.
As written, the first e
-modifier example is broken because $
won't get escape in $subject
, so PHP would have looked for unset variables. That's dangerous, too. I came up with a variant, it solves that issue but it can't handle double-quotes in the subject:
$url = 'http://example.com/';
$title = 'Make it Complex \4';
$subject = 'Call \\4 " $me an url';
$pattern = '/(.*)an()( )(url)/e';
$replace = "'\$1'.\$url.'\$3'.\$title.'$4'";
Output:
Call \4 \" $me http://example.com/ Make it Complex \4url
^ problem, not in input.
So not really fool-proof, that's why it needs the callback function because it gets the matching sub-patterns unquoted.
Upvotes: 0
Reputation: 31621
Unfortunately there is no generic way to do this, but addslashes
should be enough in most cases.
For maximum safety you can use the ${1}
syntax. E.g.
$replace = '${1}'.addslashes($url).'${3}'.addslashes($title).'${4}';
If you really want to be totally bulletproof, use a callback replacement function with preg_replace_callback()
. The string returned from the callback function is used entirely as-is, so you don't have to worry about mixing replacement syntax with normal text.
Example with preg_replace_callback()
:
class URLReplacer {
public $pattern = '/my regex/';
public $url;
public $title;
function __construct($url, $title) {
$this->url = $url;
$this->title = $title;
}
function _callback($matches) {
return $matches[1].$url.$matches[3].$title.$matches[4];
}
function replace($subject) {
return preg_replace_callback($this->pattern, array($this, '_callback'), $subject);
}
}
$repl = new URLReplacer($url, $title);
$replaced = $repl->replace($subject);
Upvotes: 0