You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用cURL在上传文件至服务器前检查文件扩展名

问题描述

我使用cURL从URL获取文件后上传至服务器,现在需要在上传前检查文件扩展名。但部分URL不显示文件扩展名(例如示例链接:https://images.unsplash.com/photo-1533450718592-29d45635f0a9?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80),希望无需完整上传即可获取文件实际扩展名,与系统允许的列表比对,同时校验文件的实际数据格式。

系统允许的扩展名列表:

  • jpeg
  • docx
  • xlsx
  • xml
  • txt
  • pptx
  • pdf
  • md
  • ods
  • odp
  • odt
  • odg
  • ots
  • ott
  • csv
  • tsv
  • rtf
  • resx
  • html
  • srt
  • vtt
  • stl
  • sbv
  • sub
  • ass
  • dfxp
  • ttml

现有代码如下:

$url = 'https://images.unsplash.com/photo-1533450718592-29d45635f0a9?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80';

function collect_file($url){
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_VERBOSE, 1);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_AUTOREFERER, false);
    curl_setopt($ch, CURLOPT_REFERER, "https://file-examples.com");
    curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    $result = curl_exec($ch);

    curl_close($ch);
    return($result);
}

function write_to_file($text,$new_filename){
    $fp = fopen($new_filename, 'w');
    fwrite($fp, $text);
    fclose($fp);
}


// start loop here

$new_file_name = uniqid() . '.jpeg';

$temp_file_contents = collect_file($url);
write_to_file($temp_file_contents,$new_file_name); 
解决方案

要实现需求,需分三步执行:

  1. 通过HEAD请求获取Content-Type:无需下载完整文件,仅请求响应头即可拿到文件的Content-Type,初步判断类型;
  2. 通过文件签名校验实际格式:若Content-Type不够准确,下载文件前若干字节,通过文件固定开头的签名(如JPEG的FF D8 FF)判断真实格式;
  3. 映射格式到扩展名并校验:将判断出的格式映射到允许的扩展名列表,校验通过后再完整下载文件。
完整改进代码
// 系统允许的扩展名列表
$allowed_extensions = [
    'jpeg', 'docx', 'xlsx', 'xml', 'txt', 'pptx', 'pdf', 'md',
    'ods', 'odp', 'odt', 'odg', 'ots', 'ott', 'csv', 'tsv',
    'rtf', 'resx', 'html', 'srt', 'vtt', 'stl', 'sbv', 'sub',
    'ass', 'dfxp', 'ttml'
];

// 文件类型映射:Content-Type => 扩展名 + 文件签名
$file_type_mapping = [
    'image/jpeg' => [
        'ext' => 'jpeg',
        'signature' => "\xFF\xD8\xFF"
    ],
    'application/pdf' => [
        'ext' => 'pdf',
        'signature' => "%PDF-"
    ],
    'application/vnd.openxmlformats-officedocument.wordprocessingml.document' => [
        'ext' => 'docx',
        'signature' => "PK\x03\x04" // Office OpenXML格式通用签名
    ],
    'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' => [
        'ext' => 'xlsx',
        'signature' => "PK\x03\x04"
    ],
    'application/vnd.openxmlformats-officedocument.presentationml.presentation' => [
        'ext' => 'pptx',
        'signature' => "PK\x03\x04"
    ],
    'text/plain' => [
        'ext' => 'txt',
        'signature' => null // 文本文件无固定签名,需结合业务场景补充校验
    ],
    'text/xml' => [
        'ext' => 'xml',
        'signature' => "<?xml"
    ],
    'text/markdown' => [
        'ext' => 'md',
        'signature' => null
    ],
    'text/csv' => [
        'ext' => 'csv',
        'signature' => null
    ],
    'application/rtf' => [
        'ext' => 'rtf',
        'signature' => "{\\rtf"
    ],
    'text/html' => [
        'ext' => 'html',
        'signature' => "<!DOCTYPE html"
    ],
    // 剩余允许的扩展名对应的映射需自行补充,例如ods/odt等的Content-Type和签名
];

// 获取URL对应的Content-Type
function get_content_type($url) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_NOBODY, true); // 仅请求响应头
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_REFERER, "https://file-examples.com");
    curl_exec($ch);
    
    $content_type = curl_getinfo($ch, CURLINFO_CONTENT_TYPE);
    curl_close($ch);
    
    // 处理带编码后缀的Content-Type(如image/jpeg; charset=utf-8)
    if ($content_type) {
        $content_type = explode(';', $content_type)[0];
    }
    return $content_type;
}

// 获取文件前N个字节用于校验签名
function get_file_header($url, $length = 10) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_RANGE, "0-".($length-1)); // 仅请求前length字节
    curl_setopt($ch, CURLOPT_REFERER, "https://file-examples.com");
    $header_data = curl_exec($ch);
    curl_close($ch);
    return $header_data;
}

// 校验文件类型是否合法
function validate_file_type($url, $allowed_extensions, $file_type_mapping) {
    $content_type = get_content_type($url);
    $valid_ext = false;

    // 先通过Content-Type判断
    if ($content_type && isset($file_type_mapping[$content_type])) {
        $ext = $file_type_mapping[$content_type]['ext'];
        $valid_ext = in_array($ext, $allowed_extensions) ? $ext : false;
    }

    // Content-Type无法判断时,通过文件签名校验
    if (!$valid_ext) {
        $file_header = get_file_header($url);
        foreach ($file_type_mapping as $type => $info) {
            if ($info['signature'] && str_starts_with($file_header, $info['signature'])) {
                $ext = $info['ext'];
                if (in_array($ext, $allowed_extensions)) {
                    $valid_ext = $ext;
                    break;
                }
            }
        }
    }

    return $valid_ext;
}

// 原文件下载函数保留,仅在校验通过后调用
function collect_file($url){
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_VERBOSE, 1);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_AUTOREFERER, false);
    curl_setopt($ch, CURLOPT_REFERER, "https://file-examples.com");
    curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    $result = curl_exec($ch);

    curl_close($ch);
    return($result);
}

function write_to_file($text,$new_filename){
    $fp = fopen($new_filename, 'w');
    fwrite($fp, $text);
    fclose($fp);
}

// 主执行流程
$url = 'https://images.unsplash.com/photo-1533450718592-29d45635f0a9?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=870&q=80';

// 先校验文件类型
$valid_ext = validate_file_type($url, $allowed_extensions, $file_type_mapping);

if ($valid_ext) {
    // 校验通过,生成正确文件名并下载保存
    $new_file_name = uniqid() . '.' . $valid_ext;
    $temp_file_contents = collect_file($url);
    write_to_file($temp_file_contents, $new_file_name);
    echo "文件已成功保存为: $new_file_name";
} else {
    echo "文件类型不允许";
}

注意事项

  • 上述file_type_mapping仅补充了部分常见类型,需根据允许的扩展名列表补全剩余类型的Content-Type和签名(例如ODS对应application/vnd.oasis.opendocument.spreadsheet,签名同样为PK\x03\x04);
  • 对于无固定签名的文本类文件(如txt、md、csv等),可结合业务场景增加额外校验逻辑(如检查内容格式);
  • CURLOPT_RANGE依赖服务器支持Range请求,若服务器不支持会返回完整文件,可根据需求增加错误处理逻辑。

内容的提问来源于stack exchange,提问作者savegramer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 23:55:30