How to implement a printf-like function in PHP? - php

I want to make a db_queryf function for my database abstraction. It will work somewhat like sqlite3_mprintf from SQLite: db_queryf('select * from pages where name=%q', $_GET['name']), where %q will produce a properly escaped string. What is the proper way of making printf-like functions in PHP? Is there any helper functions for that or I should parse it myself?

I am confused... (s)printf plainly allready exists, and you probably want to use SQLite3Stmt::bindValue more for this, unless you want to end up in escaping / sql-injection hell..

Use PDO prepared statements. Replacing into the string isn't good enough, you should be sanitizing.

sprintf()
sprintf('select * from pages where name=\'%s\'', $_GET['name']);
Its very important, that you sanitize everything in $_GET, before you use it!

okay, since I had exactly the same problem, I gave it a shot, and it seems to work quite nicely.
The following function sits inside a database wrapping class, and expects to be called like printf, where %% is transformed to a literal %, %e marks a string argument to be escaped, and %u marks a string argument to taken as-is.
LOGDB is a second database wrapping class, that is responsible for catching and logging all kinds of errors.
public static function query($format)
{
$query = $format . ' ';
$argc = func_num_args();
$argv = func_get_args();
$index_query = 0;
$index_args = 1;
while (($index_query = strpos($query, '%', $index_query)) !== false)
{
switch ($query[$index_query + 1])
{
case '%':
$query = substr_replace($query, '', $index_query, 1);
$index_query++;
break;
case 'e':
if ($index_args >= $argc)
{
LOG::failedQuery($format, "not enough arguments for format");
return false;
}
$query = substr_replace($query, DB::escape($argv[$index_args]), $index_query, 2);
$index_query += strlen($argv[$index_args]);
$index_args++;
break;
case 'u':
if ($index_args >= $argc)
{
LOG::failedQuery($format, "not enough arguments for format");
return false;
}
$query = substr_replace($query, $argv[$index_args], $index_query, 2);
$index_query += strlen($argv[$index_args]);
$index_args++;
break;
default:
LOG::failedQuery($format, "unknown control sequence '%" . $query[$index_query + 1] . "'");
return false;
}
}
if ($index_args != $argc)
{
LOG::failedQuery($format, "too many arguments for format");
return false;
}
$res = mysqli_query(self::$handle, $query);
if (!$res)
LOGDB::failedQuery($query, mysqli_error(self::$handle));
return $res;
}
Note: the code is mostly untested, chances are, it contains a bunch of bugs. use with caution :)

Related

PHP, Function breaks when numbers are passed in as arguements

I have the following function which works well, if I call it with only the first parameter:
function max_months($vehicle_age,$max_peroid,$no_older) {
$tot_age_in = $vehicle_age + 315360000;
while ($tot_age_in > 536467742) {
$tot_age_in = $tot_age_in - 31536000;
if ($tot_age_in < 536467742) {
$max_payback = floatval($tot_age_in - $vehicle_age);
$max_payback = seconds_to_month($max_payback);
break;
}
}
return $max_payback;
}
However, when I alter this function and pass in the numbers seen above as
parameters, the function breaks.
function max_months($vehicle_age,$max_peroid,$no_older) {
$tot_age_in = $vehicle_age + $max_peroid;
while ($tot_age_in > $no_older) {
$tot_age_in = $tot_age_in - $max_peroid;
if ($tot_age_in < $no_older) {
$max_payback = floatval($tot_age_in - $vehicle_age);
$max_payback = seconds_to_month($max_payback);
break;
}
}
return $max_payback;
}
I'm calling the function like so:
$max_payback = max_months($vehicle_age,315360000,536467742);
$vehicle_age is set to 288897248
So in the first instance I return a valid number, however in the second instance I return false, even though the numbers are the same. Could anyone suggest why this might be? Cheers
$max_payback is not always initialized. It's a good habit to always initialize the return value..
It is highly likely that you run out of the PHP_INT_MAX value, you can check the maximum integer value by doing
echo PHP_INT_MAX;
If the variable is bigger than the INT_MAX, it is treated like a float value. This means, that you have to deal with floating point imprecision problems. And instead of checking <, == or >, you should check for a certain range epsilon around the value to be checked.
By changing your code like below, the problem is likely solved:
function max_months($vehicle_age,$max_peroid,$no_older) {
$e = 0.0001;
$tot_age_in = $vehicle_age + $max_peroid;
while ($tot_age_in > $no_older-$e) {
$tot_age_in = $tot_age_in - $max_peroid;
if ($tot_age_in < $no_older+$e) {
$max_payback = floatval($tot_age_in - $vehicle_age);
$max_payback = seconds_to_month($max_payback);
break;
}
}
return $max_payback;
}
See also: http://php.net/manual/en/language.types.integer.php
You did not have that problem when you used the hard coded numbers because they are treated like constants and therefore you did not have the float problem.

Unable to decrypt my encrypted string in a substitution cipher

I am developing my own encryption and decryption algorithms. My encryption function is working fine, but my decryption function is not working properly. The following are my functions:
<?php
function encrypt($os,$mode=64,$pattern="tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7Cu"){
$ns="";
for($i=0;$i<strlen($os);$i++){
$ns.=$pattern[(strpos($pattern,$os[$i])+$mode)%strlen($pattern)];
}
return $ns;
}
function decrypt($os,$mode=64,$pattern="tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7Cu"){
$ns="";
for($i=0;$i<strlen($os);$i++){
$ns.=$pattern[abs(strpos($pattern,$os[$i])-$mode)%strlen($pattern)];
}
return $ns;
}
echo decrypt(encrypt("abcde"));
?>
My expected output is: abcde
But the output returned is: ejUPa
The encryption works in this way:
$new_char_index = ($char_index + $shift) % $alphabet_length;
where the modulo % handles the wrap around so that the new index is still in the alphabet range. This works well for positive numbers, but doesn't work like you would expect it to for negative numbers. Here are some examples:
echo -3 % 7; // -3
echo -11 % 7; // -4
That is why simply changing + for - doesn't work:
$new_char_index = ($char_index - $shift) % $alphabet_length;
This can lead to negative numbers. You can't access arrays with negative numbers in PHP, but you could do that in Python.
The easy fix is to make sure that the resulting index is always a positive numbers. You've tried that with abs(), but the problem is that this doesn't correctly wrap around from the other side of the alphabet, but only removes the sign which is wrong.
An easy fix is adding the alphabet length before the modulo operation in order to get a positive number:
$new_char_index = ($char_index - $shift + $alphabet_length) % $alphabet_length;
This works, because $alphabet_length % $alphabet_length == 0. It wraps to the correct position of the alphabet.
Now you only have to make sure that $shift is already in the alphabet range:
$shift = $shift % $alphabet_length;
One final improvement: you can use the same function for encryption and decryption, because the $enc_shift == -$dec_shift and the last formula should give you work for both.
This is not encryption. This is a variation on a Caeser cipher. Simply put, you should never implement your own encryption (unless you know exactly what you're doing...). This would do for obfuscation and nothing more.
As for the code itself, I suspect its an order of operations issue. Simply replacing a + with a - won't reverse the operator precedence in the encrypt() function. A handy generic string rotation function you could adapt is in the comments of this php documentation page.
If you want encryption there are many good articles about solid encryption; this is my personal opinion of a good starting point.
Here is a solution that works with every $mode and $pattern size.
You have to notice that you can only "encrypt" chars that are contained in $pattern.
<?php
function encrypt($os,$mode=64,$pattern=" tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7Cu"){
$patternLength = strlen($pattern);
if($mode < 0) {
$mode = ($patternLength - $mode) % $patternLength;
}
if($mode >= $patternLength) {
$mode %= $patternLength;
}
$ns="";
for($i=0;$i<strlen($os);$i++){
$ns.=$pattern[(strpos($pattern,$os[$i])+$mode)%strlen($pattern)];
}
return $ns;
}
function decrypt($os,$mode=64,$pattern=" tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7Cu"){
$patternLength = strlen($pattern);
if($mode < 0) {
$mode = ($patternLength - $mode) % $patternLength;
}
if($mode >= $patternLength) {
$mode %= $patternLength;
}
$ns="";
for($i=0;$i<strlen($os);$i++){
$pos = strpos($pattern,$os[$i]);
if($pos >= $mode ) {
$ns .= $pattern[$pos - $mode];
} else {
// $pos - $mode is negative so we need + sign
$ns .= $pattern[$patternLength + ($pos - $mode)];
}
}
return $ns;
}
To test this, you could do something like that:
$failed = false;
for($mode = -128; $mode <= 128; $mode++) {
// include all possible chars in the test to see if encryption and
// decryption works for all.
$allChars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 ";
if(decrypt(encrypt($allChars, $mode), $mode) != $allChars) {
echo "mode ".$mode." failed<br>";
$failed = true;
};
}
if(!$failed) {
echo "success";
}
Fixed it:
$ns.=$pattern[abs(strpos($pattern,$os[$i]))-$mode%strlen($pattern)];
In your decrypt ^^.
Just a matter of bracket placing.
Full fixed code:
function encrypt($os,$mode=64,$pattern="tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7Cu"){
$ns="";
for($i=0;$i<strlen($os);$i++){
$ns.=$pattern[(strpos($pattern,$os[$i])+$mode)%strlen($pattern)];
}
return $ns;
}
function decrypt($os,$mode=64,$pattern="tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7Cu"){
$ns="";
for($i=0;$i<strlen($os);$i++){
$ns.=$pattern[abs(strpos($pattern,$os[$i]))-$mode%strlen($pattern)];
}
return $ns;
}
echo decrypt(encrypt("abcde"));
Hopefully you can understand the difference yourself.
And now to fix the issue with spaces:
$pattern=" tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7Cu"
Take note of the space at the beginning of the string.
An additional note due to comments...
Should you wish to change $mode to let's say 62, you would need to shorten $pattern appropriately.
e.g.
function encrypt($os,$mode=62,$pattern=" tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7"){
$ns="";
for($i=0;$i<strlen($os);$i++){
$ns.=$pattern[(strpos($pattern,$os[$i])+$mode)%strlen($pattern)];
}
return $ns;
}
function decrypt($os,$mode=62,$pattern=" tyv9xXa2iUEMhZLD6YlF4BOjg8AdJz0nVHKPRTpb5smfQ1WwroIkqcN3eSG7"){
$ns="";
for($i=0;$i<strlen($os);$i++){
$ns.=$pattern[abs(strpos($pattern,$os[$i]))-$mode%strlen($pattern)];
}
return $ns;
}
echo decrypt(encrypt("abcde"));
Works just fine, notice $pattern is now missing the last 2 characters.

Cannot get functions to execute

So if the last function ended up working. And I followed all of your instructions to the T, why is this function not working properly? I've looked over it for an hour, tried rewrite it over and over again and all I get is a 0 or no return.
function marketing () {
$newsold = $_POST['newsold'];
$usedsold = $_POST['usedsold'];
$carsSold = $newsold + $usedsold;
$AdSpend = $carsSold * 275;
echo "You shoud spend roughly $AdSpend per year on advertising";
}
marketing();
You echo the values inside the function:
not:
echo "$autoProfit";
but,
<?php
function autoProfits () {
$usedprofit = 1527;
$newprofit = 800;
$newsold = $_POST['newsold'];
$usedsold = $_POST['usedsold'];
$uprofit = $usedsold * $profitused;
$nprofit = $newsold * $profitnew;
$autoProfit = $uprofit + $nprofit;
}
autoProfits();
?>
Take close att with the curly brace where to be placed.
Several things with your code:
1) By default, a form will post with GET and not POST. So either change your PHP variables to $_GET OR change your form's method to $_POST. I prefer to change the method.
<form action="calc.php" method="POST">
2) You're missing a curly brace on your function:
function makeProfit () {
if ($profit >=0) {
echo "Your company is making a profit.";
} else {
echo "Your company is not making a profit.";
}
}
3) In your function adSpend(), you should invert the line for $carsSold.
4) You have used upper and lower-case characters interchangeably in your variable names ($usedSold vs $usedsold). PHP variables are case-sensitive.
5) The "+" operator when used to combine a string and integer may work, but it would be better not to put integers in quotes.
5b) Using a comma will cause PHP to not recognize your variable as a number, so use $profitUsed = 1527; instead of $profitUsed = "1,527";
6) Your variables at the top of a PHP file are not GLOBAL. You'll either need to convert them to global variables, or (I prefer) send them as parameters to your function. An example of the corrected adSpend():
function adSpend ($newSold = 0, $usedSold = 0) {
$adSpendPerCar = 275;
$carsSold = $newSold + $usedSold;
$adSpend = $carsSold * $adSpendPerCar;
echo $AdSpend;
}
adSpend($newSold, $usedSold);
7) Finally, when you expect an integer from user input, you'd be best to verify that you have an integer. There are a lot of ways to do this, one simple method is to do something like this:
$newSold = intval($_POST['newsold']);
$usedSold = intval($_POST['usedsold']);
Edit Change your variables $profitused for $usedprofit and $profitnew for $newprofit
function autoProfits () {
$usedprofit = 1527;
$newprofit = 800;
$newsold = $_POST['newsold'];
$usedsold = $_POST['usedsold'];
$uprofit = $usedsold * $usedprofit;
$nprofit = $newsold * $newprofit;
$autoProfit = $uprofit + $nprofit;
echo $autoProfit;
}
function makeProfit () {
if ($profit >=0) {
echo "Your company is making a profit.";
} else {
echo "Your company is not making a profit.";
}
}
This function is missing the last '}' (curly bracket)
But you should also be sure when calling adSpend(), that the variables you're trying to access is set, else you won't get anything out of it.
Since you've made that setup, then you should be calling the functions right after you've set all the variables, for anything to work.
You're using undefined variables on the products. You defined $usedprofit and $newprofit but you're multiplying $profitused and $profitnew. Since they're not defined, PHP assumes they're 0.

Better solutions for a annoying nested if statements in routes of php application

I am using slim php, but this isn't much of specific in just slim, this is common among routes of different php frameworks, especially when working with rest apps.
Okay, here is the most common route that can be found in a blog or archiving application
$app->get('docs(/:year(/:month(/:day)))', function($y=0;$m=0;$d=0) use ($app){
if ($year !== 0 && $month !==0 && $day !== 0) {
// query database with conditions of year, month, day
} else if ($year !== 0 && $month !== 0) {
// query database with conditions of year, month
} else if ($year !== 0) {
// query database with conditions of year
} else {
// query database, return all
}
})
Additionally you want to add get parameters to narrow down results, like limit, offset
$app->get('docs(/:year(/:month(/:day)))', function($y=0;$m=0;$d=0) use ($app){
// additionally you optionally allowed filters like: limit, offset, all
$tmpLimit = $app->request()->get('limit');
$tmpOffSet = $app->request()->get('offset');
$limit = isset($tmpLimit) ? $tmpLimit : 10;
$offset = isset($tmpOffSet) ? $tmpOffSet : 0;
... below add the code previously, and query would change according to if filters(limit,offset) has been set.
})
Are there any more solutions to this? Have too much code tells me that I am not doing it right.
You can use middlewares(Route-Middleware/Middleware) and/or hooks(Hooks-Overview) to keep your route simple.
Route-Middleware:
$mw = function($app) {
return function() use($app) {
$tmpLimit = $app->request()->get('limit');
$tmpOffSet = $app->request()->get('offset');
$app->docsLimit = isset($tmpLimit) ? $tmpLimit : 10;
$app->docsOffSet = isset($tmpOffSet) ? $tmpOffSet : 0;
};
};
route:
$app->get('/docs(/:year(/:month(/:day)))', $mw($app),function($y=0,$m=0,$d=0) use ($app) {
echo $app->docsLimit."<br/>";
echo $app->docsOffSet."<br/>";
... your previously code
});
Hooks:
$app->hook('slim.before.router', function() use($app){
if (strpos($app->request()->getPath(), '/docs') === 0) {
$tmpLimit = $app->request()->get('limit');
$tmpOffSet = $app->request()->get('offset');
$app->docsLimit = isset($tmpLimit) ? $tmpLimit : 10;
$app->docsOffSet = isset($tmpOffSet) ? $tmpOffSet : 0;
}
});
route:
$app->get('/docs(/:year(/:month(/:day)))', function($y=0,$m=0,$d=0) use ($app) {
echo $app->docsLimit."<br/>";
echo $app->docsOffSet."<br/>";
... your previously code
});
I'd say you should look into query builder objects a little bit. For example the one in Doctrine DBAL. This allows you to contruct queries conditionally, without concatting strings together (which opens pandora's box for all kinds of possible errors and security issues):
$queryBuilder
->select('b.id', 'b.title')
->from('blog', 'b');
if ($year) {
$queryBuilder->where('b.year = :year')
->setParameter('year', $year);
}
if ($month) {
queryBuilder->where('b.month = :month')
->setParameter('month', $month);
}
if ($day) {
$queryBuilder->where('b.day = :day')
->setParameter('year', $day)
}
Besides that, it looks like you're putting all the functionality in the route, but you should look into separating some of the tasks into services. Quickly scanning the slimphp documentation I can't find any concept of "services", but all it means is that you add a separate function to the $app so you can re-use that functionality. It stands to reason you will want to fetch "blogs" from multiple routes. It should be as simple as (docs here) :
$app->findBlogPosts = function($y = null, $m = null, $d = null) use ($app) {
// do the DB stuff here, return results
}
I changed the parameters de default to null, as a $year value of 0 has no meaning. If you intend to say "this thing possibly has no value", use the language's concept of "null" - every programming language has such a concept.
Thank's to #deceze and #ramon for guiding me to the whole process of refactoring my code,
First I was given the idea of conditionally constructing the SQL statements, which lead me to this:
if ($year !== 0) {
$where[] = 'YEAR(date_created) = :year';
$bind['where'] = $year;
}
... // if month, if year..
...
$query = sprintf('SELECT * FROM table WHERE %s', join(' AND ', $where));
Document::raw_query($query, $bind)->find_array();
Which will or might cause issues, according to #ramon, he proposed to used a query builder,
so that:
I can do something like:
$queryBuilder
->select('b.id', 'b.title')
->from('blog', 'b');
Looking at the basic where methods provide by paris, as seen in its documentation, I cannot pass a native sql function in the where method of the active record library, but digging up more, I realized that it was build on top of idiorm, I just have to look at the documentation if passing function through the where clause is possible, and by playing around my IDE for a bit, I found out that where_raw exists.
And I came up with this...
$where = array();
$bind = array();
if (!is_null($day)) {
$where[] = 'DAY(`date_created`) = ?';
$bind[] = $day;
}
if (!is_null($month)) {
$where[] = 'MONTH(`date_created`) = ?';
$bind[] = $month;
}
if (!is_null($year)) {
$where[] = 'YEAR(`date_created`) = ?';
$bind[] = $year;
$query = sprintf('%s', join(' AND ', $where));
$foo = Foo::where_raw($query, $bind)
->order_by_desc('date_created')
->offset($offset)
->limit($limit)
->find_array();
} else {
$documents = Foo::order_by_desc('date_created')
->offset($offset)
->limit($limit)
->find_array();
}
I am a bit confident of accepting inputs from the user, the orm prepared the query before execution, I think that'll do some security checks.

Run php variables as it was an if statement

I've got 3 variables:
$left_side = "'Username'";
$equation = "==";
$right_side = "'Username'";
I want to test these variables as it was an if statement like so:
if($left_side $equation $right_side) {
// do something
} else {
// do something else
}
I know this works:
if(eval("return ".$left_side." ".$equation." ".$right_side.";")) {
// do something
} else {
// do something else
}
I always tought it's 'not good' to use eval. Especially when you try to run user input.
Is there an other way to do this? I tried to google it, but it's not my friend to day ;)
You may do something like this:
function testValues($val1, $equation, $val2) {
$res = false;
switch($equation) {
case "==":
$res = $val1 == $val2;
break;
case ">":
$res = $val1 > $val2;
break;
//....
default:
throw new Exception("Unknown operator");
}
return $res;
}
and than use it like:
if(testValues($left_side,$equation,$right_side)) {
//do something
} else {
//do something
}
eval is evil. And no, there's no other (easy) solution, but maybe this one helps:
if ($equation == "==") {
if ($left_side == $right_side) {
// ... your code goes here.
} else {
// Do some other stuff.
}
}
You could use switch:
$left_side = "'Username'";
$equation = "doublequal";
$right_side = "'Username'";
switch($equation){
case 'doublequal':
if ($left_side == $right_side) {
// code
}
break;
//......
}
You should never use eval() especially with user input.
eval() is evil, but call_user_func() is evil too and every framework uses this function in one place or another.
Tools aren't evil. They are just tools. Evil is the way that we use them.
As Uncle Ben said: Great power involves great responsibility :)
I find this trick
http://gonzalo123.com/2012/03/12/how-to-use-eval-without-using-eval-in-php/
the idea is create a temporary file with the PHP source code, include this file with the standard PHP’s include functions and destroy the temporary file.

Categories