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