基于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
相关产品推荐
相关产品推荐

