基于thttpd CGI的Bash后端如何正确处理二进制文件上传?
问题:Bash CGI后端无法正确获取上传的二进制文件
场景
后端基于thttpd运行CGI服务,新增文件上传功能,表单代码如下:
<form action="index.cgi" method="post" enctype="multipart/form-data"> <input type="file" name="fileToUpload" id="fileToUpload"> <input type="submit" value="Upload File" name="submit"> </form>
初始编写的Bash CGI代码:
if [ "$REQUEST_METHOD" == "POST" ]; then while IFS= read -r line; do printf "%s" "$line" >> encoded_file done fi
异常现象
文本类文件上传正常,但PNG等二进制文件出现损坏:
- 保存的
encoded_file包含multipart边界和HTTP头信息 - 提取二进制内容后,与原始文件对比发现字节差异(如原始PNG的
0D 0A被转为0A,部分空字节丢失)
保存的encoded_file部分十六进制
00000000 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D 2D ------------------------ 00000018 2D 2D 2D 2D 2D 31 36 39 33 34 34 35 39 32 39 31 34 35 30 36 31 30 30 36 -----1693445929145061006 00000030 35 34 31 34 31 35 32 32 31 32 0D 43 6F 6E 74 65 6E 74 2D 44 69 73 70 6F 5414152212.Content-Dispo 00000048 73 69 74 69 6F 6E 3A 20 66 6F 72 6D 2D 64 61 74 61 3B 20 6E 61 6D 65 3D sition: form-data; name= 00000060 22 66 69 6C 65 54 6F 55 70 6C 6F 61 64 22 3B 20 66 69 6C 65 6E 61 6D 65 "fileToUpload"; filename 00000078 3D 22 49 4D 47 5F 37 30 39 37 2E 50 4E 47 22 0D 43 6F 6E 74 65 6E 74 2D ="IMG_7097.PNG".Content- 00000090 54 79 70 65 3A 20 69 6D 61 67 65 2F 70 6E 67 0D 0D 89 50 4E 47 0D 1A 0D Type: image/png...PNG... 000000A8 49 48 44 52 01 7B 03 93 08 06 5E B6 A3 CD 01 73 52 47 42 AE CE 1C E9 38 IHDR.{....^....sRGB....8
提取后文件与原始文件对比
提取后的文件:
00000000 89 50 4E 47 0A 1A 0A 49 48 44 52 01 7B 03 93 08 06 5E B6 A3 CD 01 73 52 .PNG...IHDR.{....^....sR
原始文件:
00000000 89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52 00 00 01 7B 00 00 03 93 .PNG........IHDR...{....
更新:使用cat > encoded_file保存完整请求能保留空字节,但手动提取或用uudeview时仍丢失空字节,问题未解决。
原因分析
read命令的局限性:read是为文本处理设计的,会自动将CRLF(0D 0A)转换为LF(0A),且遇到空字节(00)会终止读取,直接导致二进制文件损坏。- multipart/form-data解析问题:直接保存的请求包含边界、HTTP头信息,提取时需要准确定位文件内容的起始和结束位置,且不能破坏原始二进制字节。
- 工具不兼容:uudeview这类工具针对UUEncoded数据设计,不适合直接解析multipart/form-data的二进制内容,会引发字节转换或丢失。
解决方案
方案1:用cat保存完整请求,再用awk精准提取文件内容
首先修改CGI代码,用cat保存完整POST请求(二进制安全):
if [ "$REQUEST_METHOD" = "POST" ]; then cat > full_request.bin fi
然后用awk解析full_request.bin,提取文件内容:
# 从CONTENT_TYPE环境变量中提取boundary BOUNDARY=$(echo "$CONTENT_TYPE" | sed -n 's/.*boundary=\([^;]*\).*/\1/p') # 用awk跳过边界和头信息,提取核心文件内容 awk -v boundary="$BOUNDARY" ' BEGIN { boundary_line = "--" boundary in_file = 0 } $0 == boundary_line { in_file = 1 next } in_file && $0 == "" { in_file = 2 next } in_file == 2 { if ($0 == boundary_line "--") exit # 若需严格保留CRLF,替换为printf "%s\r\n", $0 print } ' full_request.bin > uploaded_file.png
方案2:使用Python辅助脚本解析(更可靠)
编写Python脚本处理multipart/form-data的解析,避免Bash处理二进制的局限性:
# save_upload.py import cgi import sys form = cgi.FieldStorage(fp=sys.stdin) file_item = form.get('fileToUpload') if file_item and hasattr(file_item, 'file'): with open('uploaded_file.png', 'wb') as f: f.write(file_item.file.read())
在Bash CGI中调用该脚本:
if [ "$REQUEST_METHOD" = "POST" ]; then python3 save_upload.py fi
方案3:纯Bash用dd提取二进制内容(无需额外工具)
利用dd处理二进制数据,准确定位文件内容的起始和结束位置:
if [ "$REQUEST_METHOD" = "POST" ]; then # 提取boundary BOUNDARY=$(echo "$CONTENT_TYPE" | sed -n 's/.*boundary=\([^;]*\).*/\1/p') # 找到文件内容的起始行(跳过boundary和头信息) START=$(grep -a -n "^--$BOUNDARY$" full_request.bin | head -n1 | cut -d: -f1) START=$(($START + 4)) # 调整数值适配实际头信息行数 # 找到结束boundary的位置 END=$(grep -a -n "^--$BOUNDARY--$" full_request.bin | head -n1 | cut -d: -f1) END=$(($END - 1)) # 提取内容 dd if=full_request.bin of=uploaded_file.png skip=$START count=$(($END - $START)) bs=1 status=none fi
注意:此方法依赖请求格式固定,若头信息行数变化需手动调整
START的计算值。
内容的提问来源于stack exchange,提问作者Surepic 342
相关产品推荐
相关产品推荐

