PHP backreferences followed by variables starting with numerals - php

I was working on a dynamic way to update a config.php file and I ran into an interesting glitch that I can't quite solve. Below is my code for updating the config.php file:
if( isset( $_POST['submitted'] ) ) {
$config_keys = array();
foreach( $_POST as $key => $value ) {
if( substr( $key, 0, 7 ) == 'config-' ) {
$config_keys[ substr( $key, 7 ) ] = $value;
}
}
$config_content = file_get_contents( dirname(__FILE__) . '/../../inc/config.php' );
foreach( $config_keys as $key => $value ) {
$config_content = preg_replace(
"/config\['$key'\](\s*)=(\s*)(['\"]?).*?(['\"]?);/",
"config['$key']$1=$2$3$value$4;",
$config_content
);
$config[$key] = $value;
}
file_put_contents( dirname(__FILE__) . '/../../inc/config.php', $config_content );
}
The logic is fairly sound. It searches for any POST variables prefixed by "config-" then uses everything after "config-" as the name of the key in our config file to update. The config file takes the form:
$config['var1'] = 'value1';
$config['var2'] = 123;
$config['var3'] = '...';
In 90% of cases this works perfectly, however if $value begins with a numeral then $3 and the first numeral of $value are completely ignored during the replacement.
For instance, I have the following value in my config file:
$config['ls_key'] = '136609a7b4....'; // Rest of key has been truncated
If I don't change this value and leave the key untouched but submit my form then this line suddenly looks like so:
$config['ls_key'] = 36609a7b4...'; // Rest of key has been truncated
The lack of single quote prevents the config file from parsing (breaking the entire site) and we've lost data to boot! After reading the PHP preg_replace manual I have tried using braces in several locations (modifying "Example #1 Using backreferences followed by numeric literals"). None of the following worked:
"config['$key']$1=$2${3}$value$4;",
"config['$key']$1=$2$3${value}$4;",
"config['$key']$1=$2$3{$value}$4;",
"config['$key']$1=$2{$3}$value$4;", // This one actually leads to syntax errors
"config['$key']${1}=${2}${3}$value${4};",
The first 3 lead to the exact same problem, having no effect on the replacement. The fourth doesn't work at all (syntax errors), and the fifth actually causes EVERY backreference to be ignored. I've also tried using single quotes and concatenation like so:
'config[\'$key\']$1=$2$3' . $value . '$4;',
Again, I had the same problem as the 3 prior examples and my original script.
Hoping someone has solved this before or at least has a new idea.

Seems the double quoting interpolation is messing things up. This replacement works:
'config[\''.$key.'\']$1=$2${3}'.$value.'$4;'
Also note that you should properly escape the following (meta characters):
$key in the regex with preg_quote
$key and $value in the replacement, there is no built in function to do this (preg_quote escapes too much)
And also escape the regex delimiter and quote delimiter used if present.

Try \g{1} for the group 1 (and accordingly for the other groups)
See the php manual on backreferences
Update:
Of course Qtax is right, this is the syntax for backreferences within the regular expression. (+1 for Qtax)

Related

preg_replace with variable input

I'm quite new to this, and have borrowed some code from another post I found, I don't know if what I am trying to do is the right or the best way, its just how I am "getting" it to work..
This is the code
<?php
$yn = $_POST['YN'];
echo $yn;
$fl='config.php';
/*read operation ->*/ $tmp = fopen($fl, "r"); $content=fread($tmp,filesize($fl)); fclose($tmp);
// here goes your update
$content = preg_replace('/\$yourname = \"(.*?)\";/', '$yourname = ""$YN"";', $content);
/*write operation ->*/ $tmp =fopen($fl, "w"); fwrite($tmp, $content); fclose($tmp);
?>
I am trying to update a config file entry that matches $yourname with the POST result, I can echo $yn and it contains the correct value, but I can't get the variable to work in the regex replace,
$content = preg_replace('/\$yourname = \"(.*?)\";/', '$yourname = ""$yn"";', $content);
so if $yn = karl then im trying to update $yourname = "" in the file to $yourname = "karl"
but I can't get it to work, the closest I get is it updating the file with the variable as text, ie $yourname = "$yn".
hope someone can help
Using what you posted I managed to get it working, thank you so much :)
$content = preg_replace( '/\$yourname = \"(.*?)\";/', '$yourname = "'.$yn.'";', $content);
I would suggest using a pattern like this
preg_replace( '/'.preg_quote( $yourname ).'\s*=\s*\"[^\"]+\";/', $yourname.'="'.$yn.'";', $content);
..'\s*=\s*\"([^\"]+)\";
preg_quote( $yourname ) - escaped variable input ( literal match )
\s* - one or more spaces
= - literal (=)
\s* - one or more space
\" - literal (")
[^\"]+ - match any character not a (") greedy match as many times as possible.
\" - literal (")
; literal (;)
In this case ( because it uses double quotes ), it's (prudent?) better to use concatenation and save the headache of escaping it. No need to be fancy when simplicity will win the day.
Also be wary of mistakes like this $yn vs $YN in php variable names are case sensitive, without that knowledge it can be a major challenge to find the error, because to us humans it looks the same. Of course it doesn't help that file names, class names, functions and methods are case insensitive ( on Windows ). Really not sure if class and method names are case sensitive on linux, I try to avoid the issue and always use the same casing.
Seeing as it looks like the OP possibly wanted $yourname ( literally ) I updated the regx
preg_replace( '/\$yourname\s*=\s*\"[^\"]+\";/', '$yourname="'.$yn.'";', $content);
https://regex101.com/r/yM9cX9/1

very large php string magically turns into array

I am getting an "Array to string conversion error on PHP";
I am using the "variable" (that should be a string) as the third parameter to str_replace. So in summary (very simplified version of whats going on):
$str = "very long string";
str_replace("tag", $some_other_array, $str);
$str is throwing the error, and I have been trying to fix it all day, the thing I have tried is:
if(is_array($str)) die("its somehow an array");
serialize($str); //inserted this before str_replace call.
I have spent all day on it, and no its not something stupid like variables around the wrong way - it is something bizarre. I have even dumped it to a file and its a string.
My hypothesis:
The string is too long and php can't deal with it, turns into an array.
The $str value in this case is nested and called recursively, the general flow could be explained like this:
--code
//pass by reference
function the_function ($something, &$OFFENDING_VAR, $something_else) {
while(preg_match($something, $OFFENDING_VAR)) {
$OFFENDING_VAR = str_replace($x, y, $OFFENDING_VAR); // this is the error
}
}
So it may be something strange due to str_replace, but that would mean that at some point str_replace would have to return an array.
Please help me work this out, its very confusing and I have wasted a day on it.
---- ORIGINAL FUNCTION CODE -----
//This function gets called with multiple different "Target Variables" Target is the subject
//line, from and body of the email filled with << tags >> so the str_replace function knows
//where to replace them
function perform_replacements($replacements, &$target, $clean = TRUE,
$start_tag = '<<', $end_tag = '>>', $max_substitutions = 5) {
# Construct separate tag and replacement value arrays for use in the substitution loop.
$tags = array();
$replacement_values = array();
foreach ($replacements as $tag_text => $replacement_value) {
$tags[] = $start_tag . $tag_text . $end_tag;
$replacement_values[] = $replacement_value;
}
# TODO: this badly needs refactoring
# TODO: auto upgrade <<foo>> to <<foo_html>> if foo_html exists and acting on html template
# Construct a regular expression for use in scanning for tags.
$tag_match = '/' . preg_quote($start_tag) . '\w+' . preg_quote($end_tag) . '/';
# Perform the substitution until all valid tags are replaced, or the maximum substitutions
# limit is reached.
$substitution_count = 0;
while (preg_match ($tag_match, $target) && ($substitution_count++ < $max_substitutions)) {
$target = serialize($target);
$temp = str_replace($tags,
$replacement_values,
$target); //This is the line that is failing.
unset($target);
$target = $temp;
}
if ($clean) {
# Clean up any unused search values.
$target = preg_replace($tag_match, '', $target);
}
}
How do you know $str is the problem and not $some_other_array?
From the manual:
If search and replace are arrays, then str_replace() takes a value
from each array and uses them to search and replace on subject. If
replace has fewer values than search, then an empty string is used for
the rest of replacement values. If search is an array and replace is a
string, then this replacement string is used for every value of
search. The converse would not make sense, though.
The second parameter can only be an array if the first one is as well.

PHP explode work only with last line

I have a PHP script that include different pages for special referers:
$ref_found = false;
// get referer if exists
$referer = false;
if ( isset($_SERVER['HTTP_REFERER']) ) {
$referer = $_SERVER['HTTP_REFERER'];
// get content of list.txt
$list = explode(chr(10), file_get_contents('list.txt'));
foreach ( $list as $l ) {
if ( strlen($l) > 0 ) {
if ( strpos( $referer, $l ) ) {
$ref_found = true;
}
}
}
}
// include the correct file
if ( $ref_found ) {
require_once('special_page.html');
} else {
require_once('regular_page.html');
}
Referer DB is in simple txt file (list.txt) and it looks like this:
domain1.com
domain2.com
domain3.com
Unfortunalty this script works only for last domain from the list (domain3.com).
What shoud I add? \n ?
Or it's better idea to create domains DB in different way?
The problem is that when you explode() your list of domain names, you end up with whitespace around each item. At the very least, you will have a newline (\n) somewhere, since the linebreaks in your file are probably \r\n.
So you're checking against something like " domain1.com" or maybe "\ndomain1.com", or maybe "domain1.com\n". Since this extra whitespace doesn't exists in the referrer header, it's not matching when you expect it to.
By calling trim() on each value you find, you'll get a clean domain name that you can use to do a more useful comparison:
$list = explode("\n", file_get_contents('list.txt'));
foreach ($list as $l) {
$l = trim($l);
if ((strlen($l) > 0) && (strpos($referer, $l) !== false)) {
$ref_found = true;
break;
}
}
I made a couple other minor updates to your code as well:
I switched away from using chr() and just used a string literal ("\n"). As long as you use double-quotes, it'll be a literal newline character, instead of an actual \ and n, and the string literal is much easier to understand for somebody reading your code.
I switched from a "\r" character (chr 10) to a "\n" character (chr 13). There's several different newline formats, but the most common are "\n" and "\r\n". By exploding on "\n", your code will work with both formats, where "\r" will only work with the second.
I combined your two if statements. This is a very minor update that doesn't have much effect except to (in my opinion) make the code easier to read.
I updated your strpos() to do a literal comparison to false (!==). It's probably not an issue with this code because the referrer value will start with http://, but it's a good habit to get into. If the substring happens to occur at the beginning of the parent string, strpos() will return 0, which will be interpreted as false in your original code.
I added a break statement in your loop if you found a matching domain name. Once you find one and set the flag, there's no reason to continue checking the rest of the domains in the list, and break allows you to cancel the rest of the foreach loop.
chr(13) == "\n"
chr(10) == "\r"
"\n" is most likely what you want.

%20 is Converted to Unerscore in the $_GET Array. Are There Other Characters Like This?

I'm trying to create a form which allows the user to define own custom query key and while I was testing the validation function for the form, I've noticed that %20 in a url query key is converted to a underscore in the $_GET array.
$key = 'a b';
$key = rawurlencode($key);
$value = 'value';
print_r($_GET); // output: Array ( [a_b] => value )
echo '<p>key:' . $key . '</p>';
echo '<p>value:' . $value . '</p>';
echo '<p>test</p>';
Are there other characters converted irregularly? I'm not sure "irregular" is the right word here since there might be a rule for this behavior but I didn't expect this would happen.
PHP replaces certain characters with an underscore because they are illegal in variable names. Even though they are legal in array keys, earlier versions of PHP would put form variables directly in variables (i.e. $a_b; see Register Globals), so this conversion was put in. This is done with space, dot, open square bracket, and control characters between 128 and 159.
This is only done with the names themselves, not to, for example, any array key parameters (i.e. http://example.com/foo.php?a[b.%20c]=1) since any character is legal in an array key. (Note that the array parameter feature itself means that open square bracket will not be replaced with _ as implied by the above in certain situations - the example will give $_GET['a']['b. c'] == 1.)
Source: http://ca.php.net/variables.external
Related question: Get PHP to stop replacing '.' characters in $_GET or $_POST arrays?
This function would fix those strings.
$key = 'a b.c[d';
$key = fix_key($key);
$value = 'value';
$_GET[$key] = $value;
print_r($_GET);
echo '<p>test</p>';
function fix_key($strKey) {
$search = array(chr(32), chr(46), chr(91));
for ($i=128; $i <= 159; $i++) array_push($search, chr($i));
return str_replace ( $search , '_', $strKey);
}

How to remove commas between double quotes in PHP

Hopefully, this is an easy one. I have an array with lines that contain output from a CSV file. What I need to do is simply remove any commas that appear between double-quotes.
I'm stumbling through regular expressions and having trouble. Here's my sad-looking code:
<?php
$csv_input = '"herp","derp","hey, get rid of these commas, man",1234';
$pattern = '(?<=\")/\,/(?=\")'; //this doesn't work
$revised_input = preg_replace ( $pattern , '' , $csv_input);
echo $revised_input;
//would like revised input to echo: "herp","derp,"hey get rid of these commas man",1234
?>
Thanks VERY much, everyone.
Original Answer
You can use str_getcsv() for this as it is purposely designed for process CSV strings:
$out = array();
$array = str_getcsv($csv_input);
foreach($array as $item) {
$out[] = str_replace(',', '', $item);
}
$out is now an array of elements without any commas in them, which you can then just implode as the quotes will no longer be required once the commas are removed:
$revised_input = implode(',', $out);
Update for comments
If the quotes are important to you then you can just add them back in like so:
$revised_input = '"' . implode('","', $out) . '"';
Another option is to use one of the str_putcsv() (not a standard PHP function) implementations floating about out there on the web such as this one.
This is a very naive approach that will work only if 'valid' commas are those that are between quotes with nothing else but maybe whitespace between.
<?php
$csv_input = '"herp","derp","hey, get rid of these commas, man",1234';
$pattern = '/([^"])\,([^"])/'; //this doesn't work
$revised_input = preg_replace ( $pattern , "$1$2" , $csv_input);
echo $revised_input;
//ouput for this is: "herp","derp","hey get rid of these commas man",1234
It should def be tested more but it works in this case.
Cases where it might not work is where you don't have quotes in the string.
one,two,three,four -> onetwothreefour
EDIT : Corrected the issues with deleting spaces and neighboring letters.
Well, I haven't been lazy and written a small function to do exactly what you need:
function clean_csv_commas($csv){
$len = strlen($csv);
$inside_block = FALSE;
$out='';
for($i=0;$i<$len;$i++){
if($csv[$i]=='"'){
if($inside_block){
$inside_block=FALSE;
}else{
$inside_block=TRUE;
}
}
if($csv[$i]==',' && $inside_block){
// do nothing
}else{
$out.=$csv[$i];
}
}
return $out;
}
You might be coming at this from the wrong angle.
Instead of removing the commas from the text (presumably so you can then split the string on the commas to get the separate elements), how about writing something that works on the quotes?
Once you've found an opening quote, you can check the rest of the string; anything before the next quote is part of this element. You can add some checking here to look for escaped quotes, too, so things like:
"this is a \"quote\""
will still be read properly.
Not exactly an answer you've been looking for - But I've used it for cleaning commas in numbers in CSV.
$csv = preg_replace('%\"([^\"]*)(,)([^\"]*)\"%i','$1$3',$csv);
"3,120", 123, 345, 567 ==> 3120, 123, 345, 567

Categories