首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP 遠程下載檔案到本地/伺服器完整實作(cURL / Stream / 表單上傳)

     編輯:PHP 綜合知識

前言

從遠端 URL 下載檔案儲存至本地伺服器是常見需求(圖片搬遷、資料備份、爬蟲存檔)。PHP 提供三種主要方式:cURL(功能最全)、Stream Context(無擴展依賴)、fopen/fread(最基礎)。本文完整比較三種寫法、錯誤處理、大檔案支援與安全防護。

一、通用下載函式(推薦:cURL 優先,Stream 兜底)

<?php
/**
 * 遠程下載檔案到本地
 * @param string $url 遠端檔案 URL
 * @param string $savePath 本地儲存路徑(含檔名),空則自動取 URL 基名
 * @param int $timeout 連線/總超時秒數
 * @param array $options 額外 cURL 選項
 * @return string|false 成功回傳儲存路徑,失敗回傳 false
 */
function downloadFile($url, $savePath = '', $timeout = 60, $options = []) {
    // 1. 決定儲存路徑
    if (!$savePath) {
        $basename = basename(parse_url($url, PHP_URL_PATH));
        $savePath = sys_get_temp_dir() . DIRECTORY_SEPARATOR . $basename;
    }
    
    // 2. 確保目錄存在
    $dir = dirname($savePath);
    if (!is_dir($dir) && !mkdir($dir, 0755, true)) {
        error_log("建立目錄失敗: $dir");
        return false;
    }
    
    // 3. URL 編碼空格等特殊字元
    $url = str_replace(' ', '%20', $url);
    
    // 4. 嘗試 cURL(功能最完整)
    if (function_exists('curl_init')) {
        $ch = curl_init();
        $defaultOpts = [
            CURLOPT_URL            => $url,
            CURLOPT_TIMEOUT        => $timeout,
            CURLOPT_CONNECTTIMEOUT => min(10, $timeout),
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_FOLLOWLOCATION => true,
            CURLOPT_MAXREDIRS      => 5,
            CURLOPT_USERAGENT      => 'PHP-Downloader/1.0',
            CURLOPT_SSL_VERIFYPEER => true,  // 生產環境建議 true
            CURLOPT_SSL_VERIFYHOST => 2,
        ];
        curl_setopt_array($ch, array_merge($defaultOpts, $options));
        
        $content = curl_exec($ch);
        $errno = curl_errno($ch);
        $error = curl_error($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        curl_close($ch);
        
        if ($errno === 0 && $content !== false && $httpCode === 200) {
            if (file_put_contents($savePath, $content) !== false) {
                return $savePath;
            }
            error_log("寫入檔案失敗: $savePath");
        } else {
            error_log("cURL 下載失敗 [$errno]: $error, HTTP: $httpCode, URL: $url");
        }
    }
    
    // 5. 兜底:Stream Context(不需 cURL 擴展)
    $ctx = stream_context_create([
        'http' => [
            'method'  => 'GET',
            'timeout' => $timeout,
            'header'  => 'User-Agent: PHP-Downloader/1.0',
            'follow_location' => 1,
            'max_redirects' => 5,
        ],
        'ssl' => [
            'verify_peer' => true,
            'verify_peer_name' => true,
        ]
    ]);
    
    if (@copy($url, $savePath, $ctx)) {
        return $savePath;
    }
    
    error_log("Stream 下載失敗: $url");
    return false;
}

// 使用範例
$path = downloadFile('https://example.com/image.jpg', '/var/www/uploads/img.jpg');
if ($path) echo "下載成功: $path";
else echo "下載失敗";
?>

二、大檔案分塊下載(避免記憶體耗盡)

<?php
function downloadLargeFile($url, $savePath, $chunkSize = 8192, $timeout = 300) {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_TIMEOUT => $timeout,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_USERAGENT => 'PHP-Downloader/1.0',
        CURLOPT_SSL_VERIFYPEER => true,
        CURLOPT_WRITEFUNCTION => function($ch, $data) use ($savePath, $chunkSize) {
            $fp = fopen($savePath, 'ab');  // 追加模式
            $written = fwrite($fp, $data);
            fclose($fp);
            return $written;  // 必須回傳寫入位元組數
        },
    ]);
    
    $result = curl_exec($ch);
    $errno = curl_errno($ch);
    curl_close($ch);
    
    return $errno === 0 && $result !== false;
}

// 使用
downloadLargeFile('https://speedtest.tele2.net/100MB.zip', '/data/100MB.zip');
?>

三、Web 表單介面下載(管理後台用)

<!DOCTYPE html>
<html><head><meta charset="utf-8"><title>遠程下載工具</title></head>
<body>
    <h2>遠程檔案下載到伺服器</h2>
    <form method="post">
        <input type="url" name="url" size="80" placeholder="https://example.com/file.zip" required>
        <input type="text" name="save_as" size="40" placeholder="儲存檔名 (選填)">
        <button type="submit">開始下載</button>
    </form>
    
    <?php
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        set_time_limit(3600);  // 1小時超時
        $url = $_POST['url'] ?? '';
        $saveAs = $_POST['save_as'] ?? '';
        $destDir = __DIR__ . '/downloads/';
        
        if (!is_dir($destDir)) mkdir($destDir, 0755, true);
        $savePath = $destDir . ($saveAs ?: basename(parse_url($url, PHP_URL_PATH)));
        
        echo "<p>開始下載: $url</p>";
        $start = microtime(true);
        $ok = downloadFile($url, $savePath, 3600);
        $elapsed = microtime(true) - $start;
        
        if ($ok) {
            $size = filesize($savePath);
            echo "<p style='color:green'>成功!儲存至: $savePath (<strong>$size</strong> bytes, 耗時: <strong>{$elapsed}s</strong>)</p>";
        } else {
            echo "<p style='color:red'>下載失敗,請檢查日誌</p>";
        }
    }
    ?>
</body></html>

四、進階功能擴充

需求實作方式
斷點續傳cURL CURLOPT_RANGE + 檢查本地檔案大小 + CURLOPT_RESUME_FROM
並發下載多檔curl_multi_* 非阻塞多路複用
驗證檔案完整性下載後 hash_file('sha256', $path) 比對預期雜湊值
限速下載CURLOPT_MAX_RECV_SPEED_LARGE (cURL 7.15.5+)
代理下載CURLOPT_PROXY, CURLOPT_PROXYUSERPWD
Basic AuthCURLOPT_USERPWD => 'user:pass'

五、安全防護清單

  • SSRF 防護: 禁止下載內網 IP(10.x, 172.16-31.x, 192.168.x, 127.x, 169.254.x)、localhost、元資料服務 (169.254.169.254)。
  • 協議限制: 僅允許 http/https,封鎖 file://, ftp://, gopher://, dict://。
  • 檔案型別驗證: 下載後用 finfo_file() 檢測 MIME,拒絕執行檔。
  • 儲存路徑安全: 禁止 ../ 目錄遍歷,檔名隨機化 bin2hex(random_bytes(16))。
  • 大小限制: CURLOPT_MAXFILESIZE 或 Stream max_file_size 參數。
  • HTTPS 驗證: 生產環境 CURLOPT_SSL_VERIFYPEER => true,必要時指定 CURLOPT_CAINFO。

六、SSRF 防護範例

<?php
function isPrivateIp($ip) {
    $privateRanges = [
        ['10.0.0.0', '10.255.255.255'],
        ['172.16.0.0', '172.31.255.255'],
        ['192.168.0.0', '192.168.255.255'],
        ['127.0.0.0', '127.255.255.255'],
        ['169.254.0.0', '169.254.255.255'],
    ];
    $long = ip2long($ip);
    foreach ($privateRanges as [$start, $end]) {
        if ($long >= ip2long($start) && $long <= ip2long($end)) return true;
    }
    return false;
}

function safeDownload($url, $savePath) {
    $host = parse_url($url, PHP_URL_HOST);
    if (!$host) return false;
    
    $ips = gethostbynamel($host);
    if (!$ips) return false;
    
    foreach ($ips as $ip) {
        if (isPrivateIp($ip)) {
            error_log("SSRF 阻擋: $url -> $ip");
            return false;
        }
    }
    return downloadFile($url, $savePath);
}
?>

小結

PHP 遠程下載檔案:首選 cURL(功能完整、效能佳),兜底 Stream Context(無擴展依賴)。核心函式 downloadFile() 處理路徑建立、超時、重導向、錯誤記錄。大檔案用 CURLOPT_WRITEFUNCTION 分塊寫入避免 OOM。生產環境必須實作 SSRF 防護、檔案型別驗證、大小限制、HTTPS 驗證,避免成為攻擊跳板。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved