diff options
-rw-r--r-- | include/m_netlib.h | 2 | ||||
-rw-r--r-- | libs/win32/mir_app.lib | bin | 200778 -> 200782 bytes | |||
-rw-r--r-- | protocols/Discord/src/gateway.cpp | 12 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/proto.cpp | 7 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/proto.h | 61 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/server.cpp | 254 | ||||
-rw-r--r-- | protocols/WhatsAppWeb/src/stdafx.h | 1 | ||||
-rw-r--r-- | src/mir_app/src/mir_app.def | 2 | ||||
-rw-r--r-- | src/mir_app/src/netlib_websocket.cpp | 9 |
9 files changed, 310 insertions, 38 deletions
diff --git a/include/m_netlib.h b/include/m_netlib.h index 798313cbbd..b420e63d52 100644 --- a/include/m_netlib.h +++ b/include/m_netlib.h @@ -789,7 +789,7 @@ struct WSHeader };
// connects to a WebSocket server
-EXTERN_C MIR_APP_DLL(HNETLIBCONN) WebSocket_Connect(HNETLIBUSER, const char *szHost);
+EXTERN_C MIR_APP_DLL(HNETLIBCONN) WebSocket_Connect(HNETLIBUSER, const char *szHost, NETLIBHTTPHEADER *pHeaders = nullptr);
// validates that the provided buffer contains full WebSocket datagram
EXTERN_C MIR_APP_DLL(bool) WebSocket_InitHeader(WSHeader &hdr, const void *pData, size_t bufSize);
diff --git a/libs/win32/mir_app.lib b/libs/win32/mir_app.lib Binary files differindex f4cfb9fd08..d48b1789d0 100644 --- a/libs/win32/mir_app.lib +++ b/libs/win32/mir_app.lib diff --git a/protocols/Discord/src/gateway.cpp b/protocols/Discord/src/gateway.cpp index c713ce79d0..cd01f729c0 100644 --- a/protocols/Discord/src/gateway.cpp +++ b/protocols/Discord/src/gateway.cpp @@ -41,9 +41,17 @@ void CDiscordProto::GatewayThread(void*) bool CDiscordProto::GatewayThreadWorker() { - m_hGatewayConnection = WebSocket_Connect(m_hGatewayNetlibUser, m_szGateway + "/?encoding=json&v=6"); - if (m_hGatewayConnection == nullptr) + NETLIBHTTPHEADER hdrs[] = + { + { "Sec-WebSocket-Key", "KFShSwLlp4E6C7JZc5h4sg==" }, + { 0, 0 } + }; + + m_hGatewayConnection = WebSocket_Connect(m_hGatewayNetlibUser, m_szGateway + "/?encoding=json&v=6", hdrs); + if (m_hGatewayConnection == nullptr) { debugLogA("Gateway connection failed, exiting"); + return false; + } debugLogA("Gateway connection succeeded"); diff --git a/protocols/WhatsAppWeb/src/proto.cpp b/protocols/WhatsAppWeb/src/proto.cpp index a29e50d06f..3f2f903f0a 100644 --- a/protocols/WhatsAppWeb/src/proto.cpp +++ b/protocols/WhatsAppWeb/src/proto.cpp @@ -19,7 +19,8 @@ struct SearchParam WhatsAppProto::WhatsAppProto(const char *proto_name, const wchar_t *username) : PROTO<WhatsAppProto>(proto_name, username), - m_tszDefaultGroup(getWStringA(DBKEY_DEF_GROUP)) + m_tszDefaultGroup(getWStringA(DBKEY_DEF_GROUP)), + m_arPacketQueue(10, NumericKeySortT) { db_set_resident(m_szModuleName, "StatusMsg"); @@ -135,13 +136,13 @@ int WhatsAppProto::SetStatus(int new_status) m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); } - else if (m_pConn == nullptr && !IsStatusConnecting(m_iStatus)) { + else if (m_hServerConn == nullptr && !IsStatusConnecting(m_iStatus)) { m_iStatus = ID_STATUS_CONNECTING; ProtoBroadcastAck(NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)oldStatus, m_iStatus); ForkThread(&WhatsAppProto::ServerThread); } - else if (m_pConn != nullptr) { + else if (m_hServerConn != nullptr) { if (m_iDesiredStatus == ID_STATUS_ONLINE) { // m_pConn->sendAvailableForChat(); m_iStatus = ID_STATUS_ONLINE; diff --git a/protocols/WhatsAppWeb/src/proto.h b/protocols/WhatsAppWeb/src/proto.h index 610313ab0e..ce09908fcd 100644 --- a/protocols/WhatsAppWeb/src/proto.h +++ b/protocols/WhatsAppWeb/src/proto.h @@ -22,23 +22,52 @@ struct WAChatInfo MCONTACT hContact; }; -struct WAConnection : public MZeroedObject +class WhatsAppProto; +typedef void (WhatsAppProto:: *WA_PKT_HANDLER)(const JSONNode &node); + +struct WARequest { - EVP_PKEY *m_pKeys; // private & public keys + int pktId; + time_t issued; + WA_PKT_HANDLER pHandler; }; class WhatsAppProto : public PROTO<WhatsAppProto> { + bool m_bTerminated, m_bOnline; ptrW m_tszDefaultGroup; - CMStringA m_szJid, m_szClientId; + CMStringA m_szJid, m_szClientId, m_szClientToken; CMStringW m_tszAvatarFolder; - WAConnection *m_pConn; + EVP_PKEY *m_pKeys; // private & public keys + + bool ShowQrCode(const CMStringA &ref); + + /// Network //////////////////////////////////////////////////////////////////////////// + + int m_iPktNumber; + time_t m_iLoginTime; + HNETLIBCONN m_hServerConn; - bool ShowQrCode(void); + mir_cs m_csPacketQueue; + OBJLIST<WARequest> m_arPacketQueue; - /// Avatars ////////////////////////////////////////////////////////////////////////// + int WSSend(const CMStringA &str, WA_PKT_HANDLER = nullptr); + + void OnLoggedIn(void); + void OnLoggedOut(void); + void ProcessPacket(const JSONNode &node); + void RestoreSession(void); + bool ServerThreadWorker(void); + void StartSession(void); + void ShutdownSession(void); + + /// Request handlers /////////////////////////////////////////////////////////////////// + + void OnStartSession(const JSONNode &node); + + /// Avatars //////////////////////////////////////////////////////////////////////////// CMStringW GetAvatarFileName(MCONTACT hContact); INT_PTR __cdecl GetAvatarInfo(WPARAM, LPARAM); @@ -51,7 +80,7 @@ public: ~WhatsAppProto(); inline bool isOnline() const - { return false; + { return m_bOnline; } inline bool isOffline() const @@ -62,7 +91,7 @@ public: { return (m_iStatus == ID_STATUS_INVISIBLE); } - // PROTO_INTERFACE /////////////////////////////////////////////////////////////////// + // PROTO_INTERFACE ///////////////////////////////////////////////////////////////////// MCONTACT AddToList(int flags, PROTOSEARCHRESULT *psr) override; INT_PTR GetCaps(int type, MCONTACT hContact = NULL) override; @@ -71,31 +100,23 @@ public: int SetStatus(int iNewStatus) override; int UserIsTyping(MCONTACT hContact, int type) override; - // Services ////////////////////////////////////////////////////////////////////////// + // Services //////////////////////////////////////////////////////////////////////////// INT_PTR __cdecl SvcCreateAccMgrUI(WPARAM, LPARAM); - // Events //////////////////////////////////////////////////////////////////////////// + // Events ////////////////////////////////////////////////////////////////////////////// int __cdecl OnOptionsInit(WPARAM, LPARAM); int __cdecl OnUserInfo(WPARAM, LPARAM); int __cdecl OnBuildStatusMenu(WPARAM, LPARAM); - // Worker Threads //////////////////////////////////////////////////////////////////// - - void __cdecl stayConnectedLoop(void*); - void __cdecl sentinelLoop(void*); - - // Processing Threads //////////////////////////////////////////////////////////////// + // Processing Threads ////////////////////////////////////////////////////////////////// - void __cdecl ProcessBuddyList(void*); void __cdecl SearchAckThread(void*); void __cdecl ServerThread(void*); - // Contacts handling ///////////////////////////////////////////////////////////////// + // Contacts handling /////////////////////////////////////////////////////////////////// - void SetAllContactStatuses(int status, bool reset_client = false); - void UpdateStatusMsg(MCONTACT hContact); void RequestFriendship(MCONTACT hContact); }; diff --git a/protocols/WhatsAppWeb/src/server.cpp b/protocols/WhatsAppWeb/src/server.cpp index 8b94cb675c..8e9419ad47 100644 --- a/protocols/WhatsAppWeb/src/server.cpp +++ b/protocols/WhatsAppWeb/src/server.cpp @@ -97,7 +97,7 @@ static int random_func(uint8_t *pData, size_t size, void *) return 0; } -bool WhatsAppProto::ShowQrCode() +bool WhatsAppProto::ShowQrCode(const CMStringA &ref) { CMStringA szPubKey(getMStringA(DBKEY_PUBKEY)); if (szPubKey.IsEmpty()) { @@ -135,17 +135,253 @@ bool WhatsAppProto::ShowQrCode() return true; } -///////////////////////////////////////////////////////////////////////////////////////// +////////////////////////////////////////////////////////////////////////////////////// +// sends a piece of JSON to a server via a websocket, masked + +int WhatsAppProto::WSSend(const CMStringA &str, WA_PKT_HANDLER pHandler) +{ + if (m_hServerConn == nullptr) + return -1; + + int pktId = ++m_iPktNumber; + + CMStringA buf; + buf.Format("%d.--%d,", (int)m_iLoginTime, pktId); + if (!str.IsEmpty()) { + buf.AppendChar(','); + buf += str; + } + + if (pHandler != nullptr) { + auto *pReq = new WARequest; + pReq->issued = time(0); + pReq->pHandler = pHandler; + pReq->pktId = pktId; + + mir_cslock lck(m_csPacketQueue); + m_arPacketQueue.insert(pReq); + } + + WebSocket_Send(m_hServerConn, buf.c_str(), buf.GetLength()); + return pktId; +} + +void WhatsAppProto::OnLoggedIn() +{ + debugLogA("CDiscordProto::OnLoggedIn"); + m_bOnline = true; + // SetServerStatus(m_iDesiredStatus); +} + +void WhatsAppProto::OnLoggedOut(void) +{ + debugLogA("CDiscordProto::OnLoggedOut"); + m_bOnline = false; + m_bTerminated = true; + m_iPktNumber = 0; + + ProtoBroadcastAck(0, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)m_iStatus, ID_STATUS_OFFLINE); + m_iStatus = m_iDesiredStatus = ID_STATUS_OFFLINE; + + setAllContactStatuses(ID_STATUS_OFFLINE, false); +} + +void WhatsAppProto::RestoreSession() +{ +} + +void WhatsAppProto::ShutdownSession() +{ + if (m_bTerminated) + return; + + debugLogA("WhatsAppProto::ShutdownSession"); + + // shutdown all resources + if (m_hServerConn) + Netlib_Shutdown(m_hServerConn); + + OnLoggedOut(); +} + +void WhatsAppProto::StartSession() +{ + WORD v[4]; + Miranda_GetFileVersion(&v); + + CMStringA payload(FORMAT, "[\"admin\",\"init\",[%d,%d,%d],[\"Windows\",\"Miranda\",\"10\"],%s,true]", + v[1], v[2], v[3], m_szClientId.c_str()); + WSSend(payload, &WhatsAppProto::OnStartSession); +} + +void WhatsAppProto::OnStartSession(const JSONNode &root) +{ + int status = root["status"].as_int(); + if (status != 200) { + debugLogA("Session start failed with error %d", status); + ShutdownSession(); + return; + } + + CMStringA ref = root["ref"].as_mstring(); + ShowQrCode(ref); +} + +////////////////////////////////////////////////////////////////////////////////////// +// gateway worker thread void WhatsAppProto::ServerThread(void *) { - m_pConn = new WAConnection(); + while (ServerThreadWorker()) + ; + ShutdownSession(); +} + +bool WhatsAppProto::ServerThreadWorker() +{ + NETLIBHTTPHEADER hdrs[] = + { + { "Origin", "https://web.whatsapp.com" }, + { "Sec-WebSocket-Key", "k/hwJLznKpk3p2hxyYGzWA==" }, + { 0, 0 } + }; + + m_hServerConn = WebSocket_Connect(m_hNetlibUser, "web.whatsapp.com/ws", hdrs); + if (m_hServerConn == nullptr) { + debugLogA("Server connection failed, exiting"); + return false; + } + + debugLogA("Server connection succeeded"); + + m_szClientToken = getMStringA(DBKEY_CLIENT_SECRET); + if (m_szClientToken.IsEmpty()) + StartSession(); + else + RestoreSession(); + + bool bExit = false; + int offset = 0; + MBinBuffer netbuf; + + while (!bExit) { + if (m_bTerminated) + break; - ptrA szClientToken(getStringA(DBKEY_CLIENT_SECRET)); - if (szClientToken == nullptr) { - if (!ShowQrCode()) { - delete m_pConn; - return; + unsigned char buf[2048]; + int bufSize = Netlib_Recv(m_hServerConn, (char *)buf + offset, _countof(buf) - offset, MSG_NODUMP); + if (bufSize == 0) { + debugLogA("Gateway connection gracefully closed"); + bExit = !m_bTerminated; + break; + } + if (bufSize < 0) { + debugLogA("Gateway connection error, exiting"); + break; + } + + WSHeader hdr; + if (!WebSocket_InitHeader(hdr, buf, bufSize)) { + offset += bufSize; + continue; + } + offset = 0; + + debugLogA("Got packet: buffer = %d, opcode = %d, headerSize = %d, final = %d, masked = %d", bufSize, hdr.opCode, hdr.headerSize, hdr.bIsFinal, hdr.bIsMasked); + + // we have some additional data, not only opcode + if ((size_t)bufSize > hdr.headerSize) { + size_t currPacketSize = bufSize - hdr.headerSize; + netbuf.append(buf, bufSize); + while (currPacketSize < hdr.payloadSize) { + int result = Netlib_Recv(m_hServerConn, (char *)buf, _countof(buf), MSG_NODUMP); + if (result == 0) { + debugLogA("Gateway connection gracefully closed"); + bExit = !m_bTerminated; + break; + } + if (result < 0) { + debugLogA("Gateway connection error, exiting"); + break; + } + currPacketSize += result; + netbuf.append(buf, result); + } + } + + // read all payloads from the current buffer, one by one + size_t prevSize = 0; + while (true) { + switch (hdr.opCode) { + case 0: // text packet + case 1: // binary packet + case 2: // continuation + if (hdr.bIsFinal) { + // process a packet here + CMStringA szJson(netbuf.data() + hdr.headerSize, (int)hdr.payloadSize); + debugLogA("JSON received:\n%s", szJson.c_str()); + + int pos = szJson.Find(','); + if (pos != -1) { + CMStringA prefix = szJson.Left(pos); + szJson.Delete(0, pos); + + JSONNode root = JSONNode::parse(szJson); + if (root) { + int sessId, pktId; + if (sscanf(szJson, "%d--.%d,", &sessId, &pktId) == 2) { + auto *pReq = m_arPacketQueue.find((WARequest *)&pktId); + if (pReq != nullptr) { + (this->*pReq->pHandler)(root); + } + } + else ProcessPacket(root); + } + } + } + break; + + case 8: // close + debugLogA("server required to exit"); + bExit = true; // simply reconnect, don't exit + break; + + case 9: // ping + debugLogA("ping received"); + Netlib_Send(m_hServerConn, (char *)buf + hdr.headerSize, bufSize - int(hdr.headerSize), 0); + break; + } + + if (hdr.bIsFinal) + netbuf.remove(hdr.headerSize + hdr.payloadSize); + + if (netbuf.length() == 0) + break; + + // if we have not enough data for header, continue reading + if (!WebSocket_InitHeader(hdr, netbuf.data(), netbuf.length())) + break; + + // if we have not enough data for data, continue reading + if (hdr.headerSize + hdr.payloadSize > netbuf.length()) + break; + + debugLogA("Got inner packet: buffer = %d, opcode = %d, headerSize = %d, payloadSize = %d, final = %d, masked = %d", netbuf.length(), hdr.opCode, hdr.headerSize, hdr.payloadSize, hdr.bIsFinal, hdr.bIsMasked); + if (prevSize == netbuf.length()) { + netbuf.remove(prevSize); + debugLogA("dropping current packet, exiting"); + break; + } + + prevSize = netbuf.length(); } } -}
\ No newline at end of file + + Netlib_CloseHandle(m_hServerConn); + m_hServerConn = nullptr; + return bExit; +} + +void WhatsAppProto::ProcessPacket(const JSONNode &root) +{ +} diff --git a/protocols/WhatsAppWeb/src/stdafx.h b/protocols/WhatsAppWeb/src/stdafx.h index ee6c6a1156..171e384674 100644 --- a/protocols/WhatsAppWeb/src/stdafx.h +++ b/protocols/WhatsAppWeb/src/stdafx.h @@ -21,6 +21,7 @@ Copyright © 2019 George Hazan #include <m_history.h> #include <m_imgsrvc.h> #include <m_ignore.h> +#include <m_json.h> #include <m_langpack.h> #include <m_message.h> #include <m_netlib.h> diff --git a/src/mir_app/src/mir_app.def b/src/mir_app/src/mir_app.def index 21fcd8a36f..228fe4c0a6 100644 --- a/src/mir_app/src/mir_app.def +++ b/src/mir_app/src/mir_app.def @@ -718,4 +718,4 @@ _UnregisterSrmmLog@4 @806 NONAME ?GetType@CRtfLogWindow@@UAEHXZ @807 NONAME
_WebSocket_Send@12 @808 NONAME
_WebSocket_InitHeader@12 @809 NONAME
-_WebSocket_Connect@8 @810 NONAME
+_WebSocket_Connect@12 @810 NONAME
diff --git a/src/mir_app/src/netlib_websocket.cpp b/src/mir_app/src/netlib_websocket.cpp index 64b5fda069..e7c67fe54f 100644 --- a/src/mir_app/src/netlib_websocket.cpp +++ b/src/mir_app/src/netlib_websocket.cpp @@ -27,7 +27,7 @@ Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #include "../../libs/zlib/src/zlib.h" -MIR_APP_DLL(HNETLIBCONN) WebSocket_Connect(HNETLIBUSER nlu, const char *szHost) +MIR_APP_DLL(HNETLIBCONN) WebSocket_Connect(HNETLIBUSER nlu, const char *szHost, NETLIBHTTPHEADER *pHeaders) { CMStringA tmpHost(szHost); @@ -67,9 +67,14 @@ MIR_APP_DLL(HNETLIBCONN) WebSocket_Connect(HNETLIBUSER nlu, const char *szHost) szBuf.AppendFormat("Pragma: no-cache\r\n"); szBuf.AppendFormat("Cache-Control: no-cache\r\n"); szBuf.AppendFormat("Connection: Upgrade\r\n"); - szBuf.AppendFormat("Sec-WebSocket-Key: KFShSwLlp4E6C7JZc5h4sg==\r\n"); szBuf.AppendFormat("Sec-WebSocket-Version: 13\r\n"); szBuf.AppendFormat("Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits\r\n"); + if (pHeaders) { + while (pHeaders->szName != nullptr) { + szBuf.AppendFormat("%s: %s\r\n", pHeaders->szName, pHeaders->szValue); + pHeaders++; + } + } szBuf.AppendFormat("\r\n"); if (Netlib_Send(res, szBuf, szBuf.GetLength(), MSG_DUMPASTEXT) == SOCKET_ERROR) { Netlib_Logf(nlu, "Error establishing WebSocket connection to %s:%d, send failed", tmpHost.c_str(), conn.wPort); |