]>
Commit | Line | Data |
---|---|---|
eab9b173 CHB |
1 | /* |
2 | * MIT License | |
3 | * | |
79fb4337 | 4 | * Copyright (c) 2018-2019 powerdns.com bv |
eab9b173 CHB |
5 | * |
6 | * Permission is hereby granted, free of charge, to any person obtaining a copy | |
7 | * of this software and associated documentation files (the "Software"), to deal | |
8 | * in the Software without restriction, including without limitation the rights | |
9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
10 | * copies of the Software, and to permit persons to whom the Software is | |
11 | * furnished to do so, subject to the following conditions: | |
12 | * | |
13 | * The above copyright notice and this permission notice shall be included in all | |
14 | * copies or substantial portions of the Software. | |
15 | * | |
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
22 | * SOFTWARE. | |
23 | */ | |
24 | ||
5f7d5c56 | 25 | #include "minicurl.hh" |
5f7d5c56 | 26 | #include <stdexcept> |
a48e03da | 27 | #include <boost/format.hpp> |
5f7d5c56 | 28 | |
804880a0 RG |
29 | #ifdef CURL_STRICTER |
30 | #define getCURLPtr(x) \ | |
31 | x.get() | |
32 | #else | |
33 | #define getCURLPtr(x) \ | |
34 | x | |
35 | #endif | |
36 | ||
8340b048 RG |
37 | void MiniCurl::init() |
38 | { | |
39 | static std::atomic_flag s_init = ATOMIC_FLAG_INIT; | |
40 | ||
41 | if (s_init.test_and_set()) | |
42 | return; | |
43 | ||
44 | CURLcode code = curl_global_init(CURL_GLOBAL_ALL); | |
45 | if (code != 0) { | |
46 | throw std::runtime_error("Error initializing libcurl"); | |
47 | } | |
48 | } | |
49 | ||
63dfa8df | 50 | MiniCurl::MiniCurl(const string& useragent) |
5f7d5c56 | 51 | { |
804880a0 RG |
52 | #ifdef CURL_STRICTER |
53 | d_curl = std::unique_ptr<CURL, decltype(&curl_easy_cleanup)>(curl_easy_init(), curl_easy_cleanup); | |
54 | #else | |
5f7d5c56 | 55 | d_curl = curl_easy_init(); |
804880a0 | 56 | #endif |
8340b048 RG |
57 | if (d_curl == nullptr) { |
58 | throw std::runtime_error("Error creating a MiniCurl session"); | |
63dfa8df | 59 | } |
804880a0 | 60 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_USERAGENT, useragent.c_str()); |
5f7d5c56 | 61 | } |
62 | ||
63 | MiniCurl::~MiniCurl() | |
64 | { | |
804880a0 RG |
65 | clearHeaders(); |
66 | clearHostsList(); | |
67 | #ifndef CURL_STRICTER | |
5f7d5c56 | 68 | curl_easy_cleanup(d_curl); |
804880a0 | 69 | #endif |
5f7d5c56 | 70 | } |
71 | ||
72 | size_t MiniCurl::write_callback(char *ptr, size_t size, size_t nmemb, void *userdata) | |
73 | { | |
0e524ab0 CHB |
74 | if (userdata != nullptr) { |
75 | MiniCurl* us = static_cast<MiniCurl*>(userdata); | |
76 | us->d_data.append(ptr, size * nmemb); | |
77 | return size * nmemb; | |
78 | } | |
79 | return 0; | |
80 | } | |
81 | ||
062be76e | 82 | #if defined(LIBCURL_VERSION_NUM) && LIBCURL_VERSION_NUM >= 0x072000 // 7.32.0 |
d73de874 | 83 | size_t MiniCurl::progress_callback(void *clientp, curl_off_t /* dltotal */, curl_off_t dlnow, curl_off_t /* ultotal */, curl_off_t /* ulnow */) |
0e524ab0 CHB |
84 | { |
85 | if (clientp != nullptr) { | |
86 | MiniCurl* us = static_cast<MiniCurl*>(clientp); | |
87 | if (us->d_byteslimit > 0 && static_cast<size_t>(dlnow) > us->d_byteslimit) { | |
88 | return static_cast<size_t>(dlnow); | |
89 | } | |
90 | } | |
91 | return 0; | |
5f7d5c56 | 92 | } |
062be76e CHB |
93 | #else |
94 | size_t MiniCurl::progress_callback(void *clientp, double dltotal, double dlnow, double ultotal, double ulnow) | |
95 | { | |
96 | if (clientp != nullptr) { | |
97 | MiniCurl* us = static_cast<MiniCurl*>(clientp); | |
98 | if (us->d_byteslimit > 0 && dlnow > static_cast<double>(us->d_byteslimit)) { | |
99 | return static_cast<size_t>(dlnow); | |
100 | } | |
101 | } | |
102 | return 0; | |
103 | } | |
104 | #endif | |
5f7d5c56 | 105 | |
106 | static string extractHostFromURL(const std::string& url) | |
107 | { | |
108 | auto pos = url.find("://"); | |
109 | if(pos == string::npos) | |
110 | throw runtime_error("Can't find host part of '"+url+"'"); | |
111 | pos += 3; | |
112 | auto endpos = url.find('/', pos); | |
113 | if(endpos == string::npos) | |
114 | return url.substr(pos); | |
115 | ||
116 | return url.substr(pos, endpos-pos); | |
117 | } | |
118 | ||
3bb98d97 | 119 | void MiniCurl::setupURL(const std::string& str, const ComboAddress* rem, const ComboAddress* src, int timeout, size_t byteslimit, [[maybe_unused]] bool fastopen, bool verify) |
5f7d5c56 | 120 | { |
0fe3a254 RG |
121 | if (!d_fresh) { |
122 | curl_easy_reset(getCURLPtr(d_curl)); | |
123 | } | |
124 | else { | |
125 | d_fresh = false; | |
126 | } | |
127 | ||
128 | clearHostsList(); | |
129 | ||
804880a0 | 130 | if (rem) { |
1bc56192 | 131 | struct curl_slist *hostlist = nullptr; // THIS SHOULD BE FREED |
5f7d5c56 | 132 | |
eab9b173 | 133 | // url = http://hostname.enzo/url |
5f7d5c56 | 134 | string host4=extractHostFromURL(str); |
1bc56192 CHB |
135 | // doest the host contain port indication |
136 | std::size_t found = host4.find(':'); | |
137 | vector<uint16_t> ports{80, 443}; | |
138 | if (found != std::string::npos) { | |
139 | int port = std::stoi(host4.substr(found + 1)); | |
140 | if (port <= 0 || port > 65535) | |
141 | throw std::overflow_error("Invalid port number"); | |
142 | ports = {(uint16_t)port}; | |
143 | host4 = host4.substr(0, found); | |
144 | } | |
145 | ||
146 | for (const auto& port : ports) { | |
147 | string hcode = boost::str(boost::format("%s:%u:%s") % host4 % port % rem->toString()); | |
148 | hostlist = curl_slist_append(hostlist, hcode.c_str()); | |
149 | } | |
5f7d5c56 | 150 | |
804880a0 RG |
151 | #ifdef CURL_STRICTER |
152 | d_host_list = std::unique_ptr<struct curl_slist, decltype(&curl_slist_free_all)>(hostlist, curl_slist_free_all); | |
153 | #else | |
154 | d_host_list = hostlist; | |
155 | #endif | |
156 | ||
157 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_RESOLVE, getCURLPtr(d_host_list)); | |
5f7d5c56 | 158 | } |
25bcfaec | 159 | if(src) { |
804880a0 | 160 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_INTERFACE, src->toString().c_str()); |
25bcfaec | 161 | } |
804880a0 | 162 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_FOLLOWLOCATION, true); |
c390f850 | 163 | |
c6bf4c23 | 164 | /* only allow HTTP and HTTPS */ |
c390f850 | 165 | #if defined(LIBCURL_VERSION_NUM) && LIBCURL_VERSION_NUM >= 0x075500 // 7.85.0 |
2576ed75 RG |
166 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_PROTOCOLS_STR, "http,https"); |
167 | #else | |
804880a0 | 168 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS); |
2576ed75 | 169 | #endif |
c390f850 | 170 | |
804880a0 RG |
171 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_SSL_VERIFYPEER, verify); |
172 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_SSL_VERIFYHOST, verify ? 2 : 0); | |
173 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_FAILONERROR, true); | |
174 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_URL, str.c_str()); | |
175 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_WRITEFUNCTION, write_callback); | |
176 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_WRITEDATA, this); | |
0e524ab0 CHB |
177 | |
178 | d_byteslimit = byteslimit; | |
179 | if (d_byteslimit > 0) { | |
180 | /* enable progress meter */ | |
804880a0 | 181 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_NOPROGRESS, 0L); |
062be76e | 182 | #if defined(LIBCURL_VERSION_NUM) && LIBCURL_VERSION_NUM >= 0x072000 // 7.32.0 |
804880a0 RG |
183 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_XFERINFOFUNCTION, progress_callback); |
184 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_XFERINFODATA, this); | |
062be76e | 185 | #else |
804880a0 RG |
186 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_PROGRESSFUNCTION, progress_callback); |
187 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_PROGRESSDATA, this); | |
062be76e | 188 | #endif |
0e524ab0 CHB |
189 | } |
190 | ||
804880a0 | 191 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_TIMEOUT, static_cast<long>(timeout)); |
b2b3b79d O |
192 | #if defined(CURL_AT_LEAST_VERSION) |
193 | #if CURL_AT_LEAST_VERSION(7, 49, 0) && defined(__linux__) | |
804880a0 | 194 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_TCP_FASTOPEN, fastopen); |
b2b3b79d O |
195 | #endif |
196 | #endif | |
6ce74194 | 197 | clearHeaders(); |
5f7d5c56 | 198 | d_data.clear(); |
199 | } | |
8340b048 | 200 | |
3bb98d97 | 201 | std::string MiniCurl::getURL(const std::string& str, const ComboAddress* rem, const ComboAddress* src, int timeout, [[maybe_unused]] bool fastopen, bool verify, size_t byteslimit) |
5f7d5c56 | 202 | { |
0e524ab0 | 203 | setupURL(str, rem, src, timeout, byteslimit, fastopen, verify); |
804880a0 | 204 | auto res = curl_easy_perform(getCURLPtr(d_curl)); |
5f7d5c56 | 205 | long http_code = 0; |
804880a0 | 206 | curl_easy_getinfo(getCURLPtr(d_curl), CURLINFO_RESPONSE_CODE, &http_code); |
5f7d5c56 | 207 | |
0e524ab0 | 208 | if ((res != CURLE_OK && res != CURLE_ABORTED_BY_CALLBACK) || http_code != 200) { |
5f7d5c56 | 209 | throw std::runtime_error("Unable to retrieve URL ("+std::to_string(http_code)+"): "+string(curl_easy_strerror(res))); |
210 | } | |
0e524ab0 | 211 | std::string ret = d_data; |
5f7d5c56 | 212 | d_data.clear(); |
213 | return ret; | |
214 | } | |
215 | ||
841128ba | 216 | std::string MiniCurl::postURL(const std::string& str, const std::string& postdata, MiniCurlHeaders& headers, int timeout, bool fastopen, bool verify) |
5f7d5c56 | 217 | { |
0e524ab0 | 218 | setupURL(str, nullptr, nullptr, timeout, 0, fastopen, verify); |
6ce74194 | 219 | setHeaders(headers); |
804880a0 RG |
220 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_POSTFIELDSIZE, postdata.size()); |
221 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_POSTFIELDS, postdata.c_str()); | |
5f7d5c56 | 222 | |
804880a0 | 223 | auto res = curl_easy_perform(getCURLPtr(d_curl)); |
6ce74194 PD |
224 | |
225 | long http_code = 0; | |
804880a0 | 226 | curl_easy_getinfo(getCURLPtr(d_curl), CURLINFO_RESPONSE_CODE, &http_code); |
6ce74194 | 227 | |
eab9b173 | 228 | if(res != CURLE_OK) |
6ce74194 | 229 | throw std::runtime_error("Unable to post URL ("+std::to_string(http_code)+"): "+string(curl_easy_strerror(res))); |
5f7d5c56 | 230 | |
231 | std::string ret=d_data; | |
232 | ||
233 | d_data.clear(); | |
234 | return ret; | |
235 | } | |
6ce74194 PD |
236 | |
237 | void MiniCurl::clearHeaders() | |
238 | { | |
239 | if (d_curl) { | |
804880a0 RG |
240 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_HTTPHEADER, nullptr); |
241 | #ifdef CURL_STRICTER | |
242 | d_header_list.reset(); | |
243 | #else | |
244 | curl_slist_free_all(d_header_list); | |
245 | d_header_list = nullptr; | |
246 | #endif | |
247 | } | |
248 | } | |
249 | ||
250 | void MiniCurl::clearHostsList() | |
251 | { | |
252 | if (d_curl) { | |
253 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_RESOLVE, nullptr); | |
254 | #ifdef CURL_STRICTER | |
255 | d_host_list.reset(); | |
256 | #else | |
257 | curl_slist_free_all(d_host_list); | |
258 | d_host_list = nullptr; | |
259 | #endif | |
6ce74194 PD |
260 | } |
261 | } | |
262 | ||
263 | void MiniCurl::setHeaders(const MiniCurlHeaders& headers) | |
264 | { | |
265 | if (d_curl) { | |
266 | for (auto& header : headers) { | |
267 | std::stringstream header_ss; | |
268 | header_ss << header.first << ": " << header.second; | |
804880a0 RG |
269 | #ifdef CURL_STRICTER |
270 | struct curl_slist * list = nullptr; | |
271 | if (d_header_list) { | |
272 | list = d_header_list.release(); | |
273 | } | |
274 | d_header_list = std::unique_ptr<struct curl_slist, decltype(&curl_slist_free_all)>(curl_slist_append(list, header_ss.str().c_str()), curl_slist_free_all); | |
275 | #else | |
6ce74194 | 276 | d_header_list = curl_slist_append(d_header_list, header_ss.str().c_str()); |
804880a0 | 277 | #endif |
6ce74194 | 278 | } |
804880a0 | 279 | curl_easy_setopt(getCURLPtr(d_curl), CURLOPT_HTTPHEADER, getCURLPtr(d_header_list)); |
6ce74194 PD |
280 | } |
281 | } |