Line data Source code
1 : /**
2 : Copyright (c) 2022 Roman Katuntsev <sbkarr@stappler.org>
3 : Copyright (c) 2023 Stappler LLC <admin@stappler.dev>
4 :
5 : Permission is hereby granted, free of charge, to any person obtaining a copy
6 : of this software and associated documentation files (the "Software"), to deal
7 : in the Software without restriction, including without limitation the rights
8 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 : copies of the Software, and to permit persons to whom the Software is
10 : furnished to do so, subject to the following conditions:
11 :
12 : The above copyright notice and this permission notice shall be included in
13 : all copies or substantial portions of the Software.
14 :
15 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 : THE SOFTWARE.
22 : **/
23 :
24 : #include "SPNetworkContext.h"
25 : #include "SPNetworkData.h"
26 : #include "curl/curl.h"
27 :
28 : #ifndef SP_NETWORK_LOG
29 : #define SP_NETWORK_LOG(...)
30 : #endif
31 :
32 : namespace STAPPLER_VERSIONIZED stappler::network {
33 :
34 : #define SP_TERMINATED_DATA(view) (view.terminated()?view.data():view.str<Interface>().data())
35 :
36 : static constexpr auto SP_NETWORK_PROGRESS_TIMEOUT = TimeInterval::microseconds(250000ull);
37 : static constexpr auto s_UserAgent = "Stappler/1 CURL";
38 :
39 : struct CurlHandle;
40 :
41 : SPUNUSED static StringView getCABundle();
42 :
43 : SPUNUSED static CURL * CurlHandle_getHandle(bool reuse, memory::pool_t *pool);
44 : SPUNUSED static void CurlHandle_releaseHandle(CURL *curl, bool reuse, bool success, memory::pool_t *pool);
45 :
46 50 : static size_t _writeDummy(const void *data, size_t size, size_t nmemb, void *userptr) {
47 50 : return size * nmemb;
48 : }
49 :
50 : template <typename Interface>
51 0 : static size_t _writeDebug(CURL *handle, curl_infotype type, char *data, size_t size, void *userptr) {
52 0 : auto task = static_cast<HandleData<Interface> *>(userptr);
53 0 : task->process.debugData.write(data, size);
54 0 : return size;
55 : }
56 :
57 : template <typename Interface>
58 3012 : static size_t _writeData(char *data, size_t size, size_t nmemb, void *userptr) {
59 3012 : auto task = static_cast<HandleData<Interface> *>(userptr);
60 :
61 6024 : return std::visit([&] (auto &&arg) {
62 : using T = std::decay_t<decltype(arg)>;
63 : if constexpr (std::is_same_v<T, typename HandleData<Interface>::IOCallback>) {
64 3012 : return arg(data, size * nmemb);
65 : }
66 0 : return size_t(size * nmemb);
67 6024 : }, task->receive.data);
68 : }
69 :
70 : template <typename Interface>
71 24050 : static size_t _writeHeaders(char *data, size_t size, size_t nmemb, void *userptr) {
72 24050 : auto task = static_cast<HandleData<Interface> *>(userptr);
73 :
74 24050 : StringView reader(data, size * nmemb);
75 24050 : if (!reader.is("\r\n")) {
76 20900 : if (task->send.method != Method::Smtp) {
77 20900 : if (!reader.is("HTTP/")) {
78 17750 : auto name = reader.readUntil<StringView::Chars<':'>>();
79 17750 : reader ++;
80 :
81 17750 : name.trimChars<StringView::WhiteSpace>();
82 17750 : reader.trimChars<StringView::WhiteSpace>();
83 :
84 17750 : auto nameStr = string::tolower<Interface>(name);
85 17750 : auto valueStr = reader.str<Interface>();
86 :
87 17750 : if (task->receive.headerCallback) {
88 250 : task->receive.headerCallback(nameStr, valueStr);
89 : }
90 17750 : task->receive.parsed.emplace(std::move(nameStr), std::move(valueStr));
91 17750 : } else {
92 3150 : reader.skipUntil<StringView::CharGroup<CharGroupId::WhiteSpace>>();
93 3150 : reader.skipUntil<StringView::CharGroup<CharGroupId::Numbers>>();
94 6300 : reader.readInteger().unwrap([&] (int64_t code) {
95 3150 : task->process.responseCode = code;
96 : });
97 : }
98 : }
99 :
100 20900 : task->receive.headers.emplace_back(StringView(data, size).str<Interface>());
101 : }
102 :
103 24050 : return size * nmemb;
104 : }
105 :
106 : template <typename Interface>
107 0 : static size_t _readData(char *data, size_t size, size_t nmemb, void *userptr) {
108 0 : if (userptr != NULL) {
109 0 : auto task = static_cast<HandleData<Interface> *>(userptr);
110 :
111 0 : return std::visit([&] (auto &&arg) {
112 : using T = std::decay_t<decltype(arg)>;
113 : if constexpr (std::is_same_v<T, typename HandleData<Interface>::IOCallback>) {
114 0 : return arg(data, size * nmemb);
115 : } else if constexpr (std::is_same_v<T, typename HandleData<Interface>::Bytes>) {
116 0 : size_t remains = task->send.size;
117 0 : if (size * nmemb <= remains) {
118 0 : memcpy(data, arg.data() + (arg.size() - task->send.size), size * nmemb);
119 0 : task->send.size -= size * nmemb;
120 0 : return size * nmemb;
121 : } else {
122 0 : memcpy(data, arg.data() + (arg.size() - task->send.size), remains);
123 0 : task->send.size = 0;
124 0 : return remains;
125 : }
126 : }
127 0 : return size_t(0);
128 0 : }, task->send.data);
129 : } else {
130 0 : return 0;
131 : }
132 : }
133 :
134 : template <typename Interface>
135 2651 : static int _progress(void *userptr, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) {
136 2651 : auto task = static_cast<HandleData<Interface> *>(userptr);
137 2651 : auto timing = Time::now();
138 :
139 2651 : int uProgress = 0;
140 5302 : if (task->process.uploadProgress && ulnow != task->process.uploadProgressValue
141 5302 : && (!task->process.uploadProgressTiming || (timing - task->process.uploadProgressTiming > SP_NETWORK_PROGRESS_TIMEOUT))) {
142 0 : task->process.uploadProgressValue = ulnow;
143 0 : task->process.uploadProgressTiming = timing;
144 0 : uProgress = task->process.uploadProgress(ultotal, ulnow);
145 : }
146 :
147 2651 : int dProgress = 0;
148 5302 : if (task->process.downloadProgress && dlnow != task->process.downloadProgressValue
149 5302 : && (!task->process.downloadProgressTiming || (timing - task->process.downloadProgressTiming > SP_NETWORK_PROGRESS_TIMEOUT))) {
150 25 : task->process.downloadProgressValue = dlnow;
151 25 : task->process.downloadProgressTiming = timing;
152 25 : return task->process.downloadProgress(dltotal + task->receive.offset, dlnow + task->receive.offset);
153 : }
154 :
155 2626 : if (ultotal == ulnow || ultotal == 0) {
156 2626 : return dProgress;
157 : } else {
158 0 : return uProgress;
159 : }
160 : }
161 :
162 : template <typename Interface>
163 0 : Pair<FILE *, uint64_t> _openFile(const StringView &filename, bool readOnly, bool resume = false) {
164 0 : uint64_t pos = 0;
165 0 : FILE *file = nullptr;
166 0 : auto path = filepath::absolute<Interface>(filename);
167 0 : if (filesystem::exists(path)) {
168 0 : filesystem::Stat stat;
169 0 : if (filesystem::stat(path, stat) && stat.size) {
170 0 : pos = stat.size;
171 0 : if (!readOnly) {
172 0 : if (!resume) {
173 0 : filesystem::remove(path);
174 0 : file = filesystem::native::fopen_fn(path, "w+b");
175 : } else {
176 0 : file = filesystem::native::fopen_fn(path, "a+b");
177 : }
178 : } else {
179 0 : file = filesystem::native::fopen_fn(path, "rb");
180 : }
181 : }
182 : } else {
183 0 : if (!readOnly) {
184 0 : file = filesystem::native::fopen_fn(path, "w+b");
185 : }
186 : }
187 0 : return pair(file, pos);
188 0 : }
189 :
190 : template <typename K, typename T>
191 91425 : inline void SetOpt(bool &check, CURL *curl, K opt, const T &value) {
192 : #ifdef DEBUG
193 91425 : int err = CURLE_OK;
194 91425 : if (check) {
195 91425 : err = curl_easy_setopt(curl, opt, value);
196 91425 : if (err != CURLE_OK) {
197 : SP_NETWORK_LOG("curl_easy_setopt failed: %d %s %s", err, #name, #value);
198 0 : check = false;
199 : }
200 : }
201 : #else
202 : check = (check) ? curl_easy_setopt(curl, opt, value) == CURLE_OK : false;
203 : #endif
204 91425 : }
205 :
206 : template <typename Interface>
207 3025 : static bool _setupCurl(const HandleData<Interface> &iface, CURL *curl, char *errorBuffer) {
208 3025 : bool check = true;
209 :
210 3025 : auto CABundle = getCABundle();
211 3025 : static struct curl_blob blob { (void *)CABundle.data(), CABundle.size(), CURL_BLOB_NOCOPY };
212 :
213 3025 : SetOpt(check, curl, CURLOPT_CAINFO_BLOB, &blob);
214 :
215 3025 : SetOpt(check, curl, CURLOPT_USE_SSL, CURLUSESSL_TRY);
216 :
217 3025 : SetOpt(check, curl, CURLOPT_NOSIGNAL, 1);
218 3025 : SetOpt(check, curl, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_WHATEVER);
219 :
220 3025 : SetOpt(check, curl, CURLOPT_ERRORBUFFER, errorBuffer);
221 3025 : SetOpt(check, curl, CURLOPT_LOW_SPEED_TIME, iface.process.lowSpeedTime);
222 3025 : SetOpt(check, curl, CURLOPT_LOW_SPEED_LIMIT, iface.process.lowSpeedLimit);
223 : //SetOpt(check, curl, CURLOPT_TIMEOUT, SP_NW_READ_TIMEOUT);
224 3025 : SetOpt(check, curl, CURLOPT_CONNECTTIMEOUT, iface.process.connectTimeout);
225 :
226 3025 : if (iface.process.verifyTsl) {
227 3000 : SetOpt(check, curl, CURLOPT_SSL_VERIFYPEER, 1L);
228 3000 : SetOpt(check, curl, CURLOPT_SSL_VERIFYHOST, 2L);
229 : } else {
230 25 : SetOpt(check, curl, CURLOPT_SSL_VERIFYPEER, 0L);
231 25 : SetOpt(check, curl, CURLOPT_SSL_VERIFYHOST, 0L);
232 : }
233 :
234 3025 : SetOpt(check, curl, CURLOPT_URL, iface.send.url.data());
235 3025 : SetOpt(check, curl, CURLOPT_RESUME_FROM, 0); // drop byte-ranged GET
236 :
237 3025 : SetOpt(check, curl, CURLOPT_WRITEFUNCTION, _writeDummy);
238 3025 : SetOpt(check, curl, CURLOPT_WRITEDATA, nullptr);
239 :
240 : /* enable all supported built-in compressions */
241 3025 : SetOpt(check, curl, CURLOPT_ACCEPT_ENCODING, "");
242 :
243 3025 : return check;
244 : }
245 :
246 : template <typename Interface>
247 3025 : static bool _setupDebug(const HandleData<Interface> &iface, CURL *curl, bool debug) {
248 3025 : bool check = true;
249 3025 : if (debug) {
250 0 : SetOpt(check, curl, CURLOPT_VERBOSE, 1);
251 0 : SetOpt(check, curl, CURLOPT_DEBUGFUNCTION, _writeDebug<Interface>);
252 0 : SetOpt(check, curl, CURLOPT_DEBUGDATA, &iface);
253 : }
254 3025 : return check;
255 : }
256 :
257 : template <typename Interface>
258 3025 : static bool _setupHeaders(const HandleData<Interface> &iface, Context<Interface> *ctx,
259 : const typename HandleData<Interface>::HeaderMap &vec, curl_slist **headers) {
260 3025 : bool check = true;
261 3025 : StringView keySign;
262 3025 : if (iface.auth.authMethod == AuthMethod::PKey) {
263 0 : if (auto sign = std::get_if<typename HandleData<Interface>::String>(&iface.auth.data)) {
264 0 : keySign = StringView(*sign);
265 : }
266 : }
267 :
268 3025 : ctx->headersData.reserve(vec.size());
269 3725 : for (auto &it : vec) {
270 700 : if (iface.send.method == Method::Get || iface.send.method == Method::Head || iface.send.method == Method::Delete) {
271 50 : if (it.first == "Content-Type") {
272 0 : continue;
273 : }
274 : }
275 :
276 700 : if (it.first != "Authorization" || keySign.empty()) {
277 700 : ctx->headersData.emplace_back(string::toString<Interface>(it.first, ": ", it.second));
278 : }
279 : }
280 :
281 3025 : if (!keySign.empty()) {
282 0 : ctx->headersData.emplace_back(string::toString<Interface>("Authorization: pkey ", keySign));
283 0 : *headers = curl_slist_append(*headers, string::toString<Interface>("Authorization: pkey ", keySign).data());
284 : }
285 :
286 3725 : for (auto &it : ctx->headersData) {
287 700 : *headers = curl_slist_append(*headers, it.data());
288 : }
289 :
290 3025 : if (!ctx->headersData.empty() || *headers) {
291 700 : SetOpt(check, ctx->curl, CURLOPT_HTTPHEADER, *headers);
292 : }
293 :
294 3025 : SetOpt(check, ctx->curl, CURLOPT_HEADERFUNCTION, _writeHeaders<Interface>);
295 3025 : SetOpt(check, ctx->curl, CURLOPT_HEADERDATA, &iface);
296 :
297 3025 : return check;
298 : }
299 :
300 : template <typename Interface>
301 3025 : static bool _setupUserAgent(const HandleData<Interface> &iface, CURL *curl, const StringView &agent) {
302 3025 : bool check = true;
303 3025 : if (!agent.empty()) {
304 25 : SetOpt(check, curl, CURLOPT_USERAGENT, SP_TERMINATED_DATA(agent));
305 : } else {
306 3000 : SetOpt(check, curl, CURLOPT_USERAGENT, s_UserAgent);
307 : }
308 3025 : return check;
309 : }
310 :
311 : template <typename Interface>
312 3025 : static bool _setupUser(const HandleData<Interface> &iface, CURL *curl,
313 : const StringView &user, const StringView &password, AuthMethod m) {
314 3025 : bool check = true;
315 3025 : if (!user.empty()) {
316 400 : switch (m) {
317 400 : case AuthMethod::Basic:
318 400 : SetOpt(check, curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
319 400 : break;
320 0 : case AuthMethod::Digest:
321 0 : SetOpt(check, curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
322 0 : break;
323 0 : default:
324 0 : return false;
325 : break;
326 : }
327 400 : SetOpt(check, curl, CURLOPT_USERNAME, SP_TERMINATED_DATA(user));
328 400 : if (!password.empty()) {
329 400 : SetOpt(check, curl, CURLOPT_PASSWORD, SP_TERMINATED_DATA(password));
330 : }
331 : }
332 3025 : return check;
333 : }
334 :
335 : template <typename Interface>
336 0 : static bool _setupFrom(const HandleData<Interface> &iface, CURL *curl, const StringView &from) {
337 0 : bool check = true;
338 0 : SetOpt(check, curl, CURLOPT_MAIL_FROM, SP_TERMINATED_DATA(from));
339 0 : return check;
340 : }
341 :
342 : template <typename Interface>
343 0 : static bool _setupRecv(const HandleData<Interface> &iface, CURL *curl,
344 : const typename Interface::template VectorType<typename Interface::StringType> &vec, curl_slist **mailTo) {
345 0 : bool check = true;
346 0 : if (vec.size() > 0) {
347 0 : for (const auto &str : vec) {
348 0 : *mailTo = curl_slist_append(*mailTo, str.c_str());
349 : }
350 0 : SetOpt(check, curl, CURLOPT_MAIL_RCPT, *mailTo);
351 : }
352 0 : return check;
353 : }
354 :
355 : template <typename Interface>
356 3025 : static bool _setupProgress(const HandleData<Interface> &iface, CURL *curl) {
357 3025 : bool check = true;
358 3025 : if (iface.send.method != Method::Head && (iface.process.uploadProgress || iface.process.downloadProgress)) {
359 25 : SetOpt(check, curl, CURLOPT_NOPROGRESS, 0);
360 : } else {
361 3000 : SetOpt(check, curl, CURLOPT_NOPROGRESS, 1);
362 : }
363 3025 : SetOpt(check, curl, CURLOPT_XFERINFOFUNCTION, _progress<Interface>);
364 3025 : SetOpt(check, curl, CURLOPT_XFERINFODATA, &iface);
365 3025 : return check;
366 : }
367 :
368 : template <typename Interface>
369 3025 : static bool _setupCookies(const HandleData<Interface> &iface, CURL *curl, const StringView &cookiePath) {
370 3025 : bool check = true;
371 3025 : if (!cookiePath.empty()) {
372 700 : SetOpt(check, curl, CURLOPT_COOKIEFILE, SP_TERMINATED_DATA(cookiePath));
373 700 : SetOpt(check, curl, CURLOPT_COOKIEJAR, SP_TERMINATED_DATA(cookiePath));
374 : }
375 3025 : return check;
376 : }
377 :
378 : template <typename Interface>
379 3025 : static bool _setupProxy(const HandleData<Interface> &iface, CURL *curl, const StringView &proxy, const StringView &auth) {
380 3025 : bool check = true;
381 3025 : if (!proxy.empty()) {
382 0 : SetOpt(check, curl, CURLOPT_PROXY, SP_TERMINATED_DATA(proxy));
383 : } else {
384 3025 : SetOpt(check, curl, CURLOPT_PROXY, nullptr);
385 : }
386 :
387 3025 : if (!auth.empty()) {
388 0 : SetOpt(check, curl, CURLOPT_PROXYUSERPWD, SP_TERMINATED_DATA(auth));
389 : } else {
390 3025 : SetOpt(check, curl, CURLOPT_PROXYUSERPWD, nullptr);
391 : }
392 :
393 3025 : return true;
394 : }
395 :
396 : template <typename Interface>
397 3025 : static bool _setupReceive(HandleData<Interface> &iface, CURL *curl, FILE * & inputFile, uint64_t &inputPos) {
398 3025 : bool check = true;
399 3025 : if (iface.send.method != Method::Head) {
400 6000 : std::visit([&] (auto &&arg) {
401 : using T = std::decay_t<decltype(arg)>;
402 : if constexpr (std::is_same_v<T, typename HandleData<Interface>::String>) {
403 0 : iface.receive.offset = 0;
404 0 : std::tie(inputFile, inputPos) = _openFile<Interface>(arg, false, iface.receive.resumeDownload);
405 0 : if (inputFile) {
406 0 : SetOpt(check, curl, CURLOPT_WRITEFUNCTION, (void *)NULL);
407 0 : SetOpt(check, curl, CURLOPT_WRITEDATA, inputFile);
408 0 : if (inputPos != 0 && iface.receive.resumeDownload) {
409 0 : iface.receive.offset = inputPos;
410 0 : SetOpt(check, curl, CURLOPT_RESUME_FROM_LARGE, inputPos);
411 : }
412 : }
413 : } else if constexpr (std::is_same_v<T, typename HandleData<Interface>::IOCallback>) {
414 2850 : SetOpt(check, curl, CURLOPT_WRITEFUNCTION, _writeData<Interface>);
415 2850 : SetOpt(check, curl, CURLOPT_WRITEDATA, &iface);
416 2850 : if (iface.receive.offset > 0) {
417 0 : SetOpt(check, curl, CURLOPT_RESUME_FROM_LARGE, iface.receive.offset);
418 : }
419 : }
420 3000 : }, iface.receive.data);
421 : }
422 3025 : return check;
423 : }
424 :
425 : template <typename Interface>
426 2200 : static bool _setupMethodGet(const HandleData<Interface> &iface, CURL *curl) {
427 2200 : bool check = true;
428 2200 : SetOpt(check, curl, CURLOPT_HTTPGET, 1);
429 2200 : SetOpt(check, curl, CURLOPT_FOLLOWLOCATION, 1);
430 2200 : return check;
431 : }
432 :
433 : template <typename Interface>
434 25 : static bool _setupMethodHead(const HandleData<Interface> &iface, CURL *curl) {
435 25 : bool check = true;
436 25 : SetOpt(check, curl, CURLOPT_HTTPGET, 1);
437 25 : SetOpt(check, curl, CURLOPT_FOLLOWLOCATION, 1);
438 25 : SetOpt(check, curl, CURLOPT_NOBODY, 1);
439 25 : return check;
440 : }
441 :
442 : template <typename Interface>
443 650 : static void _setupSendData(bool &check, const HandleData<Interface> &iface, CURL *curl, FILE * & outputFile) {
444 4550 : std::visit([&] (auto &&arg) {
445 : using T = std::decay_t<decltype(arg)>;
446 : if constexpr (std::is_same_v<T, typename HandleData<Interface>::String>) {
447 : size_t size;
448 0 : std::tie(outputFile, size) = _openFile<Interface>(arg, true);
449 0 : if (outputFile) {
450 0 : SetOpt(check, curl, CURLOPT_READFUNCTION, (void *)NULL);
451 0 : SetOpt(check, curl, CURLOPT_READDATA, outputFile);
452 0 : SetOpt(check, curl, CURLOPT_POSTFIELDSIZE, size);
453 0 : SetOpt(check, curl, CURLOPT_INFILESIZE, size);
454 : }
455 : } else if constexpr (std::is_same_v<T, typename HandleData<Interface>::IOCallback>) {
456 0 : SetOpt(check, curl, CURLOPT_READFUNCTION, _readData<Interface>);
457 0 : SetOpt(check, curl, CURLOPT_READDATA, &iface);
458 0 : SetOpt(check, curl, CURLOPT_POSTFIELDSIZE, iface.send.size);
459 0 : SetOpt(check, curl, CURLOPT_INFILESIZE, iface.send.size);
460 : } else if constexpr (std::is_same_v<T, typename HandleData<Interface>::Bytes>) {
461 650 : SetOpt(check, curl, CURLOPT_POSTFIELDS, arg.data());
462 650 : SetOpt(check, curl, CURLOPT_POSTFIELDSIZE, arg.size());
463 650 : SetOpt(check, curl, CURLOPT_INFILESIZE, arg.size());
464 : }
465 650 : }, iface.send.data);
466 650 : }
467 :
468 :
469 : template <typename Interface>
470 500 : static bool _setupMethodPost(const HandleData<Interface> &iface, CURL *curl, FILE * & outputFile) {
471 500 : bool check = true;
472 500 : SetOpt(check, curl, CURLOPT_POST, 1);
473 :
474 500 : SetOpt(check, curl, CURLOPT_READFUNCTION, (void *)NULL);
475 500 : SetOpt(check, curl, CURLOPT_READDATA, (void *)NULL);
476 500 : SetOpt(check, curl, CURLOPT_POSTFIELDS, (void *)NULL);
477 500 : SetOpt(check, curl, CURLOPT_POSTFIELDSIZE, 0);
478 :
479 500 : _setupSendData(check, iface, curl, outputFile);
480 :
481 500 : return check;
482 : }
483 :
484 : template <typename Interface>
485 150 : static bool _setupMethodPut(const HandleData<Interface> &iface, CURL *curl, FILE * & outputFile) {
486 150 : bool check = true;
487 :
488 150 : SetOpt(check, curl, CURLOPT_UPLOAD, 1);
489 150 : SetOpt(check, curl, CURLOPT_READFUNCTION, (void*) NULL);
490 150 : SetOpt(check, curl, CURLOPT_READDATA, (void*) NULL);
491 150 : SetOpt(check, curl, CURLOPT_CUSTOMREQUEST, "PUT");
492 :
493 150 : _setupSendData(check, iface, curl, outputFile);
494 :
495 150 : return check;
496 : }
497 :
498 : template <typename Interface>
499 150 : static bool _setupMethodDelete(const HandleData<Interface> &iface, CURL *curl) {
500 150 : bool check = true;
501 150 : SetOpt(check, curl, CURLOPT_FOLLOWLOCATION, 1);
502 150 : SetOpt(check, curl, CURLOPT_CUSTOMREQUEST, "DELETE");
503 150 : return check;
504 : }
505 :
506 : template <typename Interface>
507 0 : static bool _setupMethodSmpt(const HandleData<Interface> &iface, CURL *curl, FILE * & outputFile) {
508 0 : bool check = true;
509 :
510 0 : SetOpt(check, curl, CURLOPT_UPLOAD, 1);
511 0 : SetOpt(check, curl, CURLOPT_READFUNCTION, (void *)NULL);
512 0 : SetOpt(check, curl, CURLOPT_READDATA, (void *)NULL);
513 0 : SetOpt(check, curl, CURLOPT_INFILESIZE, 0);
514 :
515 0 : _setupSendData(check, iface, curl, outputFile);
516 :
517 0 : SetOpt(check, curl, CURLOPT_USE_SSL, CURLUSESSL_ALL);
518 0 : return check;
519 : }
520 :
521 : template <typename Interface>
522 3025 : bool prepare(HandleData<Interface> &iface, Context<Interface> *ctx, const Callback<bool(CURL *)> &onBeforePerform) {
523 3025 : if (iface.process.debug) {
524 0 : iface.process.debugData = typename Interface::StringStreamType();
525 : }
526 :
527 3025 : iface.receive.parsed.clear();
528 3025 : iface.receive.headers.clear();
529 :
530 3025 : ctx->handle = &iface;
531 :
532 3025 : bool check = true;
533 :
534 3025 : if (ctx->share) {
535 0 : SetOpt(check, ctx->curl, CURLOPT_SHARE, iface.process.sharedHandle);
536 3025 : } else if (iface.process.shared) {
537 0 : if (!iface.process.sharedHandle) {
538 0 : iface.process.sharedHandle = curl_share_init();
539 0 : curl_share_setopt((CURLSH *)iface.process.sharedHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_COOKIE);
540 0 : curl_share_setopt((CURLSH *)iface.process.sharedHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_DNS);
541 0 : curl_share_setopt((CURLSH *)iface.process.sharedHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
542 0 : curl_share_setopt((CURLSH *)iface.process.sharedHandle, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
543 : }
544 0 : SetOpt(check, ctx->curl, CURLOPT_COOKIEFILE, "/undefined");
545 0 : SetOpt(check, ctx->curl, CURLOPT_SHARE, iface.process.sharedHandle);
546 : } else {
547 3025 : SetOpt(check, ctx->curl, CURLOPT_SHARE, nullptr);
548 : }
549 :
550 3025 : check = (check) ? _setupCurl(iface, ctx->curl, ctx->error.data()) : false;
551 3025 : check = (check) ? _setupDebug(iface, ctx->curl, iface.process.debug) : false;
552 3025 : check = (check) ? _setupHeaders(iface, ctx, iface.send.headers, &ctx->headers) : false;
553 3025 : check = (check) ? _setupUserAgent(iface, ctx->curl, iface.send.userAgent) : false;
554 3025 : if (auto u = std::get_if<Pair<typename HandleData<Interface>::String, typename HandleData<Interface>::String>>(&iface.auth.data)) {
555 3025 : check = (check) ? _setupUser(iface, ctx->curl, u->first, u->second, iface.auth.authMethod) : false;
556 : }
557 3025 : check = (check) ? _setupProgress(iface, ctx->curl) : false;
558 3025 : check = (check) ? _setupCookies(iface, ctx->curl, iface.process.cookieFile) : false;
559 3025 : check = (check) ? _setupProxy(iface, ctx->curl, iface.auth.proxyAddress, iface.auth.proxyAuth) : false;
560 3025 : check = (check) ? _setupReceive(iface, ctx->curl, ctx->inputFile, ctx->inputPos) : false;
561 :
562 3025 : switch (iface.send.method) {
563 2200 : case Method::Get:
564 2200 : check = (check) ? _setupMethodGet(iface, ctx->curl) : false;
565 2200 : break;
566 25 : case Method::Head:
567 25 : check = (check) ? _setupMethodHead(iface, ctx->curl) : false;
568 25 : break;
569 500 : case Method::Post:
570 500 : check = (check) ? _setupMethodPost(iface, ctx->curl, ctx->outputFile) : false;
571 500 : break;
572 150 : case Method::Put:
573 150 : check = (check) ? _setupMethodPut(iface, ctx->curl, ctx->outputFile) : false;
574 150 : break;
575 150 : case Method::Delete:
576 150 : check = (check) ? _setupMethodDelete(iface, ctx->curl) : false;
577 150 : break;
578 0 : case Method::Smtp:
579 0 : check = (check) ? _setupRecv(iface, ctx->curl, iface.send.recipients, &ctx->mailTo) : false;
580 0 : check = (check) ? _setupFrom(iface, ctx->curl, iface.send.from) : false;
581 0 : check = (check) ? _setupMethodSmpt(iface, ctx->curl, ctx->outputFile) : false;
582 0 : break;
583 0 : default:
584 0 : break;
585 : }
586 :
587 3025 : if (!check) {
588 0 : if (!iface.process.silent) {
589 0 : log::error("CURL", "Fail to setup: ", iface.send.url.data());
590 : }
591 0 : return false;
592 : }
593 :
594 3025 : if (onBeforePerform) {
595 0 : if (!onBeforePerform(ctx->curl)) {
596 0 : if (!iface.process.silent) {
597 0 : log::error("CURL", "onBeforePerform failed");
598 : }
599 0 : return false;
600 : }
601 : }
602 :
603 3025 : iface.process.debugData.clear();
604 3025 : iface.receive.parsed.clear();
605 3025 : iface.receive.headers.clear();
606 3025 : return true;
607 : }
608 :
609 : template <typename Interface>
610 3025 : bool finalize(HandleData<Interface> &iface, Context<Interface> *ctx, const Callback<bool(CURL *)> &onAfterPerform) {
611 3025 : iface.process.errorCode = ctx->code;
612 3025 : if (ctx->headers) {
613 700 : curl_slist_free_all(ctx->headers);
614 700 : ctx->headers = nullptr;
615 : }
616 :
617 3025 : if (ctx->mailTo) {
618 0 : curl_slist_free_all(ctx->mailTo);
619 0 : ctx->mailTo = nullptr;
620 : }
621 :
622 3025 : if (CURLE_RANGE_ERROR == iface.process.errorCode && iface.send.method == Method::Get) {
623 0 : size_t allowedRange = size_t(iface.getReceivedHeaderInt("X-Range"));
624 0 : if (allowedRange == ctx->inputPos) {
625 0 : if (!iface.process.silent) {
626 0 : log::warn("CURL", "Get 0-range is not an error, fixed error code to CURLE_OK");
627 : }
628 0 : ctx->success = true;
629 0 : iface.process.errorCode = CURLE_OK;
630 : }
631 : }
632 :
633 3025 : if (CURLE_OK == iface.process.errorCode) {
634 3025 : iface.process.performed = true;
635 3025 : if (iface.send.method != Method::Smtp) {
636 3025 : const char *ct = NULL;
637 3025 : long code = 200;
638 :
639 3025 : curl_easy_getinfo(ctx->curl, CURLINFO_RESPONSE_CODE, &code);
640 3025 : curl_easy_getinfo(ctx->curl, CURLINFO_CONTENT_TYPE, &ct);
641 3025 : if (ct) {
642 2975 : iface.receive.contentType = ct;
643 : #if LINUX
644 2975 : if (ctx->inputFile && !iface.receive.contentType.empty()) {
645 0 : fflush(ctx->inputFile);
646 0 : network_setUserAttributes(ctx->inputFile, iface.receive.contentType, Time::microseconds(iface.getReceivedHeaderInt("X-FileModificationTime")));
647 0 : fclose(ctx->inputFile);
648 0 : ctx->inputFile = nullptr;
649 : }
650 : #endif
651 : }
652 :
653 3025 : iface.process.responseCode = (long) code;
654 :
655 : SP_NETWORK_LOG("performed: %d %s %ld", (int) _method, _url.c_str(), _responseCode);
656 :
657 3025 : if (iface.process.responseCode == 416) {
658 0 : size_t allowedRange = size_t(iface.getReceivedHeaderInt("X-Range"));
659 0 : if (allowedRange == ctx->inputPos) {
660 0 : iface.process.responseCode = 200;
661 0 : if (!iface.process.silent) {
662 0 : log::warn("CURL", iface.send.url, ": Get 0-range is not an error, fixed response code to 200");
663 : }
664 : }
665 : }
666 :
667 3025 : if (iface.process.responseCode >= 200 && iface.process.responseCode < 400) {
668 2925 : ctx->success = true;
669 : } else {
670 100 : ctx->success = false;
671 : }
672 : } else {
673 0 : ctx->success = true;
674 : }
675 : } else {
676 0 : if (!iface.process.silent) {
677 0 : log::format(log::Error, "CURL", "fail to perform %s: (%ld) %s", iface.send.url.data(), iface.process.errorCode, ctx->error.data());
678 : }
679 0 : iface.process.error = ctx->error.data();
680 0 : if (iface.process.debug) {
681 0 : std::visit([&] (auto &&arg) {
682 : using T = std::decay_t<decltype(arg)>;
683 : if constexpr (std::is_same_v<T, typename HandleData<Interface>::String>) {
684 0 : std::cout << "Input file: " << arg << "\n";
685 : }
686 0 : }, iface.receive.data);
687 : }
688 0 : ctx->success = false;
689 : }
690 :
691 3025 : if (!iface.process.cookieFile.empty()) {
692 700 : auto it = iface.receive.parsed.find("set-cookie");
693 700 : if (it != iface.receive.parsed.end()) {
694 75 : iface.process.invalidate = true;
695 : }
696 : }
697 :
698 3025 : if (onAfterPerform) {
699 0 : if (!onAfterPerform(ctx->curl)) {
700 0 : ctx->success = false;
701 : }
702 : }
703 :
704 3025 : if (ctx->inputFile) {
705 0 : fflush(ctx->inputFile);
706 0 : fclose(ctx->inputFile);
707 0 : ctx->inputFile = nullptr;
708 : }
709 3025 : if (ctx->outputFile) {
710 0 : fclose(ctx->outputFile);
711 0 : ctx->outputFile = nullptr;
712 : }
713 3025 : return ctx->success;
714 : }
715 :
716 : template <typename Interface>
717 3000 : static bool _perform(Context<Interface> &ctx, HandleData<Interface> &iface,
718 : const Callback<bool(CURL *)> &onBeforePerform, const Callback<bool(CURL *)> &onAfterPerform) {
719 :
720 3000 : iface.process.performed = false;
721 3000 : iface.process.errorCode = CURLE_OK;
722 3000 : iface.process.responseCode = -1;
723 :
724 3000 : if (!ctx.curl) {
725 0 : return false;
726 : }
727 :
728 3000 : if (!prepare(iface, &ctx, onBeforePerform)) {
729 0 : return false;
730 : }
731 :
732 3000 : ctx.code = curl_easy_perform(ctx.curl);
733 3000 : return finalize(iface, &ctx, onAfterPerform);
734 : }
735 :
736 : template <>
737 0 : bool perform(HandleData<memory::PoolInterface> &iface, const Callback<bool(CURL *)> &onBeforePerform, const Callback<bool(CURL *)> &onAfterPerform) {
738 0 : auto p = memory::pool::acquire();
739 0 : Context<memory::PoolInterface> ctx;
740 0 : ctx.curl = CurlHandle_getHandle(iface.process.reuse, p);
741 0 : auto ret = _perform<memory::PoolInterface>(ctx, iface, onBeforePerform, onAfterPerform);
742 0 : CurlHandle_releaseHandle(ctx.curl, iface.process.reuse, !iface.process.invalidate && ctx.code == CURLE_OK, p);
743 0 : return ret;
744 0 : }
745 :
746 : template <>
747 3000 : bool perform(HandleData<memory::StandartInterface> &iface, const Callback<bool(CURL *)> &onBeforePerform, const Callback<bool(CURL *)> &onAfterPerform) {
748 3000 : Context<memory::StandartInterface> ctx;
749 3000 : ctx.curl = CurlHandle_getHandle(iface.process.reuse, nullptr);
750 3000 : auto ret = _perform<memory::StandartInterface>(ctx, iface, onBeforePerform, onAfterPerform);
751 3000 : CurlHandle_releaseHandle(ctx.curl, iface.process.reuse, !iface.process.invalidate && ctx.code == CURLE_OK, nullptr);
752 3000 : return ret;
753 3000 : }
754 :
755 : }
|