Issues when downloading file from Slim Framework - php

I want to download file through Slim Framework (The reason why I'm using Slim Framework is because I want to write a simple REST API). I found this post:
Download file from Slim Framework 2.4 and this post: http://help.slimframework.com/discussions/questions/359-file-download. I followed the method. Here is my code:
$app->get('/download/:id', 'authenticate', function($id) use ($app)
{
$log = '../download/'.$id.'/myfile.zip';
$res = $app->response()->headers();
$res['Content-Description'] = 'File Transfer';
$res['Content-Type'] = 'application/octet-stream';
$res['Content-Disposition'] ='attachment; filename=' . basename($log);
$res['Content-Transfer-Encoding'] = 'binary';
$res['Expires'] = '0';
$res['Cache-Control'] = 'must-revalidate';
$res['Pragma'] = 'public';
$res['Content-Length'] = filesize($log);
readfile($log);
// NOTE:
// $response = array();
// $response["error"] = false;
// echoRespnse(200, $response);
});
I'm using the Google Chrome's Advanced Rest Client application to test. The problem is the browser hung after the file downloaded.If I comments out the NOTE part in my source code, the browser won't hang again. But I got a "Unexpected token P" error.
Can anybody help? Thanks.

Try the solution here: PHP readfile() adding extra bytes to downloaded file.
You lack the calls to ob_clean, flush and exit.
The problem might be becasue of extra characters output with the rest of the file contents.

For Slim 3, I had a bit of struggle.
I struggled with this in Slim 3.0.
I was trying something like the following and the pdf binary was getting displayed on the screen.
$header['Content-Description'] = 'File Transfer';
$header['Content-Disposition'] = 'attachment; filename="' .basename("$file") . '"';
// ...
$response = $response->withHeader($header);
In order to make it work, you need to set the key/value pairs using $response->withHeader(). And it works like a charm. For example:
$response = $response->withHeader('Content-Type', 'application/pdf');
$response = $response->withHeader('Content-Description', 'File Transfer');
$response = $response->withHeader('Content-Disposition', 'attachment; filename="' .basename("$file") . '"');
$response = $response->withHeader('Content-Transfer-Encoding', 'binary');
$response = $response->withHeader('Expires', '0');
$response = $response->withHeader('Cache-Control', 'must-revalidate');
$response = $response->withHeader('Pragma', 'public');
$response = $response->withHeader('Content-Length', filesize($file));

maybe it's a little late but you can use this package for slim http file response :
https://github.com/mhndev/slim-file-response

Is the solution above working for you? Here's my code, which just outputs obscure characters directly into the browser window:
$app->get('/file/:fileid', function($fileid) use ($app)
{
$zip = new ZipArchive();
$zipname = "zipfile3.zip"; // Zip name
$zip->open($zipname, ZipArchive::CREATE);
$files = array ( "sprecher_inserts_en_VB.doc" );
foreach ($files as $file) {
$path = "uploads/".$file;
if(file_exists($path)){
$zip->addFromString(basename($path), file_get_contents($path));
}
else{
echo"file does not exist";
}
}
$zip->close();
// after that, the zip file is on the server and valid when downloaded manually (e.g. by entering its URL directly)
$res = $app->response()->headers();
$res['Content-Description'] = 'File Transfer';
$res['Content-Type'] = 'application/octet-stream';
$res['Content-Disposition'] ='attachment; filename=' . basename($zipname);
$res['Content-Transfer-Encoding'] = 'binary';
$res['Expires'] = '0';
$res['Cache-Control'] = 'must-revalidate';
$res['Pragma'] = 'public';
$res['Content-Length'] = filesize($zipname);
ob_clean();
flush();
readfile($zipname);
exit();
});
The output in the browser window is the following, no dialog for downloading the file is loaded whatsoever:

Related

PHP - Returning response with downloadable url

I am trying for hours to figure out how to return my csv file trough URL in response.
The csv file is there, url is matched but I don't know how can I return downloadable url in my response.
$rootDir = $this->container->get('kernel')->getRootDir();
$dir = $rootDir . '/../web/uploads/news/';
/// - rest of the code that is not relevant and generates my csv $fileName
$fp = fopen($fileName, 'w');
In this part I am trying to set my response headers and downloadable url with no luck:
$baseurl = $request->getScheme() . '://' . $request->getHttpHost()
. $request->getBasePath() . $dir . $fileName;
// Set response
$response = new BinaryFileResponse($dir .DIRECTORY_SEPARATOR. $fileName);
// Set headers
$d = $response->headers->makeDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
$fileName
);
$response->headers->set('Content-Disposition', $d);
return $this->success();
$response = new BinaryFileResponse($pathToFile);
$response->setContentDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
'filenameThatWillBeDownloaded.yourextension'
);
return $response;
dont forget to import the classes
use Symfony\Component\HttpFoundation\BinaryFileResponse;
use Symfony\Component\HttpFoundation\ResponseHeaderBag;

PHP ZipArchive isn't creating a gs:// zip file in Google AppEngine, Warning: filesize(): stat failed for gs://

I'm able to directly write and read files in Google Storage, but when I try to use ZipArchive to create a file, it fails. Google says that the zip extension is enabled in the GAE.
$tmpdirectory .= 'gs://#default#/tmp/user-'.$uid;
$uniqueid = uniqid() . time();
$user_visible_filename = 'export.zip';
$output_path = sprintf("%s/export.%s.zip", $tmpdirectory, $uniqueid);
$zip = new ZipArchive;
$res = $zip->open($output_path, ZipArchive::CREATE);
if ($res === true) {
foreach ($data as $datatype => $records) {
$filename = sprintf("%s/%s.csv", $tmpdirectory, $datatype);
write_csv_to_filename($records, $filename);
$localname = basename($filename);
$fileresult = $zip->addFromString($localname, file_get_contents($filename));
print "adding $localname... num files in zip: ".($fileresult ? "true" : "false")." -> ".$zip->numFiles."<br/>\n";
}
}
$closeresult = $zip->close();
print "user_visible_filename: $user_visible_filename<br/>\n";
print "zip filename: $output_path<br/>\n";
print "file size: ".filesize($output_path)."<br/>\n";
header('Content-Type: application/zip');
header('Content-Length: '.filesize($output_path));
header('Content-Disposition: attachment; filename=' . $user_visible_filename);
The above code writes some csv files and I want to bundle them together in a zip file and have their browser download it. I know that the above headers() won't work because I'm printing stuff about before them; I'm going printing the stuff out to debug what's going wrong.
I am able to write each of the CSVs to gs:// and I'm able to access their correct filesize and read their contents back after writing them.
However, when I try to read the filesize() of the zip file (gs://#default#/tmp/user-152/export.5b4565bda18481531274685.zip) it craps out with a big warning message (cannot stat file) and stack trace as if the file doesn't exist.
$zip->close(); returns false which means there was a failure, but I have no idea why it failed.
I found an alternative solution that worked: TbsZip
require_once '../inc/tbszip.php';
$tmpfile = tempnam('/tmp', 'export');
$zip = new clsTbsZip();
$zip->CreateNew($tmpfile);
foreach ($data as $datatype => $records) {
$filename = sprintf("%s/%s.csv", $tmpdirectory, $datatype);
write_csv_to_filename($records, $filename);
$localname = basename($filename);
//$fileresult = $zip->addFile($filename, $localname);
$zip->FileAdd($localname, file_get_contents($filename), TBSZIP_STRING);
}
// Flush() will send all the required headers for downloading the file.
$zip->Flush(TBSZIP_DOWNLOAD, $user_visible_filename, 'application/zip');
$zip->Close();

Symfony 2: Download multiple images files in a Zip one

I'd like download multiple files in a zip but it doesn't work: the downloaded zip folder is still empty and is like opt/lampp/htdocs/MYPROJECT/... which one I'd like simple like /filename.zip
my controller, I just get all the file names from the DB and concatenate them with the directory name where they are physically saved:
$response = new Response();
// Get the entity manager
$em = $this->getDoctrine()->getManager();
//Collect all the result from the database and zip all the files related
$files = $em->getRepository('IballotCmsBundle:Result')->findAll();
$directory = __DIR__.'/../../../../web/uploads/pinkSheet';
//$directory = __DIR__.'/../temp/de.jpg';
$filename = 'all.zip';
$zip = new ZipArchive;
$zip->open($filename, ZipArchive::CREATE);
foreach ($files as $file) {
$zip->addFile($directory.'/'.$file->getImage());
}
$zip->close();
$response->headers->set('Cache-Control', 'private');
$response->headers->set('Content-type', mime_content_type($filename));
$response->headers->set('Content-Disposition', 'attachment; filename="' . basename($filename) . '"');
$response->headers->set('Content-length', filesize($filename));
// Send headers before outputting anything
$response->sendHeaders();
$response->setContent(readfile($filename));
return $response;
I think it has to do with this line of code:
$filename = 'all.zip';
$zip = new ZipArchive;
$zip->open($filename, ZipArchive::CREATE);
You are trying to create a zip file called all.zip but you haven't specified the actual path. $filename should probably be something like this:
$filename = '/absolute/path/to/all.zip'
As for the structure of the zip file you'll need to read the docs which has a great example:
$zip = new ZipArchive;
if ($zip->open('test.zip') === TRUE) {
$zip->addFile('/path/to/index.txt', 'newname.txt');
$zip->close();
echo 'ok';
} else {
echo 'failed';
}
This will add the /path/to/index.txt file to the test.zip with the name newname.txt instead of /path/to/index.txt. You can use the second argument to control the directory (have a look at the comments in the docs for more info)

symfony huge file download

I have controller in Symfony2, which provides file download.
/**
* #Route("/polynomy/download/{polynomialId}", name="download_polynomial")
*/
public function polynomialDownloadAction( $polynomialId ) {
$em = $this->get('doctrine')->getManager();
$polynomial = $em->getRepository('AppBundle:Polynomial')->findById($polynomialId);
$filename = $polynomial->getFileLocation();
$response = new Response();
$response->headers->set('Cache-Control', 'private');
$response->headers->set('Content-type', mime_content_type($filename));
$response->headers->set('Content-Disposition', 'attachment; filename="'.basename($filename).'";');
$response->headers->set('Content-length', filesize($filename));
$response->sendHeaders();
$response->setContent(readfile($filename));
return $this->redirect($this->generateUrl('users_polynomials'));
}
This work just fine for small files (<100MB). But if I want to download file greater than 100MB I get an error (ERR_INVALID_RESPONSE).
I found, that problem is probably in php function readfile(), which causes memory exhaustion.
Is there a way in Symfony 2.6.1 how to download huge files without memory exhaustion?
Sure. You want to use Symfony\Component\HttpFoundation\BinaryFileResponse.
$response = new BinaryFileResponse($filename);
That will also take care of headers for you automatically (though of course you can add more headers if needed).
Download large file using symphony. This code streams the file.
$response = new StreamedResponse();
$i = -999999;
$response->setCallback(function () {
while($i < 999999){
echo '{"G1ello":"hualala"}';
$i = $i + 1;
}
});
$filename = "Data.json";
$contentDisposition = $response->headers->makeDisposition(ResponseHeaderBag::DISPOSITION_ATTACHMENT, $filename);
$response->headers->set('Content-Type', 'application/json');
$response->headers->set('Content-Disposition', $contentDisposition);
return $response;
It created a large file which I was able to download.

Symfony2 create and download zip file

I have one application that upload some files and then I can compress as zip file and download.
The export action:
public function exportAction() {
$files = array();
$em = $this->getDoctrine()->getManager();
$doc = $em->getRepository('AdminDocumentBundle:Document')->findAll();
foreach ($_POST as $p) {
foreach ($doc as $d) {
if ($d->getId() == $p) {
array_push($files, "../web/".$d->getWebPath());
}
}
}
$zip = new \ZipArchive();
$zipName = 'Documents-'.time().".zip";
$zip->open($zipName, \ZipArchive::CREATE);
foreach ($files as $f) {
$zip->addFromString(basename($f), file_get_contents($f));
}
$response = new Response();
$response->setContent(readfile("../web/".$zipName));
$response->headers->set('Content-Type', 'application/zip');
$response->header('Content-disposition: attachment; filename=../web/"'.$zipName.'"');
$response->header('Content-Length: ' . filesize("../web/" . $zipName));
$response->readfile("../web/" . $zipName);
return $response;
}
everything is ok until the line header.
and everytime I'm going here I got the error: "Warning: readfile(../web/Documents-1385648213.zip): failed to open stream: No such file or directory"
What is wrong?
and why when I upload the files, this files have root permissions, and the same happens for the zip file that I create.
SYMFONY 3 - 4 example :
use Symfony\Component\HttpFoundation\Response;
/**
* Create and download some zip documents.
*
* #param array $documents
* #return Symfony\Component\HttpFoundation\Response
*/
public function zipDownloadDocumentsAction(array $documents)
{
$files = [];
$em = $this->getDoctrine()->getManager();
foreach ($documents as $document) {
array_push($files, '../web/' . $document->getWebPath());
}
// Create new Zip Archive.
$zip = new \ZipArchive();
// The name of the Zip documents.
$zipName = 'Documents.zip';
$zip->open($zipName, \ZipArchive::CREATE);
foreach ($files as $file) {
$zip->addFromString(basename($file), file_get_contents($file));
}
$zip->close();
$response = new Response(file_get_contents($zipName));
$response->headers->set('Content-Type', 'application/zip');
$response->headers->set('Content-Disposition', 'attachment;filename="' . $zipName . '"');
$response->headers->set('Content-length', filesize($zipName));
#unlink($zipName);
return $response;
}
solved:
$zip->close();
header('Content-Type', 'application/zip');
header('Content-disposition: attachment; filename="' . $zipName . '"');
header('Content-Length: ' . filesize($zipName));
readfile($zipName);
apparently closing the file is important ;)
Since Symfony 3.2+ can use file helper to let file download in browser:
public function someAction()
{
// create zip file
$zip = ...;
$this->file($zip);
}
ZipArchive creates the zip file into the root directory of your website if only a name is indicated into open function like $zip->open("document.zip", ZipArchive::CREATE). Specify the path into this function like $zip->open("my/path/document.zip", ZipArchive::CREATE). Do not forget delete this file with unlink() (see doc).
Here you have an example in Symfony 4 (may work on earlier version):
use Symfony\Component\HttpFoundation\Response;
use \ZipArchive;
public function exportAction()
{
// Do your stuff with $files
$zip = new ZipArchive();
$zip_name = "../web/zipFileName.zip"; // Users should not have access to the web folder (it is for temporary files)
// Create a zip file in tmp/zipFileName.zip (overwrite if exists)
if ($zip->open($zip_name, ZipArchive::CREATE | ZipArchive::OVERWRITE) === TRUE) {
// Add your files into zip
foreach ($files as $f) {
$zip->addFromString(basename($f), file_get_contents($f));
}
$zip->close();
$response = new Response(
file_get_contents($zip_name),
Response::HTTP_OK,
['Content-Type' => 'application/zip',
'Content-Disposition' => 'attachment; filename="' . basename($zip_name) . '"',
'Content-Length' => filesize($zip_name)]);
unlink($zip_name); // Delete file
return $response;
} else {
// Throw an exception or manage the error
}
}
You may need to add "ext-zip": "*" into your Composer file to use ZipArchive and extension=zip.so in your php.ini.
Answser inspired by Create a Response object with zip file in Symfony.
I think its better that you use
$zipFilesIds = $request->request->get('zipFiles')
foreach($zipFilesIds as $zipFilesId){
//your vérification here
}
with the post variable of your id of zip = 'zipFiles'. Its better of fetching all $_POST variables.
To complete vincent response, just add this right before returning response :
...
$response->headers->set('Content-length', filesize($zipName));
unlink($zipName);
return $response;
Work for me. Where $archive_file_name = 'your_path_to_file_from_root/filename.zip'.
$zip = new \ZipArchive();
if ($zip->open($archive_file_name, \ZIPARCHIVE::CREATE | \ZIPARCHIVE::OVERWRITE) === TRUE) {
foreach ($files_data as $file_data) {
$fileUri = \Drupal::service('file_system')->realpath($file_data['file_url']);
$filename = $file_data['folder'] . $file_data['filename'];
$zip->addFile($fileUri, $filename);
}
$zip->close();
}
$response = new Response();
$response->headers->set('Cache-Control', 'private');
$response->headers->set('Content-type', 'application/zip');
$response->headers->set('Content-Disposition', 'attachment; filename="' . basename($archive_file_name) . '"');
$response->headers->set('Content-length', filesize($archive_file_name));
// Send headers before outputting anything.
$response->sendHeaders();
$response->setContent(readfile($archive_file_name));
return $response;

Categories