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

基于sys.stdin的文件上传表单支持TXT/PDF,不支持JPG的原因及优化

针对sys.stdin文件上传的通用安全解决方案

核心问题分析

你的问题根源在于:

  • 文本模式读取sys.stdin会对二进制文件(如jpg)进行编码转换,导致内容截断。
  • 正则表达式无法可靠解析multipart表单的头部,尤其是包含特殊字符的边界或文件名。

解决方案(无需第三方库)

1. 切换到二进制流读取

直接使用sys.stdin.buffer读取原始二进制数据,避免文本编码干扰,这是解决二进制文件截断的关键。

2. 手动解析Multipart表单

抛弃正则,通过字符串分割解析multipart结构,更稳定可靠:

  • 从CONTENT_TYPE环境变量(CGI场景)或请求头中提取boundary分隔符。
  • 用boundary分割表单各个部分,拆分头部与文件内容。
  • 逐行解析头部,提取文件名和Content-Type。

3. 安全加固要点

  • 路径遍历防护:仅保留文件名部分,丢弃路径前缀。
  • 文件内容验证:通过文件头特征验证文件类型(如JPG的\xff\xd8\xff)。
  • 内存优化:大文件建议分块读取写入,避免一次性加载全部数据。

完整示例代码

import sys
import os

def handle_file_upload():
    # 获取CGI环境中的Content-Type
    content_type = os.environ.get('CONTENT_TYPE', '')
    if 'multipart/form-data' not in content_type:
        print("Status: 400 Bad Request", file=sys.stderr)
        return

    # 提取multipart边界(转成二进制)
    try:
        boundary = content_type.split('boundary=')[1].strip().encode('utf-8')
    except IndexError:
        print("Status: 400 Bad Request - Invalid Content-Type", file=sys.stderr)
        return

    boundary_start = b'--' + boundary
    boundary_end = b'--' + boundary + b'--'

    # 读取二进制数据流(大文件可改为分块读取)
    raw_data = sys.stdin.buffer.read()

    # 分割表单部分,过滤无效块
    parts = [p for p in raw_data.split(boundary_start) if p and not p.startswith(boundary_end[:-2])]

    for part in parts:
        # 拆分头部与文件内容
        try:
            header_block, file_content = part.split(b'\r\n\r\n', 1)
        except ValueError:
            continue
        # 移除内容末尾的换行符(boundary前的分隔符)
        file_content = file_content.rstrip(b'\r\n')

        # 解析头部信息
        headers = header_block.decode('utf-8', errors='ignore').split('\r\n')
        filename = None
        content_type = None

        for header in headers:
            header_lower = header.lower()
            if header_lower.startswith('content-disposition'):
                if 'filename=' in header:
                    # 提取并安全处理文件名
                    filename = header.split('filename=')[1].strip('"\'')
                    filename = os.path.basename(filename)
            elif header_lower.startswith('content-type'):
                content_type = header.split(':', 1)[1].strip()

        if not filename:
            continue

        # 文件类型验证示例(JPG)
        if content_type == 'image/jpeg' and not file_content.startswith(b'\xff\xd8\xff'):
            print(f"Warning: Invalid JPG file {filename}", file=sys.stderr)
            continue

        # 分块写入文件(避免大文件占用过多内存)
        with open(filename, 'wb') as f:
            f.write(file_content)

        print(f"Successfully saved {filename}")

if __name__ == '__main__':
    handle_file_upload()

说明

  • 该脚本适配CGI环境(虚拟主机常见场景),通过CONTENT_TYPE环境变量获取表单信息。
  • 二进制流读取确保所有文件类型(文本、PDF、图片等)都能完整保存。
  • 手动解析multipart结构避免了正则的局限性,同时加入了基础的安全校验。

内容的提问来源于stack exchange,提问作者Marek Möhling

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 20:22:56