summaryrefslogtreecommitdiff
path: root/protocols
diff options
context:
space:
mode:
authorGeorge Hazan <ghazan@miranda.im>2021-04-19 11:43:23 +0300
committerGeorge Hazan <ghazan@miranda.im>2021-04-19 11:43:23 +0300
commitb78ff7e78e16a0ec2e674d750a73df71909e3a8b (patch)
tree7cbfb52b81b23be1b5a1b605cfd04114dbb49515 /protocols
parenta6d12ce24f94600dea3fb64aaa3b08c7b6be11ea (diff)
WhatsApp: binary packets decoding into JSON structures
Diffstat (limited to 'protocols')
-rw-r--r--protocols/WhatsAppWeb/WhatsAppWeb.vcxproj1
-rw-r--r--protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters3
-rw-r--r--protocols/WhatsAppWeb/src/server.cpp15
-rw-r--r--protocols/WhatsAppWeb/src/stdafx.h1
-rw-r--r--protocols/WhatsAppWeb/src/utils.cpp262
-rw-r--r--protocols/WhatsAppWeb/src/utils.h47
6 files changed, 325 insertions, 4 deletions
diff --git a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj
index 9439401cf7..0b03a0b17e 100644
--- a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj
+++ b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj
@@ -56,6 +56,7 @@
<ClInclude Include="src\proto.h" />
<ClInclude Include="src\resource.h" />
<ClInclude Include="src\stdafx.h" />
+ <ClInclude Include="src\utils.h" />
<ClInclude Include="src\version.h" />
</ItemGroup>
<ItemGroup>
diff --git a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters
index 956e44e8eb..24ccddb69d 100644
--- a/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters
+++ b/protocols/WhatsAppWeb/WhatsAppWeb.vcxproj.filters
@@ -43,6 +43,9 @@
<ClInclude Include="src\version.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="src\utils.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
</ItemGroup>
<ItemGroup>
<ResourceCompile Include="res\version.rc">
diff --git a/protocols/WhatsAppWeb/src/server.cpp b/protocols/WhatsAppWeb/src/server.cpp
index 1a66504532..ad3b94a64f 100644
--- a/protocols/WhatsAppWeb/src/server.cpp
+++ b/protocols/WhatsAppWeb/src/server.cpp
@@ -320,9 +320,6 @@ bool WhatsAppProto::ServerThreadWorker()
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) {
netbuf.append(buf, bufSize);
@@ -330,6 +327,9 @@ bool WhatsAppProto::ServerThreadWorker()
break;
}
+ offset = 0;
+ debugLogA("Got packet: buffer = %d, opcode = %d, headerSize = %d, final = %d, masked = %d", bufSize, hdr.opCode, hdr.headerSize, hdr.bIsFinal, hdr.bIsMasked);
+
// read all payloads from the current buffer, one by one
size_t prevSize = 0;
while (true) {
@@ -424,6 +424,13 @@ bool WhatsAppProto::ServerThreadWorker()
void WhatsAppProto::ProcessBinaryPacket(const MBinBuffer &buf)
{
+ WAReader reader(buf.data(), buf.length());
+
+ JSONNode root;
+ if (!reader.readNode(root))
+ return;
+
+ debugLogA("packed JSON: %s", root.write().c_str());
}
/////////////////////////////////////////////////////////////////////////////////////////
@@ -447,7 +454,7 @@ void WhatsAppProto::ProcessBlocked(const JSONNode &node)
for (auto &it : node["blocklist"]) {
auto *pUser = AddUser(it.as_string().c_str(), false);
Ignore_Ignore(pUser->hContact, IGNOREEVENT_ALL);
- Contact_RemoveFromList(pUser->hContact);
+ Contact_Hide(pUser->hContact);
}
}
diff --git a/protocols/WhatsAppWeb/src/stdafx.h b/protocols/WhatsAppWeb/src/stdafx.h
index c831328858..4434f9ba8c 100644
--- a/protocols/WhatsAppWeb/src/stdafx.h
+++ b/protocols/WhatsAppWeb/src/stdafx.h
@@ -76,6 +76,7 @@ struct ec_private_key : public signal_type_base
};
#include "db.h"
+#include "utils.h"
#include "proto.h"
#include "resource.h"
diff --git a/protocols/WhatsAppWeb/src/utils.cpp b/protocols/WhatsAppWeb/src/utils.cpp
index bc35b6ef5f..b6877f529a 100644
--- a/protocols/WhatsAppWeb/src/utils.cpp
+++ b/protocols/WhatsAppWeb/src/utils.cpp
@@ -69,3 +69,265 @@ bool WhatsAppProto::decryptBinaryMessage(size_t cbSize, const void *buf, MBinBuf
}
return true;
}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+bool WAReader::readAttributes(JSONNode &ret, int count)
+{
+ if (count == 0)
+ return true;
+
+ for (int i = 0; i < count; i++) {
+ CMStringA name = readString(readInt8());
+ if (name.IsEmpty())
+ return false;
+
+ CMStringA value = readString(readInt8());
+ if (value.IsEmpty())
+ return false;
+
+ ret << CHAR_PARAM(name, value);
+ }
+ return true;
+}
+
+uint32_t WAReader::readInt20()
+{
+ if (m_limit - m_buf < 3)
+ return 0;
+
+ int ret = (int(m_buf[0] & 0x0F) << 16) + (int(m_buf[1]) << 8) + int(m_buf[2]);
+ m_buf += 3;
+ return ret;
+}
+
+uint32_t WAReader::readIntN(int n)
+{
+ if (m_limit - m_buf < n)
+ return 0;
+
+ uint32_t res = 0;
+ for (int i = 0; i < n; i++, m_buf++)
+ res = (res <<= 8) + *m_buf;
+ return res;
+}
+
+bool WAReader::readList(JSONNode &parent, int tag)
+{
+ int size = readListSize(tag);
+ if (size == -1)
+ return false;
+
+ JSONNode list(JSON_ARRAY); list.set_name("$list$");
+ for (int i = 0; i < size; i++) {
+ JSONNode tmp;
+ if (!readNode(tmp))
+ return false;
+ list << tmp;
+ }
+
+ parent << list;
+ return true;
+}
+
+int WAReader::readListSize(int tag)
+{
+ switch (tag) {
+ case LIST_EMPTY:
+ return 0;
+ case LIST_8:
+ return readInt8();
+ case LIST_16:
+ return readInt16();
+ }
+ return -1;
+}
+
+bool WAReader::readNode(JSONNode &ret)
+{
+ int listSize = readListSize(readInt8());
+ if (listSize == -1)
+ return false;
+
+ int descrTag = readInt8();
+ if (descrTag == STREAM_END)
+ return false;
+
+ CMStringA name = readString(descrTag);
+ if (name.IsEmpty())
+ return false;
+ ret.set_name(name.c_str());
+
+ if (!readAttributes(ret, (listSize-1)>>1))
+ return false;
+
+ if ((listSize % 2) == 1)
+ return true;
+
+ int size, tag = readInt8();
+ switch (tag) {
+ case LIST_EMPTY: case LIST_8: case LIST_16:
+ readList(ret, tag);
+ break;
+
+ case BINARY_8:
+ size = readInt8();
+
+LBL_Binary:
+ if (m_limit - m_buf < size)
+ return false;
+
+ ret << CHAR_PARAM("$bin$", ptrA(mir_base64_encode(m_buf, size)));
+ m_buf += size;
+ break;
+
+ case BINARY_20:
+ size = readInt20();
+ goto LBL_Binary;
+
+ case BINARY_32:
+ size = readInt32();
+ goto LBL_Binary;
+
+ default:
+ ret << CHAR_PARAM("$str$", readString(tag).c_str());
+ }
+
+ return true;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static int unpackHex(int val)
+{
+ if (val < 0 || val > 15)
+ return -1;
+
+ return (val < 10) ? val + '0' : val - 10 + 'A';
+}
+
+static int unpackNibble(int val)
+{
+ if (val < 0 || val > 15)
+ return -1;
+
+ switch (val) {
+ case 10: return '-';
+ case 11: return '.';
+ case 15: return 0;
+ default: return '0' + val;
+ }
+}
+
+CMStringA WAReader::readPacked(int tag)
+{
+ int startByte = readInt8();
+ bool bTrim = false;
+ if (startByte & 0x80) {
+ startByte &= ~0x80;
+ bTrim = true;
+ }
+
+ CMStringA ret;
+ for (int i = 0; i < startByte; i++) {
+ BYTE b = readInt8();
+ int lower = (tag == NIBBLE_8) ? unpackNibble(b >> 4) : unpackHex(b >> 4);
+ if (lower == -1)
+ return "";
+
+ int higher = (tag == NIBBLE_8) ? unpackNibble(b & 0x0F) : unpackHex(b & 0x0F);
+ if (higher == -1)
+ return "";
+
+ ret.AppendChar(lower);
+ ret.AppendChar(higher);
+ }
+
+ return ret;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static char *SingleByteTokens[] = {
+ "", "", "", "200", "400", "404", "500", "501", "502", "action", "add",
+ "after", "archive", "author", "available", "battery", "before", "body",
+ "broadcast", "chat", "clear", "code", "composing", "contacts", "count",
+ "create", "debug", "delete", "demote", "duplicate", "encoding", "error",
+ "false", "filehash", "from", "g.us", "group", "groups_v2", "height", "id",
+ "image", "in", "index", "invis", "item", "jid", "kind", "last", "leave",
+ "live", "log", "media", "message", "mimetype", "missing", "modify", "name",
+ "notification", "notify", "out", "owner", "participant", "paused",
+ "picture", "played", "presence", "preview", "promote", "query", "raw",
+ "read", "receipt", "received", "recipient", "recording", "relay",
+ "remove", "response", "resume", "retry", "s.whatsapp.net", "seconds",
+ "set", "size", "status", "subject", "subscribe", "t", "text", "to", "true",
+ "type", "unarchive", "unavailable", "url", "user", "value", "web", "width",
+ "mute", "read_only", "admin", "creator", "short", "update", "powersave",
+ "checksum", "epoch", "block", "previous", "409", "replaced", "reason",
+ "spam", "modify_tag", "message_info", "delivery", "emoji", "title",
+ "description", "canonical-url", "matched-text", "star", "unstar",
+ "media_key", "filename", "identity", "unread", "page", "page_count",
+ "search", "media_message", "security", "call_log", "profile", "ciphertext",
+ "invite", "gif", "vcard", "frequent", "privacy", "blacklist", "whitelist",
+ "verify", "location", "document", "elapsed", "revoke_invite", "expiration",
+ "unsubscribe", "disable", "vname", "old_jid", "new_jid", "announcement",
+ "locked", "prop", "label", "color", "call", "offer", "call-id",
+ "quick_reply", "sticker", "pay_t", "accept", "reject", "sticker_pack",
+ "invalid", "canceled", "missed", "connected", "result", "audio",
+ "video", "recent" };
+
+CMStringA WAReader::readString(int tag)
+{
+ if (tag >= 3 && tag < _countof(SingleByteTokens)) {
+ CMStringA ret = SingleByteTokens[tag];
+ if (ret == "s.whatsapp.net")
+ return "c.us";
+ return ret;
+ }
+
+ switch (tag) {
+// case DICTIONARY_0: return dict0[readInt8()];
+// case DICTIONARY_1: return dict1[readInt8()];
+// case DICTIONARY_2: return dict2[readInt8()];
+// case DICTIONARY_3: return dict3[readInt8()];
+ case LIST_EMPTY:
+ return "";
+
+ case BINARY_8:
+ return readStringFromChars(readInt8());
+
+ case BINARY_20:
+ return readStringFromChars(readInt20());
+
+ case BINARY_32:
+ return readStringFromChars(readInt32());
+
+ case NIBBLE_8:
+ case HEX_8:
+ return readPacked(tag);
+
+ case JID_PAIR:
+ CMStringA s1 = readString(readInt8());
+ if (s1.IsEmpty())
+ break;
+
+ CMStringA s2 = readString(readInt8());
+ if (s2.IsEmpty())
+ break;
+
+ return CMStringA(FORMAT, "%s@%s", s1.c_str(), s2.c_str());
+ }
+
+ // error
+ return "";
+}
+
+CMStringA WAReader::readStringFromChars(int size)
+{
+ if (m_limit - m_buf < size)
+ return "";
+
+ CMStringA ret((char*)m_buf, size);
+ m_buf += size;
+ return ret;
+}
diff --git a/protocols/WhatsAppWeb/src/utils.h b/protocols/WhatsAppWeb/src/utils.h
new file mode 100644
index 0000000000..d37c8fb187
--- /dev/null
+++ b/protocols/WhatsAppWeb/src/utils.h
@@ -0,0 +1,47 @@
+/*
+
+WhatsAppWeb plugin for Miranda NG
+Copyright © 2019-21 George Hazan
+
+*/
+
+#define LIST_EMPTY 0
+#define STREAM_END 2
+#define DICTIONARY_0 236
+#define DICTIONARY_1 237
+#define DICTIONARY_2 238
+#define DICTIONARY_3 239
+#define LIST_8 248
+#define LIST_16 249
+#define JID_PAIR 250
+#define HEX_8 251
+#define BINARY_8 252
+#define BINARY_20 253
+#define BINARY_32 254
+#define NIBBLE_8 255
+
+class WAReader
+{
+ const BYTE *m_buf, *m_limit;
+
+ uint32_t readIntN(int i);
+ CMStringA readStringFromChars(int size);
+
+public:
+ WAReader(const void *buf, size_t cbLen) :
+ m_buf((BYTE*)buf),
+ m_limit((BYTE*)buf + cbLen)
+ {}
+
+ __forceinline uint32_t readInt8() { return readIntN(1); }
+ __forceinline uint32_t readInt16() { return readIntN(2); }
+ __forceinline uint32_t readInt32() { return readIntN(4); }
+
+ bool readAttributes(JSONNode &node, int count);
+ uint32_t readInt20();
+ bool readList(JSONNode &node, int tag);
+ int readListSize(int tag);
+ CMStringA readPacked(int tag);
+ CMStringA readString(int tag);
+ bool readNode(JSONNode&);
+};