diff --git a/src/slic3r/CMakeLists.txt b/src/slic3r/CMakeLists.txt index e09515f08c..78369233d1 100644 --- a/src/slic3r/CMakeLists.txt +++ b/src/slic3r/CMakeLists.txt @@ -332,6 +332,8 @@ set(SLIC3R_GUI_SOURCES Utils/Http.hpp Utils/FixModelByWin10.cpp Utils/FixModelByWin10.hpp + Utils/Jwt.cpp + Utils/Jwt.hpp Utils/Moonraker.cpp Utils/Moonraker.hpp Utils/OctoPrint.cpp diff --git a/src/slic3r/GUI/GUI_App.cpp b/src/slic3r/GUI/GUI_App.cpp index 815dc42069..c7eb2d7d9b 100644 --- a/src/slic3r/GUI/GUI_App.cpp +++ b/src/slic3r/GUI/GUI_App.cpp @@ -1448,6 +1448,13 @@ bool GUI_App::on_init_inner() this->check_updates(false); }); + Bind(wxEVT_ACTIVATE_APP, [this](const wxActivateEvent &evt) { + if (plater_) { + if (auto user_account = plater_->get_user_account()) + user_account->on_activate_app(evt.GetActive()); + } + }); + } else { #ifdef __WXMSW__ diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 3ac107988e..3568b153dd 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -6473,7 +6473,6 @@ void Plater::force_print_bed_update() void Plater::on_activate(bool active) { - this->p->user_account->on_activate_window(active); if (active) { this->p->show_delayed_error_message(); } diff --git a/src/slic3r/GUI/UserAccount.hpp b/src/slic3r/GUI/UserAccount.hpp index 22787f9d7c..967581f3b0 100644 --- a/src/slic3r/GUI/UserAccount.hpp +++ b/src/slic3r/GUI/UserAccount.hpp @@ -60,7 +60,7 @@ public: bool on_connect_printers_success(const std::string& data, AppConfig* app_config, bool& out_printers_changed); bool on_connect_uiid_map_success(const std::string& data, AppConfig* app_config, bool& out_printers_changed); - void on_activate_window(bool active) { m_communication->on_activate_window(active); } + void on_activate_app(bool active) { m_communication->on_activate_app(active); } std::string get_username() const { return m_username; } std::string get_access_token(); diff --git a/src/slic3r/GUI/UserAccountCommunication.cpp b/src/slic3r/GUI/UserAccountCommunication.cpp index 27a8dd2957..c46e7d5953 100644 --- a/src/slic3r/GUI/UserAccountCommunication.cpp +++ b/src/slic3r/GUI/UserAccountCommunication.cpp @@ -475,6 +475,10 @@ void UserAccountCommunication::enqueue_refresh() BOOST_LOG_TRIVIAL(error) << "Connect Printers endpoint connection failed - Not Logged in."; return; } + if (m_session->is_enqueued(UserAccountActionID::USER_ACCOUNT_ACTION_REFRESH_TOKEN)) { + BOOST_LOG_TRIVIAL(debug) << "User Account: Token refresh already enqueued, skipping..."; + return; + } m_session->enqueue_refresh({}); } wakeup_session_thread(); @@ -506,12 +510,19 @@ void UserAccountCommunication::init_session_thread() }); } -void UserAccountCommunication::on_activate_window(bool active) +void UserAccountCommunication::on_activate_app(bool active) { { std::lock_guard lck(m_thread_stop_mutex); m_window_is_active = active; } + auto now = std::time(nullptr); + BOOST_LOG_TRIVIAL(info) << "UserAccountCommunication activate: active " << active; + if (active && m_next_token_refresh_at > 0 && m_next_token_refresh_at - now < 60) { + BOOST_LOG_TRIVIAL(info) << "Enqueue access token refresh on activation"; + m_token_timer->Stop(); + enqueue_refresh(); + } } void UserAccountCommunication::wakeup_session_thread() @@ -527,12 +538,16 @@ void UserAccountCommunication::set_refresh_time(int seconds) { assert(m_token_timer); m_token_timer->Stop(); - int miliseconds = std::max(seconds * 1000 - 66666, 60000); - m_token_timer->StartOnce(miliseconds); + const auto prior_expiration_secs = 5 * 60; + int milliseconds = std::max((seconds - prior_expiration_secs) * 1000, 60000); + m_next_token_refresh_at = std::time(nullptr) + milliseconds / 1000; + m_token_timer->StartOnce(milliseconds); } + void UserAccountCommunication::on_token_timer(wxTimerEvent& evt) { + BOOST_LOG_TRIVIAL(info) << "UserAccountCommunication: Token refresh timer fired"; enqueue_refresh(); } void UserAccountCommunication::on_polling_timer(wxTimerEvent& evt) diff --git a/src/slic3r/GUI/UserAccountCommunication.hpp b/src/slic3r/GUI/UserAccountCommunication.hpp index f4ec0dbf99..c0859f4ca0 100644 --- a/src/slic3r/GUI/UserAccountCommunication.hpp +++ b/src/slic3r/GUI/UserAccountCommunication.hpp @@ -13,7 +13,7 @@ #include #include #include -#include +#include namespace Slic3r { namespace GUI { @@ -60,7 +60,7 @@ public: // Exchanges code for tokens and shared_session_key void on_login_code_recieved(const std::string& url_message); - void on_activate_window(bool active); + void on_activate_app(bool active); void set_username(const std::string& username); void set_remember_session(bool b); @@ -97,6 +97,7 @@ private: wxTimer* m_token_timer; wxEvtHandler* m_timer_evt_handler; + std::time_t m_next_token_refresh_at{0}; void wakeup_session_thread(); void init_session_thread(); diff --git a/src/slic3r/GUI/UserAccountSession.cpp b/src/slic3r/GUI/UserAccountSession.cpp index db81c2984e..23fe45cebf 100644 --- a/src/slic3r/GUI/UserAccountSession.cpp +++ b/src/slic3r/GUI/UserAccountSession.cpp @@ -2,6 +2,7 @@ #include "GUI_App.hpp" #include "format.hpp" #include "../Utils/Http.hpp" +#include "../Utils/Jwt.hpp" #include "I18N.hpp" #include @@ -46,15 +47,24 @@ void UserActionPost::perform(/*UNUSED*/ wxEvtHandler* evt_handler, /*UNUSED*/ co if (success_callback) success_callback(body); }); - http.perform_sync(); + http.perform_sync(HttpRetryOpt::default_retry()); } void UserActionGetWithEvent::perform(wxEvtHandler* evt_handler, const std::string& access_token, UserActionSuccessFn success_callback, UserActionFailFn fail_callback, const std::string& input) const { std::string url = m_url + input; auto http = Http::get(std::move(url)); - if (!access_token.empty()) + if (!access_token.empty()) { http.header("Authorization", "Bearer " + access_token); +#ifndef _NDEBUG + // In debug mode, also verify the token expiration + // This is here to help with "dev" accounts with shorten (sort of faked) expiration time + // The /api/v1/me will accept these tokens even if these are fake-marked as expired + if (!Utils::verify_exp(access_token) && fail_callback) { + fail_callback("Token Expired"); + } +#endif + } http.on_error([evt_handler, fail_callback, action_name = &m_action_name, fail_evt_type = m_fail_evt_type](std::string body, std::string error, unsigned status) { if (fail_callback) fail_callback(body); @@ -69,9 +79,17 @@ void UserActionGetWithEvent::perform(wxEvtHandler* evt_handler, const std::strin wxQueueEvent(evt_handler, new UserAccountSuccessEvent(succ_evt_type, body)); }); - http.perform_sync(); + http.perform_sync(HttpRetryOpt::default_retry()); } +bool UserAccountSession::is_enqueued(UserAccountActionID action_id) const { + return std::any_of( + std::begin(m_priority_action_queue), std::end(m_priority_action_queue), + [action_id](const ActionQueueData& item) { return item.action_id == action_id; } + ); +} + + void UserAccountSession::process_action_queue() { if (!m_proccessing_enabled) @@ -84,7 +102,7 @@ void UserAccountSession::process_action_queue() while (!m_priority_action_queue.empty()) { m_actions[m_priority_action_queue.front().action_id]->perform(p_evt_handler, m_access_token, m_priority_action_queue.front().success_callback, m_priority_action_queue.front().fail_callback, m_priority_action_queue.front().input); if (!m_priority_action_queue.empty()) - m_priority_action_queue.pop(); + m_priority_action_queue.pop_front(); } // regular queue has to wait until priority fills tokens if (!this->is_initialized()) @@ -115,7 +133,7 @@ void UserAccountSession::init_with_code(const std::string& code, const std::stri m_proccessing_enabled = true; // fail fn might be cancel_queue here - m_priority_action_queue.push({ UserAccountActionID::USER_ACCOUNT_ACTION_CODE_FOR_TOKEN + m_priority_action_queue.push_back({ UserAccountActionID::USER_ACCOUNT_ACTION_CODE_FOR_TOKEN , std::bind(&UserAccountSession::token_success_callback, this, std::placeholders::_1) , std::bind(&UserAccountSession::code_exchange_fail_callback, this, std::placeholders::_1) , post_fields }); @@ -123,6 +141,7 @@ void UserAccountSession::init_with_code(const std::string& code, const std::stri void UserAccountSession::token_success_callback(const std::string& body) { + BOOST_LOG_TRIVIAL(debug) << "Access token refreshed"; // Data we need std::string access_token, refresh_token, shared_session_key; int expires_in = 300; @@ -176,6 +195,7 @@ void UserAccountSession::token_success_callback(const std::string& body) void UserAccountSession::code_exchange_fail_callback(const std::string& body) { + BOOST_LOG_TRIVIAL(debug) << "Access token refresh failed, body: " << body; clear(); cancel_queue(); // Unlike refresh_fail_callback, no event was triggered so far, do it. (USER_ACCOUNT_ACTION_CODE_FOR_TOKEN does not send events) @@ -186,7 +206,7 @@ void UserAccountSession::enqueue_test_with_refresh() { // on test fail - try refresh m_proccessing_enabled = true; - m_priority_action_queue.push({ UserAccountActionID::USER_ACCOUNT_ACTION_TEST_ACCESS_TOKEN, nullptr, std::bind(&UserAccountSession::enqueue_refresh, this, std::placeholders::_1), {} }); + m_priority_action_queue.push_back({ UserAccountActionID::USER_ACCOUNT_ACTION_TEST_ACCESS_TOKEN, nullptr, std::bind(&UserAccountSession::enqueue_refresh, this, std::placeholders::_1), {} }); } @@ -197,7 +217,7 @@ void UserAccountSession::enqueue_refresh(const std::string& body) "&client_id=" + client_id() + "&refresh_token=" + m_refresh_token; - m_priority_action_queue.push({ UserAccountActionID::USER_ACCOUNT_ACTION_REFRESH_TOKEN + m_priority_action_queue.push_back({ UserAccountActionID::USER_ACCOUNT_ACTION_REFRESH_TOKEN , std::bind(&UserAccountSession::token_success_callback, this, std::placeholders::_1) , std::bind(&UserAccountSession::refresh_fail_callback, this, std::placeholders::_1) , post_fields }); @@ -216,9 +236,7 @@ void UserAccountSession::refresh_fail_callback(const std::string& body) void UserAccountSession::cancel_queue() { - while (!m_priority_action_queue.empty()) { - m_priority_action_queue.pop(); - } + m_priority_action_queue.clear(); while (!m_action_queue.empty()) { m_action_queue.pop(); } diff --git a/src/slic3r/GUI/UserAccountSession.hpp b/src/slic3r/GUI/UserAccountSession.hpp index d540319ab3..333894cded 100644 --- a/src/slic3r/GUI/UserAccountSession.hpp +++ b/src/slic3r/GUI/UserAccountSession.hpp @@ -150,7 +150,8 @@ public: void enqueue_refresh(const std::string& body); void process_action_queue(); - bool is_initialized() { return !m_access_token.empty() || !m_refresh_token.empty(); } + bool is_initialized() const { return !m_access_token.empty() || !m_refresh_token.empty(); } + bool is_enqueued(UserAccountActionID action_id) const; std::string get_access_token() const { return m_access_token; } std::string get_refresh_token() const { return m_refresh_token; } std::string get_shared_session_key() const { return m_shared_session_key; } @@ -179,7 +180,7 @@ private: long long m_next_token_timeout; std::queue m_action_queue; - std::queue m_priority_action_queue; + std::deque m_priority_action_queue; std::map> m_actions; wxEvtHandler* p_evt_handler; diff --git a/src/slic3r/GUI/WebViewDialog.cpp b/src/slic3r/GUI/WebViewDialog.cpp index eac2873152..f1d4825857 100644 --- a/src/slic3r/GUI/WebViewDialog.cpp +++ b/src/slic3r/GUI/WebViewDialog.cpp @@ -499,6 +499,7 @@ ConnectRequestHandler::ConnectRequestHandler() m_actions["PRINT"] = std::bind(&ConnectRequestHandler::on_connect_action_print, this, std::placeholders::_1); m_actions["REQUEST_OPEN_IN_BROWSER"] = std::bind(&ConnectRequestHandler::on_connect_action_request_open_in_browser, this, std::placeholders::_1); m_actions["ERROR"] = std::bind(&ConnectRequestHandler::on_connect_action_error, this, std::placeholders::_1); + m_actions["LOG"] = std::bind(&ConnectRequestHandler::on_connect_action_log, this, std::placeholders::_1); } ConnectRequestHandler::~ConnectRequestHandler() { @@ -539,7 +540,7 @@ void ConnectRequestHandler::handle_message(const std::string& message) void ConnectRequestHandler::on_connect_action_error(const std::string &message_data) { - BOOST_LOG_TRIVIAL(error) << "WebKit runtime error: " << message_data; + BOOST_LOG_TRIVIAL(error) << "WebView runtime error: " << message_data; } void ConnectRequestHandler::resend_config() @@ -547,6 +548,11 @@ void ConnectRequestHandler::resend_config() on_connect_action_request_config({}); } +void ConnectRequestHandler::on_connect_action_log(const std::string& message_data) +{ + BOOST_LOG_TRIVIAL(info) << "WebView log: " << message_data; +} + void ConnectRequestHandler::on_connect_action_request_config(const std::string& message_data) { /* @@ -636,29 +642,93 @@ wxString ConnectWebViewPanel::get_login_script(bool refresh) window.__access_token_version = 0; )", #else + refresh + ? R"( - console.log('Preparing login'); - function errorHandler(err) { + if (window._prusaSlicer_initLogin !== undefined) { + console.log('Refreshing login'); + if (window._prusaSlicer !== undefined) + _prusaSlicer.postMessage({action: 'LOG', message: 'Refreshing login'}); + _prusaSlicer_initLogin('%s', 'refresh'); + } else { + console.log('Refreshing login skipped as no _prusaSlicer_initLogin defined (yet?)'); + if (window._prusaSlicer === undefined) { + console.log('Message handler _prusaSlicer not defined yet'); + } else { + _prusaSlicer.postMessage({action: 'LOG', message: 'Refreshing login skipped as no _prusaSlicer_initLogin defined (yet?)'}); + } + } + )" + : + R"( + function _prusaSlicer_log(msg) { + console.log(msg); + if (window._prusaSlicer !== undefined) + _prusaSlicer.postMessage({action: 'LOG', message: msg}); + } + function _prusaSlicer_errorHandler(err) { const msg = { action: 'ERROR', - error: JSON.stringify(err), + error: typeof(err) === 'string' ? err : JSON.stringify(err), critical: false }; console.error('Login error occurred', msg); window._prusaSlicer.postMessage(msg); }; - window.fetch('/slicer/login', {method: 'POST', headers: {Authorization: 'Bearer %s'}}) - .then(function (resp) { - console.log('Login resp', resp); - resp.text() - .then(function (json) { console.log('Login resp body', json); return json; }) - .then(function (body) { - if (resp.status >= 400) errorHandler({status: resp.status, body}); - }); - }) - .catch(function (err){ - errorHandler({message: err.message, stack: err.stack}); + + function _prusaSlicer_delay(ms) { + return new Promise((resolve, reject) => { + setTimeout(resolve, ms); }); + } + + async function _prusaSlicer_initLogin(token, reason) { + const parts = token.split('.'); + const claims = JSON.parse(atob(parts[1])); + const now = new Date().getTime() / 1000; + if (claims.exp <= now) { + _prusaSlicer_log('Skipping initLogin as token is expired'); + return; + } + + let retry = false; + let backoff = 1000; + const maxBackoff = 64000; + do { + + let error = false; + + try { + _prusaSlicer_log('Slicer Login request (' + reason + ') ' + token.substring(token.length - 8)); + let resp = await fetch('/slicer/login', {method: 'POST', headers: {Authorization: 'Bearer ' + token}}); + let body = await resp.text(); + _prusaSlicer_log('Slicer Login resp ' + resp.status + ' (' +reason + ' ' + token.substring(token.length - 8) + ') body: ' + body); + if (resp.status >= 500 || resp.status == 408) { + retry = true; + } else { + retry = false; + if (resp.status >= 400) + _prusaSlicer_errorHandler({status: resp.status, body}); + } + } catch (e) { + _prusaSlicer_log('Slicer Login failed: ' + e.toString()); + console.error('Slicer Login failed', e.toString()); + retry = true; + } + + if (retry) { + await _prusaSlicer_delay(backoff + 1000 * Math.random()); + if (backoff < maxBackoff) { + backoff *= 2; + } + } + } while (retry); + } + if (window._prusaSlicer_initialLoad === undefined) { + console.log('Initial login'); + _prusaSlicer_initLogin('%s', 'init-load'); + window._prusaSlicer_initialLoad = true; + } )", #endif access_token @@ -678,10 +748,16 @@ void ConnectWebViewPanel::on_user_token(UserAccountSuccessEvent& e) e.Skip(); auto access_token = wxGetApp().plater()->get_user_account()->get_access_token(); assert(!access_token.empty()); - wxString javascript = get_login_script(true); + wxString javascript = get_login_script(false); + + m_browser->RemoveAllUserScripts(); + m_browser->AddUserScript(javascript); + + javascript = get_login_script(true); //m_browser->AddUserScript(javascript, wxWEBVIEW_INJECT_AT_DOCUMENT_END); BOOST_LOG_TRIVIAL(debug) << "RunScript " << javascript << "\n"; m_browser->RunScriptAsync(javascript); + resend_config(); } void ConnectWebViewPanel::on_script_message(wxWebViewEvent& evt) @@ -691,6 +767,7 @@ void ConnectWebViewPanel::on_script_message(wxWebViewEvent& evt) } void ConnectWebViewPanel::on_navigation_request(wxWebViewEvent &evt) { + BOOST_LOG_TRIVIAL(debug) << "Navigation requested to: " << into_u8(evt.GetURL()); if (evt.GetURL() == m_default_url) { m_reached_default_url = true; return; diff --git a/src/slic3r/GUI/WebViewDialog.hpp b/src/slic3r/GUI/WebViewDialog.hpp index 92619b0a50..3cec6cf48e 100644 --- a/src/slic3r/GUI/WebViewDialog.hpp +++ b/src/slic3r/GUI/WebViewDialog.hpp @@ -184,6 +184,7 @@ public: void resend_config(); protected: // action callbacs stored in m_actions + virtual void on_connect_action_log(const std::string& message_data); virtual void on_connect_action_error(const std::string& message_data); virtual void on_connect_action_request_config(const std::string& message_data); virtual void on_connect_action_request_open_in_browser(const std::string& message_data); diff --git a/src/slic3r/Utils/Http.cpp b/src/slic3r/Utils/Http.cpp index 2d648b9494..21de6b4ec1 100644 --- a/src/slic3r/Utils/Http.cpp +++ b/src/slic3r/Utils/Http.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include // IWYU pragma: keep #include #include @@ -154,7 +155,7 @@ struct Http::priv std::string curl_error(CURLcode curlcode); std::string body_size_error(); - void http_perform(); + void http_perform(const HttpRetryOpt& retry_opts = HttpRetryOpt::no_retry()); }; Http::priv::priv(const std::string &url) @@ -340,8 +341,20 @@ 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() +bool is_transient_error(CURLcode res, long http_status) { + if (res == CURLE_OK || res == CURLE_HTTP_RETURNED_ERROR) + return http_status == 408 || http_status >= 500; + return res == CURLE_COULDNT_CONNECT || res == CURLE_COULDNT_RESOLVE_HOST || + res == CURLE_OPERATION_TIMEDOUT; +} + +void Http::priv::http_perform(const HttpRetryOpt& retry_opts) +{ + using namespace std::chrono_literals; + static thread_local std::mt19937 generator; + std::uniform_int_distribution randomized_delay(retry_opts.initial_delay.count(), (retry_opts.initial_delay.count() * 3) / 2); + ::curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); ::curl_easy_setopt(curl, CURLOPT_POSTREDIR, CURL_REDIR_POST_ALL); ::curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writecb); @@ -375,7 +388,28 @@ void Http::priv::http_perform() ::curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE_LARGE, postfields.size()); } - CURLcode res = ::curl_easy_perform(curl); + bool retry; + CURLcode res; + long http_status = 0; + std::chrono::milliseconds delay = std::chrono::milliseconds(randomized_delay(generator)); + size_t num_retries = 0; + do { + res = ::curl_easy_perform(curl); + + if (res == CURLE_OK) + ::curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_status); + retry = delay >= 0ms && is_transient_error(res, http_status); + if (retry && retry_opts.max_retries > 0 && num_retries >= retry_opts.max_retries) + retry = false; + if (retry) { + num_retries++; + BOOST_LOG_TRIVIAL(error) + << "HTTP Transient error (code=" << res << ", http_status=" << http_status + << "), retrying in " << delay.count() / 1000.0f << " s"; + std::this_thread::sleep_for(delay); + delay = std::min(delay * 2, retry_opts.max_delay); + } + } while (retry); putFile.reset(); @@ -397,8 +431,6 @@ void Http::priv::http_perform() 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); } @@ -420,6 +452,23 @@ Http::Http(const std::string &url) : p(new priv(url)) {} // Public +const HttpRetryOpt& HttpRetryOpt::default_retry() +{ + using namespace std::chrono_literals; + static HttpRetryOpt val = {500ms, 64s, 0}; + return val; +} + +const HttpRetryOpt& HttpRetryOpt::no_retry() +{ + using namespace std::chrono_literals; + static HttpRetryOpt val = {0ms}; + return val; +} + + + + Http::Http(Http &&other) : p(std::move(other.p)) {} Http::~Http() @@ -609,13 +658,13 @@ Http& Http::cookie_jar(const std::string& file_path) return *this; } -Http::Ptr Http::perform() +Http::Ptr Http::perform(const HttpRetryOpt& retry_opts) { auto self = std::make_shared(std::move(*this)); if (self->p) { - auto io_thread = std::thread([self](){ - self->p->http_perform(); + auto io_thread = std::thread([self, &retry_opts](){ + self->p->http_perform(retry_opts); }); self->p->io_thread = std::move(io_thread); } @@ -623,9 +672,9 @@ Http::Ptr Http::perform() return self; } -void Http::perform_sync() +void Http::perform_sync(const HttpRetryOpt& retry_opts) { - if (p) { p->http_perform(); } + if (p) { p->http_perform(retry_opts); } } void Http::cancel() diff --git a/src/slic3r/Utils/Http.hpp b/src/slic3r/Utils/Http.hpp index 8e00b123fc..6d21570c12 100644 --- a/src/slic3r/Utils/Http.hpp +++ b/src/slic3r/Utils/Http.hpp @@ -11,10 +11,20 @@ #include #include #include - +#include namespace Slic3r { +struct HttpRetryOpt +{ + std::chrono::milliseconds initial_delay; + std::chrono::milliseconds max_delay; + size_t max_retries{0}; + + static const HttpRetryOpt& no_retry(); + static const HttpRetryOpt& default_retry(); +}; + /// Represetns a Http request class Http : public std::enable_shared_from_this { @@ -133,9 +143,9 @@ public: Http& set_referer(const std::string& referer); // Starts performing the request in a background thread - Ptr perform(); + Ptr perform(const HttpRetryOpt& retry_opts = HttpRetryOpt::no_retry()); // Starts performing the request on the current thread - void perform_sync(); + void perform_sync(const HttpRetryOpt &retry_opts = HttpRetryOpt::no_retry()); // Cancels a request in progress void cancel(); diff --git a/src/slic3r/Utils/Jwt.cpp b/src/slic3r/Utils/Jwt.cpp new file mode 100644 index 0000000000..4584471e04 --- /dev/null +++ b/src/slic3r/Utils/Jwt.cpp @@ -0,0 +1,58 @@ +#include "Jwt.hpp" + +#include +#include +#include + +#include +#include +#include + +namespace Slic3r::Utils { + +bool verify_exp(const std::string& token) +{ + size_t payload_start = token.find('.'); + if (payload_start == std::string::npos) + return false; + payload_start += 1; // payload starts after dot + + const size_t payload_end = token.find('.', payload_start); + if (payload_end == std::string::npos) + return false; + + size_t encoded_length = payload_end - payload_start; + size_t decoded_length = boost::beast::detail::base64::decoded_size(encoded_length); + + auto json_b64 = token.substr(payload_start, encoded_length); + std::replace(json_b64.begin(), json_b64.end(), '-', '+'); + std::replace(json_b64.begin(), json_b64.end(), '_', '/'); + + size_t padding = encoded_length % 4; + encoded_length += padding; + while (padding--) json_b64 += '='; + + + std::string json; + json.resize(decoded_length + 2); + size_t read_bytes, written_bytes; + std::tie(written_bytes, read_bytes) = boost::beast::detail::base64::decode(json.data(), json_b64.data(), json_b64.length()); + json.resize(written_bytes); + if (written_bytes == 0) + return false; + + namespace pt = boost::property_tree; + + pt::ptree payload; + std::istringstream iss(json); + pt::json_parser::read_json(iss, payload); + + auto exp_opt = payload.get_optional("exp"); + if (!exp_opt) + return false; + + auto now = time(nullptr); + return exp_opt.get() > now; +} + +} diff --git a/src/slic3r/Utils/Jwt.hpp b/src/slic3r/Utils/Jwt.hpp new file mode 100644 index 0000000000..b957e8df8d --- /dev/null +++ b/src/slic3r/Utils/Jwt.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace Slic3r::Utils { + +bool verify_exp(const std::string& token); + + +}