I stored an image in a PostgreSQL database with column type bytea using PHP. The problem is every time I try to load the image in a browser it does not appear. The Firefox developer console says the image is either truncated or corrupt.
The PHP code:
//code for inserting into the database
if(array_key_exists('submit_pic', $_POST)){
$user=$_SESSION['name'];
if(isset($_FILES['thumbnail'])&&$_FILES['thumbnail']['size']>0){
$fi = $_FILES['thumbnail']['tmp_name'];
$p=fopen($fi,'r');
$data=fread($p,filesize($fi));
$data=addslashes($data);
$dat= pg_escape_bytea($data);
$q="update userinfo set image='{$dat}' where email='$user'";
$e=pg_query($q)or die(pg_last_error());
// code for retreving from database
require_once('conn.php');
session_start();
$user=$_SESSION['name'];
pg_query('SET bytea_output = "escape";');
$lquery ="select image from userinfo where email='$user'";
$lq = pg_query($lquery)or die(pg_last_error());
$lqq=pg_fetch_row($lq,'image');
header("conent-type:image");
echo pg_unescape_bytea($lqq[0]);
and i need to store the uploaded image in a database- i am actually using heroku thanks
TL;DR:
Delete addslashes($data). It's redundant here.
Double-escaping .. twice
$data=fread($p,filesize($fi));
$data=addslashes($data);
$dat= pg_escape_bytea($data);
You read the data in, escape it as if it were a string literal, then convert it to bytea octal or hex escapes. It could never work that way around even if pg_escape_bytea was sane, which it isn't.
PHP's pg_escape_bytea appears to double-escape the output so it can be inserted into a string literal. This is incredibly ugly, but there doesn't appear to be an alternative that doesn't do this double-escaping, so you can't seem to use parameterised statements for bytea in PHP. You should still do so for everything else.
In this case, simply removing the addslashes line for the data read in from the file is sufficient.
Test case showing that pg_escape_bytea double-escapes (and always uses the old, inefficient octal escapes, too):
<?php
# oh-the-horror.php
print pg_escape_bytea("Blah binary\x00\x01\x02\x03\x04 blah");
?>
Run:
php oh-the-horror.php
Result:
Blah binary\\000\\001\\002\\003\\004 blah
See the doubled backslashes? That's because it's assuming you're going to interpolate it into SQL as a string, which is extremely memory inefficient, ugly, and a very bad habit. You don't seem to get any alternative, though.
Among other things this means that:
pg_unescape_bytea(pg_escape_bytea("\x01\x02\x03"));
... produces the wrong result, since pg_unescape_bytea is not actually the reverse of pg_escape_bytea. It also makes it impossible to feed the output of pg_escape_bytea into pg_query_params as a parameter, you have to interpolate it in.
Decoding
If you're using a modern PostgreSQL, it probably sets bytea_output to hex by default. That means that if I write my data to a bytea field then fetch it back, it'll look something like this:
craig=> CREATE TABLE byteademo(x bytea);
CREATE TABLE
craig=> INSERT INTO byteademo(x) VALUES ('Blah binary\\000\\001\\002\\003\\004 blah');
INSERT 0 1
craig=> SELECT * FROM byteademo ;
x
----------------------------------------------------------------------------
\x426c61682062696e6172795c3030305c3030315c3030325c3030335c30303420626c6168
(1 row)
"Um, what", you might say? It's fine, it's just PostgreSQL's slightly more compact hex representation of bytea. pg_unescape_bytea will handle it fine and produce the same raw bytes as output ... if you have a modern PHP and libpq. On older versions you'll get garbage and will need to set bytea_output to escape for pg_unescape_bytea to handle it.
What you should do instead
Use PDO.
It has sane(ish) support for bytea.
$sth = $pdo->prepare('INSERT INTO mytable(somecol, byteacol) VALUES (:somecol, :byteacol)');
$sth->bindParam(':somecol', 'bork bork bork');
$sth->bindParam(':byteacol', $thebytes, PDO::PARAM_LOB);
$sth->execute();
See:
PHP: Large Objects, which has an example of exactly what you want;
PDOStatement::bindParam
how to store serialized object with namespace in database using pdo php
Bind BYTEA to PGSQL PDO Prepared Statement in PHP5
You may also want to look in to PostgreSQL's lob (large object) support, which provides a streaming, seekable interface that's still fully transactional.
Now, on to my soap box
If PHP had a real distinction between "byte string" and "text string" types, you wouldn't even need pg_escape_bytea as the database driver could do it for you. None of this ugliness would be required. Unfortunately, there are no separate string and bytes types in PHP.
Please, use PDO with parameterised statements as much as possible.
Where you can't, at least use pg_query_params and parameterised statements. PHP's addslashes is not an alternative, it's inefficient, ugly, and doesn't understand database specific escaping rules. You still have to manually escape bytea if you're not using PDO for icky historical reasons, but everything else should go through parameterised statements.
For guidance on pg_query_params:
Bobby tables, PHP section.
The PHP manual on pg_query_params
It is better to use postgres large objects if you really have to store images in your database. In the userinfo table instead of the image itself store just a link to it as loid (large object id).
Insert an image into the database:
pg_query("begin"); // pg_lo functions need to be run in a transaction
$loid = pg_lo_import('full_path_and_file_name');
pg_query("update userinfo set loid=$loid where email='$user'");
pg_query("commit");
Retrieve an image from the database:
$rs = pg_query("select loid from userinfo where email='$user'");
$loid = pg_fetch_row($rs, 0)[0];
pg_query("begin");
$blob = pg_lo_open($loid, "r");
header("Content-type: image");
pg_lo_read_all($blob);
pg_lo_close($blob);
pg_query("commit");
The loid field is of type oid (of course you can name it as you want).
Consider using the lo type from the lo extension instead of using the oid type. Using lo gives you automatic "orphan removal", where deleting a row from a table will automatically remove the associated large object, so it's good for cases where a table row "owns" a large object.
Storing links to images is especially convenient in case you use one image more than one time. However, you should pay attention to delete unused images from your database (PHP function pg_lo_unlink()).
Large objects in postgres documentation.
PHP manual: pg_lo_import.
I found a strange way of getting this to work too without using PDO.
Use a text field in postgresql instead of bytea.
On insert, prep your data like this:
$imgdta = pg_escape_string(bin2hex($filedata));
Then when you want to display the file after your query, use:
echo pack("H*", $img["filedata"]);
I'm not going to pretend I get why, but this worked for me!
As the source of your data is a file in the file system so it seems to me efficient to find an inspiration here:
In your db create an auxiliary function, run as superuser:
create or replace function bytea_import(p_path text, p_result out bytea)
language plpgsql as $$
declare
l_oid oid;
begin
select lo_import(p_path) into l_oid;
select lo_get(l_oid) INTO p_result;
perform lo_unlink(l_oid);
end;$$
security definer;
In your php execute a query like:
#make sure that postgres will have access to the file
chmod($_FILES['thumbnail']['tmp_name'], 0644);
pg_query("update userinfo set image=(select bytea_import('".$_FILES['thumbnail']['tmp_name']."')) where email='$user'");
Related
I've gotten to a point where I absolutely need some clean way to safely pass lists/arrays from php to SQL server stored procedures and table value functions. The PHP SQL server driver still does not support table valued parameters, according to Microsoft docs.
In another question, using XML was suggested as an alternative.
Does anyone have a code sample for passing the equivalent of a TVP using an XML stream and PHP PDO or another clean alternative?
The primitive, but foolproof solution, is to pass it as a delimited string, and use a SPLIT function in your proc to convert the string to a table that you can then JOIN to.
Google SQL SPLIT FUNCTION to get free cut-n-paste code.
You could use xml, but a better, more compact idea would be to use JSON.
$myArray = array("thing 1", "thing 2", "thing 3");
$sql = $pdo->prepare("INSERT INTO `table` (`array_data`) VALUES (:myArray)");
$sql->execute(array(":myArray"=>json_encode($myArray)));
Then when you pull the data back out of the database you can convert it back into an array with:
$myArray = json_decode($res['myArray'], true);
I need to execute two SQL statements together because connection_id() in the first statement will be used in the Mysql view wp_statistics_benchmarks.
Without the connection_id(), the wp_statistics_benchmarks is an empty view. The following SQL works fine and get results:
replace into wp_params (`view_name` , `param1_val`, `connection_id`)
values ('benchmarks', 484 , connection_id())
;
select * from wp_statistic_benchmarks;
But, to work with wordpress, the following code doesn't work:
$mysqli = new mysqli(.....);
$results = $this->_wpdb->query("
replace into wp_params (`view_name`, `param1_val`, `connection_id`)
values ('benchmarks', $connected_from, $mysqli->thread_id);
select * FROM `wp_statistic_benchmarks`;"
);
How can I convert these two mysql codes into Wordpress wpdb queries?
Use the wpdb object twice.
$this->_wpdb->query('replace into ...');
$rows = $this->_wpdb->get_results('select ...')
Let me put it another way, select * from wp_stat ... and replace into wp_params ... from your original "mysql codes" are separate statements without any relation to each other.
You think that you need to run them in sequence, whereas in fact you can have a cup of coffee or even travel around the earth in between those replace into and select statements and they would still do the same thing. If that is not the case, then your question lacks information necessary to provide a good answer because wp_params is not a standard table in wordpress and neither is the view. I don't think you understand your problem.
Besides, running them as I suggest is equivalent with your "mysql codes". Moreover, $wpdb->query returns the number of affected rows or false, so you will never be able to run a select statement with $wpdb->query() to retrieve a set of tuples.
How can I convert these two mysql codes into Wordpress wpdb queries?
You can't. That's because you're using wpdb and it only supports one query per ->query() call. However, if you're using Mysqli with wpdb, you can use the multi_query() method of it with wpdb. Here is how:
To use multiple queries, you need to ensure that wpdb uses Mysqli (e.g. define the USE_EXT_MYSQL constant as FALSE in your Wordpress config).
Then you can obtain the mysqli instance from the wpdb object, either with reflection or a helper class/module:
abstract class wpdb_dbh extends wpdb
{
static function from(wpdb $wpdb) {
return $wpdb->dbh;
}
}
Mysqli is then available without creating a new instance:
$mysqli = wpdb_dbh::from($this->_wpdb);
As this is a valid Mysqli instance you can run multi query.
But just obtaining the same Mysqli instance as wpdb uses it probably the most important thing here as otherwise your open an additional connection with new mysqli(...) which you need to prevent.
Additionally take care that $mysqli->thread_id is a fitting replacement to connection_id() following the same formatting/encoding. You should be able to use connection_id() directly anyway, so I actually see not much reason to access the thread_id member, but it's perhaps only because you tried some alternatives and I'm just over-cautious.
The ';' query delimiter is purely an SQL shell convenience and is not a part of the MySQL dialect so you're correct that your code doesn't work.
Here's the actual replacement code:
$mysqli = new mysqli(.....);
$this->_wpdb->query(
"replace into wp_params
(`view_name`, `param1_val`, `connection_id`)
values ('benchmarks', $connected_from, $mysqli->thread_id)");
$results = $this->_wpdb->query("select * FROM `wp_statistic_benchmarks`");
This is the same as Ярослав's answer above.
Update:
If your code is still not working you might have to enable persistent connections in Wordpress.
Update 2:
There was a missing space between in the second query's select statement and the * shorthand all columns selector. Interestingly this may or may not cause an issue for you, it doesn't seem to bother my MySQL 5.5 command line shell.
If I understand your requirements (and I do not know wordpress), you are inserting a row to wp_params with a column called connection_id. I would assume that this value will be unique on the table. I would be tempted to add an integer autoincrement id field to the table and then get the value of that (last insert id). Then use this id in a WHERE clause when selecting from the view.
How do you escape ' on doctrine?
I made this code
$query = $em->createQuery(
"SELECT a FROM AcmeTopBundle:ArtData a WHERE
a.name = '". mysql_escape_string($name) ."'");
but when the $name is A'z
it returns error
[Doctrine\ORM\Query\QueryException]
SELECT a FROM AcmeTopBundle:ArtData a WHERE
a.name = 'A\'s'
I think I escaped by mysql_escape_string in case of using raw sql.
How can I avoid this error on doctrine?
The way I usually handle this is using parameters and querybuilder (https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/query-builder.html)...
$qb = $em->createQueryBuilder(
"SELECT a FROM AcmeTopBundle:ArtData a WHERE
a.name = :name")
->setParameter('name',$name);
$result = $qb->getQuery()->execute();
Well, even though there is accepted answer it is not for question as it is in title.
#Sven's answer comes close, but fails to mention:
Doctrine documentation
To escape user input in those scenarios use the Connection#quote() method.
And I have a gripe with "scenarios", or more with people pushing prepared statements like some holy grail. Well they are nice in theory, in practice at least in PHP they are quite shity, as they are unable to do simple stuff like IN (<list>) or multi inserts with VALUES (<bla bla>), (<more stuff>) which is a huge ass deal, as without it one ends up resorting to quite sub-optimal SQL (to put it lightly) quite commonly (well if one religiously insist on prepared statements at least).
This does not answer your question, but explains what's wrong with your code. It didn't fit into a comment.
You cannot and should not use mysql_escape_string()
It is the wrong escape function, the right one used to be mysql_real_escape_string(). Reading the documentation does not sound like it, but to properly escape, you have to know which character encoding is being used. In western encoding schemes like ASCII, ISO-8859-x or even UTF-8 it probably does not make a difference, but there are some exotic chinese encodings around which absolutely need to know whether that " byte belongs to another byte, or comes on it's own.
When using mysql_real_escape_string(), you need to have an already open DB connection created with mysql_connect(). If you don't, PHP tries to open a new connection with default user and password as defined in the php.ini file. This usually results in an error because without password the database won't let you do anything. And additionally, if you have success, then the encoding setting of this connection most likely is not the one used by Doctrine.
Using any of the mysql_* functions is wrong, because these are deprecated. The correct way would be to use mysqli_* functions.
Doctrine may use any of the three database connection methods: mysql, mysqli or PDO. You have to choose the one really being used if you want to manually call the correct escaping function. While the connection is already created. And somehow you need to grab that connection resource to allow the function you are calling to detect the used encoding.
So in the end there are plenty of reasons why it is wrong to just use any escaping that sound like it is doing the job.
The right way is to use the escaping of the database layer you are using. If you use Doctrine, the use it for escaping. Or better, avoid escaping, use prepared statements or the query builder and let Doctrine deal with the rest.
Based on https://stackoverflow.com/a/13377430/829533
you can use prepared statements http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/data-retrieval-and-manipulation.html#using-prepared-statements
From the documentation:
$date = new \DateTime("2011-03-05 14:00:21");
$stmt = $conn->prepare("SELECT * FROM articles WHERE publish_date > ?");
$stmt->bindValue(1, $date, "datetime");
$stmt->execute();
This will show how to insert the data into the database where you would normally have to use real_escape_string.
Doctrine and Symfony 3 using prepared not QueryBuilder:
// get the post value
$value = $request->request->get('value');
$sql = "INSERT INTO `table_name`
(`column_name1`,`column_name2`)
VALUES
('Static Data',?)
";
$em = $this->getDoctrine()->getManager();
$result = $em->getConnection()->prepare($sql);
$result->bindValue(1, $value);
$result->execute();
Now for a bonus to get a success/fail if you are using auto increment records:
$id = $em->getConnection()->lastInsertId();
if $id has a value then it executed the insert. If it does not the insert failed.
I'm new to PHP, but not programming. Have come from an ASP [classic] background. In brief, I'm using PHP 5.4, with FastCGI on IIS7 and SQL Server 2005 Express. I've learnt the fundamentals, and have spent quite some time looking into security.
I'm sanitising both GET and POST input data. My db connection strings are in a separate file placed outside the web root. I'm using PDO prepared statements [though I've heard query+quote perform faster] with named placeholders along with db stored procedures.
I'm trying to understand why I would need to use additional arguments within the bindParam function, particularly data type options "PDO::PARAM_STR, 12" [second argument in that example represent the data length right?].
What are the benefits of specifying the data type and length within the bindParam? Is it needed if I'm using stored procedures in which the data type and length is already specified? Also, I believe I need to use something like "PDO::PARAM_INPUT_OUTPUT" to return a value from a stored proc?
Thanks!
** EDIT **
For some reason, if I use the PDO::PARAM_STR argument, my stored procs don't seem to write data into the db. So I omitted that argument. Here's my code:
$sql1 = $conn->prepare("EXEC insert_platts :userAgent, :userIp, 1, :source");
$sql1->bindParam(':userAgent', $userAgent);
$sql1->bindParam(':userIp', $userIp);
$sql1->bindParam(':source', $source);
$sql1->execute();
Also, rather than returning the identity value from the stored proc, I'm using lastInsertId() instead:
$lastRow = $conn->lastInsertId();
print $lastRow;
No, data type and data length are not needed. I'm using mysql stored procs and the parameters are never typed values, all though I validate them of course. I guess that the reason is extra security and INOUT params. Quote:
To return an INOUT parameter from a stored procedure, use the bitwise
OR operator to set the PDO::PARAM_INPUT_OUTPUT
have you tried this?
$params = array(
':userAgent'=>$userAgent,
':userIp' => $userIp,
':source' => $source
);
$sql1 = $conn->prepare("EXEC insert_platts :userAgent, :userIp, 1, :source");
$sql1->execute($params);
About special characters: are you using correct encodings? I mean, the same encoding in the php app and the DB... sometimes is hard to work with one encoding in the scripts and other in the database.. and very often problems like that arise...
Can anybody help me to insert blob data in Mysql using MDB2 through php ?
I want to insert file into database using MDB2.
MBD2 setup is works fine.
This may help, as I had trouble with this for anyone in the future, note the quote sets the 'blob' type when sprintf injected each string generated by the quote functions. The key part appears to be using "file://" with a reference to a file for it to work this way.
$database is a mdb2 object as typically given in other examples online.
// NOTE BELOW: The quote function or lower layers - requires the file reference as below
// I could not pass the raw bytes through that were in a variable for some reason, as the
// quote method appeared to modify the bytes - maybe as it assumes a charset?
$sql = 'UPDATE %s SET %s=%s WHERE iconid=%d';
$sql = sprintf ($sql,
$database->quoteIdentifier('chanicon'),
$database->quoteIdentifier('icondata'),
$database->quote("file://".$_FILES['userfile']['tmp_name'][0], 'blob'),
$database->quote($_REQUEST['iconid'], 'integer')
);