summaryrefslogtreecommitdiff
path: root/protocols
diff options
context:
space:
mode:
authorGeorge Hazan <ghazan@miranda.im>2018-12-27 20:53:58 +0300
committerGeorge Hazan <ghazan@miranda.im>2018-12-27 20:54:07 +0300
commit38af197ebf6ff9f947d02338262ad8ebe48f26d3 (patch)
treed6729c86ee940118a8218028bf095c84113b2efa /protocols
parent7b4ac22ea5d0d655d302b0edcb753889d5f39fd6 (diff)
fixes #1668 (ICQ: contact search)
Diffstat (limited to 'protocols')
-rw-r--r--protocols/Icq10/src/http.cpp44
-rw-r--r--protocols/Icq10/src/http.h16
-rw-r--r--protocols/Icq10/src/proto.cpp23
-rw-r--r--protocols/Icq10/src/proto.h9
-rw-r--r--protocols/Icq10/src/server.cpp102
-rw-r--r--protocols/Icq10/src/utils.cpp11
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)