news 2026/9/10 2:40:48

libcurl CURLOPT_COOKIELIST 详解:内存 Cookie 引擎的注入、批量操作与安全边界

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
libcurl CURLOPT_COOKIELIST 详解:内存 Cookie 引擎的注入、批量操作与安全边界

libcurl CURLOPT_COOKIELIST 详解:内存 Cookie 引擎的注入、批量操作与安全边界

【免费下载链接】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_COOKIELIST是 libcurl 中用于直接操作"内存 Cookie 存储"的核心选项:它既可以把单个 Cookie 以 Netscape 文件行或 HTTPSet-Cookie:头的形式即时注入 Cookie 引擎,也可以通过ALLSESSFLUSHRELOAD四条特殊命令批量清空、落盘与重载 Cookie。本文基于当前仓库 docs/libcurl/opts/CURLOPT_COOKIELIST.md 展开,结合 lib/setopt.c 与 lib/cookie.c 的源码实现和 tests/libtest/lib506.c 等测试用例,讲清四种注入格式、四条管理命令的精确语义,以及绕过 Public Suffix List(PSL)检查这一关键安全边界,帮助你安全、正确地在自己应用中管理会话与持久化 Cookie。

选项概览:签名、协议与默认值

CURLOPT_COOKIELIST通过curl_easy_setopt设置,参数是一个char *字符串指针:

#include <curl/curl.h> CURLcode curl_easy_setopt(CURL *handle, CURLOPT_COOKIELIST, char *cookie);
  • 生效协议:仅 HTTP(含 HTTPS)。在仓库头文件 include/curl/curl.h 中它被声明为CURLOPT(CURLOPT_COOKIELIST, CURLOPTTYPE_STRINGPOINT, 135),属于字符串指针类选项。
  • 引入版本:7.14.1(ALL命令同期引入;SESS于 7.15.4、FLUSH于 7.17.1、RELOAD于 7.39.0 加入,详见下文"历史沿革")。
  • 默认值:NULL,即默认不执行任何注入或命令操作。
  • 返回值curl_easy_setopt返回CURLcodeCURLE_OK (0)表示成功,非零表示出错(见libcurl-errors手册)。在编译关闭 cookie 支持(CURL_DISABLE_COOKIES/CURL_DISABLE_HTTP)时,该选项会落到default分支返回CURLE_UNKNOWN_OPTION(见 lib/setopt.c)。
  • 编译器类型检查:在启用 GCC 严格类型检查时,该选项与CURLOPT_COOKIECURLOPT_COOKIEFILECURLOPT_COOKIEJAR一样要求字符串参数,见 include/curl/typecheck-gcc.h。

核心语义:一次调用,两种身份

传递给CURLOPT_COOKIELIST的字符串有且仅有两种处理路径,源码 lib/setopt.c 中的cookielist()函数清晰地区分了它们:

  1. 命令字符串:当传入的字符串与ALLSESSFLUSHRELOAD精确匹配(不区分大小写,使用curl_strequal比较)时,执行对应的批量操作。
  2. Cookie 字符串:其余任何非 NULL 字符串都被当作"一条 Cookie"解析并注入内存存储。这一行为同时启用了 Cookie 引擎——即使此前从未调用过CURLOPT_COOKIEFILE,注入后该 handle 也会开始参与 Cookie 的接收、保存与发送逻辑。

值得注意的实现细节是:命令分支中ALLSESSFLUSH均先返回CURLE_OK,而RELOAD分支直接return Curl_cookie_loadfiles(...),把加载结果作为返回值——即重载失败(如内存不足)时curl_easy_setopt本身会返回错误码,调用方可以据此感知失败。

两种可注入的 Cookie 行格式

Netscape / Mozilla 文件行格式(推荐):

域名(Tab)是否包含子域(Tab)路径(Tab)Secure标志(Tab)过期时间(Tab)名称(Tab)值

例如:

example.com FALSE / FALSE 0 foo bar

HTTPSet-Cookie:头格式

Set-Cookie: name=value; Domain=example.com; Path=/; Expires=Sat, 02 Feb 2030 11:56:27 GMT

为什么官方强烈建议使用 Netscape 格式

原文档给出了一条明确的工程建议:"强烈建议不要从 HTTP 头文件加载 Cookie,因为它是劣质的数据交换格式"。原因有二:

  • 若使用Set-Cookie格式且字符串中未指定 Domain,则该 Cookie 会被当作"任何域名都发送"的全局 Cookie:即便后续跟随了重定向,它也会被发给任意主机;而且服务器后续设置的同名 Cookie 无法覆盖它。
  • 若服务器设置了一个同名 Cookie(或你手动导入了同名 Cookie),未来传输到该服务器时两个 Cookie 都会被发送,通常不是你期望的结果。

规避方法:在Set-Cookie中显式设置 Domain(注意设置 Domain 会包含其子域),或者更优方案——直接使用 Netscape 文件格式,因为该格式的域名字段是强制性的。

多传输场景的并发注意

如果你的程序在同一个进程内发起多次传输,使用本选项要格外小心:未指定 Domain 的Set-Cookie注入会污染全局发送行为(见上文),在多个并发 easy handle 共享 Cookie 存储(CURLSHOPT_SHARE+CURL_LOCK_DATA_COOKIE)时尤其容易引入难以排查的串扰问题。仓库测试 tests/libtest/lib506.c 就专门构造了"注入 → ALL → 注入会话 Cookie → SESS → 多线程共享 Cookie 存储 → FLUSH → RELOAD"的完整链路来验证这类交互下的锁与数据一致性。

四条管理命令:ALL / SESS / FLUSH / RELOAD

在 lib/setopt.c 中,这四条命令的实现如下:

命令作用源码实现引入版本
ALL清除内存中所有CookieCurl_cookie_clearall(data->cookies)7.14.1
SESS清除内存中所有会话 Cookieexpires == 0的 Cookie)Curl_cookie_clearsess(data->cookies)7.15.4
FLUSH把当前已知的所有Cookie 写入CURLOPT_COOKIEJAR指定的文件Curl_flush_cookies(data, FALSE)7.17.1
RELOADCURLOPT_COOKIEFILE指定的文件加载所有CookieCurl_cookie_loadfiles(data, COOKIE_NOPSL \| (cookiesession ? COOKIE_NOSESSION : 0))7.39.0

补充说明:

  • SESS的判定:会话 Cookie 指没有过期时间(Netscape 格式第 5 字段为0)的 Cookie。Curl_cookie_clearsess遍历全部 hash 桶并释放这类 Cookie,持久化 Cookie 则保留。
  • RELOADCURLOPT_COOKIESESSION的联动:如果在RELOAD之前启用了CURLOPT_COOKIESESSION,则该开关会作用于本次加载,加载时所有会话 Cookie 会被直接丢弃(对应实现中的COOKIE_NOSESSION标志,见 lib/cookie.c 附近关于newsession的逻辑)。这一语义与原文档完全一致。
  • RELOAD与 PSL 的例外:注意RELOAD分支传入COOKIE_NOPSL标志,意味着从文件重载 Cookie 时也跳过 PSL 检查(与下文的注入路径行为一致)。

PSL 检查被绕过:必须自己把关的安全边界

这是原文档着墨最多、也最容易被忽视的一点:通过CURLOPT_COOKIELIST注入的 Cookie 会绕过自动 Public Suffix List(PSL)检查

原因在实现层面很直接:调用发生时 handle 内部的 PSL 引擎尚未初始化。在正常传输中,PSL 校验会阻止 Cookie 被设置在宽泛或共享域名上——例如.com.co.uk.github.io——因为如果允许,无关的子域名就能读取同一份敏感 Cookie 数据,形成安全漏洞。

验证代码路径:lib/cookie.c 中Curl_cookie_add的检查是:

if(!(flags & COOKIE_NOPSL) && is_public_suffix(data, co, domain)) goto fail;

即:只有在未设置COOKIE_NOPSL标志时才做 PSL 校验。而注入路径(lib/cookie.c 注释明确写出domain为 NULL 的情况正是"从文件加载或CURLOPT_COOKIELIST")以及上文RELOADCOOKIE_NOPSL标志都绕过了这层防护。这并非缺陷,而是设计使然——因此:

调用方必须全权负责域名合法性校验:在把 Cookie 注入 handle 之前,自行确认其 Domain 属性指向的是一个有效主机,且不是公共后缀(public suffix)。否则可能把你的 Cookie 泄漏给任意子域。

实操建议:注入前用你自己的 PSL 库(或维护的公共后缀清单)校验Domain字段;测试环境域名(如localhost)不受此问题影响——仓库测试 tests/libtest/lib3103.c 就展示了向domain=localhost注入会话 Cookie 的合法用法。

完整示例:注入、导入、导出的时序语义

原文档的示例完整演示了"注入的 Cookie 是 live cookie,文件导入不会覆盖它"这一关键时序。结合 lib/cookie.c 中co->livecookie = ci->running;(运行中注入的 Cookie 标记为 live),该行为在源码层面得到印证:

/* 以 Netscape 格式内联导入一条 Cookie。 */ #define SEP "\t" /* Tab 分隔各字段 */ int main(void) { const char *my_cookie = "example.com" /* Hostname(域名) */ SEP "FALSE" /* Include subdomains(是否包含子域) */ SEP "/" /* Path(路径) */ SEP "FALSE" /* Secure(是否仅 HTTPS 发送) */ SEP "0" /* Expiry in epoch time format. 0 == Session(过期时间,epoch;0 表示会话 Cookie) */ SEP "foo" /* Name(Cookie 名) */ SEP "bar"; /* Value(Cookie 值) */ CURL *curl = curl_easy_init(); if(curl) { CURLcode result; /* my_cookie 通过 CURLOPT_COOKIELIST 被立即导入。 */ curl_easy_setopt(curl, CURLOPT_COOKIELIST, my_cookie); /* cookies.txt 中的 Cookie 直到真正执行传输前才会被导入。 其中与 my_cookie 具有相同 hostname、path 和 name 的 Cookie 会被跳过。 因为 libcurl 已导入了 my_cookie,它被视为 "live" cookie, 而 live cookie 不会被文件中读取的 Cookie 替换。 */ curl_easy_setopt(curl, CURLOPT_COOKIEFILE, "cookies.txt"); /* 导入 */ /* Cookie 在 curl_easy_cleanup 之后被导出。 此时服务器可能已新增、删除或修改过 Cookie。 导入时被跳过的 Cookie 不会被导出。 */ curl_easy_setopt(curl, CURLOPT_COOKIEJAR, "cookies.txt"); /* 导出 */ result = curl_easy_perform(curl); /* 此期间从 cookies.txt 导入 Cookie */ curl_easy_cleanup(curl); /* 此调用后 Cookie 被导出到 cookies.txt */ } }

示例揭示的三个重要时序语义:

  1. CURLOPT_COOKIELIST注入是即时生效的;CURLOPT_COOKIEFILE的导入是惰性的(直到传输前才发生)。
  2. live cookie 优先级高于文件:同名(hostname+path+name)Cookie 若已通过COOKIELIST注入,文件导入时不会覆盖它。这源于 lib/cookie.c 中ci->runninglivecookie的判定。
  3. 导出发生在curl_easy_cleanup时,且只导出"活跃"(未被跳过)的 Cookie 集合。

与 Cookie 文件格式的衔接

CURLOPT_COOKIELIST注入的单行格式与CURLOPT_COOKIEFILE/CURLOPT_COOKIEJAR使用的 Cookie 文件格式同源(Netscape / Mozilla 格式),相关背景可进一步阅读仓库 docs/HTTP-COOKIES.md。典型文件行形如:

# Netscape HTTP Cookie File .example.com TRUE / FALSE 1893456000 foo bar

其中字段依次为:域名、是否匹配子域(TRUE/FALSE)、路径、Secure 标志、Unix 时间戳形式的过期时间(0 为会话 Cookie)、名称、值。这一格式同样适用于命令行工具curl -b cookies.txt -c cookies.txt的场景(见 docs/cmdline-opts/cookie.md 与 docs/cmdline-opts/cookie-jar.md)。

源码级验证:测试用例与实现位置

如果你想在仓库中亲手验证上述全部行为,以下是精确的入口:

  • 选项分发与命令解析:lib/setopt.c 的cookielist()——四种命令的分支、curl_strequal匹配、RELOAD返回值透传。
  • Cookie 解析与入库:lib/cookie.c 的Curl_cookie_add()——Netscape/HTTP 头两种解析路径(parse_netscape/parse_cookie_header)、__Secure-/__Host-前缀校验、PSL 检查、live cookie 标记。
  • 文件加载与惰性导入:lib/cookie.c 与Curl_cookie_loadfiles——Set-Cookie:前缀剥离、COOKIE_NOEXPIRE | COOKIE_SECURE | COOKIE_NOPSL标志组合。
  • 共享存储下的完整链路:tests/libtest/lib506.c(ALL/SESS/FLUSH/RELOAD + 共享锁)、tests/libtest/lib1905.c(multi 接口中 FLUSH 落盘)、tests/libtest/lib3103.c(无过期时间的Set-Cookie注入)。
  • 选项注册与类型:include/curl/curl.h、lib/easyoptions.c、include/curl/typecheck-gcc.h。
  • 配套选项:CURLOPT_COOKIE、CURLOPT_COOKIEFILE、CURLOPT_COOKIEJAR、CURLOPT_COOKIESESSION,以及用于反向读取 Cookie 列表的 CURLINFO_COOKIELIST。

历史沿革

能力引入版本
CURLOPT_COOKIELISTALL7.14.1
SESS7.15.4
FLUSH7.17.1
RELOAD7.39.0

使用前请确认你的 libcurl 版本不低于 7.14.1,并留意发行版打包的 libcurl 是否以CURL_DISABLE_COOKIES编译(此时该选项返回CURLE_UNKNOWN_OPTION)。

最佳实践清单

  1. 优先 Netscape 文件行格式注入 Cookie;必须用Set-Cookie时,务必显式给出Domain
  2. 注入前自行做域名校验:确认 Domain 是有效主机名且不是公共后缀(.com.co.uk.github.io等),因为 PSL 检查在此路径被绕过。
  3. 善用四条命令:需要清空状态用ALL,只清会话用SESS,要在curl_easy_cleanup之前强制落盘用FLUSH,需要从文件重建状态用RELOAD
  4. 注意RELOADCURLOPT_COOKIESESSION的组合:先启用CURLOPT_COOKIESESSIONRELOAD,会丢弃全部会话 Cookie。
  5. 记住 live cookie 语义:通过COOKIELIST注入的 Cookie 不会被随后COOKIEFILE文件导入的同名 Cookie 覆盖;导出(COOKIEJAR)时只导出活跃集合。
  6. 多传输共享 Cookie 存储时,为ALL/SESS/FLUSH/RELOAD等操作预判其全局影响,必要时结合curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE)并实现正确的锁回调(参见 tests/libtest/lib506.c 的锁测试)。

【免费下载链接】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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/10 2:39:22

Unigram未来发展规划:新功能路线图和社区发展愿景

Unigram未来发展规划&#xff1a;新功能路线图和社区发展愿景 Unigram作为Windows平台上备受欢迎的Telegram客户端&#xff0c;正在迎来令人兴奋的发展新阶段。本文将深入探讨Unigram的未来发展规划&#xff0c;揭示即将到来的功能创新和社区发展蓝图。 &#x1f680; 核心功…

作者头像 李华
网站建设 2026/9/10 2:37:14

Netty内存池与对象池:从GC抖动到堆外泄漏的实战解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 2:35:55

ooderAgent-rad:AI协同与可视化融合的企业级低代码开发实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/10 2:35:15

防火积木是什么?从UL94到电子DIY的阻燃结构件实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华