libcurl 详解 CURLINFO_FILETIME_T:安全获取远端资源的修改时间(64 位时间戳)
【免费下载链接】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
CURLINFO_FILETIME_T是 libcurl 提供的curl_easy_getinfo()查询选项,用于在传输完成后获取远端文档的修改时间(以 GMT/UTC 时区下自 1970-01-01 起的秒数表示)。它是CURLINFO_FILETIME的 64 位时间戳版本,专为在long为 32 位的系统(如 Windows)上突破 2038 年时间溢出限制而设计。本文以 docs/libcurl/opts/CURLINFO_FILETIME_T.md 为骨架,结合 lib/getinfo.c、lib/setopt.c、lib/http.c 等源码与 src/tool_operate.c 中的实际调用,完整讲解其用法、底层原理与工程实践。
一、接口定义:从 SYNOPSIS 看函数签名
CURLINFO_FILETIME_T的官方文档位于 docs/libcurl/opts/CURLINFO_FILETIME_T.md,其函数签名如下:
#include <curl/curl.h> CURLcode curl_easy_getinfo(CURL *handle, CURLINFO_FILETIME_T, curl_off_t *timep);要点解读:
- 该选项由
curl_easy_getinfo()使用,属于读取型(query)信息选项,与curl_easy_setopt()配合的写入型选项CURLOPT_FILETIME相对应(见 CURLOPT_FILETIME.md); - 传入的是指向
curl_off_t的指针,而非long指针——这正是它与旧版选项最本质的区别; - 根据 include/curl/curl.h 中的枚举定义,
CURLINFO_FILETIME = CURLINFO_LONG + 14,而CURLINFO_FILETIME_T = CURLINFO_OFF_T + 14,两者共享同一个“+14”槽位,但分别归属 LONG 与 OFF_T 两个信息类型族,说明它们在类型体系上被刻意区分。
该选项自7.59.0版本起加入(Added-in: 7.59.0,参见 docs/libcurl/symbols-in-versions),适用协议为 HTTP、FTP 与 SFTP。
二、返回值语义:epoch 秒与 -1 的含义
文档明确说明:调用者将得到一个curl_off_t,其值为远端文档的修改时间,单位为自 1970 年 1 月 1 日起的秒数(GMT/UTC 时区)。
当返回值是-1时,表示时间未知,可能的原因包括:
- 服务器未提供该信息(未返回相关时间字段);
- 服务器有意隐藏修改时间;
- 服务器不支持查询文档时间的命令(例如 FTP 服务器不支持
MDTM类命令)。
从源码实现看,这一语义被完整落实在 lib/getinfo.c 的Curl_initinfo()中:
info->filetime = -1; /* -1 is an illegal time and thus means unknown */该初始化函数在curl_easy_reset、curl_easy_duphandle以及每次 perform 会话开始时都会被调用,因此每次传输都会从“未知”状态重新开始,上一次查询得到的时间不会残留到下一次传输。
三、与 CURLINFO_FILETIME 的区别:为什么需要 _T 版本
官方文档将本选项定位为CURLINFO_FILETIME的替代方案(参见 CURLINFO_FILETIME.md):允许long为 32 位的系统提取超出 32 位时间戳范围(即 2038 年之后)的日期。
两者在源码实现上的差异非常直观。旧版CURLINFO_FILETIME(lib/getinfo.c)在取出内部时间后需要做钳制(clamp)处理,以防溢出 32 位long:
case CURLINFO_FILETIME: if(data->info.filetime > LONG_MAX) *param_longp = LONG_MAX; #if !defined(MSDOS) && !defined(__AMIGA__) else if(data->info.filetime < LONG_MIN) *param_longp = LONG_MIN; #endif else *param_longp = (long)data->info.filetime; break;而CURLINFO_FILETIME_T(lib/getinfo.c)直接原样拷贝,不做任何钳制:
case CURLINFO_FILETIME_T: *param_offt = (curl_off_t)data->info.filetime; break;由于curl_off_t被定义为足够容纳文件大小的 64 位宽整数类型,它可以无损承载远超过 2038 年的时间值。因此在涉及“长期时间戳”“未来日期”“跨世纪归档”等场景下,应优先使用CURLINFO_FILETIME_T。
四、使用前提:必须先设置 CURLOPT_FILETIME
这是本选项最关键的约束,文档用强语气强调:必须在传输开始前通过CURLOPT_FILETIME请求该信息,否则无条件得到 -1。
curl_easy_setopt(curl, CURLOPT_FILETIME, 1L);从源码看,CURLOPT_FILETIME在 lib/setopt.c 中的处理是把标志位写入会话设置:
case CURLOPT_FILETIME: /* * Try to get the file time of the remote document. The time will * later (possibly) become available using curl_easy_getinfo(). */ s->get_filetime = enabled; break;get_filetime标志随后驱动各协议层去主动收集时间信息。以 HTTP 为例,lib/http.c 中的响应头解析函数http_header_l()会专门匹配Last-Modified:头:
const char *v = (!k->http_bodyless && (data->set.timecondition ||>int main(void) { CURL *curl = curl_easy_init(); if(curl) { CURLcode result; curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/"); /* Ask for filetime */ curl_easy_setopt(curl, CURLOPT_FILETIME, 1L); result = curl_easy_perform(curl); if(result == CURLE_OK) { curl_off_t filetime; result = curl_easy_getinfo(curl, CURLINFO_FILETIME_T, &filetime); if((result == CURLE_OK) && (filetime != -1)) { time_t file_time = (time_t)filetime; printf("filetime: %s", ctime(&file_time)); } } /* always cleanup */ curl_easy_cleanup(curl); } }对示例代码的补充说明:
curl_easy_setopt(curl, CURLOPT_FILETIME, 1L)必须位于curl_easy_perform()之前,这是整个流程成立的前提;curl_easy_getinfo()返回CURLE_OK只代表调用成功,不代表时间可用,必须同时检查filetime != -1这一条件;- 由于
curl_off_t与time_t的宽度可能不同(尤其在不同平台上),将filetime显式转换为time_t后再交给ctime()是安全的做法; - 若服务器返回的时间在 32 位时间戳范围之外,旧版
CURLINFO_FILETIME得到的long会被钳制为LONG_MAX/LONG_MIN(失去真实精度),而本选项可以原样返回完整值。
六、工程实践:curl 命令行工具自身的 -R/--remote-time
CURLINFO_FILETIME_T并非仅面向库使用者,curl 命令行工具本身就在生产环境中使用它。在 src/tool_operate.c 中,-R/--remote-time选项的实现正是依赖此接口,在文件关闭后把远端时间写回本地文件:
/* File time can only be set _after_ the file has been closed */ if(!result && config->remote_time && outs->regular_file && outs->filename) { /* Ask libcurl if we got a remote file time */ curl_off_t filetime = -1; curl_easy_getinfo(per->curl, CURLINFO_FILETIME_T, &filetime); if(filetime != -1) setfiletime(filetime, outs->filename); }这段代码也印证了两个工程要点:
- 调用时机:对文件的
mtime设置必须发生在文件关闭之后,否则写入操作会覆盖刚设置的时间; - -1 的防御性检查:工具层同样先检查
filetime != -1再执行setfiletime(),避免用“未知时间”污染本地文件属性。
因此,你可以直接用一条命令行体验本选项的效果:
curl -R -o local.html https://example.com/下载完成后,local.html的修改时间会被设置为服务器返回的Last-Modified时间。
七、返回值与错误处理
curl_easy_getinfo()总是返回一个CURLcode来指示成功或失败:
CURLE_OK(0):一切正常,timep指向的变量已被填充(可能为 -1,表示时间未知);- 非零值:发生了错误,具体的错误码说明参见 libcurl-errors 文档 对应的错误码清单。
常见的错误场景与排查思路:
| 现象 | 可能原因 | 处理建议 |
|---|---|---|
| 恒返回 -1 | 未设置CURLOPT_FILETIME | 在curl_easy_perform()前设置curl_easy_setopt(curl, CURLOPT_FILETIME, 1L) |
| 恒返回 -1 | 服务器不发送Last-Modified头或不支持MDTM | 换用支持时间查询的服务器/协议验证 |
| 时间不精确 | 服务器时间粒度或时区处理差异 | 注意返回值是 GMT/UTC 秒,展示时自行做时区换算 |
| 时间被截断 | 误用了CURLINFO_FILETIME+ 32 位long | 改用CURLINFO_FILETIME_T+curl_off_t |
八、总结与参考
CURLINFO_FILETIME_T是 libcurl 面向 64 位时间戳时代的标准做法:以curl_off_t无损承载远端资源的修改时间,规避 32 位long的 2038 年问题,并贯穿 HTTP(Last-Modified头)、FTP(MDTM)、SFTP(mtime属性)等主流协议。使用时的三要素可概括为:先CURLOPT_FILETIME请求、后curl_easy_getinfo读取、务必判-1。
进一步阅读:
- CURLINFO_FILETIME_T 官方文档:本选项的权威定义;
- CURLINFO_FILETIME.md:
long版本对照; - CURLOPT_FILETIME.md:配套的请求选项;
- curl_easy_getinfo.md:查询接口总览;
- lib/getinfo.c:信息存储与返回值实现;
- lib/http.c:HTTP
Last-Modified头解析; - src/tool_operate.c:curl 工具层
--remote-time的实际用法。
【免费下载链接】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),仅供参考