]> git.ipfire.org Git - thirdparty/pdns.git/blame - pdns/minicurl.cc
Merge pull request #14032 from rgacogne/ddist-192-changelog-secpoll
[thirdparty/pdns.git] / pdns / minicurl.cc
CommitLineData
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
37void 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 50MiniCurl::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
63MiniCurl::~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
72size_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 83size_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
94size_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
106static 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 119void 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 201std::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 216std::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
237void 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
250void 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
263void 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}