curl --form/-F 完全指南:multipart/form-data 表单上传与 MIME 邮件构建
2026/9/9 21:01:42 网站建设 项目流程

curl --form/-F 完全指南:multipart/form-data 表单上传与 MIME 邮件构建

【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl

--form(短选项-F)是 curl 在命令行下模拟“浏览器提交表单”的核心选项:对 HTTP/HTTPS,它以multipart/form-data(RFC 2388)格式 POST 数据,支持文本字段、文件上传、自定义 Content-Type 与字段头;对 SMTP/IMAP,同一个选项语法被扩展为组装带多个 MIME 部件的邮件消息。读完本文,你将掌握-F的全部取值语法(@<-与 stdin)、type=/filename=/headers=/encoder=属性、引号转义规则,以及--form-string--form-escape的配合用法,并能看懂其背后的命令行解析与 libcurl MIME 调用链。

一、选项概览与适用协议

--form的元数据(见 docs/cmdline-opts/form.md 头部)给出了该选项的关键约束:

属性含义
长选项 / 短选项--form/-F指定 multipart MIME 数据
参数形式<name=content>每段name=value定义表单中的一个字段
适用协议HTTP、SMTP、IMAP详见下文两节
互斥选项dataheadupload-file--data--head--upload-file互斥
Multiappend可重复使用多次,每次追加一个部件
加入版本curl 5.0属于 curl 最早期的-F系列能力

要点是:在 HTTP 协议族下,它模拟“用户按下提交按钮后填写完成的表单”,让 curl 以Content-Type: multipart/form-data(RFC 2388)POST 数据(这也是它互斥于--data--upload-file的原因——请求体的构建方式被完全接管);而在 SMTP 与 IMAP 协议下,同样的语法被复用来组装一封 multipart MIME 邮件并发送。

从命令行入口看,--form--form-string共用同一套解析入口(src/tool_getparam.c):参数被交给formparse()解析成内部 MIME 部件树,并调用SetHTTPrequest(TOOL_HTTPREQ_MIMEPOST, ...)把请求标记为“MIME POST”。也就是说,一旦在命令行使用-F,HTTP 请求方法即被确定为携带 multipart 体的 POST。

二、HTTP 表单:三种取值来源与基本用法

--form的参数形如name=contentcontent部分有三种来源:

  1. 字面文本name=John,作为普通文本字段提交;
  2. @文件名name=@portrait.jpg,把文件作为“文件上传”字段附加到表单(part 中包含文件本体与filename元数据);
  3. <文件名name=<hugefile.txt,从文件中读取内容填充到一个文本字段

@<的本质区别是:@生成的是一个 file 上传部件(服务端收到的是文件),而<生成的是一个 text 字段,只是字段值取自某个本地文件。

三个官方示例依次演示了这三种场景:

# 1) 上传图片:表单字段名 profile,本地文件 portrait.jpg 作为输入文件 curl -F profile=@portrait.jpg https://example.com/upload.cgi # 2) 两个普通文本字段 curl -F name=John -F shoesize=11 https://example.com/ # 3) 纯文本字段 story,内容来自本地文件 curl -F "story=<hugefile.txt" https://example.com/

值得注意:--form可以被反复书写(Multi: append),每次调用都会向同一个请求里追加一个字段或文件部件。

stdin 与非常规文件的特殊语义

@<两种结构中,都可以使用单个-作为“文件名”来代表标准输入 stdin:

  • stdin 的常规情况:内容会先被 curl 完整缓冲到内存中,以便确定其大小,从而允许在需要时“重发”(resend)请求体。
  • 命名非常规文件(如命名管道 named pipe 等):数据不做缓冲,而是在真正传输时才被读取。因为传输开始前无法获知完整大小,这类数据在 HTTP 下会以 chunk 方式发送,而 IMAP 会直接拒绝。

对应源码位于 src/tool_formparse.c 的tool_mime_new_filedata():当“文件名”为-且 stdin 本身是可以fstat定位的常规文件时,curl 记录ftell偏移与st_size算出大小,保留成可定位、按需读取的流(数据指针为空,配合 seek 支持重发);否则(stdin 来自管道、终端等)调用file2memory()把 stdin 整体读进内存并记下大小。随后 tool_mime_stdin_read()/tool_mime_stdin_seek() 两个回调负责在发送阶段从内存缓冲或 stdin 文件描述符中读取数据,并支持 seek 以应对重发。

三、逐字段属性:type、filename、headers、encoder

字段值之后可用分号连接若干“属性”来细化每个部件,全部由get_param_part();分隔逐一识别(见 src/tool_formparse.c 及注释 src/tool_formparse.c)。

type= 指定 Content-Type

type=用于显式告诉服务器该字段或该文件的媒体类型:

# 指定上传文件的类型为 text/html curl -F "web=@index.html;type=text/html" example.com # 指定一个纯文本字段的类型 curl -F "name=daniel;type=text/foo" example.com

不写type=时,文本字段默认text/plain,文件则由 curl 依据本地文件扩展名猜测(如未知则按application/octet-stream类处理)。

filename= 覆盖文件上传时的名字

@文件上传部件,服务器默认看到的是本地文件路径的 basename;若想改名,使用filename=

curl -F "file=@localfile;filename=nameinpost" example.com

源码注释(src/tool_formparse.c)给出了等价于“隐藏本地真实路径”的经典用法name=@filename;filename=/dev/null,以及给伪装文件名加引号的写法name=@filename;filename="play, play, and play.txt"

headers= 添加自定义字段头

可以给单个字段附加任意自定义头部(例如加一个X-*头):

# 直接内联一个头部 curl -F "submit=OK;headers=\"X-submit-type: OK\"" example.com # 从一个文件读取头部 curl -F "submit=OK;headers=@headerfile" example.com

headers=关键字可以出现多次,前述引号规则同样适用。当头部来自文件时,处理规则如下(与 read_field_headers() 的实现逐条对应):

  • 空行被忽略;
  • #开头的行被当作注释忽略;
  • 以空格开头的行视为上一行的“续行折叠”(folded continuation),会被拼接进前一条头部;
  • 行内嵌入的回车符与行尾空白会被剥离。

官方给出的 header 文件示例:

# This file contains two headers. X-header-1: this is a header # The following header is folded. X-header-2: this is another header

上面折叠示例最终发送的将是X-header-2: this is another header

encoder= 传输编码(邮件场景常用)

encoder=为部件数据指定 Content-Transfer-Encoding,可选值有:

编码作用
binary仅添加Content-Transfer-Encoding: binary头,不改动数据
8bit仅添加Content-Transfer-Encoding: 8bit头,不改动数据
7bit只做校验:遇到 8 位字符即报传输错误(transfer error)
quoted-printable按 quoted-printable 规则编码数据
base64按 base64 规则编码数据

其中quoted-printablebase64都会把行长度限制在 76 个字符以内。

# quoted-printable 文本消息 + base64 编码的附件文件 curl -F '=text message;encoder=quoted-printable' \ -F '=@localfile;encoder=base64' ... smtp://example.com

在 src/tool_formparse.c 处可以看到每个部件最终通过curl_mime_encoder(part, node->encoder)把编码器交给 libcurl 的 MIME 引擎执行。

四、引号与转义规则(最容易踩坑的部分)

字段值按;,或行尾切分属性,因此当文件名/路径中出现,;必须用双引号包裹,否则 curl 无法正确切分:

curl -F "file=@\"local,file\";filename=\"name;in;post\"" \ https://example.com # 更省心的写法:整个参数用单引号包起来,内部仍用双引号引文件名 curl -F 'file=@"local,file";filename="name;in;post"' \ https://example.com

双引号之内的文件名如果本身还含有"\,则必须再用反斜杠转义(例如\"\\)。这与解析函数 get_param_word() 的实现一致:解析器读取引号包裹的词时,遇到\后紧跟\"的组合会跳过转义并在内存中还原出未转义的字符串;若读到"后还跟有非空白字符,则会给出 "Trailing data after quoted form parameter" 警告。

非文件数据同样适用引号规则:内容中包含分号、首尾空格或以双引号开头时必须加引号:

curl -F 'colors="red; green; blue";type=text/x-myapp' \ https://example.com

--form-escape:从百分号编码切换到反斜杠转义

默认情况下,字段名与文件名中的特殊字符是按百分号编码(percent-encoding)处理的;加上--form-escape后,则改用反斜杠转义(见 docs/cmdline-opts/form-escape.md):

curl --form-escape -F 'field\name=curl' -F 'file=@load"this' $URL

该选项单独使用(Multi: single),不能重复。测试套件中的 tests/data/test1186 正是对--form-escape行为做回归验证的用例。

--form-string:完全字面取值

--form-string--form的唯一区别是:值字符串被原样使用。开头的@<以及值内的;type=都不再具备任何特殊含义(见 docs/cmdline-opts/form-string.md):

curl --form-string "name=data" $URL

当字符串值有可能误触发@/<功能时,应优先使用--form-string而非--form。例如用户输入内容恰好以@开头时,--form会把整段当作文件名解析失败,而--form-string会安全地按普通文本提交。在 src/tool_getparam.c 中可以看到两者走同一个formparse()函数,只是literal_value标志不同(--form-stringTRUE),随后 formparse() 中所有!literal_value的分支(@<(...=)判定)都会被跳过。测试用例 tests/data/test1189 就混合使用了--form-string--form,并验证@literal<verbatim;type=xxx/yyy这类字符串在--form-string下被当作字面内容原样发送。

五、SMTP/IMAP 场景:用 -F 构建 multipart MIME 邮件

当目标是smtp://imap://(含 TLS 变体)时,curl 把上述语法扩展为组包 MIME 邮件,新增三条规则:

  1. name 可以省略:参数以=作为第一个字符(例如=plain text),此时该段只提供内容、没有字段名;
  2. 数据以(开头表示“开始一个新的 multipart”,后面可跟;type=...指定该子 multipart 的 Content-Type(如multipart/alternativemultipart/mixed);
  3. 单独的=)参数用于结束当前 multipart,退回上一层。

下面这个官方示例演示了如何发送一封内含“正文两种格式(纯文本 + HTML,用 multipart/alternative 并列)”并附带一个文本文件的邮件:

curl -F '=(;type=multipart/alternative' \ -F '=plain text message' \ -F '= <body>HTML message</body>;type=text/html' \ -F '=)' -F '=@textfile.txt' ... smtp://example.com

结合第二节与第三节的知识,这个命令中=之后的plain text message<body>...都是字面文本部件;;type=text/html为 HTML 正文指定媒体类型;=)结束 alternative 容器;最后的=@textfile.txt则作为独立附件部件挂在最外层 multipart/mixed 之下。整个命令还可以再叠加encoder=(见第三节示例),完成 quoted-printable/base64 编码后再投递。

从实现上看,这些嵌套结构在 formparse() 中通过tool_mime_new_parts()新建子容器并把mimecurrent指向子容器完成“进入下一层”,遇到=)时再把mimecurrent指回其parent实现“退回上一层”;部件树之间以prevsubparts指针串成树状结构,最终在发送前递归转换为 libcurl 的curl_mime对象。

六、从命令行参数到网络字节流:内部实现链路

把整条-F能力串起来的调用链如下,可以在仓库源码中逐步核对:

  1. 参数解析:tool_getparam.c 收到--form/--form-string,调用 formparse(),把每个<name=content>字符串解析成一个由tool_mime节点组成的树;节点类型在 tool_formparse.h 中定义,包括TOOLMIME_PARTS(容器)、TOOLMIME_DATA(内存文本)、TOOLMIME_FILE/TOOLMIME_FILEDATA(文件上传)、TOOLMIME_STDIN/TOOLMIME_STDINDATA(标准输入)等。
  2. 逐段切词:get_param_part() 以;为分隔符解析type=filename=headers=encoder=属性;get_param_word() 负责处理双引号与\"/\\转义;遇到未知前缀会打印 "skip unknown form field" 警告后跳过。
  3. stdin/pipe 的按需读取:如第二节所述,tool_mime_stdin_read/tool_mime_stdin_seek回调使 stdin 数据既支持读取也支持 seek,从而为重发场景兜底。
  4. 转换并设置到 easy handle:config2setopts.c 在真正执行前调用tool2curlmime()(src/tool_formparse.c),把内部tool_mime树递归翻译成 libcurl 的curl_mime/curl_mimepartAPI 对象(curl_mime_addpartcurl_mime_filedatacurl_mime_data_cbcurl_mime_typecurl_mime_headerscurl_mime_encodercurl_mime_name等,见 src/tool_formparse.c),最终通过CURLOPT_MIMEPOST交给 libcurl 发送引擎。
  5. 边界与容量保护:代码中对单个字段链长度设置了MAX_FORMPARTS 100000的硬性上限(src/tool_formparse.c),超出即返回CURLE_BAD_FUNCTION_ARGUMENT

测试数据目录中大量用例会对生成结果做逐字节校验,例如 tests/data/test1053 等会断言请求体中的Content-Type: multipart/form-data; boundary=...头及边界串格式,说明 multipart 请求体的边界字符串、部件排版是稳定可控、可被测试精确预期的。

七、相关选项速查

--form家族的完整配套见下表:

选项作用文档
--data/-d普通 application/x-www-form-urlencoded POST(与--form互斥)docs/cmdline-opts/data.md
--form/-Fmultipart MIME 数据(本文主角)docs/cmdline-opts/form.md
--form-string--form类似但值字面化,@/</;type=无效docs/cmdline-opts/form-string.md
--form-escape字段名/文件名转义从百分号编码改为反斜杠docs/cmdline-opts/form-escape.md

一句话总结选用策略:常规表单与文件上传用--form;只要字段值可能以@<开头、或包含会被误解析的;type=,就用--form-string保证字面提交;字段/文件名特殊字符很多且不想纠结引号层级时,可考虑--form-escape切换转义方案。深层原理则可沿 src/tool_formparse.c 一路读到 libcurl 的curl_mime接口,那里承载了 RFC 2388 multipart/form-data 与 MIME 邮件的全部组包细节。

【免费下载链接】curlA command line tool and library for transferring data with URL syntax, supporting DICT, FILE, FTP, FTPS, GOPHER, GOPHERS, HTTP, HTTPS, IMAP, IMAPS, LDAP, LDAPS, MQTT, MQTTS, POP3, POP3S, RTSP, SCP, SFTP, SMB, SMBS, SMTP, SMTPS, TELNET, TFTP, WS and WSS. libcurl offers a myriad of powerful features项目地址: https://gitcode.com/GitHub_Trending/cu/curl

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询