From 90c13efab1fd1b67625ec23815ccc195803e230e Mon Sep 17 00:00:00 2001 From: dec05eba Date: Mon, 28 Sep 2020 00:21:23 +0200 Subject: Matrix: fix login with pantalaimon proxy, fix logout crash, show real login error --- include/DownloadUtils.hpp | 2 +- plugins/FileManager.hpp | 1 + plugins/Matrix.hpp | 1 + src/DownloadUtils.cpp | 6 ++-- src/QuickMedia.cpp | 76 +++++++++++++++++++++++++------------------ src/SearchBar.cpp | 8 +++-- src/plugins/Matrix.cpp | 82 +++++++++++++++++------------------------------ 7 files changed, 87 insertions(+), 89 deletions(-) diff --git a/include/DownloadUtils.hpp b/include/DownloadUtils.hpp index 6af53ac..8f26bfc 100644 --- a/include/DownloadUtils.hpp +++ b/include/DownloadUtils.hpp @@ -20,7 +20,7 @@ namespace QuickMedia { std::string value; }; - DownloadResult download_to_string(const std::string &url, std::string &result, const std::vector &additional_args, bool use_tor, bool use_browser_useragent = false); + DownloadResult download_to_string(const std::string &url, std::string &result, const std::vector &additional_args, bool use_tor, bool use_browser_useragent = false, bool fail_on_error = true); DownloadResult download_to_string_cache(const std::string &url, std::string &result, const std::vector &additional_args, bool use_tor, bool use_browser_useragent = false); std::vector create_command_args_from_form_data(const std::vector &form_data); } \ No newline at end of file diff --git a/plugins/FileManager.hpp b/plugins/FileManager.hpp index d5d7088..f13184b 100644 --- a/plugins/FileManager.hpp +++ b/plugins/FileManager.hpp @@ -7,6 +7,7 @@ namespace QuickMedia { class FileManager : public Plugin { public: FileManager(); + virtual ~FileManager() = default; PluginResult get_files_in_directory(BodyItems &result_items); bool set_current_directory(const std::string &path); bool set_child_directory(const std::string &filename); diff --git a/plugins/Matrix.hpp b/plugins/Matrix.hpp index 760d543..97ba3cc 100644 --- a/plugins/Matrix.hpp +++ b/plugins/Matrix.hpp @@ -55,6 +55,7 @@ namespace QuickMedia { class Matrix : public Plugin { public: Matrix(); + virtual ~Matrix() = default; bool search_is_filter() override { return true; } bool search_suggestions_has_thumbnails() const override { return true; } bool search_results_has_thumbnails() const override { return false; } diff --git a/src/DownloadUtils.cpp b/src/DownloadUtils.cpp index b7636d8..e748198 100644 --- a/src/DownloadUtils.cpp +++ b/src/DownloadUtils.cpp @@ -16,12 +16,14 @@ static const char *useragent_str = "user-agent: Mozilla/5.0 (X11; Linux x86_64) namespace QuickMedia { // TODO: Add timeout - DownloadResult download_to_string(const std::string &url, std::string &result, const std::vector &additional_args, bool use_tor, bool use_browser_useragent) { + DownloadResult download_to_string(const std::string &url, std::string &result, const std::vector &additional_args, bool use_tor, bool use_browser_useragent, bool fail_on_error) { sf::Clock timer; std::vector args; if(use_tor) args.push_back("torsocks"); - args.insert(args.end(), { "curl", "-f", "-H", "Accept-Language: en-US,en;q=0.5", "-H", "Connection: keep-alive", "--compressed", "-s", "-L" }); + args.insert(args.end(), { "curl", "-H", "Accept-Language: en-US,en;q=0.5", "-H", "Connection: keep-alive", "--compressed", "-s", "-L" }); + if(fail_on_error) + args.push_back("-f"); for(const CommandArg &arg : additional_args) { args.push_back(arg.option.c_str()); args.push_back(arg.value.c_str()); diff --git a/src/QuickMedia.cpp b/src/QuickMedia.cpp index c2c0004..e07c30a 100644 --- a/src/QuickMedia.cpp +++ b/src/QuickMedia.cpp @@ -3172,7 +3172,6 @@ namespace QuickMedia { // so you dont have to retype a post that was in the middle of being posted when returning. } - // TODO: Provide a way to logout void Program::chat_login_page() { assert(current_plugin->name == "matrix"); @@ -3351,9 +3350,11 @@ namespace QuickMedia { // TODO: Filer for rooms and settings chat_input.onTextUpdateCallback = nullptr; + Page new_page = Page::CHAT; + // TODO: Show post message immediately, instead of waiting for sync. Otherwise it can take a while until we receive the message, // which happens when uploading an image. - chat_input.onTextSubmitCallback = [this, matrix, &tabs, &selected_tab, ¤t_room_id](const std::string &text) -> bool { + chat_input.onTextSubmitCallback = [matrix, &tabs, &selected_tab, ¤t_room_id, &new_page](const std::string &text) -> bool { if(tabs[selected_tab].type == ChatTabType::MESSAGES) { if(text.empty()) return false; @@ -3362,37 +3363,10 @@ namespace QuickMedia { std::string command = text; strip(command); if(command == "/upload") { - if(!file_manager) - file_manager = new FileManager(); - page_stack.push(Page::CHAT); - current_page = Page::FILE_MANAGER; - file_manager_page(); - if(selected_files.empty()) { - fprintf(stderr, "No files selected!\n"); - return true; - } else { - // TODO: Make asynchronous. - // TODO: Upload multiple files. - if(matrix->post_file(current_room_id, selected_files[0]) != PluginResult::OK) { - show_notification("QuickMedia", "Failed to upload image to room", Urgency::CRITICAL); - return false; - } else { - return true; - } - } + new_page = Page::FILE_MANAGER; + return true; } else if(command == "/logout") { - matrix->logout(); - tabs[MESSAGES_TAB_INDEX].body->clear_thumbnails(); - // TODO: Instead of doing this, exit this current function and navigate to chat login page instead. - // This doesn't currently work because at the end of this function there are futures that need to wait - // and one of them is /sync, which has a timeout of 30 seconds. That timeout has to be killed somehow. - delete current_plugin; - current_plugin = new Matrix(); - current_page = Page::CHAT_LOGIN; - chat_login_page(); - if(current_page == Page::CHAT) - chat_page(); - exit(0); + new_page = Page::CHAT_LOGIN; return true; } else { fprintf(stderr, "Error: invalid command: %s, expected /upload\n", command.c_str()); @@ -3567,6 +3541,44 @@ namespace QuickMedia { } } + switch(new_page) { + case Page::FILE_MANAGER: { + new_page = Page::CHAT; + if(!file_manager) + file_manager = new FileManager(); + page_stack.push(Page::CHAT); + current_page = Page::FILE_MANAGER; + file_manager_page(); + if(selected_files.empty()) { + fprintf(stderr, "No files selected!\n"); + } else { + // TODO: Make asynchronous. + // TODO: Upload multiple files. + if(matrix->post_file(current_room_id, selected_files[0]) != PluginResult::OK) + show_notification("QuickMedia", "Failed to upload image to room", Urgency::CRITICAL); + } + break; + } + case Page::CHAT_LOGIN: { + new_page = Page::CHAT; + matrix->logout(); + tabs[MESSAGES_TAB_INDEX].body->clear_thumbnails(); + // TODO: Instead of doing this, exit this current function and navigate to chat login page instead. + // This doesn't currently work because at the end of this function there are futures that need to wait + // and one of them is /sync, which has a timeout of 30 seconds. That timeout has to be killed somehow. + //delete current_plugin; + //current_plugin = new Matrix(); + current_page = Page::CHAT_LOGIN; + chat_login_page(); + if(current_page == Page::CHAT) + chat_page(); + exit(0); + break; + } + default: + break; + } + if(typing && start_typing_timer.getElapsedTime().asSeconds() >= typing_timeout_seconds) { fprintf(stderr, "Stopped typing\n"); typing = false; diff --git a/src/SearchBar.cpp b/src/SearchBar.cpp index e646546..419ca38 100644 --- a/src/SearchBar.cpp +++ b/src/SearchBar.cpp @@ -161,8 +161,12 @@ namespace QuickMedia { } } else if(codepoint == 13) { // Return bool clear_search = true; - if(onTextSubmitCallback) - clear_search = onTextSubmitCallback(show_placeholder ? "" : text.getString()); + if(onTextSubmitCallback) { + if(show_placeholder) + clear_search = onTextSubmitCallback(""); + else + clear_search = onTextSubmitCallback(text.getString()); + } if(clear_search) clear(); diff --git a/src/plugins/Matrix.cpp b/src/plugins/Matrix.cpp index 8b819a2..d709f73 100644 --- a/src/plugins/Matrix.cpp +++ b/src/plugins/Matrix.cpp @@ -54,7 +54,7 @@ namespace QuickMedia { char url[512]; if(next_batch.empty()) - snprintf(url, sizeof(url), "%s/_matrix/client/r0/sync?timeout=0", homeserver.c_str()); + snprintf(url, sizeof(url), "%s/_matrix/client/r0/sync?timeout=0&full_state=true", homeserver.c_str()); else snprintf(url, sizeof(url), "%s/_matrix/client/r0/sync?timeout=30000&since=%s", homeserver.c_str(), next_batch.c_str()); @@ -831,38 +831,6 @@ namespace QuickMedia { return post_message(room_id, filename, content_uri_json.asString(), MessageType::IMAGE, &message_info); } - static std::string parse_login_error_response(std::string json_str) { - if(json_str.empty()) - return "Unknown error"; - - Json::Value json_root; - Json::CharReaderBuilder json_builder; - std::unique_ptr json_reader(json_builder.newCharReader()); - std::string json_errors; - if(!json_reader->parse(&json_str[0], &json_str[json_str.size()], &json_root, &json_errors)) { - fprintf(stderr, "Matrix login response parse error: %s\n", json_errors.c_str()); - return json_str; - } - - if(!json_root.isObject()) - return json_str; - - const Json::Value &errcode_json = json_root["errcode"]; - // Yes, matrix is retarded and returns M_NOT_JSON error code when username/password is incorrect - if(errcode_json.isString() && strcmp(errcode_json.asCString(), "M_NOT_JSON") == 0) - return "Incorrect username or password"; - - return json_str; - } - - // Returns empty string on error - static std::string extract_homeserver_from_user_id(const std::string &user_id) { - size_t index = user_id.find(':'); - if(index == std::string::npos) - return ""; - return user_id.substr(index + 1); - } - PluginResult Matrix::login(const std::string &username, const std::string &password, const std::string &homeserver, std::string &err_msg) { // TODO: this is deprecated but not all homeservers have the new version. // When this is removed from future version then switch to the new login method (identifier object with the username). @@ -882,8 +850,8 @@ namespace QuickMedia { }; std::string server_response; - if(download_to_string(homeserver + "/_matrix/client/r0/login", server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK) { - err_msg = parse_login_error_response(std::move(server_response)); + if(download_to_string(homeserver + "/_matrix/client/r0/login", server_response, std::move(additional_args), use_tor, true, false) != DownloadResult::OK) { + err_msg = std::move(server_response); return PluginResult::NET_ERR; } @@ -904,6 +872,12 @@ namespace QuickMedia { return PluginResult::ERR; } + const Json::Value &error = json_root["error"]; + if(error.isString()) { + err_msg = error.asString(); + return PluginResult::ERR; + } + const Json::Value &user_id_json = json_root["user_id"]; if(!user_id_json.isString()) { err_msg = "Failed to parse matrix login response"; @@ -916,17 +890,13 @@ namespace QuickMedia { return PluginResult::ERR; } - std::string user_id = user_id_json.asString(); + // Use the user-provided homeserver instead of the one the server tells us about, otherwise this wont work with a proxy + // such as pantalaimon + json_root["homeserver"] = homeserver; - std::string homeserver_response = extract_homeserver_from_user_id(user_id); - if(homeserver_response.empty()) { - err_msg = "Missing homeserver in user id, user id: " + user_id; - return PluginResult::ERR; - } - - this->user_id = std::move(user_id); + this->user_id = user_id_json.asString(); this->access_token = access_token_json.asString(); - this->homeserver = "https://" + std::move(homeserver_response); + this->homeserver = homeserver; // TODO: Handle well_known field. The spec says clients SHOULD handle it if its provided @@ -956,6 +926,13 @@ namespace QuickMedia { if(download_to_string(homeserver + "/_matrix/client/r0/logout", server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK) return PluginResult::NET_ERR; + // Make sure all fields are reset here! + room_data_by_id.clear(); + user_id.clear(); + access_token.clear(); + homeserver.clear(); + next_batch.clear(); + return PluginResult::OK; } @@ -991,29 +968,30 @@ namespace QuickMedia { return PluginResult::ERR; } - std::string user_id = user_id_json.asString(); - std::string access_token = access_token_json.asString(); - - std::string homeserver = extract_homeserver_from_user_id(user_id); - if(homeserver.empty()) { - fprintf(stderr, "Missing homeserver in user id, user id: %s\n", user_id.c_str()); + const Json::Value &homeserver_json = json_root["homeserver"]; + if(!homeserver_json.isString()) { + fprintf(stderr, "Failed to parse matrix cached session response\n"); return PluginResult::ERR; } + std::string user_id = user_id_json.asString(); + std::string access_token = access_token_json.asString(); + std::string homeserver = homeserver_json.asString(); + std::vector additional_args = { { "-H", "Authorization: Bearer " + access_token } }; std::string server_response; // We want to make any request to the server that can verify that our token is still valid, doesn't matter which call - if(download_to_string("https://" + homeserver + "/_matrix/client/r0/account/whoami", server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK) { + if(download_to_string(homeserver + "/_matrix/client/r0/account/whoami", server_response, std::move(additional_args), use_tor, true) != DownloadResult::OK) { fprintf(stderr, "Matrix whoami response: %s\n", server_response.c_str()); return PluginResult::NET_ERR; } this->user_id = std::move(user_id); this->access_token = std::move(access_token); - this->homeserver = "https://" + homeserver; + this->homeserver = std::move(homeserver); return PluginResult::OK; } -- cgit v1.2.3