簡體   English   中英

PHP標頭在Android中下載不可讀的zip文件

[英]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檢查了很多答案,但沒有一個像

  1. 強制下載不適用於 wap 網站上 Android 手機上的瀏覽器
  2. 無法在 android 瀏覽器中使用 php 下載文件

安卓瀏覽器需要做哪些修改?

<?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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM