PHP upload to MySQL blobs corrupting files, with 100% correlation between corrupted blobs and table overhead

StackOverflow https://stackoverflow.com/questions/21964953

  •  15-10-2022
  •  | 
  •  

Question

I have a PHP application that allows users to upload files (PDF or JPG) up to 2mb, and the files are stored in MySQL rows with blob fields of up to 64kb. Files larger than 64kb are distributed across as many rows as needed, with all linked to another table with the file information. Regardless of the file, this works well - exactly once. The uploaded file can be retrieved using the PHP that queries and reconstructs it to stream to the browser, or write to a temp directory for download. No problem.

But after the first upload, two things change:

1) Additional uploads appear successful on upload, and the blobs are of the expected size, but when retrieved they are corrupted. In PDFs this means the dreaded "Files does not begin with '%PDF-'" or a more general message, depending on the browser and plugin used. For JPGs, the file will appear missing in the browser (streaming) or will only partially load, missing the bottom half.

2) The table containing the blobs shows overhead, anywhere from 500kb to 2mb.

After I use phpMyAdmin's Optimize Table, or OPTIMIZE TABLE f_file_data, the problem goes away, and the next uploaded file is fine and viewable. But after that one good upload, the problem is back and subsequent uploads are corrupted. I have repeated this little experiment many times and it's very consistent.

Table type is MyISAM, and table collation is ut8_unicode_ci. Blob field is "blob" - the 2^16+2 which is just enough for the 64kb chunks. Still a single upload creates new overhead. Has anyone seen something like this before, or have any theory?

Code for writing posted files to mysql (may be more than one):

public function writeFile($pFieldsId, $pCasesId, $pFile) {


    $bResult = false ; 

    $form_file = $pFile;

$errfile = $form_file["error"] ;

$errfile_verb = "" ;

switch($errfile){
case 0:
    $errfile_verb = "" ;
    break ;
case 1:
    $errfile_verb = "The uploaded file exceeds the upload_max_filesize directive in php.ini" ;
    break ;
case 2:
    $errfile_verb = "The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form." ;
    break ;
case 3:
    $errfile_verb = "Your file was only partially uploaded." ;
    break ;
case 4:
    $errfile_verb = "No file was uploaded." ;
    break ;
case 6:
    $errfile_verb = "Your file did not upload because the server is missing a temporary folder." ;
    break ;
case 7:
    $errfile_verb = "Your file failed to write file to disk." ;
    break ;
case 8:
    $errfile_verb = "Your file upload was stopped by extension." ;
    break ;
}

if($errfile > 0){

    $this->myError->exe_script = $_SERVER["SCRIPT_NAME"] ; 
    $this->myError->scr_file = __FILE__ ; 
    $this->myError->scr_line = __LINE__ ; 
    $this->myError->err_msg = $errfile_verb ;
    $this->myError->scr_class = __CLASS__ ; 
    $this->myError->scr_method = __METHOD__ ; 
    $this->myError->scr_function = __FUNCTION__ ; 
    $this->myError->flat_case_id = $this->CasesId ;
    $this->myError->sql_stmt = "" ; 
    $err_id = $this->myError->report();

    $this->UserErrMsg = $form_file["name"] . " was too large for upload.";

    $bResult = false ; 

    return false;

} else {


    try {

        $fileTempname = $form_file["tmp_name"];
        $fileName = addslashes($form_file["name"]);
        $fileType = $form_file["type"];
        $fileSize = $form_file["size"];

        $fileHandle = fopen($fileTempname, "r");

        $fileContent = fread($fileHandle, $fileSize);

        $fileContent = addslashes($fileContent);

    } catch (Exception $e) {

        echo 'Caught exception: ',  $e->getMessage(), "\n";
        $this->myError->exe_script = $_SERVER["SCRIPT_NAME"] ; 
        $this->myError->scr_file = __FILE__ ; 
        $this->myError->scr_line = __LINE__ ; 
        $this->myError->err_msg = "Failed file read: " . $e->getMessage();
        $this->myError->scr_class = __CLASS__ ; 
        $this->myError->scr_method = __METHOD__ ; 
        $this->myError->scr_function = __FUNCTION__ ; 
        $this->myError->flat_case_id = $this->CasesId ;
        $this->myError->sql_stmt = "" ; 
        $err_id = $this->myError->report();

    }



    if (is_uploaded_file($fileTempname)) {


        $sql = "INSERT INTO f_file SET " ;
        $sql .= "fields_id = '" . $pFieldsId . "', " ;
        $sql .= "cases_id = '" . $pCasesId . "', " ;
        $sql .= "ts = CURRENT_TIMESTAMP, " ;
        $sql .= "data_value = '" . $fileName . "', " ;
        $sql .= "file_type = '" . $fileType . "', " ;
        $sql .= "file_size = '" . $fileSize . "' " ;


        $l_success = $this->myConnect->writeData($sql); //PDO prepared statement executed


        if($l_success){

            $idx_id = $this->myConnect->LastInsertId ;

            $fp = fopen($fileTempname, "rb");

            while (!feof($fp)) {

                // Make the data mysql insert safe
                $binarydata = addslashes(fread($fp, 65535));

                $sql = "INSERT INTO f_file_data " ; 
                $sql .= "SET f_file_id = '" . $idx_id . "', " ;
                $sql .= "file_data = '" . $binarydata . "' " ;

                $f_success = $this->myConnect->writeData($sql);  //PDO prepared statement executed

                if($f_success){

                    $bResult = true ; 
                } else {
                    echo __FILE__ . ", see line " . __LINE__ ; exit();
                    $bResult = false ; 
                }


            }

            fclose($fp);

            $sql = "UPDATE " . $this->flatTable . " SET " ; 
            $sql .= "f_" . $pFieldsId . " = '" . $fileName . "' ";
            $sql .= "WHERE id = '" . $pCasesId . "' ";

            $this->myConnect->writeData($sql);  //PDO prepared statement executed


                /*
                Was there already a file for this field in f_file_data and f_file?  
                If yes, delete it.
                */

                $sql = "SELECT id as f_file_id " ; 
                $sql .= "FROM f_file " ;
                $sql .= "WHERE fields_id = '" . $pFieldsId . "' " ; 
                $sql .= "AND cases_id = '" . $pCasesId . "' " ; 
                $sql .= "AND id <> '" . $idx_id . "' " ; 

                $row = $this->myConnect->getOneRow($sql);  //PDO retrieve single row

                if($row) {

                $f_file_id = $row["f_file_id"];

                $sqlDelete = "DELETE FROM f_file " ; 
                $sqlDelete .= "WHERE id = '" . $f_file_id . "' ";

                $this->myConnect->writeData($sqlDelete);

                $sqlDelete = "DELETE FROM f_file_data " ; 
                $sqlDelete .= "WHERE f_file_id = '" . $f_file_id . "' ";

                $this->myConnect->writeData($sqlDelete); //PDO prepared statement executed

            }



        } else {

            $bResult = false ; 

            $this->myError->exe_script = $_SERVER["SCRIPT_NAME"] ; 
            $this->myError->scr_file = __FILE__ ; 
            $this->myError->scr_line = __LINE__ ; 
            $this->myError->err_msg = "Failed file insert. is_uploaded_file failed." ;
            $this->myError->scr_class = __CLASS__ ; 
            $this->myError->scr_method = __METHOD__ ; 
            $this->myError->scr_function = __FUNCTION__ ; 
            $this->myError->flat_case_id = $this->CasesId ;
            $this->myError->sql_stmt = $sql ; 
            $err_id = $this->myError->report();

        }

    } else {

        $bResult = false ; 

        $this->myError->exe_script = $_SERVER["SCRIPT_NAME"] ; 
        $this->myError->scr_file = __FILE__ ; 
        $this->myError->scr_line = __LINE__ ; 
        $this->myError->err_msg = "File was not uploaded." ;
        $this->myError->scr_class = __CLASS__ ; 
        $this->myError->scr_method = __METHOD__ ; 
        $this->myError->scr_function = __FUNCTION__ ; 
        $this->myError->flat_case_id = $this->CasesId ;
        $this->myError->sql_stmt = "" ; 
        $err_id = $this->myError->report();


    }


}

return $bResult ; 

}

And here are the table definitions, max blob size is 65kb, so there are multiple rows per file in table f_file_data:

CREATE TABLE IF NOT EXISTS `f_file` (
  `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
  `fields_id` mediumint(8) unsigned NOT NULL,
  `cases_id` mediumint(8) unsigned NOT NULL,
  `ts` datetime NOT NULL,
  `data_value` varchar(100) COLLATE utf8_unicode_ci NOT NULL COMMENT 'filename',
  `file_type` varchar(100) COLLATE utf8_unicode_ci NOT NULL,
  `file_size` bigint(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE IF NOT EXISTS `f_file_data` (
  `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
  `f_file_id` mediumint(8) unsigned NOT NULL,
  `file_data` blob NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Thanks.

No correct solution

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top