diff options
author | George Hazan <ghazan@miranda.im> | 2018-12-27 20:53:58 +0300 |
---|---|---|
committer | George Hazan <ghazan@miranda.im> | 2018-12-27 20:54:07 +0300 |
commit | 38af197ebf6ff9f947d02338262ad8ebe48f26d3 (patch) | |
tree | d6729c86ee940118a8218028bf095c84113b2efa /protocols | |
parent | 7b4ac22ea5d0d655d302b0edcb753889d5f39fd6 (diff) |
fixes #1668 (ICQ: contact search)
Diffstat (limited to 'protocols')
-rw-r--r-- | protocols/Icq10/src/http.cpp | 44 | ||||
-rw-r--r-- | protocols/Icq10/src/http.h | 16 | ||||
-rw-r--r-- | protocols/Icq10/src/proto.cpp | 23 | ||||
-rw-r--r-- | protocols/Icq10/src/proto.h | 9 | ||||
-rw-r--r-- | protocols/Icq10/src/server.cpp | 102 | ||||
-rw-r--r-- | protocols/Icq10/src/utils.cpp | 11 |
6 files changed, 186 insertions, 19 deletions
diff --git a/protocols/Icq10/src/http.cpp b/protocols/Icq10/src/http.cpp index e538450ee5..7ef26c38de 100644 --- a/protocols/Icq10/src/http.cpp +++ b/protocols/Icq10/src/http.cpp @@ -110,8 +110,8 @@ void CIcqProto::ExecuteRequest(AsyncHttpRequest *pReq) pReq->szUrl = str.GetBuffer(); } else { - pReq->pData = mir_strdup(pReq->m_szParam); pReq->dataLength = pReq->m_szParam.GetLength(); + pReq->pData = pReq->m_szParam.Detach(); } } @@ -121,7 +121,19 @@ void CIcqProto::ExecuteRequest(AsyncHttpRequest *pReq) } debugLogA("Executing request %s:\n%s", pReq->m_reqId, pReq->szUrl); - + + if (pReq->m_conn == CONN_RAPI) { + CMStringA szAgent(FORMAT, "%d Mail.ru Windows ICQ (version 10.0.1999)", DWORD(m_dwUin)); + pReq->AddHeader("User-Agent", szAgent); + + if (m_szRToken.IsEmpty()) { + if (!RefreshRobustToken()) { + delete pReq; + return; + } + } + } + NETLIBHTTPREQUEST *reply = Netlib_HttpTransaction(m_hNetlibUser, pReq); if (reply != nullptr) { if (pReq->m_pFunc != nullptr) @@ -187,3 +199,31 @@ JsonReply::~JsonReply() { json_delete(m_root); } + +///////////////////////////////////////////////////////////////////////////////////////// + +RobustReply::RobustReply(NETLIBHTTPREQUEST *pReply) +{ + if (pReply == nullptr) { + m_errorCode = 500; + return; + } + + m_errorCode = pReply->resultCode; + if (m_errorCode != 200) + return; + + m_root = json_parse(pReply->pData); + if (m_root == nullptr) { + m_errorCode = 500; + return; + } + + m_errorCode = (*m_root)["status"]["code"].as_int(); + m_results = &(*m_root)["results"]; +} + +RobustReply::~RobustReply() +{ + json_delete(m_root); +} diff --git a/protocols/Icq10/src/http.h b/protocols/Icq10/src/http.h index d5173404b7..1270d9445e 100644 --- a/protocols/Icq10/src/http.h +++ b/protocols/Icq10/src/http.h @@ -3,7 +3,7 @@ class CIcqProto; enum IcqConnection { - CONN_NONE = -1, CONN_MAIN = 0, CONN_FETCH = 1, CONN_LAST = 2 + CONN_NONE = -1, CONN_MAIN = 0, CONN_FETCH = 1, CONN_RAPI = 2, CONN_LAST = 3 }; struct AsyncHttpRequest : public MTHttpRequest<CIcqProto> @@ -30,3 +30,17 @@ public: __forceinline int error() const { return m_errorCode; } __forceinline int detail() const { return m_detailCode; } }; + +class RobustReply +{ + JSONNode *m_root = nullptr; + int m_errorCode = 0; + JSONNode* m_results = nullptr; + +public: + RobustReply(NETLIBHTTPREQUEST*); + ~RobustReply(); + + __forceinline JSONNode& results() const { return *m_results; } + __forceinline int error() const { return m_errorCode; } +}; diff --git a/protocols/Icq10/src/proto.cpp b/protocols/Icq10/src/proto.cpp index e22a123a12..9c34760ffb 100644 --- a/protocols/Icq10/src/proto.cpp +++ b/protocols/Icq10/src/proto.cpp @@ -168,19 +168,15 @@ INT_PTR CIcqProto::GetCaps(int type, MCONTACT hContact) switch (type) { case PFLAGNUM_1: - nReturn = PF1_IM | PF1_URL | PF1_AUTHREQ | PF1_BASICSEARCH | PF1_ADDSEARCHRES | - PF1_VISLIST | PF1_INVISLIST | PF1_MODEMSG | PF1_FILE | PF1_EXTSEARCH | - PF1_EXTSEARCHUI | PF1_SEARCHBYEMAIL | PF1_SEARCHBYNAME | - PF1_ADDED | PF1_CONTACT | PF1_SERVERCLIST; + nReturn = PF1_IM | PF1_URL | PF1_AUTHREQ | PF1_BASICSEARCH | PF1_ADDSEARCHRES | /*PF1_SEARCHBYNAME | TODO */ + PF1_VISLIST | PF1_INVISLIST | PF1_MODEMSG | PF1_FILE | PF1_ADDED | PF1_CONTACT | PF1_SERVERCLIST; break; case PFLAGNUM_2: - return PF2_ONLINE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND | PF2_HEAVYDND | - PF2_FREECHAT | PF2_INVISIBLE | PF2_ONTHEPHONE; + return PF2_ONLINE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND | PF2_HEAVYDND | PF2_FREECHAT | PF2_INVISIBLE | PF2_ONTHEPHONE; case PFLAGNUM_3: - return PF2_ONLINE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND | PF2_HEAVYDND | - PF2_FREECHAT | PF2_INVISIBLE; + return PF2_ONLINE | PF2_SHORTAWAY | PF2_LONGAWAY | PF2_LIGHTDND | PF2_HEAVYDND | PF2_FREECHAT | PF2_INVISIBLE; case PFLAGNUM_4: nReturn = PF4_SUPPORTIDLE | PF4_IMSENDOFFLINE | PF4_INFOSETTINGSVC | PF4_SUPPORTTYPING | PF4_AVATARS | PF4_SERVERMSGID; @@ -209,8 +205,15 @@ int CIcqProto::GetInfo(MCONTACT hContact, int infoType) HANDLE CIcqProto::SearchBasic(const wchar_t *pszSearch) { - // Failure - return nullptr; + auto *pReq = new AsyncHttpRequest(CONN_RAPI, REQUEST_POST, ICQ_ROBUST_SERVER, &CIcqProto::OnSearchResults); + + JSONNode request, params; params.set_name("params"); + params << WCHAR_PARAM("keyword", pszSearch); + request << CHAR_PARAM("method", "search") << CHAR_PARAM("reqId", pReq->m_reqId) << CHAR_PARAM("authToken", m_szRToken) + << INT_PARAM("clientId", m_iRClientId) << params; + pReq->m_szParam = ptrW(json_write(&request)); + Push(pReq); + return pReq; } //////////////////////////////////////////////////////////////////////////////////////// diff --git a/protocols/Icq10/src/proto.h b/protocols/Icq10/src/proto.h index 49832997fa..b2524b1939 100644 --- a/protocols/Icq10/src/proto.h +++ b/protocols/Icq10/src/proto.h @@ -33,8 +33,9 @@ #include "m_system.h" #include "m_protoint.h" -#define ICQ_API_SERVER "https://api.icq.net" #define ICQ_APP_ID "ic1nmMjqg7Yu-0hL" +#define ICQ_API_SERVER "https://api.icq.net" +#define ICQ_ROBUST_SERVER "https://rapi.icq.net" struct IcqCacheItem { @@ -72,9 +73,11 @@ class CIcqProto : public PROTO<CIcqProto> void ShutdownSession(void); void StartSession(void); + void OnAddClient(NETLIBHTTPREQUEST*, AsyncHttpRequest*); void OnCheckPassword(NETLIBHTTPREQUEST*, AsyncHttpRequest*); void OnFetchEvents(NETLIBHTTPREQUEST*, AsyncHttpRequest*); void OnReceiveAvatar(NETLIBHTTPREQUEST*, AsyncHttpRequest*); + void OnSearchResults(NETLIBHTTPREQUEST*, AsyncHttpRequest*); void OnSendMessage(NETLIBHTTPREQUEST*, AsyncHttpRequest*); void OnStartSession(NETLIBHTTPREQUEST*, AsyncHttpRequest*); @@ -88,9 +91,11 @@ class CIcqProto : public PROTO<CIcqProto> HNETLIBCONN m_ConnPool[CONN_LAST]; CMStringA m_szSessionKey; CMStringA m_szAToken; + CMStringA m_szRToken; CMStringA m_fetchBaseURL; CMStringA m_aimsid; LONG m_msgId = 1; + int m_iRClientId; OBJLIST<IcqOwnMessage> m_arOwnIds; @@ -101,8 +106,10 @@ class CIcqProto : public PROTO<CIcqProto> HANDLE m_evRequestsQueue; LIST<AsyncHttpRequest> m_arHttpQueue; + void CalcHash(AsyncHttpRequest*); void ExecuteRequest(AsyncHttpRequest*); void Push(MHttpRequest*); + bool RefreshRobustToken(); //////////////////////////////////////////////////////////////////////////////////////// // cache diff --git a/protocols/Icq10/src/server.cpp b/protocols/Icq10/src/server.cpp index 93603815bd..6d3c1b4da8 100644 --- a/protocols/Icq10/src/server.cpp +++ b/protocols/Icq10/src/server.cpp @@ -28,6 +28,8 @@ void CIcqProto::CheckPassword() Miranda_GetVersionText(mirVer, _countof(mirVer)); m_szAToken = getMStringA("AToken"); + m_szRToken = getMStringA("RToken"); + m_iRClientId = getDword("RClientID"); m_szSessionKey = getMStringA("SessionKey"); if (m_szAToken.IsEmpty() || m_szSessionKey.IsEmpty()) { auto *pReq = new AsyncHttpRequest(CONN_MAIN, REQUEST_POST, "https://api.login.icq.net/auth/clientLogin", &CIcqProto::OnCheckPassword); @@ -66,6 +68,53 @@ void CIcqProto::OnLoggedOut() setAllContactStatuses(ID_STATUS_OFFLINE, false); } +bool CIcqProto::RefreshRobustToken() +{ + if (!m_szRToken.IsEmpty()) + return true; + + bool bRet = false; + auto *tmp = new AsyncHttpRequest(CONN_RAPI, REQUEST_POST, ICQ_ROBUST_SERVER "/genToken"); + + time_t ts = time(0); + tmp << CHAR_PARAM("a", m_szAToken) << CHAR_PARAM("k", ICQ_APP_ID) + << CHAR_PARAM("nonce", CMStringA(FORMAT, "%d-%d", ts, rand() % 10)) << INT_PARAM("ts", ts); + CalcHash(tmp); + tmp->flags |= NLHRF_PERSISTENT; + tmp->nlc = m_ConnPool[CONN_RAPI]; + tmp->dataLength = tmp->m_szParam.GetLength(); + tmp->pData = tmp->m_szParam.Detach(); + tmp->szUrl = tmp->m_szUrl.GetBuffer(); + + CMStringA szAgent(FORMAT, "%d Mail.ru Windows ICQ (version 10.0.1999)", DWORD(m_dwUin)); + tmp->AddHeader("User-Agent", szAgent); + + NETLIBHTTPREQUEST *reply = Netlib_HttpTransaction(m_hNetlibUser, tmp); + m_ConnPool[CONN_RAPI] = nullptr; + if (reply != nullptr) { + RobustReply result(reply); + if (result.error() == 20000) { + const JSONNode &results = result.results(); + m_szRToken = results["authToken"].as_mstring(); + setString("RToken", m_szRToken); + + // now add this token + auto *add = new AsyncHttpRequest(CONN_RAPI, REQUEST_POST, ICQ_ROBUST_SERVER, &CIcqProto::OnAddClient); + JSONNode request, params; params.set_name("params"); + request << CHAR_PARAM("method", "addClient") << CHAR_PARAM("reqId", add->m_reqId) << CHAR_PARAM("authToken", m_szRToken) << params; + add->m_szParam = ptrW(json_write(&request)); + add->pUserInfo = &bRet; + ExecuteRequest(add); + } + + m_ConnPool[CONN_RAPI] = reply->nlc; + Netlib_FreeHttpRequest(reply); + } + + delete tmp; + return bRet; +} + void CIcqProto::SetServerStatus(int iStatus) { int iOldStatus = m_iStatus; m_iStatus = iStatus; @@ -121,17 +170,29 @@ void CIcqProto::StartSession() << CHAR_PARAM("k", ICQ_APP_ID) << INT_PARAM("mobile", 0) << CHAR_PARAM("nonce", nonce) << CHAR_PARAM("r", pReq->m_reqId) << INT_PARAM("rawMsg", 0) << INT_PARAM("sessionTimeout", 7776000) << INT_PARAM("ts", ts) << CHAR_PARAM("view", "online"); - CMStringA hashData(FORMAT, "POST&%s&%s", ptrA(mir_urlEncode(pReq->m_szUrl)), ptrA(mir_urlEncode(pReq->m_szParam))); - unsigned int len; - BYTE hashOut[MIR_SHA256_HASH_SIZE]; - HMAC(EVP_sha256(), m_szSessionKey, m_szSessionKey.GetLength(), (BYTE*)hashData.c_str(), hashData.GetLength(), hashOut, &len); - pReq << CHAR_PARAM("sig_sha256", ptrA(mir_base64_encode(hashOut, sizeof(hashOut)))); + CalcHash(pReq); Push(pReq); } ///////////////////////////////////////////////////////////////////////////////////////// +void CIcqProto::OnAddClient(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) +{ + bool *pRet = (bool*)pReq->pUserInfo; + + RobustReply reply(pReply); + if (reply.error() != 20000) { + *pRet = false; + return; + } + + const JSONNode &results = reply.results(); + m_iRClientId = results["clientId"].as_int(); + setDword("RClientID", m_iRClientId); + *pRet = true; +} + void CIcqProto::OnCheckPassword(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*) { JsonReply root(pReply); @@ -224,6 +285,37 @@ void CIcqProto::OnReceiveAvatar(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pRe else ProtoBroadcastAck(hContact, ACKTYPE_AVATAR, ACKRESULT_FAILED, HANDLE(&ai), 0); } +void CIcqProto::OnSearchResults(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) +{ + RobustReply root(pReply); + if (root.error() != 20000) { + ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_FAILED, (HANDLE)pReq, 0); + return; + } + + const JSONNode &results = root.results(); + + PROTOSEARCHRESULT psr = {}; + psr.cbSize = sizeof(psr); + psr.flags = PSR_UNICODE; + for (auto &it : results["data"]) { + const JSONNode &anketa = it["anketa"]; + + CMStringW wszId = it["sn"].as_mstring(); + CMStringW wszNick = anketa["nickname"].as_mstring(); + CMStringW wszFirst = anketa["firstName"].as_mstring(); + CMStringW wszLast = anketa["lastName"].as_mstring(); + + psr.id.w = wszId.GetBuffer(); + psr.nick.w = wszNick.GetBuffer(); + psr.firstName.w = wszFirst.GetBuffer(); + psr.lastName.w = wszLast.GetBuffer(); + ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_DATA, (HANDLE)pReq, LPARAM(&psr)); + } + + ProtoBroadcastAck(0, ACKTYPE_SEARCH, ACKRESULT_SUCCESS, (HANDLE)pReq); +} + void CIcqProto::OnSendMessage(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq) { IcqOwnMessage *ownMsg = (IcqOwnMessage*)pReq->pUserInfo; diff --git a/protocols/Icq10/src/utils.cpp b/protocols/Icq10/src/utils.cpp index 12ba8eaacf..412484ebda 100644 --- a/protocols/Icq10/src/utils.cpp +++ b/protocols/Icq10/src/utils.cpp @@ -34,6 +34,17 @@ IcqCacheItem* CIcqProto::FindContactByUIN(DWORD dwUin) } ///////////////////////////////////////////////////////////////////////////////////////// + +void CIcqProto::CalcHash(AsyncHttpRequest *pReq) +{ + CMStringA hashData(FORMAT, "POST&%s&%s", ptrA(mir_urlEncode(pReq->m_szUrl)), ptrA(mir_urlEncode(pReq->m_szParam))); + unsigned int len; + BYTE hashOut[MIR_SHA256_HASH_SIZE]; + HMAC(EVP_sha256(), m_szSessionKey, m_szSessionKey.GetLength(), (BYTE*)hashData.c_str(), hashData.GetLength(), hashOut, &len); + pReq << CHAR_PARAM("sig_sha256", ptrA(mir_base64_encode(hashOut, sizeof(hashOut)))); +} + +///////////////////////////////////////////////////////////////////////////////////////// // Avatars void CIcqProto::GetAvatarFileName(MCONTACT hContact, wchar_t* pszDest, size_t cbLen) |