diff options
-rw-r--r-- | protocols/VKontakte/src/misc.cpp | 131 | ||||
-rw-r--r-- | protocols/VKontakte/src/vk.h | 8 | ||||
-rw-r--r-- | protocols/VKontakte/src/vk_proto.h | 16 | ||||
-rw-r--r-- | protocols/VKontakte/src/vk_queue.cpp | 70 | ||||
-rw-r--r-- | 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, "<form ");
+ if (pFormBeg == NULL) return "";
+
+ char *pFormEnd = strstr(pFormBeg, "</form>");
+ if (pFormEnd == NULL) return "";
+
+ *pFormEnd = 0;
+
+ szAction = getAttr(pFormBeg, "action");
+
+ CMStringA result;
+ char *pFieldBeg = pFormBeg;
+ while (true) {
+ if ((pFieldBeg = strstr(pFieldBeg+1, "<input ")) == NULL)
+ break;
+
+ CMStringA type = getAttr(pFieldBeg, "type");
+ if (type != "submit") {
+ CMStringA name = getAttr(pFieldBeg, "name");
+ CMStringA value = getAttr(pFieldBeg, "value");
+ if (name == "email")
+ value = ptrA( mir_utf8encodeT( ptrT( getTStringA("Login"))));
+ else if (name == "pass")
+ value = ptrA( mir_utf8encodeT( ptrT( GetUserStoredPassword())));
+
+ if ( !result.IsEmpty())
+ result.AppendChar('&');
+ result += name + "=";
+ result += ptrA( mir_urlEncode(value));
+ }
+ }
+
+ return result;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+CMStringA loginCookie;
+
+CVkProto::AsyncHttpRequest::AsyncHttpRequest()
+{
+ cbSize = sizeof(NETLIBHTTPREQUEST);
+
+ AddHeader("Connection", "keep-alive");
+}
+
+CVkProto::AsyncHttpRequest::~AsyncHttpRequest()
+{
+ for (int i=0; i < headersCount; i++) {
+ mir_free(headers[i].szName);
+ mir_free(headers[i].szValue);
+ }
+ mir_free(headers);
+ mir_free(szUrl);
+ mir_free(pData);
+}
+
+void CVkProto::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 CVkProto::AsyncHttpRequest::Redirect(NETLIBHTTPREQUEST *nhr)
+{
+ CMStringA szCookie;
+
+ for (int i=0; i < nhr->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 <http://www.gnu.org/licenses/>.
*/
-#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<CVkProto> 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<AsyncHttpRequest> 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()
{
}
-
|