[英]PHP header downloading unreadable zip file in Android
我的 php 脚本正在将 PDF 文件转换为包含 PDF 每一页图像的 zip 文件。
加载带有图像的 zip 后,我将 zip 传输到下面的标题。
ob_start();
header('Content-Transfer-Encoding: binary');
header('Content-disposition: attachment; filename="converted.ZIP"');
header('Content-type: application/octet-stream');
ob_end_clean();
readfile($tmp_file);
unlink($tmp_file);
exit();
下载在 Windows、Linux 和 Mac 中绝对可以正常工作。但是当我从 android 设备(普通浏览器或 Chrome)请求相同的内容时,正在下载一个不可读的 zip。 通过文件资源管理器打开它时,它显示“文件已损坏或格式不受支持”,从 Android 6 开始(未在此版本下测试)。
我稍后放置了 ob_start() 和 ob_end_clean() 函数,即使它不起作用。
我从stackoverflow检查了很多答案,但没有一个像
安卓浏览器需要做哪些修改?
<?php include 'headerHandlersCopy.php';
session_start();
ob_start();
//echo session_id()."<br>";
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../css/handleConvertPDFtoJPG.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">
<title>Compressing Image</title>
</head>
<body>
<!-- Progress bar -->
<div id="wrapper">
<h1 id="head1">Compressing Image</h1>
<h1 id="head2">Converting Image</h1>
<div id="myProgress">
<div id="myBar">10%</div>
</div>
<br>
</div>
<!-- end -->
<?php
//code to display errors.
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
if ($_SERVER['REQUEST_METHOD'] == 'POST'){
$session_id = session_id();
$uploadPath = "../upload/pdfUploads/";
$pdfFileNameWithOutExt = basename($_FILES["pdfDoc"]["name"],"pdf");
$dotRemovedFileNameTemp = str_replace(".", "", $pdfFileNameWithOutExt);
$dotRemovedFileName = $session_id.$dotRemovedFileNameTemp;
$imgExt = ".jpg";
$fileNameLocationFormat = $uploadPath.$dotRemovedFileName.$imgExt;
$fileNameLocation = $uploadPath.$dotRemovedFileName;
$status = null;
$imagick = new Imagick();
# to get number of pages in the pdf to run loop below.
# the below function generates unreadable images for each page.
$imagick->pingImage($_FILES['pdfDoc']['tmp_name']);
$noOfPagesInPDF = $imagick->getNumberImages();
$imagick->readImage($_FILES['pdfDoc']['tmp_name']);
$statusMsg = "test";
# writing pdf into images.
try {
$imagick->writeImages($fileNameLocationFormat, true);
$status = 1;
}
catch(Exception $e) {
echo 'Message: ' .$e->getMessage();
$status = 0;
}
$files = array();
# storing converted images into array.
# only including the readable images into the
$arrayEndIndex = ($noOfPagesInPDF * 2)-1;
for ($x = $arrayEndIndex; $x >= $noOfPagesInPDF; $x--) {
array_push($files,"{$fileNameLocation}-{$x}.jpg" );
}
# create new zip object
$zip = new ZipArchive();
# create a temp file & open it
$tmp_file = tempnam('.', '');
$zip->open($tmp_file, ZipArchive::CREATE);
# loop through each file
foreach ($files as $file) {
# download file
$download_file = file_get_contents($file);
#add it to the zip
$zip->addFromString(basename($file), $download_file);
}
# close zip
$zip->close();
# file cleaning code
# only those pdf files will be deleted which the current user uploaded.
# we match the sesion id of the user and delte the files which contains the same session id in the file name.
# file naming format is: session_id + destination + fileName + extension
$files = glob("../upload/pdfUploads/{$session_id}*"); // get all file names
foreach($files as $file){ // iterate files
if(is_file($file)) {
unlink($file); // delete file
}
}
// send the file to the browser as a download
ob_end_clean();
header('Content-Description: File Transfer');
header('Content-type: application/octet-stream');
header('Content-disposition: attachment; filename="geek.zip"');
//header("Content-Length: " . filesize($tmp_file));
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
flush();
readfile($tmp_file);
unlink($tmp_file);
//filesize($tmp_file) causing the "error opening the file" when opening the zip even in PC browsers.
}
?>
该问题似乎是由操作处理顺序和在对客户端的响应中包含 HTML 引起的。
为了避免这些问题,我建议为POST
请求处理程序使用单独的脚本文件,而不是在程序上将其包含在同一个视图脚本中。 否则,将POST
请求处理包装在脚本顶部的if
条件中,并以exit
结束以阻止响应进一步继续。
这部分导致了filesize()
调用的问题,因为包含 zip 文件和附加 HTML 的响应大小与仅 zip 文件的文件大小不同。
以下内容在适用于 Windows 和 Android 11 的 Google Chrome 中进行了测试。
# code to display errors
// USE ERROR REPORTING TO LOG FILES INSTEAD
# ini_set('display_errors', 1);
# ini_set('display_startup_errors', 1);
# error_reporting(E_ALL);
if (!session_id()) {
// always ensure session is not already started before starting
session_start();
}
if ('POST' === $_SERVER['REQUEST_METHOD'] &&
!empty($_FILES['pdfDoc']) && // ensure files were uploaded
UPLOAD_ERR_OK === $_FILES['pdfDoc']['error'] // ensure file uploaded without errors
) {
$session_id = session_id();
// removed redundant variable names
// use absolute path with __DIR__ instead of relative
$uploadSessionPath = $uploadPath = __DIR__ . '/../upload/pdfUploads/';
$uploadSessionPath .= $session_id; // append session path
// ensure upload directory exists
if (!is_dir($uploadPath) && !mkdir($uploadPath, 0777, true) && !is_dir($uploadPath)) {
throw new \RuntimeException(sprintf('Directory "%s" was not created', $uploadPath));
}
$fileNameLocation = $uploadSessionPath . str_replace('.', '', basename($_FILES['pdfDoc']['name'], 'pdf'));
# convert pdf pages into images and save as JPG in the upload session path.
try {
$pdfDocFile = $_FILES['pdfDoc']['tmp_name'];
$imagick = new Imagick();
# get number of pages in the pdf to loop over images below.
$imagick->pingImage($pdfDocFile);
$noOfPagesInPDF = $imagick->getNumberImages();
$imagick->setResolution(150, 150); // greatly improve image quality
$imagick->readImage($pdfDocFile);
$imagick->writeImages($fileNameLocation . '.jpg', true);
} catch (Exception $e) {
throw $e; //handle the exception properly - don't ignore it...
}
// ensure there are pages to zip
if ($noOfPagesInPDF > 0) {
// reduced to single iteration of files to reduce redundancy
# create a temp file & open it
$zipFile = tempnam(__DIR__, ''); // use absolute path instead of relative
# create new zip object
$zip = new ZipArchive();
$zip->open($zipFile, ZipArchive::CREATE);
# store converted images to zip file only including the readable images
$arrayEndIndex = ($noOfPagesInPDF * 2) - 1;
for ($x = $arrayEndIndex; $x >= $noOfPagesInPDF; $x--) {
$file = sprintf('%s-%d.jpg', $fileNameLocation, $x);
clearstatcache(false, $file); // ensure stat cache is clear
// ensure file exists and is readable
if (is_file($file) && is_readable($file)) {
// use ZipArchive::addFile instead of ZipArchive::addFromString(file_get_contents) to reduce overhead
$zip->addFile($file, basename($file));
}
}
$zip->close();
# file cleaning code
# only those pdf files will be deleted which the current user uploaded.
# we match the session id of the user and delete the files which contains the same session id in the file name.
# file naming format is: session_id + destination + fileName + extension
foreach (glob("$uploadSessionPath*") as $file) {
clearstatcache(false, $file); // ensure stat cache is clear
// ensure a file exists and can be deleted
if (is_file($file) && is_writable($file)) {
unlink($file);
}
}
# send the file to the browser as a download
if (is_file($zipFile) && is_readable($zipFile)) {
header('Content-Description: File Transfer');
header('Content-type: application/octet-stream');
header('Content-disposition: attachment; filename="geek.zip"');
header('Content-Length: ' . filesize($zipFile)); // Content-Length is a best-practice to ensure client receives the expected response, if it breaks the download - something went wrong
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Cache-Control: must-revalidate');
header('Pragma: public');
readfile($zipFile);
if (is_writable($zipFile)) {
unlink($zipFile);
}
exit; // stop processing
}
// no pages in PDF were found - do something else
}
// file was not sent as a response - do something else
}
// use absolute path __DIR__ and always require dependencies to ensure they are included
// do not know what this contains...
require_once __DIR__ . '/headerHandlersCopy.php';
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="../css/handleConvertPDFtoJPG.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css">
<title>Compressing Image</title>
</head>
<body>
<!-- Progress bar -->
<div id="wrapper">
<h1 id="head1">Compressing Image</h1>
<h1 id="head2">Converting Image</h1>
<div id="myProgress">
<div id="myBar">10%</div>
</div>
<br>
</div>
Android 文件管理器截图
最后作为一般提示,不要使用 PHP 结束标记?>
来结束 PHP 脚本上下文,除非将上下文更改为非 PHP 输出,如 HTML 或文本。 否则,在?>
之后存在的换行符/空格和其他不可见字符将包含在响应输出中,由于include
问题或导致响应损坏,通常会导致意外结果,例如带有文件下载数据和重定向。
仅 PHP 响应
<?php
// ...
echo 'PHP ends automatically without closing tag';
用 PHP 结束响应
<html>
</html>
<?php
echo 'PHP ends automatically without closing tag';
与 PHP 的混合响应
<html>
<?php
echo 'Mixed PHP continues as HTML';
?>
</html>
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.