1

I have a PHP code that allows me to send a pdf file by email with SendGrid and to insert this file as a BLOB in a MySQL database.

Everything works fine but the file inserted in the database is always a [BLOB - 20,0 kio] file, I can't figure out if it is inserted correctly and how to retrieve it from the database...

enter image description here

Thanks for your help

<?php

 
 $filename2 = 'test.pdf';

 $file_encoded = base64_encode(file_get_contents("C:/wamp64/www/final/API_label/PDF/$filename2"));
 $email->addAttachment($file_encoded, "application/pdf", "$filename2", "attachment");

 $sendgrid = new \SendGrid('SG.Ebi-CnMATfeehrmw89O5CuSNfPk');
 try {
     // $response = $sendgrid->send($email);
     // print $response->statusCode() . "\n";
     // print_r($response->headers());
     // print $response->body() . "\n";
 } catch (Exception $e) {
     // echo 'Caught exception: ' .  $e->getMessage() . "\n";/
 }

      //Insertion of the values in the database
      try {
         // Connect to db
         $db = new db('mysql:dbname=jotform; host=localhost', 'root', '');
         $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

         // Set SQL
         $sql = 'INSERT INTO DHL (submission_id, formID, identite, email, adresse, telephone, label, commercial_invoice) 
         VALUES (:submission_id, :formID, :NOM, :EMAIL, :ADRESSE, :TELEPHONE, :file_encoded, :COMMERCIAL_INVOICE)';

         // Prepare query
         $query = $db->prepare($sql);
         $query->bindParam(':file_encoded', $file_encoded, PDO::PARAM_LOB);

         // Execute query
         $query->execute(array(':submission_id' => $submission_id, ':formID' => $formID, ':NOM' => $NOM, ':EMAIL' => $EMAIL, ':ADRESSE' => $ADRESSE, ':TELEPHONE' => $TELEPHONE, 
         ':file_encoded' => $file_encoded, ':COMMERCIAL_INVOICE' => $COMMERCIAL_INVOICE));
     } catch (PDOException $e) {
         echo 'Error: ' . $e->getMessage();
     }
 

}

?>

Code to dowload:

    //PDO PART
include '../include/classe_PDO.php';

 try {
  // Connect to db
  $db = new db('mysql:dbname=jotform; host=localhost', 'root', '');
  $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);

  // Set SQL
  $sql = "SELECT * FROM `dhl` WHERE `submission_id` = '5094071540419221255'";

  foreach  ($db->query($sql) as $row) {
    
          $filedata = $row['label']; //get base64 data from query result
          $decoded = base64_decode($filedata); //decode base64 to binary
          
          //set suitable HTTP response headers
          header('Content-Description: File Transfer'); 
          header('Content-Type: application/octet-stream'); 
          header('Content-Disposition: attachment; filename="label.pdf"');
          header('Expires: 0'); 
          header('Cache-Control: must-revalidate'); 
          header('Pragma: public'); 
          //output the binary file data in the body of the response
          echo $decoded; 
          exit;
}



} catch (PDOException $e) {
  echo 'Error: ' . $e->getMessage();
}         


?>
Eric27
  • 89
  • 1
  • 7
  • https://stackoverflow.com/questions/32329586/how-do-i-download-a-file-using-php-and-mysql-db is likely to help you – ADyson Oct 06 '21 at 16:49
  • I'm sorry but I can't understand how to do it in the link... @ADyson – Eric27 Oct 07 '21 at 09:21
  • Why, what didn't you understand exactly? Did you try to use any of it in your code? It shows how to get the blob data from the table and how to provide it as a download to the user. – ADyson Oct 07 '21 at 09:24
  • Yes I did it but the problem is that the file in my database is base64 encoded and when I want to decode it to download it doesn't work... – Eric27 Oct 07 '21 at 09:44
  • If the file is base64 encoded then it's text and you don't really need to store it in a blob. blob is for binary data. But then again, since blob exists there's not really any need to base64 encode it to begin with... I assume you did that for the purpose of attaching it to the email? – ADyson Oct 07 '21 at 09:47
  • P.S. Actually the first link I gave might not be the best,I just realised it doesn't really cover the database part - sorry. https://www.mysqltutorial.org/php-mysql-blob/ should fill that gap. – ADyson Oct 07 '21 at 09:51
  • Yes I encoded it in base64 to send it as an attachment by email which works well, but I would like to insert it in my database to be able to download it if the email is lost for example – Eric27 Oct 07 '21 at 09:51
  • But if you want to use base64 then it's probably simpler to use a text type, and then just decode it when you query it: https://stackoverflow.com/questions/25239361/how-to-store-base64-information-in-a-mysql-table – ADyson Oct 07 '21 at 09:52
  • So the best is to record it as I do but the column in varchar type and then I decode the base 64 to display it? – Eric27 Oct 07 '21 at 09:56
  • As per that link, "text" type is better than "varchar", but yes that should work. – ADyson Oct 07 '21 at 10:12
  • So I remove the blob part from my code? And then I do something like this ? https://stackoverflow.com/questions/34698016/download-pdf-from-base64-string – Eric27 Oct 07 '21 at 10:13
  • Yes but I don't think you really need to store it in a file on the server first, though. You should just be able to echo the decoded data directly to the output – ADyson Oct 07 '21 at 10:15
  • You think it's not the right way to store it in a database? How do I get it back if I need it. In fact I am doing a SAV form and this PDF file is a shipping label of the package to be repaired that I send to the customer by email but I would like to keep it to find it if the customer did not receive the email – Eric27 Oct 07 '21 at 10:18
  • No that's not what I said at all. I was referring specifically to the part of the answer in that link you gave, where it saves the "$decoded" data to a file and then uses readFile to read it back again for the output. That seems redundant to me. I see no reason why you can't just echo the $decoded variable directly, in place of readFile. – ADyson Oct 07 '21 at 10:23
  • I just updated my question with the code to download, it can be reduced you say? If I do an echo directly it doesn't show the decoded pdf – Eric27 Oct 07 '21 at 10:29
  • So what does it do instead, in that scenario? Code always does _something_, so if you get an error or other unexpected output please mention it, then it's easier to debug. – ADyson Oct 07 '21 at 11:08
  • BTW your overall code is a bit strange - you're looping through all the rows in the database table, but if you have more than one row it won't really work because you can't trigger multiple downloads from a single HTTP request. (Fortunately you `exit` at the end of the first download otherwise this would cause an error. But surely you actually want to download a specific file, not just the first one in the table?) – ADyson Oct 07 '21 at 11:09
  • But I was thinking the download part of the code could probably be reduced to: `$filedata = $row['label']; $filename = 'label.pdf'; $decoded = base64_decode($filedata); header('Content-Description: File Transfer'); header('Content-Type: application/octet-stream'); header('Content-Disposition: attachment; filename="'test.pdf'"'); header('Expires: 0'); header('Cache-Control: must-revalidate'); header('Pragma: public'); echo $filedata; exit;`. (N.B. I changed some of the variable names because they weren't accurate about what the variable actually contained!) – ADyson Oct 07 '21 at 11:12
  • As an aside, why do you use PDO in one code and mysqli in another? That makes no sense. Use one database library across your whole application. – ADyson Oct 07 '21 at 11:16
  • Thanks for your help, I have modified actually I only want to upload a specific file. I have an error on the line `header('Content-Disposition: attachment; filename="'test.pdf'"); " : Parse error: syntax error, unexpected 'test' (T_STRING), expecting ')'` . And yes I'm going to do everything just in PDO. – Eric27 Oct 07 '21 at 12:02
  • You mean download, not upload :-) – ADyson Oct 07 '21 at 12:06
  • Anyway it's just a basic syntax error because of the quote marks, apologies. Change to `filename="test.pdf"` – ADyson Oct 07 '21 at 12:07
  • Yes sorry for the mistake!! – Eric27 Oct 07 '21 at 12:07
  • The downloaded pdf file is impossible to open... – Eric27 Oct 07 '21 at 12:10
  • Impossible in what way? Which application did you use? What error did you get? Did you check the raw data of the downloaded file (e.g. you could use Notepad++ or similar to examine the raw data)? How does it compare to a version downloaded using your original code? If it doesn't work then fine, you can go back to using the version based on readFile(), but be aware it leaves a file sitting on your server's disk which you don't really need. – ADyson Oct 07 '21 at 12:12
  • I am using Adobe Acrobat Reader DC to open the PDF which marks when opening the file that the file is damaged. When I open the file with Notepad++ I just have the base 64 encoded string in it. I prefer your method which does not keep this useless file – Eric27 Oct 07 '21 at 12:17
  • Sorry another typo, which you can spot yourself if you look carefully. Look at my `echo` statement...what is the mistake (especially bearing in mind what you've just told me about the file contents)? – ADyson Oct 07 '21 at 12:21
  • Oh yes indeed! You have to echo the $decoded, it works there – Eric27 Oct 07 '21 at 12:23
  • So it's all working correctly now? If so I will add my code as the answer. – ADyson Oct 07 '21 at 12:25
  • Yes everything works now! I just deleted your second line `$filename = 'label.pdf';` which is not used and I have it in MySQL instead of PDO – Eric27 Oct 07 '21 at 12:27
  • 1
    Added answer below for you to mark as accepted (and upvote as well if you feel generous!). Thanks. – ADyson Oct 07 '21 at 12:34

1 Answers1

1

You need to

  1. Store the data in a TEXT column in MySQL, not BLOB, because a base64 string is text, not binary data.

  2. Query that field from your database to get the base64 data

  3. Decode the base64 data to get the original binary data.

  4. Send that data for download, setting the appropriate headers for your browser to understand that it's a file download rather than a web page.

You seem to have the SQL part sorted now, judging by the update to your questions, so here I'll just show a simpler way to download the data, without needing to write a file to the server disk first (which you'd then need to clean up later).

$filedata = $row['label']; //get base64 data from query result
$decoded = base64_decode($filedata); //decode base64 to binary

//set suitable HTTP response headers
header('Content-Description: File Transfer'); 
header('Content-Type: application/octet-stream'); 
header('Content-Disposition: attachment; filename="test.pdf"');
header('Expires: 0'); 
header('Cache-Control: must-revalidate'); 
header('Pragma: public'); 
//output the binary file data in the body of the response
echo $decoded; 
exit;
ADyson
  • 57,178
  • 14
  • 51
  • 63
  • Thank you for your answer and detailed explanations. Just modify your line like this `header('Content-Disposition: attachment; filename="test.pdf"');` – Eric27 Oct 07 '21 at 12:40
  • 1
    Apologies I forgot to fix that again when I copied it. Amended now. Thankyou :-) – ADyson Oct 07 '21 at 12:42
  • And last question, the way I do my data insertion and retrieval is correct in SQL? It is not open to SQL injections? – Eric27 Oct 07 '21 at 13:12
  • 1
    You're parametering everything in your insert query so yes that one is safe. And the select query you did not use any data from user input, so automatically it's safe...but if you changed to read from any input or querystring or anything, you would need to use parameters. I advise using PDO throughout all your code, it's confusing to switch between libraries and increases your chances of mistakes. See [How can I prevent SQL injection in PHP?](https://stackoverflow.com/questions/60174/how-can-i-prevent-sql-injection-in-php) as a quick reference if you're ever unsure if you're doing it correctly – ADyson Oct 07 '21 at 13:16
  • 1
    The only extra advice I would give is: Never configure your web app to login to the database as `root`. Root can do whatever it likes, so if there is accidentally any vulnerabiliy, it leaves your database wide open to hackers. Instead create a separate user account specifically for this app which has only the permissions it actually _needs_ in order to work properly. Don't even use "root" as a shortcut during development or testing, because you need to test your account permissions as well - otherwise when you go live you might have unexpected errors relating to the user account setup. – ADyson Oct 07 '21 at 13:18
  • Yes I updated my code in my question, I just put the select in PDO too. Okay thanks for the advice, as I was developing in test on my computer I stay in root as this is not the final database I use here for my tests. – Eric27 Oct 07 '21 at 13:21
  • I come back to your answer, I would like to download two labels in a zip file from my database. They are in two different columns. I doubled your code but it still only downloads the first one... – Eric27 Oct 29 '21 at 09:15
  • You can only download one file at a time in a HTTP response. You'd need to first create a zip file, put the other files into it, and then download the single zip file. (The user can then unzip it themselves). That can't be achieved by duplicating the code I've shown. In fact my code would mostly stay the same, it would be the process of creating the zip file beforehand which you would need to add. – ADyson Oct 29 '21 at 09:26
  • Ok thanks for the answer, I just need to create a zip file and put my 2 $row values in it then upload it? – Eric27 Oct 29 '21 at 09:42
  • upload it? You mean download? – ADyson Oct 29 '21 at 09:46
  • Yes, sorry for the mistake – Eric27 Oct 29 '21 at 09:46
  • Ok then yes that is what you would need to do – ADyson Oct 29 '21 at 09:51
  • The zip file creation that I find are all done with the creation of a path : https://stackoverflow.com/questions/4914750/how-to-zip-a-whole-folder-using-php do you know if it is possible to create it without trace on a server? – Eric27 Oct 29 '21 at 09:53
  • Off the top of my head I'm not sure, you would have to maybe do some research, and ask a new stackoverflow question about it if you get stuck. – ADyson Oct 29 '21 at 09:57
  • Thanks for the advice, I will do that – Eric27 Oct 29 '21 at 09:57