phoenix不直接解析xml上传,仅封装为%plug.upload{};需手动流式解析并禁用xxe,且必须启用:multipart解析器。

Phoenix 本身不直接解析 XML 文件上传——它只负责接收原始二进制流并封装为 %Plug.Upload{} 结构体;XML 解析必须由你手动完成,且需在安全校验后进行。
如何识别并提取上传的 XML 文件
前端提交 multipart/form-data 表单时,XML 文件会作为 %Plug.Upload{} 进入控制器。关键点是:它不是字符串,而是指向临时磁盘文件的句柄,路径在 upload.path,原始文件名在 upload.filename。
- 务必检查
upload.content_type是否为"text/xml"或"application/xml",不能只靠扩展名 - 不要直接用
File.read!(upload.path)—— 大文件会爆内存;应流式读取或限制大小 - 若需验证 XML 格式,建议用
:xmerl(Erlang 内置)或第三方库如xml_builder+saxy(更轻量、防 XXE)
为什么不能跳过 Plug.Parsers 配置就收 XML?
Phoenix 默认的 Plug.Parsers 不会自动解析 XML 请求体(只处理 urlencoded 和 json),但文件上传走的是 :multipart 分支——它不依赖 XML 解析器,只依赖底层 Plug.Upload 机制。所以你不需要配置 XML parser,但必须确保 endpoint 中启用了 :multipart:
plug Plug.Parsers, parsers: [:urlencoded, :multipart, :json], pass: ["*/*"], json_decoder: Phoenix.json_library(), length: 100_000_000
否则,整个 upload 字段会是空的,连 %Plug.Upload{} 都不会生成。
如何安全地解析上传的 XML 并防止 XXE 攻击
Erlang 的 :xmerl 默认启用外部实体(XXE),直接解析恶意 XML 可导致读取任意本地文件、发起内网请求。必须禁用:
- 用
:xmerl.simple_form/2时传[external_entities: false] - 更推荐用
saxy(纯 Elixir SAX 解析器),默认无外部实体支持,且内存友好 - 始终在解析前做基础校验:
String.starts_with?(File.stream!(upload.path) |> Enum.take(1) |> hd(), "<?xml ")
示例(使用 saxy):
defp parse_xml_upload(%Plug.Upload{path: path}) do
case Saxy.parse_file(path, MyXMLHandler, []) do
{:ok, _result} -> {:ok, "valid xml"}
{:error, reason} -> {:error, "XML parse failed: #{inspect(reason)}"}
end
end
常见错误:上传后 upload 是 nil 或空 map
这通常不是 XML 特有问题,而是 multipart 流被提前消费或配置遗漏:
- 检查浏览器 DevTools Network 面板:请求是否真含
Content-Type: multipart/form-data; boundary=...?表单是否漏了multipart属性? - 确认 controller 参数匹配字段名,比如前端
<input name="document" type="file">,后端要写params["document"],不是params[:document] - 避免在 plug chain 中多次调用
read_body/2或fetch_query_params/2—— multipart body 只能读一次
最隐蔽的坑:Phoenix LiveView 下用 allow_upload 时,XML 文件会被自动转成二进制流并丢弃原始 %Plug.Upload{},此时必须改用普通 HTML 表单或自定义 JS 上传逻辑。










