安全使用libcurl的正确姿势

在我们的项目中,数次遇到 libcurl 导致的应用程序崩溃问题,这里总结了一下使用 libcurl 的正确姿势。

 1: #include <curl/curl.h>
 2: #include <stdint.h>
 3: #include <string.h>
 4: 
 5: 
 6: #define RESPONSE_BODY_SIZE 128
 7: 
 8: static size_t write_function(const void *buffer, const size_t size, const size_t nmemb, void *user_p)
 9: {
10:     char* response_body = (char*)user_p;
11:     uint32_t response_body_len = strlen(response_body);
12:     uint32_t len = size*nmemb;
13:     if (len > RESPONSE_BODY_SIZE - response_body_len - 1) {
14:         len = RESPONSE_BODY_SIZE - response_body_len - 1;
15:     }
16:     memcpy(response_body + response_body_len, buffer, len);
17:     return size*nmemb;
18: }
19: 
20: int main(int argc, char *argv[])
21: {
22:     const char* url = "http://www.example.com/dns_servers";
23:     struct curl_slist *headers = NULL;
24:     headers = curl_slist_append(headers, "Content-Type: application/json");
25:     const char* request_body = "{\"host\": \"8.8.8.8\", \"port\": 53}";
26: 
27:     CURL *curl;
28:     CURLcode res;
29:     char response_body[RESPONSE_BODY_SIZE] = {'\0'};
30:     long response_code = 0;
31: 
32:     curl = curl_easy_init();
33:     if(curl) {
34:         curl_easy_setopt(curl, CURLOPT_URL, url);
35:         curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
36:         curl_easy_setopt(curl, CURLOPT_POSTFIELDS, request_body);
37:         curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(request_body));
38:         curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L);
39:         curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
40:         curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_function);
41:         curl_easy_setopt(curl, CURLOPT_WRITEDATA, response_body);
42:         res = curl_easy_perform(curl);
43:         if (res == CURLE_OK) {
44:             res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
45:         }
46:         if(res != CURLE_OK) {
47:             fprintf(stderr, "request to %s error(%d): %s", url, res, curl_easy_strerror(res));
48:         }
49:         curl_easy_cleanup(curl);
50:     }
51: 
52:     curl_slist_free_all(headers);
53:     if (response_code == 201) {
54:         fprintf(stderr, "request to %s successful: %s\n", url, response_body);
55:         return 0;
56:     }
57: 
58:     fprintf(stderr, "request to %s response failed(%ld): %s\n", url, response_code, response_body);
59:     return 1;
60: }

上面的示例代码要注意的地方:

16
buffer不是 \0 结尾的
17
总是返回 size*nmemb
38
总是设置这个选项

libcurl 不支持异步 dns 解析时,会通过 signal 的方式实现 dns 解析设置超时, signal 会导致多线程程序崩溃,后台服务通常都是多线程的,所以应该总是设置这个选项(但是 libcurl 不支持异步 dns 解析时,超时选项将被忽略)。

可以通过运行 curl --version 命令或调用 curl_version 函数查看 libcurl 是否支持异步 dns 解析,调用 curl_version_info 函数还可以获得具体的 c-ares 库版本号。

编译 libcurl 时,通过为 configure 指定 --enable-threaded-resolver--enable-ares 选项启用异步 dns 解析。

44
状态响应码变量必须是 long 类型

否则会由于内存越界导致程序崩溃。

强烈建议 gcc 编译时添加 -Wall 选项, libcurlgcc 提供了类型检查,能够在编译期检查一些类型不匹配的错误,如下编译输出:

install.c: In function 'install':
/usr/include/curl/typecheck-gcc.h:120:36: error: call to '_curl_easy_getinfo_err_long' declared with attribute warning: curl_easy_getinfo expects a pointer to long for this info [-Werror]
         _curl_easy_getinfo_err_long();                                        \
                                    ^
install.c:58:19: note: in expansion of macro 'curl_easy_getinfo'
             res = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
                   ^
cc1: all warnings being treated as errors

c