1 #include "Http.hpp"
2
3 #include <cstdlib>
4 #include <functional>
5 #include <thread>
6 #include <deque>
7 #include <sstream>
8 #include <exception>
9 #include <boost/filesystem/fstream.hpp>
10 #include <boost/filesystem/path.hpp>
11 #include <boost/filesystem.hpp>
12 #include <boost/format.hpp>
13 #include <boost/log/trivial.hpp>
14
15 #include <curl/curl.h>
16
17 #ifdef OPENSSL_CERT_OVERRIDE
18 #include <openssl/x509.h>
19 #endif
20
21 #include <libslic3r/libslic3r.h>
22 #include <libslic3r/Utils.hpp>
23 #include <slic3r/GUI/I18N.hpp>
24 #include <slic3r/GUI/format.hpp>
25
26 namespace fs = boost::filesystem;
27
28
29 namespace Slic3r {
30
31
32 // Private
33
34 struct CurlGlobalInit
35 {
36 static std::unique_ptr<CurlGlobalInit> instance;
37 std::string message;
38
CurlGlobalInitSlic3r::CurlGlobalInit39 CurlGlobalInit()
40 {
41 #ifdef OPENSSL_CERT_OVERRIDE // defined if SLIC3R_STATIC=ON
42
43 // Look for a set of distro specific directories. Don't change the
44 // order: https://bugzilla.redhat.com/show_bug.cgi?id=1053882
45 static const char * CA_BUNDLES[] = {
46 "/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL 6
47 "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc.
48 "/usr/share/ssl/certs/ca-bundle.crt",
49 "/usr/local/share/certs/ca-root-nss.crt", // FreeBSD
50 "/etc/ssl/cert.pem",
51 "/etc/ssl/ca-bundle.pem" // OpenSUSE Tumbleweed
52 };
53
54 namespace fs = boost::filesystem;
55 // Env var name for the OpenSSL CA bundle (SSL_CERT_FILE nomally)
56 const char *const SSL_CA_FILE = X509_get_default_cert_file_env();
57 const char * ssl_cafile = ::getenv(SSL_CA_FILE);
58
59 if (!ssl_cafile)
60 ssl_cafile = X509_get_default_cert_file();
61
62 int replace = true;
63 if (!ssl_cafile || !fs::exists(fs::path(ssl_cafile))) {
64 const char * bundle = nullptr;
65 for (const char * b : CA_BUNDLES) {
66 if (fs::exists(fs::path(b))) {
67 ::setenv(SSL_CA_FILE, bundle = b, replace);
68 break;
69 }
70 }
71
72 if (!bundle)
73 message = _u8L("Could not detect system SSL certificate store. "
74 "PrusaSlicer will be unable to establish secure "
75 "network connections.");
76 else
77 message = Slic3r::GUI::format(
78 _L("PrusaSlicer detected system SSL certificate store in: %1%"),
79 bundle);
80
81 message += "\n" + Slic3r::GUI::format(
82 _L("To specify the system certificate store manually, please "
83 "set the %1% environment variable to the correct CA bundle "
84 "and restart the application."),
85 SSL_CA_FILE);
86 }
87
88 #endif // OPENSSL_CERT_OVERRIDE
89
90 if (CURLcode ec = ::curl_global_init(CURL_GLOBAL_DEFAULT)) {
91 message += _u8L("CURL init has failed. PrusaSlicer will be unable to establish "
92 "network connections. See logs for additional details.");
93
94 BOOST_LOG_TRIVIAL(error) << ::curl_easy_strerror(ec);
95 }
96 }
97
~CurlGlobalInitSlic3r::CurlGlobalInit98 ~CurlGlobalInit() { ::curl_global_cleanup(); }
99 };
100
101 std::unique_ptr<CurlGlobalInit> CurlGlobalInit::instance;
102
103 struct Http::priv
104 {
105 enum {
106 DEFAULT_TIMEOUT_CONNECT = 10,
107 DEFAULT_SIZE_LIMIT = 5 * 1024 * 1024,
108 };
109
110 ::CURL *curl;
111 ::curl_httppost *form;
112 ::curl_httppost *form_end;
113 ::curl_slist *headerlist;
114 // Used for reading the body
115 std::string buffer;
116 // Used for storing file streams added as multipart form parts
117 // Using a deque here because unlike vector it doesn't ivalidate pointers on insertion
118 std::deque<fs::ifstream> form_files;
119 std::string postfields;
120 std::string error_buffer; // Used for CURLOPT_ERRORBUFFER
121 size_t limit;
122 bool cancel;
123 std::unique_ptr<fs::ifstream> putFile;
124
125 std::thread io_thread;
126 Http::CompleteFn completefn;
127 Http::ErrorFn errorfn;
128 Http::ProgressFn progressfn;
129
130 priv(const std::string &url);
131 ~priv();
132
133 static bool ca_file_supported(::CURL *curl);
134 static size_t writecb(void *data, size_t size, size_t nmemb, void *userp);
135 static int xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
136 static int xfercb_legacy(void *userp, double dltotal, double dlnow, double ultotal, double ulnow);
137 static size_t form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp);
138
139 void set_timeout_connect(long timeout);
140 void form_add_file(const char *name, const fs::path &path, const char* filename);
141 void set_post_body(const fs::path &path);
142 void set_post_body(const std::string &body);
143 void set_put_body(const fs::path &path);
144
145 std::string curl_error(CURLcode curlcode);
146 std::string body_size_error();
147 void http_perform();
148 };
149
priv(const std::string & url)150 Http::priv::priv(const std::string &url)
151 : curl(::curl_easy_init())
152 , form(nullptr)
153 , form_end(nullptr)
154 , headerlist(nullptr)
155 , error_buffer(CURL_ERROR_SIZE + 1, '\0')
156 , limit(0)
157 , cancel(false)
158 {
159 Http::tls_global_init();
160
161 if (curl == nullptr) {
162 throw Slic3r::RuntimeError(std::string("Could not construct Curl object"));
163 }
164
165 set_timeout_connect(DEFAULT_TIMEOUT_CONNECT);
166 ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // curl makes a copy internally
167 ::curl_easy_setopt(curl, CURLOPT_USERAGENT, SLIC3R_APP_NAME "/" SLIC3R_VERSION);
168 ::curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error_buffer.front());
169 }
170
~priv()171 Http::priv::~priv()
172 {
173 ::curl_easy_cleanup(curl);
174 ::curl_formfree(form);
175 ::curl_slist_free_all(headerlist);
176 }
177
ca_file_supported(::CURL * curl)178 bool Http::priv::ca_file_supported(::CURL *curl)
179 {
180 #ifdef _WIN32
181 bool res = false;
182 #else
183 bool res = true;
184 #endif
185
186 if (curl == nullptr) { return res; }
187
188 #if LIBCURL_VERSION_MAJOR >= 7 && LIBCURL_VERSION_MINOR >= 48
189 ::curl_tlssessioninfo *tls;
190 if (::curl_easy_getinfo(curl, CURLINFO_TLS_SSL_PTR, &tls) == CURLE_OK) {
191 if (tls->backend == CURLSSLBACKEND_SCHANNEL || tls->backend == CURLSSLBACKEND_DARWINSSL) {
192 // With Windows and OS X native SSL support, cert files cannot be set
193 res = false;
194 }
195 }
196 #endif
197
198 return res;
199 }
200
writecb(void * data,size_t size,size_t nmemb,void * userp)201 size_t Http::priv::writecb(void *data, size_t size, size_t nmemb, void *userp)
202 {
203 auto self = static_cast<priv*>(userp);
204 const char *cdata = static_cast<char*>(data);
205 const size_t realsize = size * nmemb;
206
207 const size_t limit = self->limit > 0 ? self->limit : DEFAULT_SIZE_LIMIT;
208 if (self->buffer.size() + realsize > limit) {
209 // This makes curl_easy_perform return CURLE_WRITE_ERROR
210 return 0;
211 }
212
213 self->buffer.append(cdata, realsize);
214
215 return realsize;
216 }
217
xfercb(void * userp,curl_off_t dltotal,curl_off_t dlnow,curl_off_t ultotal,curl_off_t ulnow)218 int Http::priv::xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
219 {
220 auto self = static_cast<priv*>(userp);
221 bool cb_cancel = false;
222
223 if (self->progressfn) {
224 Progress progress(dltotal, dlnow, ultotal, ulnow);
225 self->progressfn(progress, cb_cancel);
226 }
227
228 if (cb_cancel) { self->cancel = true; }
229
230 return self->cancel;
231 }
232
xfercb_legacy(void * userp,double dltotal,double dlnow,double ultotal,double ulnow)233 int Http::priv::xfercb_legacy(void *userp, double dltotal, double dlnow, double ultotal, double ulnow)
234 {
235 return xfercb(userp, dltotal, dlnow, ultotal, ulnow);
236 }
237
form_file_read_cb(char * buffer,size_t size,size_t nitems,void * userp)238 size_t Http::priv::form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp)
239 {
240 auto stream = reinterpret_cast<fs::ifstream*>(userp);
241
242 try {
243 stream->read(buffer, size * nitems);
244 } catch (const std::exception &) {
245 return CURL_READFUNC_ABORT;
246 }
247
248 return stream->gcount();
249 }
250
set_timeout_connect(long timeout)251 void Http::priv::set_timeout_connect(long timeout)
252 {
253 ::curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, timeout);
254 }
255
form_add_file(const char * name,const fs::path & path,const char * filename)256 void Http::priv::form_add_file(const char *name, const fs::path &path, const char* filename)
257 {
258 // We can't use CURLFORM_FILECONTENT, because curl doesn't support Unicode filenames on Windows
259 // and so we use CURLFORM_STREAM with boost ifstream to read the file.
260
261 if (filename == nullptr) {
262 filename = path.string().c_str();
263 }
264
265 form_files.emplace_back(path, std::ios::in | std::ios::binary);
266 auto &stream = form_files.back();
267 stream.seekg(0, std::ios::end);
268 size_t size = stream.tellg();
269 stream.seekg(0);
270
271 if (filename != nullptr) {
272 ::curl_formadd(&form, &form_end,
273 CURLFORM_COPYNAME, name,
274 CURLFORM_FILENAME, filename,
275 CURLFORM_CONTENTTYPE, "application/octet-stream",
276 CURLFORM_STREAM, static_cast<void*>(&stream),
277 CURLFORM_CONTENTSLENGTH, static_cast<long>(size),
278 CURLFORM_END
279 );
280 }
281 }
282
283 //FIXME may throw! Is the caller aware of it?
set_post_body(const fs::path & path)284 void Http::priv::set_post_body(const fs::path &path)
285 {
286 std::ifstream file(path.string());
287 std::string file_content { std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>() };
288 postfields = std::move(file_content);
289 }
290
set_post_body(const std::string & body)291 void Http::priv::set_post_body(const std::string &body)
292 {
293 postfields = body;
294 }
295
set_put_body(const fs::path & path)296 void Http::priv::set_put_body(const fs::path &path)
297 {
298 boost::system::error_code ec;
299 boost::uintmax_t filesize = file_size(path, ec);
300 if (!ec) {
301 putFile = std::make_unique<fs::ifstream>(path);
302 ::curl_easy_setopt(curl, CURLOPT_READDATA, (void *) (putFile.get()));
303 ::curl_easy_setopt(curl, CURLOPT_INFILESIZE, filesize);
304 }
305 }
306
curl_error(CURLcode curlcode)307 std::string Http::priv::curl_error(CURLcode curlcode)
308 {
309 return (boost::format("%1%:\n%2%\n[Error %3%]")
310 % ::curl_easy_strerror(curlcode)
311 % error_buffer.c_str()
312 % curlcode
313 ).str();
314 }
315
body_size_error()316 std::string Http::priv::body_size_error()
317 {
318 return (boost::format("HTTP body data size exceeded limit (%1% bytes)") % limit).str();
319 }
320
http_perform()321 void Http::priv::http_perform()
322 {
323 ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
324 ::curl_easy_setopt(curl, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL);
325 ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writecb);
326 ::curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast<void*>(this));
327 ::curl_easy_setopt(curl, CURLOPT_READFUNCTION, form_file_read_cb);
328
329 ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
330 #if LIBCURL_VERSION_MAJOR >= 7 && LIBCURL_VERSION_MINOR >= 32
331 ::curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xfercb);
332 ::curl_easy_setopt(curl, CURLOPT_XFERINFODATA, static_cast<void*>(this));
333 #ifndef _WIN32
334 (void)xfercb_legacy; // prevent unused function warning
335 #endif
336 #else
337 ::curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, xfercb);
338 ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, static_cast<void*>(this));
339 #endif
340
341 ::curl_easy_setopt(curl, CURLOPT_VERBOSE, get_logging_level() >= 5);
342
343 if (headerlist != nullptr) {
344 ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
345 }
346
347 if (form != nullptr) {
348 ::curl_easy_setopt(curl, CURLOPT_HTTPPOST, form);
349 }
350
351 if (!postfields.empty()) {
352 ::curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfields.c_str());
353 ::curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, postfields.size());
354 }
355
356 CURLcode res = ::curl_easy_perform(curl);
357
358 putFile.reset();
359
360 if (res != CURLE_OK) {
361 if (res == CURLE_ABORTED_BY_CALLBACK) {
362 if (cancel) {
363 // The abort comes from the request being cancelled programatically
364 Progress dummyprogress(0, 0, 0, 0);
365 bool cancel = true;
366 if (progressfn) { progressfn(dummyprogress, cancel); }
367 } else {
368 // The abort comes from the CURLOPT_READFUNCTION callback, which means reading file failed
369 if (errorfn) { errorfn(std::move(buffer), "Error reading file for file upload", 0); }
370 }
371 }
372 else if (res == CURLE_WRITE_ERROR) {
373 if (errorfn) { errorfn(std::move(buffer), body_size_error(), 0); }
374 } else {
375 if (errorfn) { errorfn(std::move(buffer), curl_error(res), 0); }
376 };
377 } else {
378 long http_status = 0;
379 ::curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status);
380
381 if (http_status >= 400) {
382 if (errorfn) { errorfn(std::move(buffer), std::string(), http_status); }
383 } else {
384 if (completefn) { completefn(std::move(buffer), http_status); }
385 }
386 }
387 }
388
Http(const std::string & url)389 Http::Http(const std::string &url) : p(new priv(url)) {}
390
391
392 // Public
393
Http(Http && other)394 Http::Http(Http &&other) : p(std::move(other.p)) {}
395
~Http()396 Http::~Http()
397 {
398 assert(! p || ! p->putFile);
399 if (p && p->io_thread.joinable()) {
400 p->io_thread.detach();
401 }
402 }
403
404
timeout_connect(long timeout)405 Http& Http::timeout_connect(long timeout)
406 {
407 if (timeout < 1) { timeout = priv::DEFAULT_TIMEOUT_CONNECT; }
408 if (p) { p->set_timeout_connect(timeout); }
409 return *this;
410 }
411
size_limit(size_t sizeLimit)412 Http& Http::size_limit(size_t sizeLimit)
413 {
414 if (p) { p->limit = sizeLimit; }
415 return *this;
416 }
417
header(std::string name,const std::string & value)418 Http& Http::header(std::string name, const std::string &value)
419 {
420 if (!p) { return * this; }
421
422 if (name.size() > 0) {
423 name.append(": ").append(value);
424 } else {
425 name.push_back(':');
426 }
427 p->headerlist = curl_slist_append(p->headerlist, name.c_str());
428 return *this;
429 }
430
remove_header(std::string name)431 Http& Http::remove_header(std::string name)
432 {
433 if (p) {
434 name.push_back(':');
435 p->headerlist = curl_slist_append(p->headerlist, name.c_str());
436 }
437
438 return *this;
439 }
440
441 // Authorization by HTTP digest, based on RFC2617.
auth_digest(const std::string & user,const std::string & password)442 Http& Http::auth_digest(const std::string &user, const std::string &password)
443 {
444 curl_easy_setopt(p->curl, CURLOPT_USERNAME, user.c_str());
445 curl_easy_setopt(p->curl, CURLOPT_PASSWORD, password.c_str());
446 curl_easy_setopt(p->curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
447
448 return *this;
449 }
450
auth_basic(const std::string & user,const std::string & password)451 Http& Http::auth_basic(const std::string &user, const std::string &password)
452 {
453 curl_easy_setopt(p->curl, CURLOPT_USERNAME, user.c_str());
454 curl_easy_setopt(p->curl, CURLOPT_PASSWORD, password.c_str());
455 curl_easy_setopt(p->curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
456
457 return *this;
458 }
459
ca_file(const std::string & name)460 Http& Http::ca_file(const std::string &name)
461 {
462 if (p && priv::ca_file_supported(p->curl)) {
463 ::curl_easy_setopt(p->curl, CURLOPT_CAINFO, name.c_str());
464 }
465
466 return *this;
467 }
468
form_add(const std::string & name,const std::string & contents)469 Http& Http::form_add(const std::string &name, const std::string &contents)
470 {
471 if (p) {
472 ::curl_formadd(&p->form, &p->form_end,
473 CURLFORM_COPYNAME, name.c_str(),
474 CURLFORM_COPYCONTENTS, contents.c_str(),
475 CURLFORM_END
476 );
477 }
478
479 return *this;
480 }
481
form_add_file(const std::string & name,const fs::path & path)482 Http& Http::form_add_file(const std::string &name, const fs::path &path)
483 {
484 if (p) { p->form_add_file(name.c_str(), path.c_str(), nullptr); }
485 return *this;
486 }
487
form_add_file(const std::string & name,const fs::path & path,const std::string & filename)488 Http& Http::form_add_file(const std::string &name, const fs::path &path, const std::string &filename)
489 {
490 if (p) { p->form_add_file(name.c_str(), path.c_str(), filename.c_str()); }
491 return *this;
492 }
493
set_post_body(const fs::path & path)494 Http& Http::set_post_body(const fs::path &path)
495 {
496 if (p) { p->set_post_body(path);}
497 return *this;
498 }
499
set_post_body(const std::string & body)500 Http& Http::set_post_body(const std::string &body)
501 {
502 if (p) { p->set_post_body(body); }
503 return *this;
504 }
505
set_put_body(const fs::path & path)506 Http& Http::set_put_body(const fs::path &path)
507 {
508 if (p) { p->set_put_body(path);}
509 return *this;
510 }
511
on_complete(CompleteFn fn)512 Http& Http::on_complete(CompleteFn fn)
513 {
514 if (p) { p->completefn = std::move(fn); }
515 return *this;
516 }
517
on_error(ErrorFn fn)518 Http& Http::on_error(ErrorFn fn)
519 {
520 if (p) { p->errorfn = std::move(fn); }
521 return *this;
522 }
523
on_progress(ProgressFn fn)524 Http& Http::on_progress(ProgressFn fn)
525 {
526 if (p) { p->progressfn = std::move(fn); }
527 return *this;
528 }
529
perform()530 Http::Ptr Http::perform()
531 {
532 auto self = std::make_shared<Http>(std::move(*this));
533
534 if (self->p) {
535 auto io_thread = std::thread([self](){
536 self->p->http_perform();
537 });
538 self->p->io_thread = std::move(io_thread);
539 }
540
541 return self;
542 }
543
perform_sync()544 void Http::perform_sync()
545 {
546 if (p) { p->http_perform(); }
547 }
548
cancel()549 void Http::cancel()
550 {
551 if (p) { p->cancel = true; }
552 }
553
get(std::string url)554 Http Http::get(std::string url)
555 {
556 return std::move(Http{std::move(url)});
557 }
558
post(std::string url)559 Http Http::post(std::string url)
560 {
561 Http http{std::move(url)};
562 curl_easy_setopt(http.p->curl, CURLOPT_POST, 1L);
563 return http;
564 }
565
put(std::string url)566 Http Http::put(std::string url)
567 {
568 Http http{std::move(url)};
569 curl_easy_setopt(http.p->curl, CURLOPT_UPLOAD, 1L);
570 return http;
571 }
572
ca_file_supported()573 bool Http::ca_file_supported()
574 {
575 ::CURL *curl = ::curl_easy_init();
576 bool res = priv::ca_file_supported(curl);
577 if (curl != nullptr) { ::curl_easy_cleanup(curl); }
578 return res;
579 }
580
tls_global_init()581 std::string Http::tls_global_init()
582 {
583 if (!CurlGlobalInit::instance)
584 CurlGlobalInit::instance = std::make_unique<CurlGlobalInit>();
585
586 return CurlGlobalInit::instance->message;
587 }
588
tls_system_cert_store()589 std::string Http::tls_system_cert_store()
590 {
591 std::string ret;
592
593 #ifdef OPENSSL_CERT_OVERRIDE
594 ret = ::getenv(X509_get_default_cert_file_env());
595 #endif
596
597 return ret;
598 }
599
url_encode(const std::string & str)600 std::string Http::url_encode(const std::string &str)
601 {
602 ::CURL *curl = ::curl_easy_init();
603 if (curl == nullptr) {
604 return str;
605 }
606 char *ce = ::curl_easy_escape(curl, str.c_str(), str.length());
607 std::string encoded = std::string(ce);
608
609 ::curl_free(ce);
610 ::curl_easy_cleanup(curl);
611
612 return encoded;
613 }
614
operator <<(std::ostream & os,const Http::Progress & progress)615 std::ostream& operator<<(std::ostream &os, const Http::Progress &progress)
616 {
617 os << "Http::Progress("
618 << "dltotal = " << progress.dltotal
619 << ", dlnow = " << progress.dlnow
620 << ", ultotal = " << progress.ultotal
621 << ", ulnow = " << progress.ulnow
622 << ")";
623 return os;
624 }
625
626
627 }
628