libcurl CURLOPT_CRLF 选项详解:Unix 换行符到 CRLF 的转换机制与源码实现
【免费下载链接】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
导读
CURLOPT_CRLF是 libcurl 提供的一个用于在上传/下载传输过程中把 Unix 换行符(LF)转换为 CRLF 换行符(\r\n)的 legacy 选项。它在 FTP、SMTP 等传统协议与大型机(如 MVS/OS/390)场景中仍有实际价值。本文以 docs/libcurl/opts/CURLOPT_CRLF.md 为主线,结合仓库中 lib/setopt.c、lib/sendf.c、lib/ftp.c、lib/imap.c 等源码,完整讲解该选项的用法、底层转换 reader 的实现原理、协议差异与注意事项。读完后你将能正确判断何时启用 CRLF 转换、理解其对上传字节数和文件大小校验的影响,并能追踪该选项从curl_easy_setopt到实际字节流的完整调用链。
NAME 与原型:一个开关型 long 参数
该选项的功能定义为 "CRLF conversion",其 API 原型如下(对应文档 SYNOPSIS 节):
#include <curl/curl.h> CURLcode curl_easy_setopt(CURL *handle, CURLOPT_CRLF, long conv);从参数签名可以看出,它接受一个long类型的开关值:传入1开启转换,传入0关闭转换。选项在 curl 7.1 版本中加入(文档 frontmatter 中的Added-in: 7.1),属于最早一批 libcurl 选项之一。
在当前的 libcurl 内部,该选项被登记在 lib/easyoptions.c 的选项表中:
{ "CRLF", CURLOPT_CRLF, CURLOT_LONG, 0 },CURLOT_LONG表示其参数类型为长整型,与文档中 "Pass a long" 的描述一致。命令行工具curl也提供了对应的--crlf选项(见 docs/cmdline-opts/crlf.md),其 Help 文案为 "Convert LF to CRLF in upload",可用于 FTP、SMTP 协议的上传场景。
DESCRIPTION:转换行为与 legacy 定位
文档对行为的描述非常简洁:
Pass a long. If the value is set to 1 (one), libcurl converts Unix newlines to CRLF newlines on transfers. Disable this option again by setting the value to 0 (zero).
This is a legacy option of questionable use.
要点提炼:
- 开启条件:
conv值为1时,libcurl 在传输过程中把 Unix 换行符(\n,0x0A)转换为 CRLF(\r\n,0x0D 0x0A)。 - 关闭方式:将值设为
0(默认值即0)即可禁用。 - 官方态度:文档明确标注这是 "a legacy option of questionable use"(一个存疑的遗留选项)。这意味着新代码应谨慎使用,仅在确有协议兼容或大型机场景需求时启用。
值得注意的细节:转换方向是单向的 LF → CRLF,它不会把 CRLF 转换回 LF,也不负责内容的其他换行风格处理。如果数据中已经存在\r\n对,转换器会识别并跳过,避免产生\r\r\n的双 CR 错误(见下文源码分析)。
源码实现:setopt 到转换 Reader 的完整链路
1. 参数接收:setopt.c 中的 "Kludgy option"
CURLOPT_CRLF的实际处理位于 lib/setopt.c:
case CURLOPT_CRLF: /* * Kludgy option to enable CRLF conversions. Subject for removal. */ s->crlf = enabled; break;源码注释直言这是 "Kludgy option"(笨拙的选项),且 "Subject for removal"(随时可能被移除),与文档中 "legacy option of questionable use" 的定位互相印证。这里接收到的值被存入 easy handle 的设置结构体data->set.crlf,其类型定义在 lib/urldata.h:
BIT(crlf); /* convert crlf on ftp upload(?) */BIT()宏将该字段声明为位域布尔值,且注释用疑问句 "on ftp upload(?)" 表明其生效范围在历史上与 FTP 上传强相关——实际上该选项对所有上传路径均可能生效(见下文)。
2. 转换执行:sendf.c 中的 cr-lineconv Reader
真正的字节级转换并不散落在各协议实现中,而是由一个名为cr-lineconv(struct cr_lc)的客户端读取器(client reader)统一完成,实现在 lib/sendf.c。
状态上下文(lib/sendf.c):
struct cr_lc_ctx { struct Curl_creader super; struct bufq buf; BIT(read_eos); /* we read an EOS from the next reader */ BIT(eos); /* we have returned an EOS */ BIT(prev_cr); /* the last byte was a CR */ };内部使用一个 16KB 软上限缓冲区(Curl_bufq_init2(&ctx->buf, (16 * 1024), 1, BUFQ_OPT_SOFT_LIMIT))暂存待转换数据,并用prev_cr记录上一个字节是否为\r,以便跨数据块正确判断\r\n边界。
核心转换逻辑(lib/sendf.c):
/* at least one \n might need conversion to '\r\n', place into ctx->buf */ for(i = start = 0; i < nread; ++i) { /* if this byte is not an LF character, or if the preceding character is a CR (meaning this already is a CRLF pair), go to next */ if((buf[i] != '\n') || ctx->prev_cr) { ctx->prev_cr = (buf[i] == '\r'); continue; } ctx->prev_cr = FALSE; /* on a soft limit bufq, we do not need to check length */ result = Curl_bufq_cwrite(&ctx->buf, buf + start, i - start, &n); if(!result) result = Curl_bufq_cwrite(&ctx->buf, STRCONST("\r\n"), &n); if(result) return result; start = i + 1; }这段循环揭示了转换的关键规则:
- 逐字节扫描输入;
- 遇到
\n且前一个字节不是\r时,在\n前插入一个\r,输出\r\n; - 如果
\n前一个字节已经是\r(即输入本身就是标准 CRLF),则直接跳过,保持原样,避免产生\r\r\n; - 若一批数据末尾的
\r可能与下一批数据的\n配对,则由prev_cr状态跨批次记忆。
另外还做了快速路径优化(lib/sendf.c):当memchr(buf, '\n', nread)找不到任何\n时,整批数据原样透传,不进入逐字节转换循环,保证无 LF 内容的性能不受影响。
Reader 的挂载时机(lib/sendf.c):
clen = r->crt->total_length(data, r); /* if we do not have 0 length init, and CRLF conversion is wanted, * add the reader for it */ if(clen && #ifdef CURL_PREFER_LF_LINEENDS (data->set.crlf ||>else if(data->state.upload) { if((ftp->transfer == PPTRANSFER_BODY) && (data->state.infilesize != -1) && /* upload with known size */ ((!data->set.crlf && !data->state.prefer_ascii && /* no conversion */ (data->state.infilesize !=>/* Check we know the size of the upload. This takes all readers * into account. Especially crlf conversions which make the size * unpredictable, e.g. -1. */>curl --crlf -T file ftp://example.com/--crlf与--use-ascii(即 libcurl 的CURLOPT_TRANSFERTEXT/ASCII 模式)是两条不同的路径:前者无条件做 LF→CRLF 转换,后者设置 ASCII 传输模式;在部分平台二者会同时触发 cr-lineconv reader(见上文CURL_PREFER_LF_LINEENDS分支),因此组合使用时需注意转换不会重复进行——reader 对已存在的\r\n会跳过,不会产生\r\r\n。
典型使用示例
以下完整示例来自文档的 EXAMPLE 节,展示了最小可用代码:
int main(void) { CURL *curl = curl_easy_init(); if(curl) { CURLcode result; curl_easy_setopt(curl, CURLOPT_URL, "ftp://example.com/"); curl_easy_setopt(curl, CURLOPT_CRLF, 1L); result = curl_easy_perform(curl); curl_easy_cleanup(curl); } }实际应用中,更常见的组合是配合上传一起使用:
CURL *curl = curl_easy_init(); if(curl) { FILE *fp = fopen("local_unix_text.txt", "rb"); curl_easy_setopt(curl, CURLOPT_URL, "ftp://example.com/upload.txt"); curl_easy_setopt(curl, CURLOPT_UPLOAD, 1L); curl_easy_setopt(curl, CURLOPT_READDATA, fp); curl_easy_setopt(curl, CURLOPT_CRLF, 1L); /* LF -> CRLF */ curl_easy_perform(curl); curl_easy_cleanup(curl); fclose(fp); }实战建议:
- 只对纯文本内容开启;二进制内容中可能出现的
0x0A会被误转换,破坏数据完整性; - 与
CURLOPT_UPLOAD配合做 FTP/SMTP 文本上传时使用;HTTP 上传一般无需该选项(HTTP 协议层由 Content-Length 精确控制); - 不要与 IMAP
APPEND一起使用(会返回CURLE_UPLOAD_FAILED); - 对既有代码而言,若项目没有大型机或古老 FTP 服务器兼容需求,应保持默认值 0 并考虑用 ASCII 模式等替代手段。
RETURN VALUE:返回值语义
curl_easy_setopt调用该选项后返回CURLcode:
CURLE_OK (0):设置成功;- 非零值:发生错误,具体错误码参见 docs/libcurl/libcurl-errors.md。
由于该选项只是把一个 long 值写入内部结构体(s->crlf = enabled,见 lib/setopt.c),正常情况下总是返回CURLE_OK;参数类型不匹配等调用错误会在 setopt 的公共入口统一拦截。
DEFAULT 与 AVAILABILITY
- 默认值:
0(关闭)。所有新建的 easy handle 默认不执行任何换行转换。 - 可用性:自 curl7.1起加入,所有协议均接受该选项(文档 frontmatter 中
Protocol: All),但如前所述,其转换效果主要体现在上传路径(FTP、SMTP),并对 IMAP 等需要预知大小的协议产生实际限制。命令行--crlf自 curl 5.7 起提供。 - 兼容性:选项在 setopt 阶段只做状态记录,真正的转换行为由
cr-lineconvreader 在传输阶段执行(lib/sendf.c),因此开启/关闭的代价极小,不会影响连接建立与协议协商。
相关选项
该选项的 See-also 链接指向两个转换回调(历史遗留,与字符集转换相关,与行尾转换是不同机制):
CURLOPT_CONV_FROM_NETWORK_FUNCTION:从网络编码转换为本地编码的回调;CURLOPT_CONV_TO_NETWORK_FUNCTION:从本地编码转换为网络编码的回调。
命令行侧的对应选项为--crlf(docs/cmdline-opts/crlf.md)与--use-ascii。若想进一步了解 easy 选项的通用设置/获取机制,可查阅 docs/libcurl/curl_easy_setopt.md 与 docs/libcurl/curl_easy_getinfo.md。
小结
| 项目 | 结论 |
|---|---|
| 选项名 | CURLOPT_CRLF,long 型开关 |
| 默认值 | 0(关闭) |
| 加入版本 | curl 7.1(--crlf命令行选项自 5.7) |
| 核心行为 | 上传时把 LF 逐字节转换为 CRLF,已存在的\r\n保持原样 |
| 源码位置 | 参数存储 lib/setopt.c、结构体 lib/urldata.h、转换 reader lib/sendf.c |
| 主要限制 | 转换后大小不可预测:FTP 放宽校验、IMAPAPPEND直接拒绝 |
| 适用场景 | 大型机(MVS/OS/390)文本上传、旧式 FTP/SMTP 文本服务器兼容 |
CURLOPT_CRLF是一个功能简单但副作用隐蔽的 legacy 选项:打开开关只是一行代码,但转换会悄然改变上传字节数与大小校验语义。理解 lib/sendf.c 中cr-lineconvreader 的"遇\n前无\r才插入\r"规则,以及 lib/ftp.c、lib/imap.c 中的两处大小校验分支,是安全使用该选项的关键。在绝大多数现代场景下,保持默认的 0 值即可。
【免费下载链接】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),仅供参考