Skip to content

Commit

Permalink
fixes #1668 (ICQ: contact search)
Browse files Browse the repository at this point in the history
  • Loading branch information
George Hazan committed Dec 27, 2018
1 parent 7b4ac22 commit 38af197
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 19 deletions.
44 changes: 42 additions & 2 deletions protocols/Icq10/src/http.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}

Expand All @@ -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)
Expand Down Expand Up @@ -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);
}
16 changes: 15 additions & 1 deletion protocols/Icq10/src/http.h
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand All @@ -30,3 +30,17 @@ class JsonReply
__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; }
};
23 changes: 13 additions & 10 deletions protocols/Icq10/src/proto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

////////////////////////////////////////////////////////////////////////////////////////
Expand Down
9 changes: 8 additions & 1 deletion protocols/Icq10/src/proto.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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*);

Expand All @@ -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;

Expand All @@ -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
Expand Down
102 changes: 97 additions & 5 deletions protocols/Icq10/src/server.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down
11 changes: 11 additions & 0 deletions protocols/Icq10/src/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ IcqCacheItem* CIcqProto::FindContactByUIN(DWORD dwUin)
return m_arCache.find((IcqCacheItem*)&dwUin);
}

/////////////////////////////////////////////////////////////////////////////////////////

void CIcqProto::CalcHash(AsyncHttpRequest *pReq)
{
CMStringA hashData(FORMAT, "POST&%s&%s", ptrA(mir_urlEncode(pReq->m_szUrl)), ptrA(mir_urlEncode(pReq->m_szParam)));

This comment has been minimized.

Copy link
@EionRobb

EionRobb Dec 27, 2018

This should get GET for GET requests

This comment has been minimized.

Copy link
@georgehazan

georgehazan Dec 28, 2018

Member

This should get GET for GET requests

We have no signed GET requests for now, so why bother

This comment has been minimized.

Copy link
@EionRobb

EionRobb Dec 28, 2018

Save yourself the confusion later?

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

Expand Down

0 comments on commit 38af197

Please sign in to comment.