From f9d22579b0d94894770361b871141e6c0f76842b Mon Sep 17 00:00:00 2001 From: George Hazan Date: Sun, 6 Oct 2013 20:27:53 +0000 Subject: VK: first version that logs in git-svn-id: http://svn.miranda-ng.org/main/trunk@6379 1316c22d-e87f-b044-9b9b-93d7a3e3ba9c --- protocols/VKontakte/src/misc.cpp | 131 ++++++++++++++++++++++++++++++++++ protocols/VKontakte/src/vk.h | 8 ++- protocols/VKontakte/src/vk_proto.h | 16 +++-- protocols/VKontakte/src/vk_queue.cpp | 70 ++++++++++-------- protocols/VKontakte/src/vk_thread.cpp | 98 ++++++++++++++++++++++++- 5 files changed, 287 insertions(+), 36 deletions(-) diff --git a/protocols/VKontakte/src/misc.cpp b/protocols/VKontakte/src/misc.cpp index b45ce5da7f..a7486fce91 100644 --- a/protocols/VKontakte/src/misc.cpp +++ b/protocols/VKontakte/src/misc.cpp @@ -42,3 +42,134 @@ int CVkProto::SetServerStatus(int iStatus) { return 0; } + +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; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// 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) { +LBL_NotFound: + *pEnd = '>'; + return ""; + } + p1 += strlen(szAttrName); + if (p1[0] != '=' || p1[1] != '\"') + goto LBL_NotFound; + + p1 += 2; + char *p2 = strchr(p1, '\"'); + if (p2 == NULL) + goto LBL_NotFound; + + *pEnd = '>'; + return CMStringA(p1, (int)(p2-p1)); +} + +CMStringA CVkProto::AutoFillForm(char *pBody, CMStringA &szAction) +{ + char *pFormBeg = strstr(pBody, "
"); + if (pFormEnd == NULL) return ""; + + *pFormEnd = 0; + + szAction = getAttr(pFormBeg, "action"); + + CMStringA result; + char *pFieldBeg = pFormBeg; + while (true) { + if ((pFieldBeg = strstr(pFieldBeg+1, "headersCount; i++) { + LPCSTR szValue = nhr->headers[i].szValue; + if (!_stricmp(nhr->headers[i].szName, "Location")) + replaceStr(szUrl, szValue); + else if (!_stricmp(nhr->headers[i].szName, "Set-cookie")) { + if ( strstr(szValue, "login.vk.com")) { + if (!szCookie.IsEmpty()) + szCookie.Append("; "); + + LPCSTR p = strchr(szValue, ';'); + if (p == NULL) + szCookie += szValue; + else + szCookie.Append(szValue, p-szValue); + } + } + } + + if (!szCookie.IsEmpty()) + loginCookie = szCookie; +} diff --git a/protocols/VKontakte/src/vk.h b/protocols/VKontakte/src/vk.h index 51723a527d..d6a9a0f9d0 100644 --- a/protocols/VKontakte/src/vk.h +++ b/protocols/VKontakte/src/vk.h @@ -15,9 +15,13 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#define VK_APP_ID 3917910 +#define VK_APP_ID "3917910" #define VK_API_URL "api.vk.com" #define VK_REDIRECT_URL "http://" VK_API_URL "/blank.html" -extern HINSTANCE hInst; \ No newline at end of file +typedef NETLIBHTTPHEADER HttpParam; + +extern HINSTANCE hInst; + +LPCSTR findHeader(NETLIBHTTPREQUEST *hdr, LPCSTR szField); diff --git a/protocols/VKontakte/src/vk_proto.h b/protocols/VKontakte/src/vk_proto.h index 2965ab8e74..ceece84e38 100644 --- a/protocols/VKontakte/src/vk_proto.h +++ b/protocols/VKontakte/src/vk_proto.h @@ -95,16 +95,18 @@ struct CVkProto : public PROTO private: struct AsyncHttpRequest : public NETLIBHTTPREQUEST, public MZeroedObject { - ~AsyncHttpRequest() - { - mir_free(szUrl); - } + AsyncHttpRequest(); + ~AsyncHttpRequest(); + + void AddHeader(LPCSTR, LPCSTR); + void Redirect(NETLIBHTTPREQUEST*); VK_REQUEST_HANDLER m_pFunc; time_t m_expireTime; }; LIST m_arRequestsQueue; CRITICAL_SECTION m_csRequestsQueue; + CMStringA m_prevUrl, m_savedCookie; HANDLE m_evRequestsQueue; HANDLE m_hWorkerThread; bool m_bTerminated; @@ -112,10 +114,14 @@ private: void InitQueue(); void UninitQueue(); void ExecuteRequest(AsyncHttpRequest*); - bool PushAsyncHttpRequest(int iRequestType, LPCSTR szUrl, bool bSecure, VK_REQUEST_HANDLER pFunc, int iTimeout = 10000); + bool PushAsyncHttpRequest(int iRequestType, LPCSTR szUrl, bool bSecure, VK_REQUEST_HANDLER pFunc, int nParams = 0, NETLIBHTTPHEADER *pParams = 0, int iTimeout = 10000); + bool PushAsyncHttpRequest(AsyncHttpRequest*, int iTimeout = 10000); int SetupConnection(void); void __cdecl WorkerThread(void*); + CMStringA AutoFillForm(char*, CMStringA&); + void ConnectionFailed(int iReason); + void OnLoggedIn(); void OnLoggedOut(); void ShutdownSession(); diff --git a/protocols/VKontakte/src/vk_queue.cpp b/protocols/VKontakte/src/vk_queue.cpp index 9dd6b8b382..be8c8868eb 100644 --- a/protocols/VKontakte/src/vk_queue.cpp +++ b/protocols/VKontakte/src/vk_queue.cpp @@ -50,43 +50,52 @@ int CVkProto::SetupConnection() void CVkProto::ExecuteRequest(AsyncHttpRequest *pReq) { - int bytesSent = CallService(MS_NETLIB_SENDHTTPREQUEST, (WPARAM)m_hNetlibConn, (LPARAM)pReq); - if (bytesSent > 0) { - NETLIBHTTPREQUEST *reply = (NETLIBHTTPREQUEST*)CallService(MS_NETLIB_RECVHTTPHEADERS, (WPARAM)m_hNetlibConn, 0); - if (reply != NULL) { - (this->*(pReq->m_pFunc))(reply); - CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)reply); - } + NETLIBHTTPREQUEST *reply = (NETLIBHTTPREQUEST*)CallService(MS_NETLIB_HTTPTRANSACTION, (WPARAM)m_hNetlibUser, (LPARAM)pReq); + if (reply != NULL) { + (this->*(pReq->m_pFunc))(reply); + CallService(MS_NETLIB_FREEHTTPREQUESTSTRUCT, 0, (LPARAM)reply); } delete pReq; } ///////////////////////////////////////////////////////////////////////////////////////// -static NETLIBHTTPHEADER hdrs[3] = -{ - { "Connection", "keep-alive" }, - { "User-Agent", "Mozilla/4.0 (compatible; MSIE 5.5)" }, - { "Host", VK_API_URL } -}; - -bool CVkProto::PushAsyncHttpRequest(int iRequestType, LPCSTR szUrl, bool bSecure, VK_REQUEST_HANDLER pFunc, int iTimeout) +bool CVkProto::PushAsyncHttpRequest(int iRequestType, LPCSTR szUrl, bool bSecure, VK_REQUEST_HANDLER pFunc, int nParams, NETLIBHTTPHEADER *pParams, int iTimeout) { if ( !SetupConnection()) return false; AsyncHttpRequest *pReq = new AsyncHttpRequest(); - pReq->cbSize = sizeof(NETLIBHTTPREQUEST); - pReq->requestType = iRequestType; - pReq->headers = hdrs; - pReq->headersCount = SIZEOF(hdrs); - pReq->szUrl = mir_strdup(szUrl); - pReq->nlc = m_hNetlibConn; - pReq->flags = NLHRF_PERSISTENT | NLHRF_HTTP11 | NLHRF_REDIRECT | NLHRF_NODUMP; + pReq->flags = NLHRF_PERSISTENT | NLHRF_HTTP11 | NLHRF_REDIRECT; if (bSecure) pReq->flags |= NLHRF_SSL; - pReq->m_expireTime = time(0) + iTimeout; + + CMStringA url; + if (nParams > 0) { + url = VK_API_URL; + url += szUrl; + for (int i=0; i < nParams; i++) { + url.AppendChar((i == 0) ? '?' : '&'); + url += pParams[i].szName; + url.AppendChar('='); + url += ptrA( mir_urlEncode(pParams[i].szValue)); + } + } + else { + url = szUrl; + pReq->flags |= NLHRF_REMOVEHOST | NLHRF_SMARTREMOVEHOST; + } + + pReq->requestType = iRequestType; + pReq->szUrl = mir_strdup(url); pReq->m_pFunc = pFunc; + return PushAsyncHttpRequest(pReq, iTimeout); +} + +bool CVkProto::PushAsyncHttpRequest(AsyncHttpRequest *pReq, int iTimeout) +{ + pReq->nlc = m_hNetlibConn; + pReq->m_expireTime = time(0) + iTimeout; { mir_cslock lck(m_csRequestsQueue); m_arRequestsQueue.insert(pReq); @@ -99,14 +108,19 @@ bool CVkProto::PushAsyncHttpRequest(int iRequestType, LPCSTR szUrl, bool bSecure void CVkProto::WorkerThread(void*) { + m_bTerminated = false; m_szAccessToken = getStringA("AccessToken"); if (m_szAccessToken != NULL) - RequestMyInfo(); + OnLoggedIn(); else { // Initialize new OAuth session - CMStringA szUrl; - szUrl.Format("/oauth/authorize?client_id=%d&scope=%s&redirect_uri=%s&display=wap&response_type=token", - VK_APP_ID, "friends,photos,audio,video,wall,messages,offline", VK_REDIRECT_URL); - PushAsyncHttpRequest(REQUEST_GET, szUrl, false, &CVkProto::OnOAuthAuthorize); + HttpParam params[] = { + { "client_id", VK_APP_ID }, + { "scope", "friends,photos,audio,video,wall,messages,offline" }, + { "redirect_uri", VK_REDIRECT_URL }, + { "display", "wap" }, + { "response_type", "token" } + }; + PushAsyncHttpRequest(REQUEST_GET, "/oauth/authorize", false, &CVkProto::OnOAuthAuthorize, SIZEOF(params), params); } while(true) { diff --git a/protocols/VKontakte/src/vk_thread.cpp b/protocols/VKontakte/src/vk_thread.cpp index 71ee02c2a3..23ef63165a 100644 --- a/protocols/VKontakte/src/vk_thread.cpp +++ b/protocols/VKontakte/src/vk_thread.cpp @@ -27,19 +27,115 @@ void CVkProto::ShutdownSession() OnLoggedOut(); } +void CVkProto::ConnectionFailed(int iReason) +{ + delSetting("AccessToken"); + + ProtoBroadcastAck(NULL, ACKTYPE_LOGIN, ACKRESULT_FAILED, NULL, iReason); + + ShutdownSession(); +} + +void CVkProto::OnLoggedIn() +{ + m_bOnline = true; + + ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, m_iDesiredStatus); + m_iStatus = m_iDesiredStatus; + + RequestMyInfo(); +} + void CVkProto::OnLoggedOut() { m_hWorkerThread = 0; + m_bOnline = false; ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, ID_STATUS_OFFLINE); m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; } +///////////////////////////////////////////////////////////////////////////////////////// + +extern CMStringA loginCookie; + +static char VK_TOKEN_BEG[] = "access_token="; + void CVkProto::OnOAuthAuthorize(NETLIBHTTPREQUEST *reply) { + if (reply->resultCode == 302) { // manual redirect + LPCSTR pszLocation = findHeader(reply, "Location"); + if (pszLocation) { + if ( !_strnicmp(pszLocation, VK_REDIRECT_URL, sizeof(VK_REDIRECT_URL)-1)) { + m_szAccessToken = NULL; + LPCSTR p = strstr(pszLocation, VK_TOKEN_BEG); + if (p) { + p += sizeof(VK_TOKEN_BEG)-1; + for (LPCSTR q = p+1; *q; q++) { + if (*q == '&' || *q == '=' || *q == '\"') { + m_szAccessToken = mir_strndup(p, q-p); + break; + } + } + if (m_szAccessToken == NULL) + m_szAccessToken = mir_strdup(p); + setString("AccessToken", m_szAccessToken); + } + else delSetting("AccessToken"); + + OnLoggedIn(); + } + else { + AsyncHttpRequest *pReq = new AsyncHttpRequest(); + pReq->requestType = REQUEST_GET; + pReq->flags = NLHRF_PERSISTENT | NLHRF_HTTP11 | NLHRF_REMOVEHOST | NLHRF_SMARTREMOVEHOST; + pReq->m_pFunc = &CVkProto::OnOAuthAuthorize; + pReq->AddHeader("Referer", m_prevUrl); + pReq->Redirect(reply); + if (pReq->szUrl) { + if ( strstr(pReq->szUrl, "login.vk.com")) + pReq->AddHeader("Cookie", loginCookie); + m_prevUrl = pReq->szUrl; + } + PushAsyncHttpRequest(pReq); + } + } + else ConnectionFailed(LOGINERR_NOSERVER); + return; + } + + if (reply->resultCode != 200) { // something went wrong +LBL_NoForm: + ConnectionFailed(LOGINERR_NOSERVER); + return; + } + + if ( strstr(reply->pData, ptrA( mir_utf8encodecp("неверный логин или пароль", 1251)))) { + ConnectionFailed(LOGINERR_WRONGPASSWORD); + return; + } + + // Application requests access to user's account + if ( !strstr(reply->pData, "form method=\"post\"")) + goto LBL_NoForm; + + CMStringA szAction; + CMStringA szBody = AutoFillForm(reply->pData, szAction); + if (szAction.IsEmpty() || szBody.IsEmpty()) + goto LBL_NoForm; + + AsyncHttpRequest *pReq = new AsyncHttpRequest(); + pReq->requestType = REQUEST_POST; + pReq->flags = NLHRF_PERSISTENT | NLHRF_HTTP11 | NLHRF_REMOVEHOST | NLHRF_SMARTREMOVEHOST; + pReq->pData = mir_strdup(szBody); + pReq->dataLength = szBody.GetLength(); + pReq->szUrl = mir_strdup(szAction); m_prevUrl = pReq->szUrl; + pReq->m_pFunc = &CVkProto::OnOAuthAuthorize; + pReq->AddHeader("Content-Type", "application/x-www-form-urlencoded"); + pReq->Redirect(reply); + PushAsyncHttpRequest(pReq); } void CVkProto::RequestMyInfo() { } - -- cgit v1.2.3