diff options
Diffstat (limited to 'protocols/FacebookRM/src/communication.cpp')
-rw-r--r-- | protocols/FacebookRM/src/communication.cpp | 1151 |
1 files changed, 0 insertions, 1151 deletions
diff --git a/protocols/FacebookRM/src/communication.cpp b/protocols/FacebookRM/src/communication.cpp deleted file mode 100644 index 3b9f97d235..0000000000 --- a/protocols/FacebookRM/src/communication.cpp +++ /dev/null @@ -1,1151 +0,0 @@ -/* - -Facebook plugin for Miranda Instant Messenger -_____________________________________________ - -Copyright © 2009-11 Michal Zelinka, 2011-17 Robert Pösel, 2017-19 Miranda NG team - -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 2 of the License, or -(at your option) any later version. - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with this program. If not, see <http://www.gnu.org/licenses/>. - -*/ - -#include "stdafx.h" - -void facebook_client::client_notify(const wchar_t *message) -{ - parent->NotifyEvent(parent->m_tszUserName, message, 0, EVENT_CLIENT); -} - -void facebook_client::info_notify(const wchar_t *message) -{ - parent->NotifyEvent(parent->m_tszUserName, message, 0, EVENT_OTHER); -} - -http::response facebook_client::sendRequest(HttpRequest *request) -{ - http::response resp; - - if (parent->isOffline()) { - resp.code = HTTP_CODE_FAKE_OFFLINE; - return resp; - } - - // Check and append user defined locale if request doesn't have it forced already - if (!parent->m_locale.empty() && request->m_szUrl.Find("&locale=") == -1) - request << CHAR_PARAM("locale", parent->m_locale.c_str()); - - request->AddHeader("Accept-Language", "en,en-US;q=0.9"); - request->AddHeader("Accept", "*/*"); - request->AddHeader("User-Agent", Netlib_GetUserAgent()); - request->AddHeader("Cookie", ptrA(load_cookies())); // FIXME: Rework load_cookies to not do strdup - - if (request->requestType == REQUEST_POST) - request->AddHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8"); - - mir_cslockfull s(fcb_conn_lock_); s.unlock(); - - // Set persistent connection (or not) - switch (request->Persistent) { - case HttpRequest::PersistentType::NONE: - request->nlc = nullptr; - request->flags &= ~NLHRF_PERSISTENT; - break; - case HttpRequest::PersistentType::CHANNEL: - request->nlc = hChannelCon; - request->flags |= NLHRF_PERSISTENT; - break; - case HttpRequest::PersistentType::MESSAGES: - request->nlc = hMessagesCon; - request->flags |= NLHRF_PERSISTENT; - break; - case HttpRequest::PersistentType::DEFAULT: - s.lock(); - request->nlc = hFcbCon; - request->flags |= NLHRF_PERSISTENT; - break; - } - - parent->debugLogA("@@@ Sending request to '%s'", request->m_szUrl.c_str()); - - // Send the request - NLHR_PTR pnlhr(request->Send(handle_)); - - // Remember the persistent connection handle (or not) - switch (request->Persistent) { - case HttpRequest::PersistentType::NONE: - break; - case HttpRequest::PersistentType::CHANNEL: - hChannelCon = pnlhr ? pnlhr->nlc : nullptr; - break; - case HttpRequest::PersistentType::MESSAGES: - hMessagesCon = pnlhr ? pnlhr->nlc : nullptr; - break; - case HttpRequest::PersistentType::DEFAULT: - s.unlock(); - hFcbCon = pnlhr ? pnlhr->nlc : nullptr; - break; - } - - // Check and copy response data - if (pnlhr != nullptr) { - parent->debugLogA("@@@ Got response with code %d", pnlhr->resultCode); - store_headers(&resp, pnlhr->headers, pnlhr->headersCount); - resp.code = pnlhr->resultCode; - resp.data = pnlhr->pData ? pnlhr->pData : ""; - } - else { - parent->debugLogA("!!! No response from server (time-out)"); - resp.code = HTTP_CODE_FAKE_DISCONNECTED; - // Better to have something set explicitely as this value is compaired in all communication requests - } - - // Get Facebook's error message - if (resp.code == HTTP_CODE_OK) { - std::string::size_type pos = resp.data.find("\"error\":"); - if (pos != std::string::npos) { - pos += 8; - int error_num = atoi(resp.data.substr(pos, resp.data.find(",", pos) - pos).c_str()); - if (error_num != 0) { - std::string error; - - pos = resp.data.find("\"errorDescription\":\"", pos); - if (pos != std::string::npos) { - pos += 20; - - std::string::size_type pos2 = resp.data.find("\",\"", pos); - if (pos2 == std::string::npos) - pos2 = resp.data.find("\"", pos); - - error = resp.data.substr(pos, pos2 - pos); - error = utils::text::trim(utils::text::html_entities_decode(utils::text::remove_html(utils::text::slashu_to_utf8(error)))); - error = ptrA(mir_utf8decodeA(error.c_str())); - } - - std::string title; - pos = resp.data.find("\"errorSummary\":\"", pos); - if (pos != std::string::npos) { - pos += 16; - title = resp.data.substr(pos, resp.data.find("\"", pos) - pos); - title = utils::text::trim(utils::text::html_entities_decode(utils::text::remove_html(utils::text::slashu_to_utf8(title)))); - title = ptrA(mir_utf8decodeA(title.c_str())); - } - - bool silent = resp.data.find("\"silentError\":1") != std::string::npos; - - resp.error_number = error_num; - resp.error_text = error; - resp.error_title = title; - resp.code = HTTP_CODE_FAKE_ERROR; - - parent->debugLogA("!!! Received Facebook error: %d -- %s", error_num, error.c_str()); - if (request->NotifyErrors && !silent) - client_notify(_A2T(error.c_str())); - } - } - } - - // Delete the request object - delete request; - - return resp; -} - -bool facebook_client::handle_entry(const std::string &method) -{ - parent->debugLogA(" >> Entering %s()", method.c_str()); - return true; -} - -bool facebook_client::handle_success(const std::string &method) -{ - parent->debugLogA(" << Quitting %s()", method.c_str()); - reset_error(); - return true; -} - -bool facebook_client::handle_error(const std::string &method, int action) -{ - increment_error(); - parent->debugLogA("!!! %s(): Something with Facebook went wrong", method.c_str()); - - bool result = (error_count_ <= (UINT)parent->getByte(FACEBOOK_KEY_TIMEOUTS_LIMIT, FACEBOOK_TIMEOUTS_LIMIT)); - if (action == FORCE_DISCONNECT || action == FORCE_QUIT) - result = false; - - if (!result) { - reset_error(); - - if (action != FORCE_QUIT) - parent->SetStatus(ID_STATUS_OFFLINE); - } - - return result; -} - -////////////////////////////////////////////////////////////////////////////// - -std::string facebook_client::get_server_type() -{ - BYTE server_type = parent->getByte(FACEBOOK_KEY_SERVER_TYPE, 0); - if (server_type >= _countof(server_types)) - server_type = 0; - return server_types[server_type].id; -} - -std::string facebook_client::get_privacy_type() -{ - BYTE privacy_type = parent->getByte(FACEBOOK_KEY_PRIVACY_TYPE, 0); - if (privacy_type >= _countof(privacy_types)) - privacy_type = 0; - return privacy_types[privacy_type].id; -} - - -char* facebook_client::load_cookies() -{ - mir_cslock s(cookies_lock_); - - std::string cookieString; - - if (!cookies.empty()) - for (auto &iter : cookies) { - cookieString.append(iter.first); - cookieString.append(1, '='); - cookieString.append(iter.second); - cookieString.append(1, ';'); - } - - return mir_strdup(cookieString.c_str()); -} - -void facebook_client::store_headers(http::response* resp, NETLIBHTTPHEADER* headers, int headersCount) -{ - mir_cslock c(cookies_lock_); - - for (int i = 0; i < headersCount; i++) { - std::string header_name = headers[i].szName; - std::string header_value = headers[i].szValue; - - if (header_name == "Set-Cookie") { - std::string::size_type pos = header_value.find("="); - std::string cookie_name = header_value.substr(0, pos); - std::string cookie_value = header_value.substr(pos + 1, header_value.find(";", pos) - pos - 1); - - if (cookie_value == "deleted") - cookies.erase(cookie_name); - else - cookies[cookie_name] = cookie_value; - } - else resp->headers[header_name] = header_value; - } -} - -void facebook_client::clear_cookies() -{ - mir_cslock s(cookies_lock_); - - if (!cookies.empty()) - cookies.clear(); -} - -void facebook_client::clear_notifications() -{ - mir_cslock s(notifications_lock_); - - for (auto &it : notifications) { - if (it.second->hWndPopup != nullptr) - PUDeletePopup(it.second->hWndPopup); // close popup - - delete it.second; - } - - notifications.clear(); -} - -void facebook_client::clear_chatrooms() -{ - for (auto &it : chat_rooms) - delete it.second; - - chat_rooms.clear(); -} - -/** - * Clears readers info for all contacts from readers list and db - */ -void facebook_client::clear_readers() -{ - for (auto &it : readers) { - if (parent->isChatRoom(it.first)) - parent->delSetting(it.first, FACEBOOK_KEY_MESSAGE_READERS); - - parent->delSetting(it.first, FACEBOOK_KEY_MESSAGE_READ); - } - readers.clear(); -} - -/** - * Inserts info to readers list, db and writes to statusbar - */ -void facebook_client::insert_reader(MCONTACT hContact, time_t timestamp, const std::string &readerId) -{ - if (!hContact) - return; - - if (parent->isChatRoom(hContact)) { - std::string tid = ptrA(parent->getStringA(hContact, "ChatRoomID")); - - std::string name = readerId; - - // get name of this participant from chatroom's participants list - auto itRoom = chat_rooms.find(tid); - if (itRoom != chat_rooms.end()) { - facebook_chatroom *chatroom = itRoom->second; - std::map<std::string, chatroom_participant> participants = chatroom->participants; - - // try to get name of this participant - auto participant = participants.find(readerId); - if (participant != participants.end()) - name = participant->second.nick; - } - - std::wstring treaders; - - // Load old readers - ptrW told(parent->getWStringA(hContact, FACEBOOK_KEY_MESSAGE_READERS)); - if (told) - treaders = std::wstring(told) + L", "; - - // Append new reader name and remember them - std::string reader = utils::text::prepare_name(name, true); - treaders += _A2T(reader.c_str(), CP_UTF8); - parent->setWString(hContact, FACEBOOK_KEY_MESSAGE_READERS, treaders.c_str()); - } - - parent->setDword(hContact, FACEBOOK_KEY_MESSAGE_READ, timestamp); - readers.insert(std::make_pair(hContact, timestamp)); - parent->MessageRead(hContact); - if (g_bMessageState) { - MessageReadData data(timestamp, MRD_TYPE_READTIME); - CallService(MS_MESSAGESTATE_UPDATE, hContact, (LPARAM)&data); - } -} - -/** - * Removes info from readers list, db and clears statusbar - */ -void facebook_client::erase_reader(MCONTACT hContact) -{ - if (parent->isChatRoom(hContact)) - parent->delSetting(hContact, FACEBOOK_KEY_MESSAGE_READERS); - - parent->delSetting(hContact, FACEBOOK_KEY_MESSAGE_READ); - - readers.erase(hContact); - Srmm_SetStatusText(hContact, nullptr); -} - -void loginError(FacebookProto *proto, std::string error_str) -{ - utils::text::replace_all(&error_str, "<br \\/>", "\n"); - utils::text::replace_all(&error_str, "\n\n\n", "\n\n"); - - error_str = utils::text::trim( - utils::text::html_entities_decode( - utils::text::remove_html(error_str))); - - proto->debugLogA("!!! Login error: %s", !error_str.empty() ? error_str.c_str() : "Unknown error"); - - wchar_t buf[200]; - mir_snwprintf(buf, TranslateT("Login error: %s"), - (error_str.empty()) ? TranslateT("Unknown error") : ptrW(mir_utf8decodeW(error_str.c_str()))); - proto->facy.client_notify(buf); -} - -void parseJsCookies(const std::string &search, const std::string &data, std::map<std::string, std::string> &cookies) -{ - std::string::size_type pos = 0; - while ((pos = data.find(search, pos)) != std::string::npos) { - pos += search.length(); - - std::string::size_type pos2 = data.find("\",\"", pos); - if (pos2 == std::string::npos) - continue; - - std::string name = utils::url::encode(data.substr(pos, pos2 - pos)); - - pos = pos2 + 3; - pos2 = data.find("\"", pos); - if (pos2 == std::string::npos) - continue; - - std::string value = data.substr(pos, pos2 - pos); - cookies[name] = utils::url::encode(utils::text::html_entities_decode(value)); - } -} - -bool facebook_client::login(const char *username, const char *password) -{ - handle_entry("login"); - - username_ = username; - password_ = password; - - std::string postData; - std::string getData; - - if (cookies.empty()) { - // Set device ID - ptrA device(parent->getStringA(FACEBOOK_KEY_DEVICE_ID)); - if (device != nullptr) - cookies["datr"] = device; - - // Get initial cookies - http::response resp = sendRequest(loginRequest()); - - // Also parse cookies set by JavaScript - parseJsCookies("[\"CookieCore\",\"setWithoutChecksIfFirstPartyContext\",[],[\"", resp.data, cookies); - - // Parse hidden inputs and other data - std::string form = utils::text::source_get_value(&resp.data, 2, "<form", "</form>"); - utils::text::replace_all(&form, "\\\"", "\""); - - postData = utils::text::source_get_form_data(&form, true); - getData = utils::text::source_get_value(&form, 2, "login.php?login_attempt=1&", "\""); - } - - // Send validation - http::response resp = sendRequest(loginRequest(username, password, getData.c_str(), postData.c_str())); - - // Save Device ID - if (!cookies["datr"].empty()) - parent->setString(FACEBOOK_KEY_DEVICE_ID, cookies["datr"].c_str()); - - if (resp.code == HTTP_CODE_FOUND && resp.headers.find("Location") != resp.headers.end()) { - std::string location = resp.headers["Location"]; - - // Check for invalid requests - if (location.find("invalid_request.php") != std::string::npos) { - client_notify(TranslateT("Login error: Invalid request.")); - parent->debugLogA("!!! Login error: Invalid request."); - return handle_error("login", FORCE_QUIT); - } - - // Check whether login checks are required - if (location.find("/checkpoint/") != std::string::npos) { - resp = sendRequest(setupMachineRequest()); - - if (resp.data.find("login_approvals_no_phones") != std::string::npos) { - // Code approval - but no phones in account - loginError(parent, utils::text::source_get_value(&resp.data, 4, "login_approvals_no_phones", "<div", ">", "</div>")); - return handle_error("login", FORCE_QUIT); - } - - if (resp.data.find("name=\"submit[Continue]\"") != std::string::npos) { - int attempt = 0; - // Check if we need to put approval code (aka "two-factor auth") - while (resp.data.find("id=\"approvals_code\"") != std::string::npos) { - parent->debugLogA(" Login info: Approval code required."); - - std::string fb_dtsg = utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\""); - std::string nh = utils::text::source_get_value(&resp.data, 3, "name=\"nh\"", "value=\"", "\""); - - CFacebookGuardDialog guardDialog(parent, fb_dtsg.c_str()); - if (guardDialog.DoModal() != DIALOG_RESULT_OK) { - parent->SetStatus(ID_STATUS_OFFLINE); - return false; - } - - // We need verification code from user (he can get it via Facebook application on phone or by requesting code via SMS) - const char *givenCode = guardDialog.GetCode(); - - HttpRequest *request = setupMachineRequest(fb_dtsg.c_str(), nh.c_str(), "Continue"); - request->Body << CHAR_PARAM("approvals_code", givenCode); - resp = sendRequest(request); - - if (resp.data.find("id=\"approvals_code\"") != std::string::npos) { - // We get no error message if we put wrong code. Facebook just shows same form again. - if (++attempt >= 3) { - client_notify(TranslateT("You entered too many invalid verification codes. Plugin will disconnect.")); - parent->debugLogA("!!! Login error: Too many invalid attempts to verification code."); - return handle_error("login", FORCE_QUIT); - } - else client_notify(TranslateT("You entered wrong verification code. Try it again.")); - } - else // After successful verification is showed different page - classic form to save device (as handled at the bottom) - break; - } - - // Save this actual device - if (resp.data.find("name=\"submit[Continue]\"") != std::string::npos && resp.data.find("name=\"name_action_selected\"") != std::string::npos) { - std::string fb_dtsg = utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\""); - std::string nh = utils::text::source_get_value(&resp.data, 3, "name=\"nh\"", "value=\"", "\""); - - HttpRequest *request = setupMachineRequest(fb_dtsg.c_str(), nh.c_str(), "Continue"); - request->Body << CHAR_PARAM("name_action_selected", "save_device"); // Save device - or "dont_save" - resp = sendRequest(request); - } - - // Check if we need to approve also last unapproved device - - // 1) Continue to check last unknown login - if (resp.data.find("name=\"submit[Continue]\"") != std::string::npos) { - std::string fb_dtsg = utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\""); - std::string nh = utils::text::source_get_value(&resp.data, 3, "name=\"nh\"", "value=\"", "\""); - resp = sendRequest(setupMachineRequest(fb_dtsg.c_str(), nh.c_str(), "Continue")); - } - - // In this step might be needed identity confirmation - if (resp.data.find("name=\"birthday_captcha_") != std::string::npos) { - // Account is locked and needs identity confirmation - client_notify(TranslateT("Login error: Your account is temporarily locked. You need to confirm this device from web browser.")); - parent->debugLogA("!!! Login error: Birthday confirmation."); - return handle_error("login", FORCE_QUIT); - } - - // 2) Approve last unknown login - if (resp.data.find("name=\"submit[This was me]\"") != std::string::npos) { - CMStringW tszTitle; - tszTitle.AppendFormat(L"%s - %s", parent->m_tszUserName, TranslateT("Check last login")); - CMStringW tszMessage(TranslateT("Do you recognize this activity?")); - - std::string activity = utils::text::slashu_to_utf8(utils::text::source_get_value(&resp.data, 3, "<body", "</strong></div>", "</div>")); - activity = utils::text::trim(utils::text::html_entities_decode(utils::text::remove_html(activity))); - if (!activity.empty()) - tszMessage.AppendFormat(L"\n\n%s", ptrW(mir_utf8decodeW(activity.c_str()))); - - if (MessageBox(nullptr, tszMessage, tszTitle, MB_ICONWARNING | MB_YESNO | MB_DEFBUTTON1) != IDYES) { - // We will cancel connecting right away, because we don't want to handle password changing via Miranda - client_notify(TranslateT("Login error: You need to confirm last unknown login or revoke it from web browser.")); - return handle_error("login", FORCE_QUIT); - } - - std::string fb_dtsg = utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\""); - std::string nh = utils::text::source_get_value(&resp.data, 3, "name=\"nh\"", "value=\"", "\""); - - // Recognize device (or "This wasn't me" - this will force to change account password) - resp = sendRequest(setupMachineRequest(fb_dtsg.c_str(), nh.c_str(), "This was me")); - - // 3) Save last device - fb_dtsg = utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\""); - nh = utils::text::source_get_value(&resp.data, 3, "name=\"nh\"", "value=\"", "\""); - - HttpRequest *request = setupMachineRequest(fb_dtsg.c_str(), nh.c_str(), "Continue"); - request->Body << CHAR_PARAM("name_action_selected", "save_device"); // Save device - or "dont_save" - resp = sendRequest(request); - } - } - else if (resp.data.find("name=\"submit[Get Started]\"") != std::string::npos) { - if (!parent->getBool(FACEBOOK_KEY_TRIED_DELETING_DEVICE_ID)) { - // Try to remove DeviceID and login again - cookies["datr"] = ""; - parent->delSetting(FACEBOOK_KEY_DEVICE_ID); - parent->setByte(FACEBOOK_KEY_TRIED_DELETING_DEVICE_ID, 1); - return login(username, password); - } - else { - // Reset flag - parent->delSetting(FACEBOOK_KEY_TRIED_DELETING_DEVICE_ID); - // Facebook things that computer was infected by malware and needs cleaning - client_notify(TranslateT("Login error: Facebook thinks your computer is infected. Solve it by logging in via 'private browsing' mode of your web browser and run their antivirus check.")); - parent->debugLogA("!!! Login error: Facebook requires computer scan."); - return handle_error("login", FORCE_QUIT); - } - } - } - } - - switch (resp.code) { - case HTTP_CODE_FAKE_DISCONNECTED: - // When is error only because timeout, try login once more - if (handle_error("login")) - return login(username, password); - else - return handle_error("login", FORCE_QUIT); - - case HTTP_CODE_OK: // OK page returned, but that is regular login page we don't want in fact - // Check whether captcha code is required - if (resp.data.find("id=\"captcha\"") != std::string::npos) { - client_notify(TranslateT("Login error: Captcha code is required. You need to confirm this device from web browser.")); - parent->debugLogA("!!! Login error: Captcha code is required."); - return handle_error("login", FORCE_QUIT); - } - else { - // Get and notify error message - std::string error = utils::text::slashu_to_utf8(utils::text::source_get_value(&resp.data, 3, "[\"LoginFormError\"", "\"__html\":\"", "\"}")); - if (error.empty()) - error = utils::text::slashu_to_utf8(utils::text::source_get_value(&resp.data, 3, "role=\"alert\"", ">", "</div")); - if (error.empty()) - error = utils::text::slashu_to_utf8(utils::text::source_get_value(&resp.data, 3, "id=\"globalContainer\"", ">", "</div")); - if (error.empty()) - error = utils::text::slashu_to_utf8(utils::text::source_get_value(&resp.data, 2, "<strong>", "</strong")); - loginError(parent, error); - } - __fallthrough; - - case HTTP_CODE_FORBIDDEN: // Forbidden - case HTTP_CODE_NOT_FOUND: // Not Found - default: - return handle_error("login", FORCE_QUIT); - - case HTTP_CODE_FOUND: // Found and redirected somewhere - if (resp.headers.find("Location") != resp.headers.end()) { - std::string redirectUrl = resp.headers["Location"]; - std::string expectedUrl = HTTP_PROTO_SECURE FACEBOOK_SERVER_REGULAR "/"; - - // Remove eventual parameters - std::string::size_type pos = redirectUrl.rfind("?"); - if (pos != std::string::npos) - redirectUrl = redirectUrl.substr(0, pos); - - if (redirectUrl != expectedUrl) { - // Unexpected redirect, but we try to ignore it - maybe we were logged in anyway - parent->debugLogA("!!! Login error: Unexpected redirect: %s (Original: %s) (Expected: %s)", redirectUrl.c_str(), resp.headers["Location"].c_str(), expectedUrl.c_str()); - } - } - - if (cookies.find("c_user") != cookies.end()) { - // Probably logged in, everything seems OK - this->self_.user_id = cookies.find("c_user")->second; - parent->setString(FACEBOOK_KEY_ID, this->self_.user_id.c_str()); - parent->debugLogA(" Got self user id: %s", this->self_.user_id.c_str()); - return handle_success("login"); - } - else { - client_notify(TranslateT("Login error, probably bad login credentials.")); - parent->debugLogA("!!! Login error, probably bad login credentials."); - return handle_error("login", FORCE_QUIT); - } - } -} - -bool facebook_client::logout() -{ - handle_entry("logout"); - - http::response resp = sendRequest(logoutRequest()); - - this->username_.clear(); - this->password_.clear(); - this->self_.user_id.clear(); - - switch (resp.code) { - case HTTP_CODE_OK: - case HTTP_CODE_FOUND: - return handle_success("logout"); - - default: - return false; // Logout not finished properly, but..okay, who cares :P - } -} - -bool facebook_client::home() -{ - handle_entry("home"); - - // get fb_dtsg - http::response resp = sendRequest(dtsgRequest()); - - this->dtsg_ = utils::text::source_get_value(&resp.data, 3, "name=\"fb_dtsg\"", "value=\"", "\""); - { - // Compute ttstamp from dtsg_ - std::stringstream csrf; - for (unsigned int i = 0; i < this->dtsg_.length(); i++) - csrf << (int)this->dtsg_.at(i); - - this->ttstamp_ = "2" + csrf.str(); - } - - if (this->dtsg_.empty()) { - parent->debugLogA("!!! Empty dtsg. Source code:\n%s", resp.data.c_str()); - client_notify(TranslateT("Could not load communication token. You should report this and wait for plugin update.")); - return handle_error("home", FORCE_QUIT); - } - - parent->debugLogA(" Got self dtsg"); - - resp = sendRequest(homeRequest()); - - switch (resp.code) { - case HTTP_CODE_OK: - { - std::string touchSearch = "{\"id\":" + this->self_.user_id; - std::string touchData = utils::text::source_get_value(&resp.data, 2, touchSearch.c_str(), "}"); - - // Get real name (from touch version) - if (!touchData.empty()) - this->self_.real_name = utils::text::html_entities_decode(utils::text::slashu_to_utf8(utils::text::source_get_value(&touchData, 2, "\"name\":\"", "\""))); - - // Another attempt to get real name (from mbasic version) - if (this->self_.real_name.empty()) - this->self_.real_name = utils::text::source_get_value(&resp.data, 4, "id=\"root", "<strong", ">", "</strong>"); - - // Try to get name again, if we've got some some weird version of Facebook - if (this->self_.real_name.empty()) - this->self_.real_name = utils::text::source_get_value(&resp.data, 5, "id=\"root", "</a>", "<div", ">", "</div>"); - - // Another attempt to get name - if (this->self_.real_name.empty()) - this->self_.real_name = utils::text::source_get_value(&resp.data, 5, "id=\"root", "</td>", "<div", ">", "</td>"); - - // Get and strip optional nickname - std::string::size_type pos = this->self_.real_name.find("<span class=\"alternate_name\">"); - if (pos != std::string::npos) { - this->self_.nick = utils::text::source_get_value(&this->self_.real_name, 2, "<span class=\"alternate_name\">(", ")</span>"); - parent->debugLogA(" Got self nick name: %s", this->self_.nick.c_str()); - - this->self_.real_name = this->self_.real_name.substr(0, pos - 1); - } - - // Another attempt to get optional nickname - if (this->self_.nick.empty()) - this->self_.nick = utils::text::html_entities_decode(utils::text::slashu_to_utf8(utils::text::source_get_value(&resp.data, 3, "class=\\\"alternate_name\\\"", ">(", ")\\u003C\\/"))); - - this->self_.real_name = utils::text::remove_html(this->self_.real_name); - parent->debugLogA(" Got self real name (nickname): %s (%s)", this->self_.real_name.c_str(), this->self_.nick.c_str()); - parent->SaveName(0, &this->self_); - - // Get avatar (from touch version) - if (!touchData.empty()) - this->self_.image_url = utils::text::html_entities_decode(utils::text::slashu_to_utf8(utils::text::source_get_value(&touchData, 2, "\"pic\":\"", "\""))); - - // Another attempt to get avatar(from mbasic version) - if (this->self_.image_url.empty()) - this->self_.image_url = utils::text::source_get_value(&resp.data, 3, "id=\"root", "<img src=\"", "\""); - - // Another attempt to get avatar - if (this->self_.image_url.empty()) { - this->self_.image_url = utils::text::source_get_value(&resp.data, 3, "id=\"root", "/photo.php?", "\""); - - // Prepare this special url (not direct image url) to be handled correctly in CheckAvatarChange() - // It must contain "/" at the beginning and also shouldn't contain "?" as parameters after that are stripped - if (!this->self_.image_url.empty()) - this->self_.image_url = "/" + this->self_.image_url; - } - - // Final attempt to get avatar as on some pages is only link to photo page and not link to image itself - if (this->self_.image_url.empty()) { - http::response resp2 = sendRequest(profilePictureRequest(self_.user_id.c_str())); - - // Get avatar (from mbasic version of photo page) - this->self_.image_url = utils::text::html_entities_decode(utils::text::source_get_value(&resp2.data, 3, "id=\"root", "<img src=\"", "\"")); - - // Get avatar (from touch version) - if (this->self_.image_url.empty()) - this->self_.image_url = utils::text::html_entities_decode(utils::text::source_get_value(&resp2.data, 3, "id=\"root", "background-image: url("", "")")); - - // Sometimes even Facebook doesn't show any picture at all! So just ignore this error in that case... - if (this->self_.image_url.empty()) { - parent->debugLogA("!!! Empty avatar even from avatar page. Source code:\n%s", resp2.data.c_str()); - - // Set dumb avatar "url" (see how it works in CheckAvatarChange()) so we can't tell if/when the avatar changed, but it will be loaded at least once - this->self_.image_url = "/NO_AVATAR/"; - } - } - - parent->debugLogA(" Got self avatar: %s", this->self_.image_url.c_str()); - parent->CheckAvatarChange(0, this->self_.image_url); - - // Get logout hash - this->logout_hash_ = utils::text::source_get_value2(&resp.data, "/logout.php?h=", "&\""); - parent->debugLogA(" Got self logout hash: %s", this->logout_hash_.c_str()); - - if (this->self_.real_name.empty() || this->self_.image_url.empty() || this->logout_hash_.empty()) { - parent->debugLogA("!!! Empty nick/avatar/hash. Source code:\n%s", resp.data.c_str()); - client_notify(TranslateT("Could not load all required data. Plugin may still work correctly, but you should report this and wait for plugin update.")); - } - } - return handle_success("home"); - - case HTTP_CODE_FOUND: - // Work-around for replica_down, f**king hell what's that? - parent->debugLogA("!!! REPLICA_DOWN is back in force!"); - return this->home(); - - default: - return handle_error("home", FORCE_QUIT); - } -} - -bool facebook_client::chat_state(bool online) -{ - handle_entry("chat_state"); - - http::response resp = sendRequest(setVisibilityRequest(online)); - if (!resp.error_title.empty()) - return handle_error("chat_state"); - - return handle_success("chat_state"); -} - -bool facebook_client::reconnect() -{ - handle_entry("reconnect"); - - // Request reconnect - http::response resp = sendRequest(reconnectRequest()); - if (resp.code != HTTP_CODE_OK) - return handle_error("reconnect", FORCE_DISCONNECT); - - std::string redir = utils::text::source_get_value(&resp.data, 2, "\"redirect\":\"", "\""); - if (!redir.empty()) { - redir = utils::text::html_entities_decode(redir); - parent->debugLogA("Redirecting to %s", redir.c_str()); - - auto *p = new HttpRequest(REQUEST_GET, FACEBOOK_SERVER_REGULAR); - p->m_szUrl = redir.c_str(); - resp = sendRequest(p); - if (resp.code != HTTP_CODE_OK) - return handle_error("reconnect", FORCE_DISCONNECT); - } - - this->chat_channel_ = utils::text::source_get_value(&resp.data, 2, "\"user_channel\":\"", "\""); - parent->debugLogA(" Got self channel: %s", this->chat_channel_.c_str()); - - this->chat_channel_partition_ = utils::text::source_get_value2(&resp.data, "\"partition\":", ",}"); - parent->debugLogA(" Got self channel partition: %s", this->chat_channel_partition_.c_str()); - - this->chat_channel_host_ = utils::text::source_get_value(&resp.data, 2, "\"host\":\"", "\""); - parent->debugLogA(" Got self channel host: %s", this->chat_channel_host_.c_str()); - - this->chat_sequence_num_ = utils::text::source_get_value2(&resp.data, "\"seq\":", ",}"); - parent->debugLogA(" Got self sequence number: %s", this->chat_sequence_num_.c_str()); - - this->chat_conn_num_ = utils::text::source_get_value2(&resp.data, "\"max_conn\":", ",}"); - parent->debugLogA(" Got self max_conn: %s", this->chat_conn_num_.c_str()); - - this->chat_sticky_num_ = utils::text::source_get_value(&resp.data, 2, "\"sticky_token\":\"", "\""); - parent->debugLogA(" Got self sticky_token: %s", this->chat_sticky_num_.c_str()); - - activity_ping(); - return handle_success("reconnect"); -} - -bool facebook_client::channel() -{ - handle_entry("channel"); - - // Get updates - http::response resp = sendRequest(channelRequest(PULL)); - if (resp.data.empty()) // Something went wrong - return handle_error("channel"); - - // Load traceId, if present - std::string traceId = utils::text::source_get_value(&resp.data, 2, "\"tr\":\"", "\""); - if (!traceId.empty()) - this->chat_traceid_ = traceId; - - std::string type = utils::text::source_get_value(&resp.data, 2, "\"t\":\"", "\""); - parent->debugLogA("Pull response type = %s", type.c_str()); - - if (type == "continue" || type == "heartbeat") { - // Everything is OK, no new message received - } - else if (type == "lb") { - // Some new stuff (idk how does it work yet) - this->chat_sticky_pool_ = utils::text::source_get_value(&resp.data, 2, "\"pool\":\"", "\""); - parent->debugLogA(" Got self sticky pool: %s", this->chat_sticky_pool_.c_str()); - - this->chat_sticky_num_ = utils::text::source_get_value2(&resp.data, "\"sticky\":\"", "\""); - parent->debugLogA(" Got self sticky number: %s", this->chat_sticky_num_.c_str()); - } - else if (type == "refresh") { - // Requested relogin (due to some settings change, removing this session, etc.) - parent->debugLogA("!!! Requested refresh"); - - this->chat_sequence_num_ = utils::text::source_get_value2(&resp.data, "\"seq\":", ",}"); - parent->debugLogA(" Got self sequence number: %s", this->chat_sequence_num_.c_str()); - - this->chat_reconnect_reason_ = utils::text::source_get_value2(&resp.data, "\"reason\":", ",}"); - parent->debugLogA(" Got reconnect reason: %s", this->chat_reconnect_reason_.c_str()); - - return this->reconnect(); - } - else if (!type.empty()) { // for "msg", "fullReload" and maybe also other types - // Something has been received, throw to new thread to process - parent->debugLogA("*** Starting processing messages"); - { - std::vector<facebook_message> messages; - if (parent->ParseMessages(resp.data, messages) == EXIT_SUCCESS) { - parent->ReceiveMessages(messages); - - if (parent->getBool(FACEBOOK_KEY_EVENT_NOTIFICATIONS_ENABLE, DEFAULT_EVENT_NOTIFICATIONS_ENABLE)) - parent->ShowNotifications(); - - parent->debugLogA("*** Messages processed"); - } - else parent->debugLogA("*** Error processing messages"); - } - - // Get new sequence number - std::string seq = utils::text::source_get_value2(&resp.data, "\"seq\":", ",}"); - parent->debugLogA(" Got self sequence number: %s", seq.c_str()); - - if (type == "msg") { - // Update msgs_recv number for every "msg" type we receive (during fullRefresh/reload responses it stays the same) - this->chat_msgs_recv_++; - } - - if (!seq.empty()) - this->chat_sequence_num_ = seq; - } - else // No type? This shouldn't happen unless there is a big API change. - return handle_error("channel"); - - // Return - switch (resp.code) { - case HTTP_CODE_OK: - return handle_success("channel"); - - case HTTP_CODE_GATEWAY_TIMEOUT: - // Maybe we have same clientid as other connected client, try to generate different one - this->chat_clientid_ = utils::text::rand_string(8, "0123456789abcdef", &this->random_); - - // Intentionally fall to handle_error() below - case HTTP_CODE_FAKE_DISCONNECTED: - case HTTP_CODE_FAKE_ERROR: - default: - return handle_error("channel"); - } -} - -bool facebook_client::activity_ping() -{ - // Don't send ping when we are not online - if (parent->m_iStatus != ID_STATUS_ONLINE) - return true; - - handle_entry("activity_ping"); - - http::response resp = sendRequest(channelRequest(PING)); - - // Remember this last ping time - parent->m_pingTS = ::time(0); - - if (resp.data.empty() || resp.data.find("\"t\":\"pong\"") == resp.data.npos) { - // Something went wrong - return handle_error("activity_ping"); - } - - return handle_success("activity_ping"); -} - -int facebook_client::send_message(int seqid, MCONTACT hContact, const std::string &message_text, std::string *error_text, const std::string &captcha_persist_data, const std::string &captcha) -{ - handle_entry("send_message"); - - bool isChatRoom = parent->isChatRoom(hContact); - - ptrA userId(parent->getStringA(hContact, FACEBOOK_KEY_ID)); - ptrA threadId(parent->getStringA(hContact, FACEBOOK_KEY_TID)); - - // Check if we have userId/threadId to be able to send message - if ((isChatRoom && (threadId == nullptr || !mir_strcmp(threadId, "null"))) || (!isChatRoom && (userId == nullptr || !mir_strcmp(userId, "null")))) { - // This shouldn't happen unless user manually deletes some data via Database Editor++ - *error_text = Translate("Contact doesn't have required data in database."); - - handle_error("send_message"); - return SEND_MESSAGE_ERROR; - } - - // Probably we can generate any random messageID, it just have to be numeric and don't start with "0". We will receive it in response as "client_message_id". - std::string messageId = utils::text::rand_string(10, "123456789", &this->random_); - - http::response resp; - { - mir_cslock s(send_message_lock_); - resp = sendRequest(sendMessageRequest(userId, threadId, messageId.c_str(), message_text.c_str(), isChatRoom, captcha.c_str(), captcha_persist_data.c_str())); - - *error_text = resp.error_text; - - if (resp.error_number == 0) { - // Everything is OK - // Remember this message id - std::string mid = utils::text::source_get_value(&resp.data, 2, "\"message_id\":\"", "\""); - if (mid.empty()) - mid = utils::text::source_get_value(&resp.data, 2, "\"mid\":\"", "\""); // TODO: This is probably not used anymore - - // For classic contacts remember last message id - if (!parent->isChatRoom(hContact)) - parent->setString(hContact, FACEBOOK_KEY_MESSAGE_ID, mid.c_str()); - - // Get timestamp - std::string timestamp = utils::text::source_get_value(&resp.data, 2, "\"timestamp\":", ","); - time_t time = utils::time::from_string(timestamp); - - // Remember last action timestamp for history sync - parent->setDword(FACEBOOK_KEY_LAST_ACTION_TS, time); - - // For classic conversation we try to replace timestamp of added event in OnPreCreateEvent() - if (seqid > 0) - messages_timestamp.insert(std::make_pair(seqid, time)); - - // We have this message in database, so ignore further tries (in channel) to add it again - messages_ignore.insert(std::make_pair(mid, 0)); - } - } - - switch (resp.error_number) { - case 0: - // Everything is OK - break; - - // case 1356002: // You are offline (probably you can't use mercury or some other request when chat is offline) - - case 1356003: // Contact is offline - parent->setWord(hContact, "Status", ID_STATUS_OFFLINE); - return SEND_MESSAGE_ERROR; - - case 1356026: // Contact has alternative client - client_notify(TranslateT("Need confirmation for sending messages to other clients.\nOpen Facebook website and try to send message to this contact again!")); - return SEND_MESSAGE_ERROR; - - case 1357007: // Security check (captcha) is required - { - std::string imageUrl = utils::text::html_entities_decode(utils::text::slashu_to_utf8(utils::text::source_get_value(&resp.data, 3, "img class=\\\"img\\\"", "src=\\\"", "\\\""))); - std::string captchaPersistData = utils::text::source_get_value(&resp.data, 3, "\\\"captcha_persist_data\\\"", "value=\\\"", "\\\""); - - parent->debugLogA(" Got imageUrl (first): %s", imageUrl.c_str()); - parent->debugLogA(" Got captchaPersistData (first): %s", captchaPersistData.c_str()); - - http::response capResp = sendRequest(refreshCaptchaRequest(captchaPersistData.c_str())); - if (capResp.code == HTTP_CODE_OK) { - imageUrl = utils::text::html_entities_decode(utils::text::slashu_to_utf8(utils::text::source_get_value(&capResp.data, 3, "img class=\\\"img\\\"", "src=\\\"", "\\\""))); - captchaPersistData = utils::text::source_get_value(&capResp.data, 3, "\\\"captcha_persist_data\\\"", "value=\\\"", "\\\""); - - parent->debugLogA(" Got imageUrl (second): %s", imageUrl.c_str()); - parent->debugLogA(" Got captchaPersistData (second): %s", captchaPersistData.c_str()); - - std::string result; - if (!parent->RunCaptchaForm(imageUrl, result)) { - *error_text = Translate("User cancel captcha challenge."); - return SEND_MESSAGE_CANCEL; - } - - return send_message(seqid, hContact, message_text, error_text, captchaPersistData, result); - } - } - return SEND_MESSAGE_CANCEL; // Cancel because we failed to load captcha image so we can't continue only with error - - //case 1404123: // Blocked sending messages (with URLs) because Facebook think our computer is infected with malware - - default: // Other error - parent->debugLogA("!!! Send message error #%d: %s", resp.error_number, resp.error_text.c_str()); - return SEND_MESSAGE_ERROR; - } - - switch (resp.code) { - case HTTP_CODE_OK: - handle_success("send_message"); - return SEND_MESSAGE_OK; - - case HTTP_CODE_FAKE_ERROR: - case HTTP_CODE_FAKE_DISCONNECTED: - default: - *error_text = Translate("Timeout when sending message."); - - handle_error("send_message"); - return SEND_MESSAGE_ERROR; - } -} - -bool facebook_client::post_status(status_data *status) -{ - if (status == nullptr || (status->text.empty() && status->url.empty())) - return false; - - handle_entry("post_status"); - - if (status->isPage) { - // Switch to page identity by which name we will share this post - sendRequest(switchIdentityRequest(status->user_id.c_str())); - } - - std::string linkData; - if (!status->url.empty()) { - http::response resp = sendRequest(linkScraperRequest(status)); - - std::string temp = utils::text::html_entities_decode(utils::text::slashu_to_utf8(resp.data)); - std::string form = utils::text::source_get_value(&temp, 2, "<form", "</form>"); - utils::text::replace_all(&form, "\\\"", "\""); - linkData += utils::text::source_get_form_data(&form); - // FIXME: Rework to some "scraped_link" structure to simplify working with it? - } - - http::response resp = sendRequest(sharePostRequest(status, linkData.c_str())); - - if (status->isPage) // Switch back to our identity - sendRequest(switchIdentityRequest(self_.user_id.c_str())); - - // cleanup status elements (delete users) - for (std::vector<facebook_user*>::size_type i = 0; i < status->users.size(); i++) - delete status->users[i]; - status->users.clear(); - - if (resp.isValid()) { - parent->NotifyEvent(parent->m_tszUserName, TranslateT("Status update was successful."), 0, EVENT_OTHER); - return handle_success("post_status"); - } - - return handle_error("post_status"); -} - -////////////////////////////////////////////////////////////////////////////// - -bool facebook_client::save_url(const std::string &url, const std::wstring &filename, HNETLIBCONN &nlc) -{ - NETLIBHTTPREQUEST req = { sizeof(req) }; - req.requestType = REQUEST_GET; - req.szUrl = const_cast<char*>(url.c_str()); - req.flags = NLHRF_HTTP11 | NLHRF_REDIRECT | NLHRF_PERSISTENT | NLHRF_NODUMP; - req.nlc = nlc; - - bool ret = false; - NLHR_PTR resp(Netlib_HttpTransaction(handle_, &req)); - if (resp) { - nlc = resp->nlc; - parent->debugLogA("@@@ Saving URL %s to file %s", url.c_str(), _T2A(filename.c_str())); - - // Create folder if necessary - std::wstring dir = filename.substr(0, filename.rfind('\\')); - if (_waccess(dir.c_str(), 0)) - CreateDirectoryTreeW(dir.c_str()); - - // Write to file - FILE *f = _wfopen(filename.c_str(), L"wb"); - if (f != nullptr) { - fwrite(resp->pData, 1, resp->dataLength, f); - fclose(f); - - ret = _waccess(filename.c_str(), 0) == 0; - } - } - else nlc = nullptr; - - return ret; -} - -bool facebook_client::sms_code(const char *fb_dtsg) -{ - http::response resp = sendRequest(loginSmsRequest(fb_dtsg)); - - if (resp.data.find("\"is_valid\":true", 0) == std::string::npos) { - // Code wasn't send - client_notify(TranslateT("Error occurred when requesting verification SMS code.")); - return false; - } - - parent->NotifyEvent(parent->m_tszUserName, TranslateT("Verification SMS code was sent to your mobile phone."), 0, EVENT_OTHER); - return true; -} |