summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/m_netlib.h2
-rw-r--r--libs/win32/mir_app.libbin200778 -> 200782 bytes
-rw-r--r--protocols/Discord/src/gateway.cpp12
-rw-r--r--protocols/WhatsAppWeb/src/proto.cpp7
-rw-r--r--protocols/WhatsAppWeb/src/proto.h61
-rw-r--r--protocols/WhatsAppWeb/src/server.cpp254
-rw-r--r--protocols/WhatsAppWeb/src/stdafx.h1
-rw-r--r--src/mir_app/src/mir_app.def2
-rw-r--r--src/mir_app/src/netlib_websocket.cpp9
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
index f4cfb9fd08..d48b1789d0 100644
--- a/libs/win32/mir_app.lib
+++ b/libs/win32/mir_app.lib
Binary files differ
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);