PHP: Recursive htmlspecialchars on object - php

I want to establish a generic sanitizer for my data that comes from various sources. With sanitizing I mean (at this stage) applying htmlspecialchars to strings. Now, the data that comes from these sources can be anything from an object to an array to a string, all nested (and complicated), and the format is always a bit different.
So I thought of a recursive htmlspecialchars function that applies itself to arrays and objects, and only applies htmlspecialchars to strings, but how do I walk an object recursively?
Thanks.
EDIT: I think I should have mentioned this - I am actually building a RIA that relies heavily on JS and JSON for client-server communication. The only thing the server does is fetching stuff from the database and returning it to the client via JSON, in the following format:
{"stat":"ok","data":{...}}
Now as I said, data could be anything, not only coming from a DB in the form of strings, but also coming from an XML
The workflow to process the JSON is as follows:
Fetch data from the DB/XML (source encoding is iso-8859-1)
Put them into the "data" array
Recursively convert from iso-8859-1 to utf-8 using
private function utf8_encode_deep(&$input) {
if (is_string($input)) {
$input = $this -> str_encode_utf8($input);
} else if (is_array($input)) {
foreach ($input as &$value) {
$this -> utf8_encode_deep($value);
}
unset($value);
} else if (is_object($input)) {
$vars = array_keys(get_object_vars($input));
foreach ($vars as $var) {
$this -> utf8_encode_deep($input -> $var);
}
}
}
Use PHP's json_encode to convert the data into JSON
Send (echo) the data to the client
Render the data using JS (e.g. putting into a table)
And somewhere in between that, the data should be somehow sanitized (at this stage only htmlspecialchars). Now the question should have been: Where to sanitize, using what method?

You can try the following
class MyClass {
public $var1 = '<b>value 1</b>';
public $var2 = '<b>value 2</b>';
public $var3 = array('<b>value 3</b>');
}
$list = array();
$list[0]['nice'] = range("A", "C");
$list[0]['bad'] = array("<div>A</div>","<div>B</div>","<div>C</div>",new MyClass());
$list["<b>gloo</b>"] = array(new MyClass(),"<b>WOW</b>");
var_dump(__htmlspecialchars($list));
Function Used
function __htmlspecialchars($data) {
if (is_array($data)) {
foreach ( $data as $key => $value ) {
$data[htmlspecialchars($key)] = __htmlspecialchars($value);
}
} else if (is_object($data)) {
$values = get_class_vars(get_class($data));
foreach ( $values as $key => $value ) {
$data->{htmlspecialchars($key)} = __htmlspecialchars($value);
}
} else {
$data = htmlspecialchars($data);
}
return $data;
}
Output Something like
array
0 =>
array
'nice' =>
array
0 => string 'A' (length=1)
1 => string 'B' (length=1)
2 => string 'C' (length=1)
'bad' =>
array
0 => string '<div>A</div>' (length=24)
1 => string '<div>B</div>' (length=24)
2 => string '<div>C</div>' (length=24)
3 =>
object(MyClass)[1]
...
array
0 =>
object(MyClass)[2]
public 'var1' => string '<b>value 1</b>' (length=26)
public 'var2' => string '<b>value 2</b>' (length=26)
public 'var3' =>
array
...

You would only want to escape when outputting into HTML. And you cannot output a complete array or object into HTML, so escaping everything seems invalid.
You have one level of indirection because of your JSON output. So you cannot decide in PHP what context the data is used for - JSON is still plain text, not HTML.
So to decide whether any data inside the JSON must be escaped for HTML we must know how your Javascript is using the JSON data.
Example: If your JSON is seen as plain text, and contains something like <b>BOLD</b>, then the expected outcome when used inside any HTML is exactly this text, including the chars that look like HTML tags, but no bold typesetting. This will only happen if your Javascript client handles this test as plain text, e.g. it DOES NOT use innerHTML() to place it on the page, because that would activate the HTML tags, but only innerText() or textContent() or any other convenience method in e.g. jQuery (.text()).
If on the other hand you expect the JSON to include readymade HTML that is fed into innerHTML(), then you have to escape this string before it is put into JSON. BUT you must escape the whole string only if you do not want to add any formatting to it. Otherwise you are in a situation that uses templates for mixing predefined formatting with user content: The user content has to be escaped when put into HTML context, but the result must not - otherwise Javascript cannot put it into innerHTML() and enable the formatting.
Basically a global escaping for everything inside your array or object most likely is wrong, unless you know for everything that it will be used in a HTML context by your Javascript.

function htmlrecursive($data){
if (is_array($data) && count($data) > 1){
foreach ($data as &$d){
$d = htmlrecursive($d);
}
} else if (!is_array($data)){
return htmlspecialchars($data);
}
else {
return htmlspecialchars($data[0])
}
}
htmlrecursive($array);

For objects you need to implement The ArrayAccess interface then you can do a array walk recursive
Also check this question Getting an object to work with array_walk_recursive in PHP

Related

PHP - How to parse json data and compare it

I hope you all having a great day. I'm developing a simple wordpress plugin and I've never worked with JSON encoded data on PHP before so I need some help here.
I have this simple function with a single parameter that's called in a 'foreach' loop and inside this function I have an array of values pulled from DB and it's JSON encoded, it goes like this:
array (size=4)
0 => string '[{"videoUrl":"https://vimeo.com/493156200","playlistID":"7992"}]' (length=64)
1 => string '[{"videoUrl":"https://vimeo.com/365531165","playlistID":"7992"}]' (length=64)
2 => string '[{"videoUrl":"https://vimeo.com/365531139","playlistID":"7992"}]' (length=64)
3 => string '[{"videoUrl":"https://vimeo.com/521605944","playlistID":"7992"}]' (length=64)
My function goes like this:
$playlist_id = 7992;
function videostatuschecker($x){
$unlockedvideos = ['json encoded data illustrated above'];
// x value is like this: $x = 'https://vimeo.com/493156200';
if (in_array($x, $unlockedvideos)){
return 'unlocked';
}
else{
return 'locked';
}
}
Video URL will be passed through my function's parameter variable and playlist ID is defined in the parent function so it's static.
Now what I need is, I need to compare that $x value to "videoUrl" values in the array and compare its 'playlistID' to $playlist_id variable and if they both match, I need the function to return 'unlocked' and vice versa for the else statement. Any help is much appreciated, thank you so much.
You could do something like this:
function videostatuschecker($video_url, $playlist_id){
$unlockedvideos = [
'[{"videoUrl":"https://vimeo.com/493156200","playlistID":"7992"}]',
'[{"videoUrl":"https://vimeo.com/365531165","playlistID":"7992"}]',
'[{"videoUrl":"https://vimeo.com/365531139","playlistID":"7992"}]',
'[{"videoUrl":"https://vimeo.com/521605944","playlistID":"7992"}]'
];
foreach ($unlockedvideos as $unlockedvideo) {
$decoded = json_decode($unlockedvideo);
if ($playlist_id == $decoded[0]->playlistID && $video_url == $decoded[0]->videoUrl) {
return 'unlocked';
}
}
return 'locked';
}
print(videostatuschecker('https://vimeo.com/493156200', 7992));
Please note that your $unlockedvideos is an array of arrays. Is that what you want?

How to `unstring` an object wrapped in a string without calling it?

I have an array with values that are objects wrapped as strings, how can I turn them back into objects without calling them? ['randomNumber' => '$this->faker->randomNumber()']
This array is being passed into a stub (writing a php file):
$replace = [
'{{mappedResult}}' => var_export($mappedResults, 1),
];
instead of getting this array:
$mappedResults = ['randomNumber' => $this->faker->randomNumber()]
I get this array instead:
$mappedResults = ['randomNumber' => '$this->faker->randomNumber()']
If you want to use the return value of the method call as the value in the map, you can use eval:
foreach ($mappedResults as $key => $value)
$mappedResults[$key] = eval($value)
Note eval returns NULL unless a return statement is called in the code string. Then the value passed to return is returned. If there is a parse error in the code string, eval() returns FALSE.
Hence you don't want to call and evaluate the code and based on my understanding you want to recreate the php file, you should use a custom export function that can remove quotations:
function export($input) {
$response = [];
foreach($input as $key => $value)
$response[] = var_export($key, true) . " => " . $value;
return "[".implode(",",$response)."]";
}

PHP json_encode($_POST) and mySQL issue

I am trying to json_encode($_POST) in PHP, but I have one small problem.
On my $_POST sometimes I get some encoded JSON as example below:
Array
(
[module] => {"media":true}
)
Where module contains a JSON string. My problem is when I use json_encode($POST); I get a result like this:
{"module":"{\"petMedia\":true}"}
Trying to insert into mySQL JSON column, I get this error
Invalid JSON text: "Missing a comma or '}' after an object member.
It's not possible for me to decode the string before because not always I get encoded JSON.
Thanks.
If I had POST variables coming in where sometimes they have json encoded in it, or not... one way to handle it, would be a cleanup loop.
Lets say this is the POST in:
$_POST -> 'var1' = 'some string'
-> 'var2' = '2315'
-> 'var3' = '{"some":"json"}'
Now, I would setup a little cleaner, because you cannot just json_encode($_POST) as you found out. It double encodes the var3.
$clean = array();
foreach($_POST as $key => $val) {
json_decode($val);// test
if (json_last_error() == JSON_ERROR_NONE) {
$clean[$key] = json_decode($val);// $val is json, so pre-decode it
} else {
$clean[$key] = $val;// its not real json, so assign straight
}
}
print_r($clean);
print_r(json_encode($clean));
Results in:
Array (
[var1] => some string
[var2] => 2315
[var3] => stdClass Object
(
[some] => json
)
)
// this then is a json encoded string, which is good:
{"var1":"some string","var2":2315,"var3":{"some":"json"}}
Basically, this then makes a full object which you can now store into the database safely, since all variables and their values are json encodable.
I hope this helps you get started, and you dont actually have normal variables inside json strings, that are not json encoded, and and and and ;)
A variation on a theme perhaps
/* emulate POST */
$_POST=array(
'php'=>'fantastic',
'javascript'=>'awesome',
'asp'=>'awful',
'module'=>json_encode( array( 'media' => true ) )
);
/* process the POST array and decode any json data within */
array_walk( $_POST, function( &$value ){
$tmp=json_decode( $value );
if( json_last_error() == 0 )$value=$tmp;
});
/* encode the POST data */
$encoded=json_encode( $_POST );
Outputs:
{"php":"fantastic","javascript":"awesome","asp":"awful","module":{"media":true}}

Create JSON using PHP with data which contains JSON

How can I create JSON using PHP with data which contains JSON without including a bunch of escape characters in the JSON, and without converting JSON first to an array or object, and then back to JSON?
<?php
/*
GIVEN: Data from DB contained in array $a.
I know that sometimes JSON shouldn't be stored in a DB, but please assume this is a good case for doing so.
*/
$a[0]=json_encode(['a'=>5,'b'=>'hello']);
$a[1]=json_encode(['a'=>2,'b'=>'how are you']);
$a[2]=json_encode(['a'=>7,'b'=>'goodby']);
$o=[
['x'=>321,'y'=>$a[0]],
['x'=>123,'y'=>$a[1]],
['x'=>111,'y'=>$a[2]],
];
echo('<pre>'.print_r($o,1).'</pre>');
echo(json_encode($o));
/*
Undesired result containing a bunch of escape characters. Granted, they are benign, however, will increase network trafic.
[{"x":321,"y":"{\"a\":5,\"b\":\"hello\"}"},{"x":123,"y":"{\"a\":2,\"b\":\"how are you\"}"},{"x":111,"y":"{\"a\":7,\"b\":\"goodby\"}"}]
*/
$o=[
['x'=>321,'y'=>json_decode($a[0])],
['x'=>123,'y'=>json_decode($a[1])],
['x'=>111,'y'=>json_decode($a[2])],
];
echo('<pre>'.print_r($o,1).'</pre>');
echo(json_encode($o));
/*
Desired result, however, is there a more efficient way to do this?
[{"x":321,"y":{"a":5,"b":"hello"}},{"x":123,"y":{"a":2,"b":"how are you"}},{"x":111,"y":{"a":7,"b":"goodby"}}]
*/
No, there is no faster way then to decode, mutate and encode again.
You could however combine the decoding code closely with your database queries. If you have a data model class, you would do the decoding there, so that the code that calls this service will never see the JSON, but always the decoded data.
Then, again, in the data model, where you write to the database, you would perform the JSON encoding at the last moment.
This way you hide the JSON factor behind the walls of the data layer, and the rest of the application will not have to be aware of that.
Manipulating JSON directly
Another solution consists of writing a library that can juggle with JSON, giving possibilities to set values inside JSON without the caller having to decode/encode. This option requires much more code (or you could find an existing library), and so it is not really my first recommendation. But here is a naive example of a function that could exist in that library:
function json_set($json, $path, $value) {
$arr = json_decode($json, true);
$ref =& $arr;
$props = explode("/", $path);
$finalProp = array_pop($props);
foreach ($props as $key) {
if (!isset($ref[$key])) $ref[$key] = [];
$ref =& $ref[$key];
}
$obj = json_decode($value);
$ref[$finalProp] = $obj ? $obj : $value;
return json_encode($arr);
}
This allows you to provide an existing JSON string, a path pointing to a certain spot inside that JSON, and a value that should be put there. That value could itself also be JSON.
Here is how you would use it in your case, given the JSON values in $a which you got from the database:
$o=json_encode([
['x'=>321],
['x'=>123],
['x'=>111],
]);
$o = json_set($o, '0/y', $a[0]);
$o = json_set($o, '1/y', $a[1]);
$o = json_set($o, '2/y', $a[2]);
echo $o;
Output:
[{"x":321,"y":{"a":5,"b":"hello"}},
{"x":123,"y":{"a":2,"b":"how are you"}},
{"x":111,"y":{"a":7,"b":"goodby"}}]

php json_encode + modernizr object

I'm looking for better and more robust solution for echoing out yepnope feature tests using php. The output should look something like :
{
test : Modernizr.geolocation,
yep : 'normal.js',
nope : ['polyfill.js', 'wrapper.js']
}
From an output like:
$l10n = array(
'test' => 'Modernizr.geolocation',
'yep' => "'normal.js'",
'nope' => array("'polyfill.js'", "'wrapper.js'")
);
Obviously, there is the issue of quotation marks being wrapped around the json object. I can't help but wonder if there's a different class altogether that caters to creating mixed javascript objects containing raw javascript as well as strings.
json_encode returns the JSON representation of a value, the point is JSON representation is not a javascript object, JSON is a subset of the javascript object literal, so you need to do the convert in javascript.
var l10n = <?php echo json_encode($l10n); ?>;
if (l10n.test === "Modernizr.geolocation") {
l10n.test = Modernizr.geolocation;
}

Categories