#!/usr/bin/env python
# coding: utf-8
"""
表单数据解析模块
提供表单数据解析功能,支持 URL 编码表单和 multipart 表单
"""
import re
from tempfile import TemporaryFile
from urllib.parse import unquote_plus
from ..cache import FormCache
from email.message import Message
# 创建表单数据缓存实例
_form_cache = FormCache(max_size=1000, default_ttl=300)
# 正则表达式
form_dict_match = re.compile(r"(.+)\[([^\[\]]+)\]").match
[文档]
def parse_multipart_wsgi(wsgi_input, boundary, content_length, max_upload_size=52428800):
"""
解析 WSGI 环境下的 multipart 表单数据
Args:
wsgi_input: WSGI 输入流
boundary: multipart boundary
content_length: 内容长度
max_upload_size: 最大上传大小
Returns:
(posts, files): 表单字段字典和上传文件字典
"""
if content_length > max_upload_size:
from ..exceptions import HttpError
raise HttpError(413, f"Request body too large. Maximum size is {max_upload_size} bytes")
boundary = boundary.encode("utf-8")
begin_boundary = b"--" + boundary
end_boundary = b"--" + boundary + b"--"
posts = {}
files = {}
data = wsgi_input.read(content_length)
parts = data.split(begin_boundary)
for part in parts[1:]:
if part.strip() == end_boundary:
break
header_end = part.find(b"\r\n\r\n")
if header_end == -1:
continue
headers_part = part[:header_end]
content = part[header_end + 4:]
headers = {}
for line in headers_part.split(b"\r\n"):
if b":" in line:
k, v = line.split(b":", 1)
k = k.strip().upper()
v = v.strip()
k = k.decode("utf-8")
v = v.decode("utf-8")
headers[k] = v
disposition = headers.get("CONTENT-DISPOSITION", "")
disposition, params = parse_header(disposition)
name = params.get("name", "")
filename = params.get("filename")
if filename:
fp = TemporaryFile(mode="w+b")
fp.write(content)
fp.seek(0)
files[name] = fp
else:
content = content.decode("utf-8")
posts[name] = content.strip()
return posts, files
[文档]
def parse_multipart_asgi(body, boundary, max_upload_size=52428800):
"""
解析 ASGI 环境下的 multipart 表单数据
Args:
body: 请求体字节数据
boundary: multipart boundary
max_upload_size: 最大上传大小
Returns:
(posts, files): 表单字段字典和上传文件字典
"""
if len(body) > max_upload_size:
from ..exceptions import HttpError
raise HttpError(413, f"Request body too large. Maximum size is {max_upload_size} bytes")
boundary = boundary.encode("utf-8")
begin_boundary = b"--" + boundary
end_boundary = b"--" + boundary + b"--"
posts = {}
files = {}
parts = body.split(begin_boundary)
for part in parts[1:]:
if part.strip() == end_boundary:
break
header_end = part.find(b"\r\n\r\n")
if header_end == -1:
continue
headers_part = part[:header_end]
content = part[header_end + 4:]
headers = {}
for line in headers_part.split(b"\r\n"):
if b":" in line:
k, v = line.split(b":", 1)
k = k.strip().upper()
v = v.strip()
k = k.decode("utf-8")
v = v.decode("utf-8")
headers[k] = v
disposition = headers.get("CONTENT-DISPOSITION", "")
disposition, params = parse_header(disposition)
name = params.get("name", "")
filename = params.get("filename")
if filename:
fp = TemporaryFile(mode="w+b")
fp.write(content)
fp.seek(0)
files[name] = fp
else:
content = content.decode("utf-8")
posts[name] = content.strip()
return posts, files
[文档]
def parse_multipart_stream(rw, boundary, max_upload_size=52428800, DEFAULT_BUFFER_SIZE=8192):
"""
解析流式环境下的 multipart 表单数据
Args:
rw: 读写流对象
boundary: multipart boundary
max_upload_size: 最大上传大小
DEFAULT_BUFFER_SIZE: 默认缓冲区大小
Returns:
(posts, files): 表单字段字典和上传文件字典
"""
from io import DEFAULT_BUFFER_SIZE as _DEFAULT_BUFFER_SIZE
from io import BytesIO
boundary = boundary.encode("utf-8")
begin_boundary = b"--%s" % boundary
end_boundary = b"--%s--" % boundary
posts = {}
files = {}
s = rw.readline(DEFAULT_BUFFER_SIZE or _DEFAULT_BUFFER_SIZE).strip()
while True:
if s.strip() != begin_boundary:
assert s.strip() == end_boundary
break
headers = {}
s = rw.readline(DEFAULT_BUFFER_SIZE or _DEFAULT_BUFFER_SIZE).strip()
while s:
s = s.decode("utf-8")
k, v = s.split(":", 1)
headers[k.strip().upper()] = v.strip()
s = rw.readline(DEFAULT_BUFFER_SIZE or _DEFAULT_BUFFER_SIZE).strip()
disposition = headers["CONTENT-DISPOSITION"]
disposition, params = parse_header(disposition)
name = params["name"]
filename = params.get("filename")
if filename:
fp = TemporaryFile(mode="w+b")
s = rw.readline(DEFAULT_BUFFER_SIZE or _DEFAULT_BUFFER_SIZE)
while s.strip() != begin_boundary and s.strip() != end_boundary:
fp.write(s)
s = rw.readline(DEFAULT_BUFFER_SIZE or _DEFAULT_BUFFER_SIZE)
fp.seek(0)
files[name] = fp
else:
fp = BytesIO()
s = rw.readline(DEFAULT_BUFFER_SIZE or _DEFAULT_BUFFER_SIZE)
while s.strip() != begin_boundary and s.strip() != end_boundary:
fp.write(s)
s = rw.readline(DEFAULT_BUFFER_SIZE or _DEFAULT_BUFFER_SIZE)
fp.seek(0)
posts[name] = fp.getvalue().decode("utf-8").strip()
return posts, files
__all__ = [
'parse_header',
'parse_form',
'parse_multipart_wsgi',
'parse_multipart_asgi',
'parse_multipart_stream',
'_form_cache',
'form_dict_match',
]