File download. Http class temporarily copied.

This commit is contained in:
David Kocik 2022-05-23 16:36:12 +02:00
parent dba3c95297
commit ad5b0d0bed
8 changed files with 1209 additions and 0 deletions

View File

@ -110,6 +110,7 @@ if (SLIC3R_GUI)
message(STATUS "wx libs: ${wxWidgets_LIBRARIES}")
add_subdirectory(slic3r)
add_subdirectory(downloader)
endif()

View File

@ -0,0 +1,21 @@
#cmake_minimum_required(VERSION 3.13)
#project(PrusaSlicer_Downloader)
add_executable(PrusaSlicer_Downloader WIN32
Downloader.cpp
Downloader.hpp
FileGet.cpp
FileGet.hpp
FromSlicer/Http.cpp
FromSlicer/Http.hpp
)
set_target_properties(PrusaSlicer_Downloader PROPERTIES OUTPUT_NAME "prusa-slicer-downloader")
#encoding_check(PrusaSlicer_Downloader)
target_link_libraries(PrusaSlicer_Downloader libslic3r_gui)

View File

@ -0,0 +1,122 @@
#include "Downloader.hpp"
#include "FileGet.hpp"
#include <iostream>
#include <vector>
#include <string>
#include <boost/nowide/convert.hpp>
#include <wx/event.h>
namespace Downloader {
enum
{
ID_Hello = 1,
};
wxBEGIN_EVENT_TABLE(DownloadFrame, wxFrame)
EVT_MENU(ID_Hello, DownloadFrame::OnHello)
EVT_MENU(wxID_EXIT, DownloadFrame::OnExit)
EVT_MENU(wxID_ABOUT, DownloadFrame::OnAbout)
wxEND_EVENT_TABLE()
bool DownloadApp::OnInit()
{
DownloadFrame* frame = new DownloadFrame("Hello World", wxPoint(50, 50), wxSize(450, 340));
frame->Show(true);
return true;
}
DownloadFrame::DownloadFrame(const wxString& title, const wxPoint& pos, const wxSize& size)
: wxFrame(NULL, wxID_ANY, title, pos, size)
{
wxMenu* menuFile = new wxMenu;
menuFile->Append(ID_Hello, "&Hello...\tCtrl-H",
"Help string shown in status bar for this menu item");
menuFile->AppendSeparator();
menuFile->Append(wxID_EXIT);
wxMenu* menuHelp = new wxMenu;
menuHelp->Append(wxID_ABOUT);
wxMenuBar* menuBar = new wxMenuBar;
menuBar->Append(menuFile, "&File");
menuBar->Append(menuHelp, "&Help");
SetMenuBar(menuBar);
CreateStatusBar();
SetStatusText("Welcome to wxWidgets!");
m_log_label = new wxStaticText(this, wxID_ANY, "Log:");
auto* sizer = new wxBoxSizer(wxHORIZONTAL);
sizer->Add(m_log_label, wxEXPAND);
SetSizer(sizer);
// Bind(EVT_FILE_COMPLETE, &on_complete, this);
Bind(EVT_FILE_COMPLETE, &DownloadFrame::on_complete, this);
Bind(EVT_FILE_PROGRESS, &DownloadFrame::on_progress, this);
Bind(EVT_FILE_ERROR, &DownloadFrame::on_error, this);
}
void DownloadFrame::OnExit(wxCommandEvent& event)
{
Close(true);
}
void DownloadFrame::OnAbout(wxCommandEvent& event)
{
wxMessageBox("This is a wxWidgets' Hello world sample",
"About Hello World", wxOK | wxICON_INFORMATION);
}
void DownloadFrame::OnHello(wxCommandEvent& event)
{
std::string test_url = "https%3A%2F%2Fmedia.printables.com%2Fmedia%2Fprints%2F152208%2Fstls%2F1431590_8b8287b3-03b1-4cbe-82d0-268a0affa171%2Ff1_logo.stl";
std::shared_ptr<FileGet> file_get = FileGet(get_next_id(), test_url, this, boost::filesystem::path("C:\\Users\\User\\Downloads")) .get();
}
void DownloadFrame::log(const wxString& msg)
{
m_log_lines++;
wxString old_log = m_log_label->GetLabel();
if (m_log_lines > 10) {
size_t endline = old_log.Find('\n');
endline = old_log.find('\n', endline + 1);
if (endline != wxString::npos) {
old_log = "Log:\n" + old_log.substr(endline + 1);
}
}
m_log_label->SetLabel(old_log +"\n"+ msg);
m_full_log += +"\n" + msg;
}
void DownloadFrame::on_progress(wxCommandEvent& event)
{
log(std::to_string(event.GetInt()) + ": " + event.GetString());
//SetStatusText("Progress: " + std::to_string(event.GetInt()));
}
void DownloadFrame::on_error(wxCommandEvent& event)
{
log(std::to_string(event.GetInt()) + ": " + event.GetString());
//SetStatusText(event.GetString().c_str());
}
void DownloadFrame::on_complete(wxCommandEvent& event)
{
log(std::to_string(event.GetInt()) + ": Download complete " + event.GetString());
}
//wxIMPLEMENT_APP_NO_MAIN(MyApp);
//int main()
//{
// wxEntry();
// return 0;
//}
//int APIENTRY WinMain(HINSTANCE /* hInstance */, HINSTANCE /* hPrevInstance */, PWSTR /* lpCmdLine */, int /* nCmdShow */)
//{
// wxEntry();
// return 0;
//}
}
wxIMPLEMENT_APP(Downloader::DownloadApp);

View File

@ -0,0 +1,37 @@
#ifndef slic3r_Downloader_hpp_
#define slic3r_Downloader_hpp_
#include <wx/wx.h>
namespace Downloader {
class DownloadApp : public wxApp
{
public:
virtual bool OnInit();
};
class DownloadFrame : public wxFrame
{
public:
DownloadFrame(const wxString& title, const wxPoint& pos, const wxSize& size);
private:
void OnHello(wxCommandEvent& event);
void OnExit(wxCommandEvent& event);
void OnAbout(wxCommandEvent& event);
void on_progress(wxCommandEvent& event);
void on_error(wxCommandEvent& event);
void on_complete(wxCommandEvent& event);
wxDECLARE_EVENT_TABLE();
void log(const wxString& msg);
int m_next_id { 0 };
int get_next_id() {return ++m_next_id; }
wxStaticText* m_log_label;
size_t m_log_lines { 0 };
wxString m_full_log;
};
}
#endif

179
src/downloader/FileGet.cpp Normal file
View File

@ -0,0 +1,179 @@
#include "FileGet.hpp"
#include <thread>
#include <curl/curl.h>
#include <boost/nowide/fstream.hpp>
namespace Downloader {
namespace {
std::string escape_url(const std::string& unescaped)
{
std::string ret_val;
CURL* curl = curl_easy_init();
if (curl) {
int decodelen;
char* decoded = curl_easy_unescape(curl, unescaped.c_str(), unescaped.size(), &decodelen);
if (decoded) {
ret_val = std::string(decoded);
curl_free(decoded);
}
curl_easy_cleanup(curl);
}
return ret_val;
}
std::string filename_from_url(const std::string& url)
{
// TODO: can it be done with curl?
size_t slash = url.find_last_of("/");
if (slash == std::string::npos && slash != url.size()-1)
return std::string();
return url.substr(slash + 1, url.size() - slash + 1);
}
unsigned get_current_pid()
{
#ifdef WIN32
return GetCurrentProcessId();
#else
return ::getpid();
#endif
}
}
// int = DOWNLOAD ID; string = file path
wxDEFINE_EVENT(EVT_FILE_COMPLETE, wxCommandEvent);
// int = DOWNLOAD ID; string = error msg
wxDEFINE_EVENT(EVT_FILE_ERROR, wxCommandEvent);
// int = DOWNLOAD ID; string = progress percent
wxDEFINE_EVENT(EVT_FILE_PROGRESS, wxCommandEvent);
struct FileGet::priv
{
const int m_id;
std::string m_url;
std::string m_filename;
std::thread m_io_thread;
wxEvtHandler* m_evt_handler;
boost::filesystem::path m_dest_folder;
priv(int ID, std::string&& url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder);
void get_perform();
};
FileGet::priv::priv(int ID, std::string&& url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder)
: m_id(ID)
, m_url(std::move(url))
, m_evt_handler(evt_handler)
, m_dest_folder(dest_folder)
{
}
void FileGet::priv::get_perform()
{
assert(m_evt_handler);
assert(!m_url.empty());
m_url = escape_url(m_url);
assert(!m_url.empty());
m_filename = filename_from_url(m_url);
assert(!m_filename.empty());
assert(boost::filesystem::is_directory(m_dest_folder));
Downloader::Http::get(m_url)
//.size_limit(size_limit)
.on_progress([&](Downloader::Http::Progress progress, bool& cancel) {
wxCommandEvent* evt = new wxCommandEvent(EVT_FILE_PROGRESS);
if (progress.dlnow == 0)
evt->SetString("0");
else
evt->SetString(std::to_string((float)progress.dltotal / (float)progress.dlnow));
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
})
.on_error([&](std::string body, std::string error, unsigned http_status) {
wxCommandEvent* evt = new wxCommandEvent(EVT_FILE_ERROR);
evt->SetString(error);
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
})
.on_complete([&](std::string body, unsigned /* http_status */) {
size_t body_size = body.size();
// TODO:
//if (body_size != expected_size) {
// return;
//}
boost::filesystem::path dest_path = m_dest_folder / m_filename;
std::string extension = boost::filesystem::extension(dest_path);
std::string just_filename = m_filename.substr(0, m_filename.size() - extension.size());
std::string final_filename = just_filename;
size_t version = 0;
while (boost::filesystem::exists(m_dest_folder / (final_filename + extension)))
{
++version;
final_filename = just_filename + "("+std::to_string(version)+")";
}
m_filename = final_filename + extension;
boost::filesystem::path tmp_path = m_dest_folder / (m_filename + "." + std::to_string(get_current_pid()) + ".download");
dest_path = m_dest_folder / m_filename;
try
{
boost::nowide::fstream file(tmp_path.string(), std::ios::out | std::ios::binary | std::ios::trunc);
file.write(body.c_str(), body.size());
file.close();
boost::filesystem::rename(tmp_path, dest_path);
}
catch (const std::exception&)
{
//TODO: report?
//error_message = GUI::format("Failed to write and move %1% to %2%", tmp_path, dest_path);
wxCommandEvent* evt = new wxCommandEvent(EVT_FILE_ERROR);
evt->SetString("Failed to write and move.");
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
return;
}
wxCommandEvent* evt = new wxCommandEvent(EVT_FILE_COMPLETE);
evt->SetString(dest_path.string());
evt->SetInt(m_id);
m_evt_handler->QueueEvent(evt);
})
.perform_sync();
}
FileGet::FileGet(int ID, std::string url, wxEvtHandler* evt_handler, const boost::filesystem::path& dest_folder)
: p(new priv(ID, std::move(url), evt_handler, dest_folder))
, m_ID(ID)
{}
FileGet::FileGet(FileGet&& other) : p(std::move(other.p)), m_ID(other.get_ID()) {}
FileGet::~FileGet()
{
if (p && p->m_io_thread.joinable()) {
p->m_io_thread.detach();
}
}
std::shared_ptr<FileGet> FileGet::get()
{
auto self = std::make_shared<FileGet>(std::move(*this));
if (self->p) {
auto io_thread = std::thread([self]() {
self->p->get_perform();
});
self->p->m_io_thread = std::move(io_thread);
}
return self;
}
}

View File

@ -0,0 +1,32 @@
#ifndef slic3r_FileGet_hpp_
#define slic3r_FileGet_hpp_
#include "FromSlicer/Http.hpp"
#include <memory>
#include <string>
#include <wx/event.h>
#include <wx/frame.h>
#include <boost/filesystem.hpp>
namespace Downloader {
class FileGet : public std::enable_shared_from_this<FileGet> {
private:
struct priv;
public:
FileGet(int ID, std::string url, wxEvtHandler* evt_handler,const boost::filesystem::path& dest_folder);
FileGet(FileGet&& other);
~FileGet();
std::shared_ptr<FileGet> get();
const int get_ID() const { return m_ID; }
private:
std::unique_ptr<priv> p;
const int m_ID;
};
wxDECLARE_EVENT(EVT_FILE_COMPLETE, wxCommandEvent);
wxDECLARE_EVENT(EVT_FILE_PROGRESS, wxCommandEvent);
wxDECLARE_EVENT(EVT_FILE_ERROR, wxCommandEvent);
}
#endif

View File

@ -0,0 +1,668 @@
#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>
//#include <slic3r/GUI/I18N.hpp>
//#include <slic3r/GUI/format.hpp>
namespace fs = boost::filesystem;
namespace Downloader {
// Private
struct CurlGlobalInit
{
static std::unique_ptr<CurlGlobalInit> instance;
std::string message;
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))) {
const char * bundle = nullptr;
for (const char * b : CA_BUNDLES) {
if (fs::exists(fs::path(b))) {
::setenv(SSL_CA_FILE, bundle = b, replace);
break;
}
}
if (!bundle)
message = _u8L("Could not detect system SSL certificate store. "
"PrusaSlicer will be unable to establish secure "
"network connections.");
else
message = Slic3r::GUI::format(
_L("PrusaSlicer detected system SSL certificate store in: %1%"),
bundle);
message += "\n" + Slic3r::GUI::format(
_L("To specify the system certificate store manually, please "
"set the %1% environment variable to the correct CA bundle "
"and restart the application."),
SSL_CA_FILE);
}
#endif // OPENSSL_CERT_OVERRIDE
if (CURLcode ec = ::curl_global_init(CURL_GLOBAL_DEFAULT)) {
message += "CURL init has failed. PrusaSlicer will be unable to establish "
"network connections. See logs for additional details.";
//BOOST_LOG_TRIVIAL(error) << ::curl_easy_strerror(ec);
}
}
~CurlGlobalInit() { ::curl_global_cleanup(); }
};
std::unique_ptr<CurlGlobalInit> CurlGlobalInit::instance;
struct Http::priv
{
enum {
DEFAULT_TIMEOUT_CONNECT = 10,
DEFAULT_TIMEOUT_MAX = 0,
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::unique_ptr<fs::ifstream> putFile;
std::thread io_thread;
Http::CompleteFn completefn;
Http::ErrorFn errorfn;
Http::ProgressFn progressfn;
Http::IPResolveFn ipresolvefn;
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 set_timeout_max(long timeout);
void form_add_file(const char *name, const fs::path &path, const char* filename);
void set_post_body(const fs::path &path);
void set_post_body(const std::string &body);
void set_put_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)
{
Http::tls_global_init();
if (curl == nullptr) {
throw std::exception();//Slic3r::RuntimeError(std::string("Could not construct Curl object"));
}
set_timeout_connect(DEFAULT_TIMEOUT_CONNECT);
set_timeout_max(DEFAULT_TIMEOUT_MAX);
::curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); // curl makes a copy internally
::curl_easy_setopt(curl, CURLOPT_USERAGENT, "SLicer-Downloader");
::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)
{
#if defined(_WIN32) || defined(__APPLE__)
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
// DK: OSX is now not building CURL and links system one, thus we do not know which backend is installed. Still, false will be returned since the ifdef at the begining if this function.
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::set_timeout_max(long timeout)
{
::curl_easy_setopt(curl, CURLOPT_TIMEOUT, 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
);
}
}
//FIXME may throw! Is the caller aware of it?
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 = std::move(file_content);
}
void Http::priv::set_post_body(const std::string &body)
{
postfields = body;
}
void Http::priv::set_put_body(const fs::path &path)
{
boost::system::error_code ec;
boost::uintmax_t filesize = file_size(path, ec);
if (!ec) {
putFile = std::make_unique<fs::ifstream>(path);
::curl_easy_setopt(curl, CURLOPT_READDATA, (void *) (putFile.get()));
::curl_easy_setopt(curl, CURLOPT_INFILESIZE, filesize);
}
}
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);
putFile.reset();
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); }
if (ipresolvefn) {
char* ct;
res = curl_easy_getinfo(curl, CURLINFO_PRIMARY_IP, &ct);
if ((CURLE_OK == res) && ct) {
ipresolvefn(ct);
}
}
}
}
}
Http::Http(const std::string &url) : p(new priv(url)) {}
// Public
Http::Http(Http &&other) : p(std::move(other.p)) {}
Http::~Http()
{
assert(! p || ! p->putFile);
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::timeout_max(long timeout)
{
if (timeout < 1) { timeout = priv::DEFAULT_TIMEOUT_MAX; }
if (p) { p->set_timeout_max(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;
}
// Authorization by HTTP digest, based on RFC2617.
Http& Http::auth_digest(const std::string &user, const std::string &password)
{
curl_easy_setopt(p->curl, CURLOPT_USERNAME, user.c_str());
curl_easy_setopt(p->curl, CURLOPT_PASSWORD, password.c_str());
curl_easy_setopt(p->curl, CURLOPT_HTTPAUTH, CURLAUTH_DIGEST);
return *this;
}
Http& Http::auth_basic(const std::string &user, const std::string &password)
{
curl_easy_setopt(p->curl, CURLOPT_USERNAME, user.c_str());
curl_easy_setopt(p->curl, CURLOPT_PASSWORD, password.c_str());
curl_easy_setopt(p->curl, CURLOPT_HTTPAUTH, CURLAUTH_BASIC);
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;
}
#ifdef WIN32
// Tells libcurl to ignore certificate revocation checks in case of missing or offline distribution points for those SSL backends where such behavior is present.
// This option is only supported for Schannel (the native Windows SSL library).
Http& Http::ssl_revoke_best_effort(bool set)
{
if(p && set){
::curl_easy_setopt(p->curl, CURLOPT_SSL_OPTIONS, CURLSSLOPT_REVOKE_BEST_EFFORT);
}
return *this;
}
#endif // WIN32
Http& Http::set_post_body(const fs::path &path)
{
if (p) { p->set_post_body(path);}
return *this;
}
Http& Http::set_post_body(const std::string &body)
{
if (p) { p->set_post_body(body); }
return *this;
}
Http& Http::set_put_body(const fs::path &path)
{
if (p) { p->set_put_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& Http::on_ip_resolve(IPResolveFn fn)
{
if (p) { p->ipresolvefn = 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 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;
}
Http Http::put(std::string url)
{
Http http{std::move(url)};
curl_easy_setopt(http.p->curl, CURLOPT_UPLOAD, 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::tls_global_init()
{
if (!CurlGlobalInit::instance)
CurlGlobalInit::instance = std::make_unique<CurlGlobalInit>();
return CurlGlobalInit::instance->message;
}
std::string Http::tls_system_cert_store()
{
std::string ret;
#ifdef OPENSSL_CERT_OVERRIDE
ret = ::getenv(X509_get_default_cert_file_env());
#endif
return ret;
}
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;
}
}

View File

@ -0,0 +1,149 @@
#ifndef slic3r_Http_hpp_
#define slic3r_Http_hpp_
#include <memory>
#include <string>
#include <functional>
#include <boost/filesystem/path.hpp>
namespace Downloader {
/// Represetns a Http request
class Http : public std::enable_shared_from_this<Http> {
private:
struct priv;
public:
struct Progress
{
size_t dltotal; // Total bytes to download
size_t dlnow; // Bytes downloaded so far
size_t ultotal; // Total bytes to upload
size_t ulnow; // Bytes uploaded so far
Progress(size_t dltotal, size_t dlnow, size_t ultotal, size_t ulnow) :
dltotal(dltotal), dlnow(dlnow), ultotal(ultotal), ulnow(ulnow)
{}
};
typedef std::shared_ptr<Http> Ptr;
typedef std::function<void(std::string /* body */, unsigned /* http_status */)> CompleteFn;
// A HTTP request may fail at various stages of completeness (URL parsing, DNS lookup, TCP connection, ...).
// If the HTTP request could not be made or failed before completion, the `error` arg contains a description
// of the error and `http_status` is zero.
// If the HTTP request was completed but the response HTTP code is >= 400, `error` is empty and `http_status` contains the response code.
// In either case there may or may not be a body.
typedef std::function<void(std::string /* body */, std::string /* error */, unsigned /* http_status */)> ErrorFn;
// See the Progress struct above.
// Writing true to the `cancel` reference cancels the request in progress.
typedef std::function<void(Progress, bool& /* cancel */)> ProgressFn;
typedef std::function<void(std::string/* address */)> IPResolveFn;
Http(Http &&other);
// Note: strings are expected to be UTF-8-encoded
// These are the primary constructors that create a HTTP object
// for a GET and a POST request respectively.
static Http get(std::string url);
static Http post(std::string url);
static Http put(std::string url);
~Http();
Http(const Http &) = delete;
Http& operator=(const Http &) = delete;
Http& operator=(Http &&) = delete;
// Sets a maximum connection timeout in seconds
Http& timeout_connect(long timeout);
// Sets a maximum total request timeout in seconds
Http& timeout_max(long timeout);
// Sets a maximum size of the data that can be received.
// A value of zero sets the default limit, which is is 5MB.
Http& size_limit(size_t sizeLimit);
// Sets a HTTP header field.
Http& header(std::string name, const std::string &value);
// Removes a header field.
Http& remove_header(std::string name);
// Authorization by HTTP digest, based on RFC2617.
Http& auth_digest(const std::string &user, const std::string &password);
// Basic HTTP authorization
Http& auth_basic(const std::string &user, const std::string &password);
// Sets a CA certificate file for usage with HTTPS. This is only supported on some backends,
// specifically, this is supported with OpenSSL and NOT supported with Windows and OS X native certificate store.
// See also ca_file_supported().
Http& ca_file(const std::string &filename);
// Add a HTTP multipart form field
Http& form_add(const std::string &name, const std::string &contents);
// Add a HTTP multipart form file data contents, `name` is the name of the part
Http& form_add_file(const std::string &name, const boost::filesystem::path &path);
// Same as above except also override the file's filename with a custom one
Http& form_add_file(const std::string &name, const boost::filesystem::path &path, const std::string &filename);
#ifdef WIN32
// Tells libcurl to ignore certificate revocation checks in case of missing or offline distribution points for those SSL backends where such behavior is present.
// This option is only supported for Schannel (the native Windows SSL library).
Http& ssl_revoke_best_effort(bool set);
#endif // WIN32
// Set the file contents as a POST request body.
// The data is used verbatim, it is not additionally encoded in any way.
// This can be used for hosts which do not support multipart requests.
Http& set_post_body(const boost::filesystem::path &path);
// Set the POST request body.
// The data is used verbatim, it is not additionally encoded in any way.
// This can be used for hosts which do not support multipart requests.
Http& set_post_body(const std::string &body);
// Set the file contents as a PUT request body.
// The data is used verbatim, it is not additionally encoded in any way.
// This can be used for hosts which do not support multipart requests.
Http& set_put_body(const boost::filesystem::path &path);
// Callback called on HTTP request complete
Http& on_complete(CompleteFn fn);
// Callback called on an error occuring at any stage of the requests: Url parsing, DNS lookup,
// TCP connection, HTTP transfer, and finally also when the response indicates an error (status >= 400).
// Therefore, a response body may or may not be present.
Http& on_error(ErrorFn fn);
// Callback called on data download/upload prorgess (called fairly frequently).
// See the `Progress` structure for description of the data passed.
// Writing a true-ish value into the cancel reference parameter cancels the request.
Http& on_progress(ProgressFn fn);
// Callback called after succesful HTTP request (after on_complete callback)
// Called if curl_easy_getinfo resolved just used IP address.
Http& on_ip_resolve(IPResolveFn fn);
// Starts performing the request in a background thread
Ptr perform();
// Starts performing the request on the current thread
void perform_sync();
// Cancels a request in progress
void cancel();
// Tells whether current backend supports seting up a CA file using ca_file()
static bool ca_file_supported();
// Return empty string on success or error message on fail.
static std::string tls_global_init();
static std::string tls_system_cert_store();
// converts the given string to an url_encoded_string
static std::string url_encode(const std::string &str);
private:
Http(const std::string &url);
std::unique_ptr<priv> p;
};
std::ostream& operator<<(std::ostream &, const Http::Progress &);
}
#endif