@softtynet/zca-js 2.3.5 → 2.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/apis/sync/downloadTransferSync.js +19 -1
- package/dist/call-engine/ZaloCallTransport.js +28 -2
- package/dist/call-engine/ZaloRtcSession.js +9 -1
- package/dist/cjs/apis/sync/downloadTransferSync.cjs +19 -1
- package/dist/cjs/call-engine/ZaloCallTransport.cjs +28 -2
- package/dist/cjs/call-engine/ZaloRtcSession.cjs +9 -1
- package/dist/cjs/constants/protocol.cjs +1 -1
- package/dist/cjs/crypto/aes.cjs +3 -1
- package/dist/cjs/crypto/zdb-cipher.cjs +10 -3
- package/dist/constants/protocol.d.ts +1 -1
- package/dist/constants/protocol.js +1 -1
- package/dist/crypto/aes.js +3 -1
- package/dist/crypto/zdb-cipher.js +10 -3
- package/package.json +1 -1
|
@@ -10,13 +10,31 @@ export const downloadTransferSyncFactory = apiFactory()((api, ctx, utils) => {
|
|
|
10
10
|
return async function downloadTransferSync(options) {
|
|
11
11
|
let downloadUrl = "";
|
|
12
12
|
if (typeof options === "string") {
|
|
13
|
+
let parsedUrl;
|
|
14
|
+
try {
|
|
15
|
+
parsedUrl = new URL(options);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
throw new ZaloApiError("Invalid downloadTransferSync URL");
|
|
19
|
+
}
|
|
20
|
+
if (parsedUrl.protocol !== "https:" && parsedUrl.protocol !== "http:") {
|
|
21
|
+
throw new ZaloApiError(`Unsupported protocol in downloadTransferSync URL: ${parsedUrl.protocol}`);
|
|
22
|
+
}
|
|
23
|
+
if (parsedUrl.hostname === "169.254.169.254" || parsedUrl.hostname === "metadata.google.internal") {
|
|
24
|
+
throw new ZaloApiError("Blocked internal metadata hostname in downloadTransferSync URL");
|
|
25
|
+
}
|
|
13
26
|
downloadUrl = options;
|
|
14
27
|
}
|
|
15
28
|
else {
|
|
16
29
|
if (!options.key || !options.exp || !options.sig) {
|
|
17
30
|
throw new ZaloApiError("Missing key, exp or sig in options");
|
|
18
31
|
}
|
|
19
|
-
|
|
32
|
+
const qs = new URLSearchParams({
|
|
33
|
+
key: String(options.key),
|
|
34
|
+
exp: String(options.exp),
|
|
35
|
+
sig: String(options.sig),
|
|
36
|
+
});
|
|
37
|
+
downloadUrl = `${ZALO_DOMAINS.TRANSFER_SYNC}${ZALO_ENDPOINTS.TRANSFER_SYNC.DOWNLOAD_V2}?${qs.toString()}`;
|
|
20
38
|
}
|
|
21
39
|
const response = await utils.request(downloadUrl, {
|
|
22
40
|
method: "GET",
|
|
@@ -8,6 +8,7 @@ import { Buffer } from "node:buffer";
|
|
|
8
8
|
*/
|
|
9
9
|
export const DEFAULT_ZALOCALL_IPC_KEY_BASE64 = "yjAF9oqMWl6XfXYJn9mA7w==";
|
|
10
10
|
const MAX_IPC_CHUNK_LENGTH = 4000;
|
|
11
|
+
const MAX_IPC_RECEIVE_BUFFER_LENGTH = 4 * 1024 * 1024;
|
|
11
12
|
/**
|
|
12
13
|
* Tầng giao thức IPC双kênh (`receiveSocketPath` & `sendSocketPath`) mã hoá AES-128-CBC (Hex + `$` framing + 4000-char chunking)
|
|
13
14
|
* khớp 100% với module `3Zc2` / `4KIH` của Zalo PC và `zrtc_call::ZIpcClientMac` (`0x10012eb50`).
|
|
@@ -107,11 +108,31 @@ export class ZaloCallTransport extends EventEmitter {
|
|
|
107
108
|
await Promise.all([
|
|
108
109
|
new Promise((resolve, reject) => {
|
|
109
110
|
this.receiveServer.once("error", reject);
|
|
110
|
-
this.receiveServer.listen(this.receiveSocketPath, () =>
|
|
111
|
+
this.receiveServer.listen(this.receiveSocketPath, () => {
|
|
112
|
+
if (process.platform !== "win32") {
|
|
113
|
+
try {
|
|
114
|
+
fs.chmodSync(this.receiveSocketPath, 0o600);
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Ignore chmod error if unsupported
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
resolve();
|
|
121
|
+
});
|
|
111
122
|
}),
|
|
112
123
|
new Promise((resolve, reject) => {
|
|
113
124
|
this.sendServer.once("error", reject);
|
|
114
|
-
this.sendServer.listen(this.sendSocketPath, () =>
|
|
125
|
+
this.sendServer.listen(this.sendSocketPath, () => {
|
|
126
|
+
if (process.platform !== "win32") {
|
|
127
|
+
try {
|
|
128
|
+
fs.chmodSync(this.sendSocketPath, 0o600);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// Ignore chmod error if unsupported
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
resolve();
|
|
135
|
+
});
|
|
115
136
|
}),
|
|
116
137
|
]);
|
|
117
138
|
this.emit("ready");
|
|
@@ -200,6 +221,11 @@ export class ZaloCallTransport extends EventEmitter {
|
|
|
200
221
|
}
|
|
201
222
|
}
|
|
202
223
|
handleReceiveChunk(chunk) {
|
|
224
|
+
if (this.receiveBuffer.length + chunk.length > MAX_IPC_RECEIVE_BUFFER_LENGTH) {
|
|
225
|
+
this.receiveBuffer = "";
|
|
226
|
+
this.emit("error", new Error("IPC receive buffer exceeded maximum allowed size (4MB)"));
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
203
229
|
this.receiveBuffer += chunk;
|
|
204
230
|
let dollarIdx = this.receiveBuffer.indexOf("$");
|
|
205
231
|
while (dollarIdx !== -1) {
|
|
@@ -2789,7 +2789,12 @@ export class ZaloRtcSession extends EventEmitter {
|
|
|
2789
2789
|
}
|
|
2790
2790
|
}
|
|
2791
2791
|
else if (this.fuaFragments.length > 0) {
|
|
2792
|
-
this.fuaFragments.
|
|
2792
|
+
if (this.fuaFragments.length >= 512) {
|
|
2793
|
+
this.fuaFragments = [];
|
|
2794
|
+
}
|
|
2795
|
+
else {
|
|
2796
|
+
this.fuaFragments.push(body);
|
|
2797
|
+
}
|
|
2793
2798
|
}
|
|
2794
2799
|
if (isEnd && this.fuaFragments.length > 0) {
|
|
2795
2800
|
const fullNal = Buffer.concat(this.fuaFragments);
|
|
@@ -2823,6 +2828,9 @@ export class ZaloRtcSession extends EventEmitter {
|
|
|
2823
2828
|
else if (nalType === 5) {
|
|
2824
2829
|
this.currentAccessUnitHasIdr = true;
|
|
2825
2830
|
}
|
|
2831
|
+
if (this.currentAccessUnitNals.length >= 256) {
|
|
2832
|
+
this.currentAccessUnitNals = [];
|
|
2833
|
+
}
|
|
2826
2834
|
this.currentAccessUnitNals.push(Buffer.from(nal));
|
|
2827
2835
|
}
|
|
2828
2836
|
flushIncomingAccessUnit(timestamp, ssrc) {
|
|
@@ -15,13 +15,31 @@ const downloadTransferSyncFactory = utils.apiFactory()((api, ctx, utils) => {
|
|
|
15
15
|
return async function downloadTransferSync(options) {
|
|
16
16
|
let downloadUrl = "";
|
|
17
17
|
if (typeof options === "string") {
|
|
18
|
+
let parsedUrl;
|
|
19
|
+
try {
|
|
20
|
+
parsedUrl = new URL(options);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw new ZaloApiError.ZaloApiError("Invalid downloadTransferSync URL");
|
|
24
|
+
}
|
|
25
|
+
if (parsedUrl.protocol !== "https:" && parsedUrl.protocol !== "http:") {
|
|
26
|
+
throw new ZaloApiError.ZaloApiError(`Unsupported protocol in downloadTransferSync URL: ${parsedUrl.protocol}`);
|
|
27
|
+
}
|
|
28
|
+
if (parsedUrl.hostname === "169.254.169.254" || parsedUrl.hostname === "metadata.google.internal") {
|
|
29
|
+
throw new ZaloApiError.ZaloApiError("Blocked internal metadata hostname in downloadTransferSync URL");
|
|
30
|
+
}
|
|
18
31
|
downloadUrl = options;
|
|
19
32
|
}
|
|
20
33
|
else {
|
|
21
34
|
if (!options.key || !options.exp || !options.sig) {
|
|
22
35
|
throw new ZaloApiError.ZaloApiError("Missing key, exp or sig in options");
|
|
23
36
|
}
|
|
24
|
-
|
|
37
|
+
const qs = new URLSearchParams({
|
|
38
|
+
key: String(options.key),
|
|
39
|
+
exp: String(options.exp),
|
|
40
|
+
sig: String(options.sig),
|
|
41
|
+
});
|
|
42
|
+
downloadUrl = `${domains.ZALO_DOMAINS.TRANSFER_SYNC}${endpoints.ZALO_ENDPOINTS.TRANSFER_SYNC.DOWNLOAD_V2}?${qs.toString()}`;
|
|
25
43
|
}
|
|
26
44
|
const response = await utils.request(downloadUrl, {
|
|
27
45
|
method: "GET",
|
|
@@ -11,6 +11,7 @@ var node_buffer = require('node:buffer');
|
|
|
11
11
|
*/
|
|
12
12
|
const DEFAULT_ZALOCALL_IPC_KEY_BASE64 = "yjAF9oqMWl6XfXYJn9mA7w==";
|
|
13
13
|
const MAX_IPC_CHUNK_LENGTH = 4000;
|
|
14
|
+
const MAX_IPC_RECEIVE_BUFFER_LENGTH = 4 * 1024 * 1024;
|
|
14
15
|
/**
|
|
15
16
|
* Tầng giao thức IPC双kênh (`receiveSocketPath` & `sendSocketPath`) mã hoá AES-128-CBC (Hex + `$` framing + 4000-char chunking)
|
|
16
17
|
* khớp 100% với module `3Zc2` / `4KIH` của Zalo PC và `zrtc_call::ZIpcClientMac` (`0x10012eb50`).
|
|
@@ -110,11 +111,31 @@ class ZaloCallTransport extends EventEmitter {
|
|
|
110
111
|
await Promise.all([
|
|
111
112
|
new Promise((resolve, reject) => {
|
|
112
113
|
this.receiveServer.once("error", reject);
|
|
113
|
-
this.receiveServer.listen(this.receiveSocketPath, () =>
|
|
114
|
+
this.receiveServer.listen(this.receiveSocketPath, () => {
|
|
115
|
+
if (process.platform !== "win32") {
|
|
116
|
+
try {
|
|
117
|
+
fs.chmodSync(this.receiveSocketPath, 0o600);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Ignore chmod error if unsupported
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
resolve();
|
|
124
|
+
});
|
|
114
125
|
}),
|
|
115
126
|
new Promise((resolve, reject) => {
|
|
116
127
|
this.sendServer.once("error", reject);
|
|
117
|
-
this.sendServer.listen(this.sendSocketPath, () =>
|
|
128
|
+
this.sendServer.listen(this.sendSocketPath, () => {
|
|
129
|
+
if (process.platform !== "win32") {
|
|
130
|
+
try {
|
|
131
|
+
fs.chmodSync(this.sendSocketPath, 0o600);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// Ignore chmod error if unsupported
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
resolve();
|
|
138
|
+
});
|
|
118
139
|
}),
|
|
119
140
|
]);
|
|
120
141
|
this.emit("ready");
|
|
@@ -203,6 +224,11 @@ class ZaloCallTransport extends EventEmitter {
|
|
|
203
224
|
}
|
|
204
225
|
}
|
|
205
226
|
handleReceiveChunk(chunk) {
|
|
227
|
+
if (this.receiveBuffer.length + chunk.length > MAX_IPC_RECEIVE_BUFFER_LENGTH) {
|
|
228
|
+
this.receiveBuffer = "";
|
|
229
|
+
this.emit("error", new Error("IPC receive buffer exceeded maximum allowed size (4MB)"));
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
206
232
|
this.receiveBuffer += chunk;
|
|
207
233
|
let dollarIdx = this.receiveBuffer.indexOf("$");
|
|
208
234
|
while (dollarIdx !== -1) {
|
|
@@ -2792,7 +2792,12 @@ class ZaloRtcSession extends EventEmitter {
|
|
|
2792
2792
|
}
|
|
2793
2793
|
}
|
|
2794
2794
|
else if (this.fuaFragments.length > 0) {
|
|
2795
|
-
this.fuaFragments.
|
|
2795
|
+
if (this.fuaFragments.length >= 512) {
|
|
2796
|
+
this.fuaFragments = [];
|
|
2797
|
+
}
|
|
2798
|
+
else {
|
|
2799
|
+
this.fuaFragments.push(body);
|
|
2800
|
+
}
|
|
2796
2801
|
}
|
|
2797
2802
|
if (isEnd && this.fuaFragments.length > 0) {
|
|
2798
2803
|
const fullNal = node_buffer.Buffer.concat(this.fuaFragments);
|
|
@@ -2826,6 +2831,9 @@ class ZaloRtcSession extends EventEmitter {
|
|
|
2826
2831
|
else if (nalType === 5) {
|
|
2827
2832
|
this.currentAccessUnitHasIdr = true;
|
|
2828
2833
|
}
|
|
2834
|
+
if (this.currentAccessUnitNals.length >= 256) {
|
|
2835
|
+
this.currentAccessUnitNals = [];
|
|
2836
|
+
}
|
|
2829
2837
|
this.currentAccessUnitNals.push(node_buffer.Buffer.from(nal));
|
|
2830
2838
|
}
|
|
2831
2839
|
flushIncomingAccessUnit(timestamp, ssrc) {
|
|
@@ -12,7 +12,7 @@ const ZALO_PROTOCOL = {
|
|
|
12
12
|
API_VERSION: 689,
|
|
13
13
|
DEFAULT_API_VERSION: 689,
|
|
14
14
|
/** Phiên bản SDK hiện tại */
|
|
15
|
-
SDK_VERSION: "2.3.
|
|
15
|
+
SDK_VERSION: "2.3.6",
|
|
16
16
|
/** Mã zpw_type đặc thù dành riêng cho phân hệ Gọi Thoại & Video (binary C++ ZaloCall) */
|
|
17
17
|
VOICECALL_API_TYPE: 24,
|
|
18
18
|
/** Phiên bản mã hóa ZCID (ParamDecryptor) */
|
package/dist/cjs/crypto/aes.cjs
CHANGED
|
@@ -170,7 +170,9 @@ function encryptPin(pin) {
|
|
|
170
170
|
}
|
|
171
171
|
function validatePin(encryptedPin, pin) {
|
|
172
172
|
const hash = crypto.createHash("md5").update(pin).digest("hex");
|
|
173
|
-
|
|
173
|
+
const a = Buffer.from(hash, "utf8");
|
|
174
|
+
const b = Buffer.from(String(encryptedPin), "utf8");
|
|
175
|
+
return a.length === b.length && crypto.timingSafeEqual(a, b);
|
|
174
176
|
}
|
|
175
177
|
|
|
176
178
|
exports.ParamsEncryptor = ParamsEncryptor;
|
|
@@ -149,6 +149,9 @@ function parseZdbHeader(zdbPlaintext) {
|
|
|
149
149
|
throw new ZaloApiError.ZaloApiError("Buffer is not a valid decrypted ZDB4.0 container");
|
|
150
150
|
}
|
|
151
151
|
const count = zdbPlaintext.readUInt32BE(COUNT_OFFSET);
|
|
152
|
+
if (count > 100000) {
|
|
153
|
+
throw new ZaloApiError.ZaloApiError(`ZDB4.0 header entry count exceeds safety limit (${count})`);
|
|
154
|
+
}
|
|
152
155
|
let offset = COUNT_OFFSET + 4;
|
|
153
156
|
const entries = [];
|
|
154
157
|
for (let i = 0; i < count; i++) {
|
|
@@ -156,12 +159,16 @@ function parseZdbHeader(zdbPlaintext) {
|
|
|
156
159
|
throw new ZaloApiError.ZaloApiError("Truncated ZDB4.0 header while reading entry name length");
|
|
157
160
|
}
|
|
158
161
|
const nameLen = zdbPlaintext.readUInt32BE(offset);
|
|
159
|
-
if (offset + 4 + nameLen + 4 > zdbPlaintext.length) {
|
|
160
|
-
throw new ZaloApiError.ZaloApiError("Truncated ZDB4.0 header while reading entry metadata");
|
|
162
|
+
if (nameLen > 4096 || offset + 4 + nameLen + 4 > zdbPlaintext.length) {
|
|
163
|
+
throw new ZaloApiError.ZaloApiError("Truncated or invalid ZDB4.0 header while reading entry metadata");
|
|
161
164
|
}
|
|
162
|
-
const
|
|
165
|
+
const rawName = zdbPlaintext
|
|
163
166
|
.toString("utf8", offset + 4, offset + 4 + nameLen)
|
|
164
167
|
.replace(/\0+$/, "");
|
|
168
|
+
const name = rawName.split(/[\\/]/).pop() || rawName;
|
|
169
|
+
if (name === ".." || name === "." || name.includes("..")) {
|
|
170
|
+
throw new ZaloApiError.ZaloApiError(`Unsafe entry name in ZDB4.0 header: ${rawName}`);
|
|
171
|
+
}
|
|
165
172
|
const size = zdbPlaintext.readUInt32BE(offset + 4 + nameLen);
|
|
166
173
|
entries.push({ name, size });
|
|
167
174
|
offset += 4 + nameLen + 4;
|
|
@@ -10,7 +10,7 @@ export declare const ZALO_PROTOCOL: {
|
|
|
10
10
|
readonly API_VERSION: 689;
|
|
11
11
|
readonly DEFAULT_API_VERSION: 689;
|
|
12
12
|
/** Phiên bản SDK hiện tại */
|
|
13
|
-
readonly SDK_VERSION: "2.3.
|
|
13
|
+
readonly SDK_VERSION: "2.3.6";
|
|
14
14
|
/** Mã zpw_type đặc thù dành riêng cho phân hệ Gọi Thoại & Video (binary C++ ZaloCall) */
|
|
15
15
|
readonly VOICECALL_API_TYPE: 24;
|
|
16
16
|
/** Phiên bản mã hóa ZCID (ParamDecryptor) */
|
|
@@ -10,7 +10,7 @@ export const ZALO_PROTOCOL = {
|
|
|
10
10
|
API_VERSION: 689,
|
|
11
11
|
DEFAULT_API_VERSION: 689,
|
|
12
12
|
/** Phiên bản SDK hiện tại */
|
|
13
|
-
SDK_VERSION: "2.3.
|
|
13
|
+
SDK_VERSION: "2.3.6",
|
|
14
14
|
/** Mã zpw_type đặc thù dành riêng cho phân hệ Gọi Thoại & Video (binary C++ ZaloCall) */
|
|
15
15
|
VOICECALL_API_TYPE: 24,
|
|
16
16
|
/** Phiên bản mã hóa ZCID (ParamDecryptor) */
|
package/dist/crypto/aes.js
CHANGED
|
@@ -167,5 +167,7 @@ export function encryptPin(pin) {
|
|
|
167
167
|
}
|
|
168
168
|
export function validatePin(encryptedPin, pin) {
|
|
169
169
|
const hash = crypto.createHash("md5").update(pin).digest("hex");
|
|
170
|
-
|
|
170
|
+
const a = Buffer.from(hash, "utf8");
|
|
171
|
+
const b = Buffer.from(String(encryptedPin), "utf8");
|
|
172
|
+
return a.length === b.length && crypto.timingSafeEqual(a, b);
|
|
171
173
|
}
|
|
@@ -146,6 +146,9 @@ export function parseZdbHeader(zdbPlaintext) {
|
|
|
146
146
|
throw new ZaloApiError("Buffer is not a valid decrypted ZDB4.0 container");
|
|
147
147
|
}
|
|
148
148
|
const count = zdbPlaintext.readUInt32BE(COUNT_OFFSET);
|
|
149
|
+
if (count > 100000) {
|
|
150
|
+
throw new ZaloApiError(`ZDB4.0 header entry count exceeds safety limit (${count})`);
|
|
151
|
+
}
|
|
149
152
|
let offset = COUNT_OFFSET + 4;
|
|
150
153
|
const entries = [];
|
|
151
154
|
for (let i = 0; i < count; i++) {
|
|
@@ -153,12 +156,16 @@ export function parseZdbHeader(zdbPlaintext) {
|
|
|
153
156
|
throw new ZaloApiError("Truncated ZDB4.0 header while reading entry name length");
|
|
154
157
|
}
|
|
155
158
|
const nameLen = zdbPlaintext.readUInt32BE(offset);
|
|
156
|
-
if (offset + 4 + nameLen + 4 > zdbPlaintext.length) {
|
|
157
|
-
throw new ZaloApiError("Truncated ZDB4.0 header while reading entry metadata");
|
|
159
|
+
if (nameLen > 4096 || offset + 4 + nameLen + 4 > zdbPlaintext.length) {
|
|
160
|
+
throw new ZaloApiError("Truncated or invalid ZDB4.0 header while reading entry metadata");
|
|
158
161
|
}
|
|
159
|
-
const
|
|
162
|
+
const rawName = zdbPlaintext
|
|
160
163
|
.toString("utf8", offset + 4, offset + 4 + nameLen)
|
|
161
164
|
.replace(/\0+$/, "");
|
|
165
|
+
const name = rawName.split(/[\\/]/).pop() || rawName;
|
|
166
|
+
if (name === ".." || name === "." || name.includes("..")) {
|
|
167
|
+
throw new ZaloApiError(`Unsafe entry name in ZDB4.0 header: ${rawName}`);
|
|
168
|
+
}
|
|
162
169
|
const size = zdbPlaintext.readUInt32BE(offset + 4 + nameLen);
|
|
163
170
|
entries.push({ name, size });
|
|
164
171
|
offset += 4 + nameLen + 4;
|