summaryrefslogtreecommitdiff
path: root/protocols/WhatsAppWeb/src/wareader.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'protocols/WhatsAppWeb/src/wareader.cpp')
-rw-r--r--protocols/WhatsAppWeb/src/wareader.cpp429
1 files changed, 429 insertions, 0 deletions
diff --git a/protocols/WhatsAppWeb/src/wareader.cpp b/protocols/WhatsAppWeb/src/wareader.cpp
new file mode 100644
index 0000000000..0f6df4b31a
--- /dev/null
+++ b/protocols/WhatsAppWeb/src/wareader.cpp
@@ -0,0 +1,429 @@
+/*
+
+WhatsAppWeb plugin for Miranda NG
+Copyright © 2019-22 George Hazan
+
+*/
+
+#include "stdafx.h"
+#include "dicts.h"
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// WAReader class members
+
+bool WAReader::readAttributes(WANode *pNode, 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;
+
+ pNode->addAttr(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(WANode *pParent, int tag)
+{
+ int size = readListSize(tag);
+ if (size == -1)
+ return false;
+
+ for (int i = 0; i < size; i++) {
+ WANode *pNew = readNode();
+ if (pNew == nullptr)
+ return false;
+ pParent->children.push_back(pNew);
+ }
+
+ 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;
+}
+
+WANode *WAReader::readNode()
+{
+ int listSize = readListSize(readInt8());
+ if (listSize == -1)
+ return nullptr;
+
+ int descrTag = readInt8();
+ if (descrTag == STREAM_END)
+ return nullptr;
+
+ CMStringA name = readString(descrTag);
+ if (name.IsEmpty())
+ return nullptr;
+
+ std::unique_ptr<WANode> ret(new WANode());
+ ret->title = name.c_str();
+
+ if (!readAttributes(ret.get(), (listSize - 1) >> 1))
+ return nullptr;
+
+ if ((listSize % 2) == 1)
+ return ret.release();
+
+ int size, tag = readInt8();
+ switch (tag) {
+ case LIST_EMPTY: case LIST_8: case LIST_16:
+ readList(ret.get(), tag);
+ break;
+
+ case BINARY_8:
+ size = readInt8();
+
+LBL_Binary:
+ if (m_limit - m_buf < size)
+ return false;
+
+ ret->content.assign((void *)m_buf, size);
+ m_buf += size;
+ break;
+
+ case BINARY_20:
+ size = readInt20();
+ goto LBL_Binary;
+
+ case BINARY_32:
+ size = readInt32();
+ goto LBL_Binary;
+
+ default:
+ CMStringA str = readString(tag);
+ ret->content.assign(str.GetBuffer(), str.GetLength() + 1);
+ }
+
+ return ret.release();
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+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);
+ }
+
+ if (bTrim && !ret.IsEmpty())
+ ret.Truncate(ret.GetLength() - 1);
+ return ret;
+}
+
+CMStringA WAReader::readString(int tag)
+{
+ if (tag >= 1 && 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 AD_JID:
+ {
+ int agent = readInt8();
+ int device = readInt8();
+ CMStringA user = readString(readInt8());
+ return user + "@c.us";
+ }
+
+ case JID_PAIR:
+ CMStringA s1 = readString(readInt8());
+ CMStringA s2 = readString(readInt8());
+ if (s1.IsEmpty() && 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;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// WAWriter class members
+
+void WAWriter::writeByte(uint8_t b)
+{
+ body.append(&b, 1);
+}
+
+void WAWriter::writeIntN(int value, int n)
+{
+ for (int i = n - 1; i >= 0; i--)
+ writeByte((value >> i * 8) & 0xFF);
+}
+
+void WAWriter::writeInt20(int value)
+{
+ writeByte((value >> 16) & 0xFF);
+ writeByte((value >> 8) & 0xFF);
+ writeByte(value & 0xFF);
+}
+
+void WAWriter::writeLength(int value)
+{
+ if (value >= (1 << 20)) {
+ writeByte(BINARY_32);
+ writeInt32(value);
+ }
+ else if (value >= 256) {
+ writeByte(BINARY_20);
+ writeInt20(value);
+ }
+ else {
+ writeByte(BINARY_8);
+ writeInt8(value);
+ }
+}
+
+void WAWriter::writeListSize(int length)
+{
+ if (length == 0)
+ writeByte(LIST_EMPTY);
+ else if (length < 256) {
+ writeByte(LIST_8);
+ writeInt8(length);
+ }
+ else {
+ writeByte(LIST_16);
+ writeInt16(length);
+ }
+}
+
+void WAWriter::writeNode(const WANode *pNode)
+{
+ int numAttrs = (int)pNode->attrs.size();
+ int hasContent = pNode->content.length() != 0;
+ writeListSize(2 * numAttrs + 1 + hasContent);
+
+ writeString(pNode->title.c_str());
+
+ // write attributes
+ for (auto &it : pNode->attrs) {
+ if (it->value.IsEmpty())
+ continue;
+
+ writeString(it->name.c_str());
+ writeString(it->value.c_str());
+ }
+
+ // write contents
+ if (pNode->content.length()) {
+ writeLength((int)pNode->content.length());
+ body.append(pNode->content.data(), pNode->content.length());
+ }
+
+ // write children
+ if (pNode->children.size()) {
+ writeListSize((int)pNode->children.size());
+ for (auto &it : pNode->children)
+ writeNode(it);
+ }
+}
+
+void WAWriter::writeString(const char *str, bool bRaw)
+{
+ if (!bRaw && !mir_strcmp(str, "c.us")) {
+ writeToken("s.whatsapp.net");
+ return;
+ }
+
+ if (writeToken(str))
+ return;
+
+ auto *pszDelimiter = strchr(str, '@');
+ if (pszDelimiter) {
+ writeByte(JID_PAIR);
+
+ if (pszDelimiter == str) // empty jid
+ writeByte(LIST_EMPTY);
+ else
+ writePacked(CMStringA(str, int(pszDelimiter - str)));
+ }
+ else {
+ int len = (int)strlen(str);
+ writeLength(len);
+ body.append(str, len);
+ }
+}
+
+bool WAWriter::writeToken(const char *str)
+{
+ for (auto &it : SingleByteTokens)
+ if (!strcmp(str, it)) {
+ writeByte(int(&it - SingleByteTokens));
+ return true;
+ }
+
+ return false;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
+static BYTE packNibble(char c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+
+ switch (c) {
+ case '-': return 10;
+ case '.': return 11;
+ case 0: return 15;
+ }
+
+ return -1;
+}
+
+static BYTE packHex(char c)
+{
+ if (c == 0)
+ return 15;
+
+ if (c >= '0' && c <= '9')
+ return c - '0';
+
+ if (c >= 'A' && c <= 'F')
+ return c - 'A';
+
+ if (c >= 'a' && c <= 'f')
+ return c - 'a';
+
+ return -1;
+}
+
+static BYTE packPair(int type, char c1, char c2)
+{
+ BYTE b1 = (type == NIBBLE_8) ? packNibble(c1) : packHex(c1);
+ BYTE b2 = (type == NIBBLE_8) ? packNibble(c2) : packHex(c2);
+ return (b1 << 4) + b2;
+}
+
+void WAWriter::writePacked(const CMStringA &str)
+{
+ if (str.GetLength() > 254)
+ return;
+
+ // all symbols of str can be a nibble?
+ int type = (strspn(str, "0123456789-.") == str.GetLength()) ? NIBBLE_8 : HEX_8;
+
+ int len = str.GetLength() / 2;
+ BYTE firstByte = (len % 2) == 0 ? 0 : 0x80;
+ writeByte(firstByte | len);
+
+ const char *p = str;
+ for (int i = 0; i < len; i++, p += 2)
+ writeByte(packPair(type, p[0], p[1]));
+
+ if (firstByte != 0)
+ writeByte(packPair(type, p[0], 0));
+}