/*
Copyright (c) 2013-15 Miranda NG project (http://miranda-ng.org)
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 version 2
of the License.
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 .
*/
#include "stdafx.h"
static const char* szImageTypes[] = { "photo_2560", "photo_1280", "photo_807", "photo_604", "photo_256", "photo_130", "photo_128", "photo_75", "photo_64" };
static const char* szGiftTypes[] = { "thumb_256", "thumb_96", "thumb_48" };
JSONNode nullNode(JSON_NULL);
bool IsEmpty(LPCTSTR str)
{
if (str == NULL)
return true;
if (str[0] == 0)
return true;
return false;
}
bool IsEmpty(LPCSTR str)
{
if (str == NULL)
return true;
if (str[0] == 0)
return true;
return false;
}
LPCSTR findHeader(NETLIBHTTPREQUEST *pReq, LPCSTR szField)
{
for (int i = 0; i < pReq->headersCount; i++)
if (!_stricmp(pReq->headers[i].szName, szField))
return pReq->headers[i].szValue;
return NULL;
}
bool tlstrstr(TCHAR* _s1, TCHAR* _s2)
{
TCHAR s1[1024], s2[1024];
_tcsncpy_s(s1, _s1, _TRUNCATE);
CharLowerBuff(s1, _countof(s1));
_tcsncpy_s(s2, _s2, _TRUNCATE);
CharLowerBuff(s2, _countof(s2));
return _tcsstr(s1, s2) != NULL;
}
/////////////////////////////////////////////////////////////////////////////////////////
static IconItem iconList[] =
{
{ LPGEN("Captcha form icon"), "key", IDI_KEYS },
{ LPGEN("Notification icon"), "notification", IDI_NOTIFICATION },
{ LPGEN("Read message icon"), "read", IDI_READMSG },
{ LPGEN("Visit profile icon"), "profile", IDI_VISITPROFILE },
{ LPGEN("Load server history icon"), "history", IDI_HISTORY },
{ LPGEN("Add to friend list icon"), "addfriend", IDI_FRIENDADD },
{ LPGEN("Delete from friend list icon"), "delfriend", IDI_FRIENDDEL },
{ LPGEN("Report abuse icon"), "abuse", IDI_ABUSE },
{ LPGEN("Ban user icon"), "ban", IDI_BAN },
{ LPGEN("Broadcast icon"), "broadcast", IDI_BROADCAST },
{ LPGEN("Status icon"), "status", IDI_STATUS },
{ LPGEN("Wall message icon"), "wall", IDI_WALL }
};
void InitIcons()
{
Icon_Register(hInst, LPGEN("Protocols")"/"LPGEN("VKontakte"), iconList, _countof(iconList), "VKontakte");
}
HANDLE GetIconHandle(int iCommand)
{
for (int i = 0; i < _countof(iconList); i++)
if (iconList[i].defIconID == iCommand)
return iconList[i].hIcolib;
return 0;
}
/////////////////////////////////////////////////////////////////////////////////////////
char* ExpUrlEncode(const char *szUrl, bool strict)
{
const char szHexDigits[] = "0123456789ABCDEF";
if (szUrl == NULL)
return NULL;
const BYTE *s;
int outputLen;
for (outputLen = 0, s = (const BYTE*)szUrl; *s; s++)
if ((*s & 0x80 && !strict) || // UTF-8 multibyte
('0' <= *s && *s <= '9') || //0-9
('A' <= *s && *s <= 'Z') || //ABC...XYZ
('a' <= *s && *s <= 'z') || //abc...xyz
*s == '~' || *s == '-' || *s == '_' || *s == '.' || *s == ' ')
outputLen++;
else
outputLen += 3;
char *szOutput = (char*)mir_alloc(outputLen + 1);
if (szOutput == NULL)
return NULL;
char *d = szOutput;
for (s = (const BYTE*)szUrl; *s; s++)
if ((*s & 0x80 && !strict) || // UTF-8 multibyte
('0' <= *s && *s <= '9') || //0-9
('A' <= *s && *s <= 'Z') || //ABC...XYZ
('a' <= *s && *s <= 'z') || //abc...xyz
*s == '~' || *s == '-' || *s == '_' || *s == '.')
*d++ = *s;
else if (*s == ' ')
*d++ = '+';
else {
*d++ = '%';
*d++ = szHexDigits[*s >> 4];
*d++ = szHexDigits[*s & 0xF];
}
*d = '\0';
return szOutput;
}
/////////////////////////////////////////////////////////////////////////////////////////
ULONG AsyncHttpRequest::m_reqCount = 0;
AsyncHttpRequest::AsyncHttpRequest()
{
cbSize = sizeof(NETLIBHTTPREQUEST);
m_bApiReq = true;
AddHeader("Connection", "keep-alive");
AddHeader("Accept-Encoding", "booo");
pUserInfo = NULL;
m_iRetry = MAX_RETRIES;
bNeedsRestart = false;
bIsMainConn = false;
m_pFunc = NULL;
bExpUrlEncode = false;
m_reqNum = ::InterlockedIncrement(&m_reqCount);
m_priority = rpLow;
}
AsyncHttpRequest::AsyncHttpRequest(CVkProto *ppro, int iRequestType, LPCSTR _url, bool bSecure, VK_REQUEST_HANDLER pFunc, RequestPriority rpPriority)
{
cbSize = sizeof(NETLIBHTTPREQUEST);
m_bApiReq = true;
bIsMainConn = false;
bExpUrlEncode = ppro->m_bUseNonStandardUrlEncode;
AddHeader("Connection", "keep-alive");
AddHeader("Accept-Encoding", "booo");
flags = VK_NODUMPHEADERS | NLHRF_DUMPASTEXT | NLHRF_HTTP11 | NLHRF_REDIRECT;
if (bSecure)
flags |= NLHRF_SSL;
if (*_url == '/') { // relative url leads to a site
m_szUrl = ((bSecure) ? "https://" : "http://") + CMStringA("api.vk.com");
m_szUrl += _url;
bIsMainConn = true;
}
else m_szUrl = _url;
if (bSecure)
this << CHAR_PARAM("access_token", ppro->m_szAccessToken);
requestType = iRequestType;
m_pFunc = pFunc;
pUserInfo = NULL;
m_iRetry = MAX_RETRIES;
bNeedsRestart = false;
m_reqNum = ::InterlockedIncrement(&m_reqCount);
m_priority = rpPriority;
}
AsyncHttpRequest::~AsyncHttpRequest()
{
for (int i = 0; i < headersCount; i++) {
mir_free(headers[i].szName);
mir_free(headers[i].szValue);
}
mir_free(headers);
mir_free(pData);
}
void AsyncHttpRequest::AddHeader(LPCSTR szName, LPCSTR szValue)
{
headers = (NETLIBHTTPHEADER*)mir_realloc(headers, sizeof(NETLIBHTTPHEADER)*(headersCount + 1));
headers[headersCount].szName = mir_strdup(szName);
headers[headersCount].szValue = mir_strdup(szValue);
headersCount++;
}
void AsyncHttpRequest::Redirect(NETLIBHTTPREQUEST *nhr)
{
for (int i = 0; i < nhr->headersCount; i++) {
LPCSTR szValue = nhr->headers[i].szValue;
if (!_stricmp(nhr->headers[i].szName, "Location"))
m_szUrl = szValue;
}
}
/////////////////////////////////////////////////////////////////////////////////////////
TCHAR* CVkProto::GetUserStoredPassword()
{
debugLogA("CVkProto::GetUserStoredPassword");
ptrA szRawPass(getStringA("Password"));
return (szRawPass != NULL) ? mir_utf8decodeT(szRawPass) : NULL;
}
void CVkProto::SetAllContactStatuses(int iStatus)
{
debugLogA("CVkProto::SetAllContactStatuses (%d)", iStatus);
for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) {
if (isChatRoom(hContact))
SetChatStatus(hContact, iStatus);
else if (getWord(hContact, "Status") != iStatus)
setWord(hContact, "Status", iStatus);
if (iStatus == ID_STATUS_OFFLINE) {
SetMirVer(hContact, -1);
db_unset(hContact, m_szModuleName, "ListeningTo");
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////
MCONTACT CVkProto::FindUser(LONG dwUserid, bool bCreate)
{
if (!dwUserid)
return NULL;
for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) {
LONG dbUserid = getDword(hContact, "ID", -1);
if (dbUserid == -1)
continue;
if (dbUserid == dwUserid)
return hContact;
}
if (!bCreate)
return NULL;
MCONTACT hNewContact = (MCONTACT)CallService(MS_DB_CONTACT_ADD);
Proto_AddToContact(hNewContact, m_szModuleName);
setDword(hNewContact, "ID", dwUserid);
db_set_ts(hNewContact, "CList", "Group", m_defaultGroup);
return hNewContact;
}
MCONTACT CVkProto::FindChat(LONG dwUserid)
{
if (!dwUserid)
return NULL;
for (MCONTACT hContact = db_find_first(m_szModuleName); hContact; hContact = db_find_next(hContact, m_szModuleName)) {
LONG dbUserid = getDword(hContact, "vk_chat_id", -1);
if (dbUserid == -1)
continue;
if (dbUserid == dwUserid)
return hContact;
}
return NULL;
}
/////////////////////////////////////////////////////////////////////////////////////////
bool CVkProto::CheckMid(LIST &lList, int guid)
{
for (int i = lList.getCount() - 1; i >= 0; i--)
if ((int)lList[i] == guid) {
lList.remove(i);
return true;
}
return false;
}
/////////////////////////////////////////////////////////////////////////////////////////
JSONNode& CVkProto::CheckJsonResponse(AsyncHttpRequest *pReq, NETLIBHTTPREQUEST *reply, JSONNode &root)
{
debugLogA("CVkProto::CheckJsonResponse");
root = JSONNode::parse(reply->pData);
if (!root)
return nullNode;
if (!CheckJsonResult(pReq, root))
return nullNode;
return root["response"];
}
bool CVkProto::CheckJsonResult(AsyncHttpRequest *pReq, const JSONNode &jnNode)
{
debugLogA("CVkProto::CheckJsonResult");
if (!jnNode)
return false;
const JSONNode &jnError = jnNode["error"];
const JSONNode &jnErrorCode = jnError["error_code"];
if (!jnError || !jnErrorCode)
return true;
int iErrorCode = jnErrorCode.as_int();
debugLogA("CVkProto::CheckJsonResult %d", iErrorCode);
CVkFileUploadParam * fup = (CVkFileUploadParam *)pReq->pUserInfo;
CVkSendMsgParam *param = (CVkSendMsgParam*)pReq->pUserInfo;
switch (iErrorCode) {
case VKERR_AUTHORIZATION_FAILED:
ConnectionFailed(LOGINERR_WRONGPASSWORD);
break;
case VKERR_ACCESS_DENIED:
if (time(NULL) - getDword("LastAccessTokenTime", 0) > 60 * 60 * 24) {
debugLogA("CVkProto::CheckJsonResult VKERR_ACCESS_DENIED (AccessToken fail?)");
setDword("LastAccessTokenTime", (DWORD)time(NULL));
delSetting("AccessToken");
ShutdownSession();
return false;
}
debugLogA("CVkProto::CheckJsonResult VKERR_ACCESS_DENIED");
MsgPopup(NULL, TranslateT("Access denied! Data will not be sent or received."), TranslateT("Error"), true);
break;
case VKERR_CAPTCHA_NEEDED:
ApplyCaptcha(pReq, jnError);
break;
case VKERR_COULD_NOT_SAVE_FILE:
case VKERR_INVALID_ALBUM_ID:
case VKERR_INVALID_SERVER:
case VKERR_INVALID_HASH:
case VKERR_INVALID_AUDIO:
case VKERR_AUDIO_DEL_COPYRIGHT:
case VKERR_INVALID_FILENAME:
case VKERR_INVALID_FILESIZE:
if (fup)
fup->iErrorCode = iErrorCode;
break;
case VKERR_FLOOD_CONTROL:
pReq->m_iRetry = 0;
case VKERR_UNKNOWN:
case VKERR_TOO_MANY_REQ_PER_SEC:
case VKERR_INTERNAL_SERVER_ERR:
if (pReq->m_iRetry > 0) {
pReq->bNeedsRestart = true;
Sleep(500); //Pause for fix err
debugLogA("CVkProto::CheckJsonResult Retry = %d", pReq->m_iRetry);
pReq->m_iRetry--;
}
else {
CMString msg;
msg.AppendFormat(TranslateT("Error %d. Data will not be sent or received."), iErrorCode);
MsgPopup(NULL, msg, TranslateT("Error"), true);
debugLogA("CVkProto::CheckJsonResult SendError");
}
break;
case VKERR_HIMSELF_AS_FRIEND:
case VKERR_YOU_ON_BLACKLIST:
case VKERR_USER_ON_BLACKLIST:
if (param)
param->iCount = iErrorCode;
break;
case VKERR_INVALID_PARAMETERS:
MsgPopup(NULL, TranslateT("One of the parameters specified was missing or invalid"), TranslateT("Error"), true);
break;
case VKERR_ACC_WALL_POST_DENIED:
MsgPopup(NULL, TranslateT("Access to adding post denied"), TranslateT("Error"), true);
break;
}
return iErrorCode == 0;
}
void CVkProto::OnReceiveSmth(NETLIBHTTPREQUEST *reply, AsyncHttpRequest *pReq)
{
JSONNode jnRoot;
const JSONNode &jnResponse = CheckJsonResponse(pReq, reply, jnRoot);
debugLogA("CVkProto::OnReceiveSmth %d", jnResponse.as_int());
}
/////////////////////////////////////////////////////////////////////////////////////////
// Quick & dirty form parser
static CMStringA getAttr(char *szSrc, LPCSTR szAttrName)
{
char *pEnd = strchr(szSrc, '>');
if (pEnd == NULL)
return "";
*pEnd = 0;
char *p1 = strstr(szSrc, szAttrName);
if (p1 == NULL) {
*pEnd = '>';
return "";
}
p1 += mir_strlen(szAttrName);
if (p1[0] != '=' || p1[1] != '\"') {
*pEnd = '>';
return "";
}
p1 += 2;
char *p2 = strchr(p1, '\"');
*pEnd = '>';
if (p2 == NULL)
return "";
return CMStringA(p1, (int)(p2-p1));
}
bool CVkProto::AutoFillForm(char *pBody, CMStringA &szAction, CMStringA& szResult)
{
debugLogA("CVkProto::AutoFillForm");
szResult.Empty();
char *pFormBeg = strstr(pBody, "