123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527 |
- #include "Http.hpp"
- #include <cstdlib>
- #include <functional>
- #include <thread>
- #include <deque>
- #include <sstream>
- #include <exception>
- #include <boost/filesystem/fstream.hpp>
- #include <boost/filesystem/path.hpp>
- #include <boost/filesystem.hpp>
- #include <boost/format.hpp>
- #include <boost/log/trivial.hpp>
- #include <curl/curl.h>
- #ifdef OPENSSL_CERT_OVERRIDE
- #include <openssl/x509.h>
- #endif
- #include "libslic3r/libslic3r.h"
- #include "libslic3r/Utils.hpp"
- namespace fs = boost::filesystem;
- namespace Slic3r {
- // Private
- struct CurlGlobalInit
- {
- static std::unique_ptr<CurlGlobalInit> instance;
- CurlGlobalInit()
- {
- #ifdef OPENSSL_CERT_OVERRIDE // defined if SLIC3R_STATIC=ON
-
- // Look for a set of distro specific directories. Don't change the
- // order: https://bugzilla.redhat.com/show_bug.cgi?id=1053882
- static const char * CA_BUNDLES[] = {
- "/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL 6
- "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc.
- "/usr/share/ssl/certs/ca-bundle.crt",
- "/usr/local/share/certs/ca-root-nss.crt", // FreeBSD
- "/etc/ssl/cert.pem",
- "/etc/ssl/ca-bundle.pem" // OpenSUSE Tumbleweed
- };
-
- namespace fs = boost::filesystem;
- // Env var name for the OpenSSL CA bundle (SSL_CERT_FILE nomally)
- const char *const SSL_CA_FILE = X509_get_default_cert_file_env();
- const char * ssl_cafile = ::getenv(SSL_CA_FILE);
-
- if (!ssl_cafile)
- ssl_cafile = X509_get_default_cert_file();
-
- int replace = true;
-
- if (!ssl_cafile || !fs::exists(fs::path(ssl_cafile)))
- for (const char * bundle : CA_BUNDLES) {
- if (fs::exists(fs::path(bundle))) {
- ::setenv(SSL_CA_FILE, bundle, replace);
- break;
- }
- }
- BOOST_LOG_TRIVIAL(info)
- << "Detected OpenSSL root CA store: " << ::getenv(SSL_CA_FILE);
- #endif
-
- ::curl_global_init(CURL_GLOBAL_DEFAULT);
- }
-
- ~CurlGlobalInit() { ::curl_global_cleanup(); }
- };
- std::unique_ptr<CurlGlobalInit> CurlGlobalInit::instance;
- struct Http::priv
- {
- enum {
- DEFAULT_TIMEOUT_CONNECT = 10,
- DEFAULT_SIZE_LIMIT = 5 * 1024 * 1024,
- };
- ::CURL *curl;
- ::curl_httppost *form;
- ::curl_httppost *form_end;
- ::curl_slist *headerlist;
- // Used for reading the body
- std::string buffer;
- // Used for storing file streams added as multipart form parts
- // Using a deque here because unlike vector it doesn't ivalidate pointers on insertion
- std::deque<fs::ifstream> form_files;
- std::string postfields;
- std::string error_buffer; // Used for CURLOPT_ERRORBUFFER
- size_t limit;
- bool cancel;
- std::thread io_thread;
- Http::CompleteFn completefn;
- Http::ErrorFn errorfn;
- Http::ProgressFn progressfn;
- priv(const std::string &url);
- ~priv();
- static bool ca_file_supported(::CURL *curl);
- static size_t writecb(void *data, size_t size, size_t nmemb, void *userp);
- static int xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
- static int xfercb_legacy(void *userp, double dltotal, double dlnow, double ultotal, double ulnow);
- static size_t form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp);
- void set_timeout_connect(long timeout);
- void form_add_file(const char *name, const fs::path &path, const char* filename);
- void set_post_body(const fs::path &path);
- std::string curl_error(CURLcode curlcode);
- std::string body_size_error();
- void http_perform();
- };
- Http::priv::priv(const std::string &url)
- : curl(::curl_easy_init())
- , form(nullptr)
- , form_end(nullptr)
- , headerlist(nullptr)
- , error_buffer(CURL_ERROR_SIZE + 1, '\0')
- , limit(0)
- , cancel(false)
- {
- if (!CurlGlobalInit::instance)
- CurlGlobalInit::instance = std::make_unique<CurlGlobalInit>();
-
- if (curl == nullptr) {
- throw std::runtime_error(std::string("Could not construct Curl object"));
- }
- set_timeout_connect(DEFAULT_TIMEOUT_CONNECT);
- ::curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // curl makes a copy internally
- ::curl_easy_setopt(curl, CURLOPT_USERAGENT, SLIC3R_APP_NAME "/" SLIC3R_VERSION);
- ::curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, &error_buffer.front());
- }
- Http::priv::~priv()
- {
- ::curl_easy_cleanup(curl);
- ::curl_formfree(form);
- ::curl_slist_free_all(headerlist);
- }
- bool Http::priv::ca_file_supported(::CURL *curl)
- {
- #ifdef _WIN32
- bool res = false;
- #else
- bool res = true;
- #endif
- if (curl == nullptr) { return res; }
- #if LIBCURL_VERSION_MAJOR >= 7 && LIBCURL_VERSION_MINOR >= 48
- ::curl_tlssessioninfo *tls;
- if (::curl_easy_getinfo(curl, CURLINFO_TLS_SSL_PTR, &tls) == CURLE_OK) {
- if (tls->backend == CURLSSLBACKEND_SCHANNEL || tls->backend == CURLSSLBACKEND_DARWINSSL) {
- // With Windows and OS X native SSL support, cert files cannot be set
- res = false;
- }
- }
- #endif
- return res;
- }
- size_t Http::priv::writecb(void *data, size_t size, size_t nmemb, void *userp)
- {
- auto self = static_cast<priv*>(userp);
- const char *cdata = static_cast<char*>(data);
- const size_t realsize = size * nmemb;
- const size_t limit = self->limit > 0 ? self->limit : DEFAULT_SIZE_LIMIT;
- if (self->buffer.size() + realsize > limit) {
- // This makes curl_easy_perform return CURLE_WRITE_ERROR
- return 0;
- }
- self->buffer.append(cdata, realsize);
- return realsize;
- }
- int Http::priv::xfercb(void *userp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow)
- {
- auto self = static_cast<priv*>(userp);
- bool cb_cancel = false;
- if (self->progressfn) {
- Progress progress(dltotal, dlnow, ultotal, ulnow);
- self->progressfn(progress, cb_cancel);
- }
- if (cb_cancel) { self->cancel = true; }
- return self->cancel;
- }
- int Http::priv::xfercb_legacy(void *userp, double dltotal, double dlnow, double ultotal, double ulnow)
- {
- return xfercb(userp, dltotal, dlnow, ultotal, ulnow);
- }
- size_t Http::priv::form_file_read_cb(char *buffer, size_t size, size_t nitems, void *userp)
- {
- auto stream = reinterpret_cast<fs::ifstream*>(userp);
- try {
- stream->read(buffer, size * nitems);
- } catch (const std::exception &) {
- return CURL_READFUNC_ABORT;
- }
- return stream->gcount();
- }
- void Http::priv::set_timeout_connect(long timeout)
- {
- ::curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, timeout);
- }
- void Http::priv::form_add_file(const char *name, const fs::path &path, const char* filename)
- {
- // We can't use CURLFORM_FILECONTENT, because curl doesn't support Unicode filenames on Windows
- // and so we use CURLFORM_STREAM with boost ifstream to read the file.
- if (filename == nullptr) {
- filename = path.string().c_str();
- }
- form_files.emplace_back(path, std::ios::in | std::ios::binary);
- auto &stream = form_files.back();
- stream.seekg(0, std::ios::end);
- size_t size = stream.tellg();
- stream.seekg(0);
- if (filename != nullptr) {
- ::curl_formadd(&form, &form_end,
- CURLFORM_COPYNAME, name,
- CURLFORM_FILENAME, filename,
- CURLFORM_CONTENTTYPE, "application/octet-stream",
- CURLFORM_STREAM, static_cast<void*>(&stream),
- CURLFORM_CONTENTSLENGTH, static_cast<long>(size),
- CURLFORM_END
- );
- }
- }
- void Http::priv::set_post_body(const fs::path &path)
- {
- std::ifstream file(path.string());
- std::string file_content { std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>() };
- postfields = file_content;
- }
- std::string Http::priv::curl_error(CURLcode curlcode)
- {
- return (boost::format("%1%:\n%2%\n[Error %3%]")
- % ::curl_easy_strerror(curlcode)
- % error_buffer.c_str()
- % curlcode
- ).str();
- }
- std::string Http::priv::body_size_error()
- {
- return (boost::format("HTTP body data size exceeded limit (%1% bytes)") % limit).str();
- }
- void Http::priv::http_perform()
- {
- ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
- ::curl_easy_setopt(curl, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL);
- ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writecb);
- ::curl_easy_setopt(curl, CURLOPT_WRITEDATA, static_cast<void*>(this));
- ::curl_easy_setopt(curl, CURLOPT_READFUNCTION, form_file_read_cb);
- ::curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L);
- #if LIBCURL_VERSION_MAJOR >= 7 && LIBCURL_VERSION_MINOR >= 32
- ::curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, xfercb);
- ::curl_easy_setopt(curl, CURLOPT_XFERINFODATA, static_cast<void*>(this));
- #ifndef _WIN32
- (void)xfercb_legacy; // prevent unused function warning
- #endif
- #else
- ::curl_easy_setopt(curl, CURLOPT_PROGRESSFUNCTION, xfercb);
- ::curl_easy_setopt(curl, CURLOPT_PROGRESSDATA, static_cast<void*>(this));
- #endif
- ::curl_easy_setopt(curl, CURLOPT_VERBOSE, get_logging_level() >= 5);
- if (headerlist != nullptr) {
- ::curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headerlist);
- }
- if (form != nullptr) {
- ::curl_easy_setopt(curl, CURLOPT_HTTPPOST, form);
- }
- if (!postfields.empty()) {
- ::curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postfields.c_str());
- ::curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, postfields.size());
- }
- CURLcode res = ::curl_easy_perform(curl);
- if (res != CURLE_OK) {
- if (res == CURLE_ABORTED_BY_CALLBACK) {
- if (cancel) {
- // The abort comes from the request being cancelled programatically
- Progress dummyprogress(0, 0, 0, 0);
- bool cancel = true;
- if (progressfn) { progressfn(dummyprogress, cancel); }
- } else {
- // The abort comes from the CURLOPT_READFUNCTION callback, which means reading file failed
- if (errorfn) { errorfn(std::move(buffer), "Error reading file for file upload", 0); }
- }
- }
- else if (res == CURLE_WRITE_ERROR) {
- if (errorfn) { errorfn(std::move(buffer), body_size_error(), 0); }
- } else {
- if (errorfn) { errorfn(std::move(buffer), curl_error(res), 0); }
- };
- } else {
- long http_status = 0;
- ::curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status);
- if (http_status >= 400) {
- if (errorfn) { errorfn(std::move(buffer), std::string(), http_status); }
- } else {
- if (completefn) { completefn(std::move(buffer), http_status); }
- }
- }
- }
- Http::Http(const std::string &url) : p(new priv(url)) {}
- // Public
- Http::Http(Http &&other) : p(std::move(other.p)) {}
- Http::~Http()
- {
- if (p && p->io_thread.joinable()) {
- p->io_thread.detach();
- }
- }
- Http& Http::timeout_connect(long timeout)
- {
- if (timeout < 1) { timeout = priv::DEFAULT_TIMEOUT_CONNECT; }
- if (p) { p->set_timeout_connect(timeout); }
- return *this;
- }
- Http& Http::size_limit(size_t sizeLimit)
- {
- if (p) { p->limit = sizeLimit; }
- return *this;
- }
- Http& Http::header(std::string name, const std::string &value)
- {
- if (!p) { return * this; }
- if (name.size() > 0) {
- name.append(": ").append(value);
- } else {
- name.push_back(':');
- }
- p->headerlist = curl_slist_append(p->headerlist, name.c_str());
- return *this;
- }
- Http& Http::remove_header(std::string name)
- {
- if (p) {
- name.push_back(':');
- p->headerlist = curl_slist_append(p->headerlist, name.c_str());
- }
- return *this;
- }
- Http& Http::ca_file(const std::string &name)
- {
- if (p && priv::ca_file_supported(p->curl)) {
- ::curl_easy_setopt(p->curl, CURLOPT_CAINFO, name.c_str());
- }
- return *this;
- }
- Http& Http::form_add(const std::string &name, const std::string &contents)
- {
- if (p) {
- ::curl_formadd(&p->form, &p->form_end,
- CURLFORM_COPYNAME, name.c_str(),
- CURLFORM_COPYCONTENTS, contents.c_str(),
- CURLFORM_END
- );
- }
- return *this;
- }
- Http& Http::form_add_file(const std::string &name, const fs::path &path)
- {
- if (p) { p->form_add_file(name.c_str(), path.c_str(), nullptr); }
- return *this;
- }
- Http& Http::form_add_file(const std::string &name, const fs::path &path, const std::string &filename)
- {
- if (p) { p->form_add_file(name.c_str(), path.c_str(), filename.c_str()); }
- return *this;
- }
- Http& Http::set_post_body(const fs::path &path)
- {
- if (p) { p->set_post_body(path);}
- return *this;
- }
- Http& Http::on_complete(CompleteFn fn)
- {
- if (p) { p->completefn = std::move(fn); }
- return *this;
- }
- Http& Http::on_error(ErrorFn fn)
- {
- if (p) { p->errorfn = std::move(fn); }
- return *this;
- }
- Http& Http::on_progress(ProgressFn fn)
- {
- if (p) { p->progressfn = std::move(fn); }
- return *this;
- }
- Http::Ptr Http::perform()
- {
- auto self = std::make_shared<Http>(std::move(*this));
- if (self->p) {
- auto io_thread = std::thread([self](){
- self->p->http_perform();
- });
- self->p->io_thread = std::move(io_thread);
- }
- return self;
- }
- void Http::perform_sync()
- {
- if (p) { p->http_perform(); }
- }
- void Http::cancel()
- {
- if (p) { p->cancel = true; }
- }
- Http Http::get(std::string url)
- {
- return std::move(Http{std::move(url)});
- }
- Http Http::post(std::string url)
- {
- Http http{std::move(url)};
- curl_easy_setopt(http.p->curl, CURLOPT_POST, 1L);
- return http;
- }
- bool Http::ca_file_supported()
- {
- ::CURL *curl = ::curl_easy_init();
- bool res = priv::ca_file_supported(curl);
- if (curl != nullptr) { ::curl_easy_cleanup(curl); }
- return res;
- }
- std::string Http::url_encode(const std::string &str)
- {
- ::CURL *curl = ::curl_easy_init();
- if (curl == nullptr) {
- return str;
- }
- char *ce = ::curl_easy_escape(curl, str.c_str(), str.length());
- std::string encoded = std::string(ce);
- ::curl_free(ce);
- ::curl_easy_cleanup(curl);
- return encoded;
- }
- std::ostream& operator<<(std::ostream &os, const Http::Progress &progress)
- {
- os << "Http::Progress("
- << "dltotal = " << progress.dltotal
- << ", dlnow = " << progress.dlnow
- << ", ultotal = " << progress.ultotal
- << ", ulnow = " << progress.ulnow
- << ")";
- return os;
- }
- }
|