-1

I'm trying to download all the image files (.jpg format) stored as mediumblobs in a MySQL table.

The following PHP code allows the individual images to be displayed and saved individually without a problem.

blob2img.php

<td>
<form method="post" action="download_img.php">
<input type='submit' name='submit' <?php $imgCode = $row['IMAGEID']; echo "value='View imgId: " . $imgId . "'"; ?> />
<input type='hidden' name='imgId' <?php echo "value='" . $imgId . "'"; ?> />
</form>
</td>

download_img.php

<?php
    $img_id = $_REQUEST['imgId'];
    require_once "db3.php";
    if(isset($img_id)) {
        $myCode = $img_id;
        $sql = "select * from images where IMAGEID='" . $myCode . "'";

        if (!$result=mysqli_query($con, $sql)){
        echo mysqli_error($con);
        } else {
            $row = mysqli_fetch_array($result);
        header('Cache-Control: no-cache');
        header('Accept-Ranges: bytes');
        header('Content-Type: image/jpeg');
                header('Content-Transfer-Encoding: binary');
        header('Content-Disposition: attachment; filename="'. basename($row["IMAGEID"]) . '_' . basename($row["LOCATION"]) . '"');
            echo $row["IMAGEDATA"];
        }
    }
?>

The issue is that there are thousands of images stored in the table and it would far more preferable to be able to download the whole lot in one go. When the download_img.php file is dispensed with and the above ... block in the blob2img.php file is replaced with the following code:

<?php
    require_once 'db3.php';
    $result = mysqli_query($con,"SELECT IMAGEID, LOCATION FROM images");
    if (mysqli_num_rows($result) > 0) {
?>
<table class='table table-bordered table-striped'>
    <tr>
        <td>ImageId</td>
        <td>Location</td>
        <td>ImageData</td>
    </tr>
    <?php
        $i=0;
        while($row = mysqli_fetch_array($result)) {
    ?>
    <tr>
        <td><?php echo $row["IMAGEID"]; ?></td>
        <td><?php echo $row["LOCATION"]; ?></td>
        <td>
            <?php
                header('Content-Type: text/html; charset=utf-8');
                if (!file_exists($file)) {
                    $basedir = '/home/user/Documents/retrieved-images/';
                    $id = $row["IMAGEID"];
                    $loc = $row["LOCATION"];
                    $data = $row["IMAGEDATA"];
                    $file = $basedir . $id . "_" . $loc;
                    $fp=fopen($file,"wb");
                        echo "Opening File: $file<br>";
                        fwrite($fp,$data);
                        echo "Writing File: $file<br>";
                        fclose($fp);
                        echo "Closing File: $file<br>";
                        sleep(1);
                } else {
                        echo "Skipping File: $file<br>";
                }
            ?>
        </td>
    </tr>
    <?php
        $i++;
        }
    ?>
</table>

... the table is loaded row by row and the file downloads appear to be processed as all the opening file, writing file and closing file messages are returned as expected, however none of the image files get written to the target directory. It cannot be a permissions issue as user has full read/write priveleges for the the target directory specified for $basedir.

If the following line of code:

    $result = mysqli_query($con,"SELECT IMAGEID, LOCATION FROM images");

is replaced with:

    $result = mysqli_query($con,"SELECT IMAGEID, LOCATION, IMAGEDATA FROM images");

only the page title is returned. For some reason if IMAGEDATA is included in the select process at that stage it prevents the table from being loaded.

If the following lines of code:

                    $id = $row["IMAGEID"];
                    $loc = $row["LOCATION"];
                    $data = $row["IMAGEDATA"];

are replaced with:

                    $sql = "select * from images where IMAGEID='" . $row["IMAGEID"] . "'";
                    $id = $sql["IMAGEID"];
                    $loc = $sql["LOCATION"];
                    $data = $sql["IMAGEDATA"];

data is returned in a random order and $sql["LOCATION"] returns the actual image file name, but again does not result in any image files being saved to the target directory.

Further to siride's and Robert's initial feedback I've tried replacing the following lines of code:

                if (!file_exists($file)) {
                    $basedir = '/home/user/Documents/retrieved-images/';
                    $id = $row["IMAGEID"];
                    $loc = $row["LOCATION"];
                    $data = $row["IMAGEDATA"];
                    $file = $basedir . $id . "_" . $loc;

with:

                    $basedir = '/tmp/retrieved-images/';
                    $id = $row["IMAGEID"];
                    $loc = $row["LOCATION"];
                    $data = "select IMAGEDATA from images where IMAGEID='" . $id . "'";
                    $file = $basedir . $id . "_" . $loc;
                    if (!file_exists($file)) {

... but that change has made no difference either.

$basedir = '/tmp/'; to /tmp/ actually results in the "Skipping file: ..." message whereas $basedir = '/tmp/retrieved-images/'; does not.

Any assistance would be greatly appreciated.

ridgedale
  • 190
  • 1
  • 1
  • 14
  • You usually can't write to any directories except /tmp. Your best bet is to save the files there and then create a zip file and output it for download like you did in the first example. – siride Feb 13 '22 at 15:46
  • I think that You should specify variable `$file` before `if (!file_exists($file)) {`. It seems that in first loop `$file` is empty, so `!file_exists($file)` is true, but in another loop `$file` is the previously saved file, so it exists. – Robert Feb 13 '22 at 16:02
  • **Warning:** You are wide open to [SQL Injections](https://php.net/manual/en/security.database.sql-injection.php) and should use parameterized **prepared statements** instead of manually building your queries. They are provided by [PDO](https://php.net/manual/pdo.prepared-statements.php) or by [MySQLi](https://php.net/manual/mysqli.quickstart.prepared-statements.php). Never trust any kind of input! Even when your queries are executed only by trusted users, [you are still in risk of corrupting your data](http://bobby-tables.com/). [Escaping is not enough!](https://stackoverflow.com/q/5741187) – Dharman Feb 13 '22 at 16:34
  • I prefer not to store images in a database. Instead, there are a lot of tools like Filezilla and rsync that facilitate moving files around. – Rick James Feb 14 '22 at 03:02
  • @siride: Thank you for your reply. Changing $basedir to /tmp/retrieved-images/ or /tmp/ makes no difference. The image files are still not written. – ridgedale Feb 14 '22 at 07:55
  • @Robert: I've moved the declaration of the ```$file``` variable before ```if (!file_exists($file)) {``` but that has not made any difference either. As indicated the initial select statement does not include the IMAGEDATA field. If the IMAGEDATA field is included in that select statement nothing is returned. Thinking that IMAGEDATA may therefore not be available I've also tried changing the ```$data``` variable to specifically fetch the IMAGEDATA associated with ```$id``` and that has not worked either. – ridgedale Feb 14 '22 at 08:11
  • @Robert: As referred to above I tried replacing: ``` if (!file_exists($file)) { $basedir = '/home/user/Documents/retrieved-images/'; $id = $row["IMAGEID"]; $loc = $row["LOCATION"]; $data = $row["IMAGEDATA"]; $file = $basedir . $id . "_" . $loc;``` with: – ridgedale Feb 14 '22 at 08:15
  • @Robert: ``` $basedir = '/tmp/'; // Needs trailing slash $id = $row["IMAGEID"]; $loc = $row["LOCATION"]; $data = "select IMAGEDATA from images where IMAGEID='" . $id . "'"; $file = $basedir . $id . "_" . $loc; if (!file_exists($file)) { ``` but still no joy. – ridgedale Feb 14 '22 at 08:20
  • @Dharman: Thanks for your reply and advisory, but the recovery of the data is being carried out offline. I've no idea whther this is relevant or not but the recovery of the image files is being carried out under CentOS 8. – ridgedale Feb 14 '22 at 08:24
  • @Rick James: The image files are trying to be recovered from a supplied backup. – ridgedale Feb 14 '22 at 08:30
  • 1
    Maybe there are some memory limit issues in PHP? Do you have `error_reporting` enabled? If there is a lot of data from mysql query to put in memory, then PHP will crash. Maybe try `mysqli_use_result`. See this: https://stackoverflow.com/questions/51550061/mysqli-query-single-query-gerenating-php-fatal-error-allowed-memory-size-exhaus – Robert Feb 14 '22 at 09:44
  • The PHP default may be `memory_limit = 128M`; see `php.ini`. That is enough for a few typical images today, but not enough if you are going to do much image processing with the `image...` functions in PHP. – Rick James Feb 14 '22 at 16:53
  • @Robert Thank you kindly. It was a memory limit issue. The error logs revealed the following error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 229376 bytes) in /var/www/html/... The key to the resolution was the mysqli_use_result reference and link. I'll add the final solution. Many thanks again for your assistance. – ridgedale Feb 15 '22 at 16:02
  • @Rick James: Thank you for your assistance. It was a memory limit issue as you indicated. Thanks again for your help. – ridgedale Feb 15 '22 at 16:03

1 Answers1

0

The issue, as pointed out by Robert and Rick James, was a memory limit issue. Robert's reference to mysqli_use_result and link pointed me in the right direction to resolve the issue.

Below is the code that allowed all the images to be downloaded from the database in one go:

<table class='table table-bordered table-striped'>
    <tr>
        <td>ImageId</td>
        <td>Location</td>
        <td>ImageData</td>
    </tr>
    <?php
        require_once 'db3.php';
        mysqli_real_query($con, 'SELECT * FROM images');
        $result = mysqli_use_result($con);
        header('Content-Type: text/html; charset=utf-8');
        while ($row = mysqli_fetch_row($result)) {
    ?>
    <tr>
        <td><?php echo $row[0]; ?></td> 
        <td><?php echo $row[5]; ?></td>
        <td>
            <?php
                if (!file_exists($file)) {
                    $basedir = '/tmp/';
                    $id = $row[0];          // 0 here and above refers to the location where the IMAGEID data is stored in the returned row array.
                    $loc = $row[5];         // 5 here and above refers to the location where the LOCATION data is stored in the returned row array.
                    $data = $row[9];        // 9 refers to the location where the IMAGEDATA data is stored in the returned row array.
                    $file = $basedir . $id . "_" . $loc;
                    $fp=fopen($file,"wb");
                        echo "Opening File: $file<br>";
                        fwrite($fp,$data);
                        echo "Writing File: $file<br>";
                        fclose($fp);
                        echo "Closing File: $file<br>";
                        sleep(1);
                } else {
                        echo "Skipping File: $file<br>";
                }
            ?>
        </td>
    </tr>
    <?php
        }
        mysqli_free_result($result);
    ?>
</table>

Many thanks again to everyone for their input.

ridgedale
  • 190
  • 1
  • 1
  • 14