I am trying to download a file saved in the server and allow the user to extract it from the front end based on the date selected. The application is built using the codeigniter framework.
I am currently using the download helper from codeigniter and it works perfectly when downloading csv files, but the zip file seems to be corrupted always. One thing I notice is that original zip is 101kb in size but the downloaded zip file is 182kb. I have checked the mime type, make sure the output buffer is cleared before sending data but I can’t figure out the issue. I would greatly appreciate to hear your opinion.
API CALL:
private function download_get_archive( $year, $month, $day, $type)
{
$date = DateTime::createFromFormat(
'Y-m-d H:i:s', "{$year}-{$month}-{$day} 00:00:00", new DateTimeZone( 'America/New_York' )
);
$archive_file = implode( DIRECTORY_SEPARATOR, [
rtrim( $this->config->item( 'archive_dir' ), '\/' ),
$date->format( 'Y' ),
$date->format( 'm' ),
$date->format( 'd' ),
'profiles' . ($type === 'legacy' ? '.csv' : '.zip')
] );
if( !file_exists( $archive_file ) ) {
log_message( 'error',
__METHOD__ . ": Could not find '{$archive_file}'"
);
$this->output->set_status_header( 404 );
return;
}
$this->load->helper( 'download' );
header( 'Access-Control-Allow-Origin: *' );
header( 'Access-Control-Allow-Headers: Content-Type' );
force_download( $archive_file, null, true );
}
download_helper.php:
function force_download($filename = '', $data = '', $set_mime = FALSE)
{
if ($filename === '' OR $data === '')
{
return;
}
elseif ($data === NULL)
{
if ( ! @is_file($filename) OR ($filesize = @filesize($filename)) === FALSE)
{
return;
}
$filepath = $filename;
$filename = explode('/', str_replace(DIRECTORY_SEPARATOR, '/', $filename));
$filename = end($filename);
}
else
{
$filesize = strlen($data);
}
// Set the default MIME type to send
$mime = 'application/octet-stream';
$x = explode('.', $filename);
$extension = end($x);
if ($set_mime === TRUE)
{
if (count($x) === 1 OR $extension === '')
{
/* If we're going to detect the MIME type,
* we'll need a file extension.
*/
return;
}
// Load the mime types
$mimes =& get_mimes();
// Only change the default MIME if we can find one
if (isset($mimes[$extension])) {
if ($extension === 'zip') {
// Use the second element of the array, which is 'application/zip'
$mime = is_array($mimes[$extension]) ? $mimes[$extension][1] : $mimes[$extension];
} else {
$mime = is_array($mimes[$extension]) ? $mimes[$extension][0] : $mimes[$extension];
}
}
}
/* It was reported that browsers on Android 2.1 (and possibly older as well)
* need to have the filename extension upper-cased in order to be able to
* download it.
*
* Reference: http://digiblog.de/2011/04/19/android-and-the-download-file-headers/
*/
if (count($x) !== 1 && isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/Androids(1|2.[01])/', $_SERVER['HTTP_USER_AGENT']))
{
$x[count($x) - 1] = strtoupper($extension);
$filename = implode('.', $x);
}
if ($data === NULL && ($fp = @fopen($filepath, 'rb')) === FALSE)
{
return;
}
// Clean output buffer
if (ob_get_level() !== 0 && @ob_end_clean() === FALSE)
{
@ob_clean();
}
// Generate the server headers
header('Content-Type: '. $mime);
header('Content-Disposition: attachment; filename="'.$filename.'"');
header('Expires: 0');
header('Content-Transfer-Encoding: binary');
header('Content-Length: '.$filesize);
header('Cache-Control: private, no-transform, no-store, must-revalidate');
// If we have raw data - just dump it
if ($data !== NULL)
{
exit($data);
}
// Flush 1MB chunks of data
while ( ! feof($fp) && ($data = fread($fp, 1048576)) !== FALSE)
{
echo $data;
}
fclose($fp);
exit;
}
}
Note: The reason I am using $mimes$extension fro zip files is because $mimes[$extension][0] translates to x-zip and I wanted to keep the mime as accurate as possible. Additionally, here is the response string I receive as a result of the API call:
Frontend Export.js:
const handleMenusExportDaypart = type => {
message.info('Export started')
let exportDate = date.split('-')
exportDate.push('daypart')
menusApi.exportMenus(exportDate).then(
response => {
fileDownload(response, `${date}.zip`)
},
error => {
notification.error({ message: 'Error', description: getErrorMessage(error) })
}
)
}
The response I get is a binary string I believe and part of me believes that it is the way I am handling data in the frontend that is causing problems since the response seems accurate. In the frontend I am using the fileDownload function from ‘js-file-download’ library:
declare module 'js-file-download' {
export default function fileDownload(
data: string | ArrayBuffer | ArrayBufferView | Blob,
filename: string,
mime?: string,
bom?: string
): void;
}
The original zip file I am trying to download unzips perfectly, contains the contents correctly, but the zip file that is being downloaded is corrupted. I have tried switching browsers to see wether if it was a browser related issue but that didn’t seem to help either. Please let me know your opinion, I really appreciate your time.