appium-ios-remotexpc 0.0.1

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.
Files changed (92) hide show
  1. package/.github/dependabot.yml +38 -0
  2. package/.github/workflows/format-check.yml +43 -0
  3. package/.github/workflows/lint-and-build.yml +40 -0
  4. package/.github/workflows/pr-title.yml +16 -0
  5. package/.github/workflows/publish.js.yml +42 -0
  6. package/.github/workflows/test-validation.yml +40 -0
  7. package/.mocharc.json +8 -0
  8. package/.prettierignore +3 -0
  9. package/.prettierrc +17 -0
  10. package/.releaserc +37 -0
  11. package/CHANGELOG.md +63 -0
  12. package/LICENSE +201 -0
  13. package/README.md +178 -0
  14. package/assets/images/ios-arch.png +0 -0
  15. package/eslint.config.js +45 -0
  16. package/package.json +78 -0
  17. package/scripts/test-tunnel-creation.ts +378 -0
  18. package/src/base-plist-service.ts +83 -0
  19. package/src/base-socket-service.ts +55 -0
  20. package/src/index.ts +34 -0
  21. package/src/lib/apple-tv/constants.ts +83 -0
  22. package/src/lib/apple-tv/errors.ts +31 -0
  23. package/src/lib/apple-tv/tlv/decoder.ts +68 -0
  24. package/src/lib/apple-tv/tlv/encoder.ts +33 -0
  25. package/src/lib/apple-tv/tlv/index.ts +6 -0
  26. package/src/lib/apple-tv/tlv/pairing-tlv.ts +31 -0
  27. package/src/lib/apple-tv/types.ts +58 -0
  28. package/src/lib/apple-tv/utils/buffer-utils.ts +90 -0
  29. package/src/lib/apple-tv/utils/index.ts +2 -0
  30. package/src/lib/apple-tv/utils/uuid-generator.ts +43 -0
  31. package/src/lib/lockdown/index.ts +468 -0
  32. package/src/lib/pair-record/index.ts +8 -0
  33. package/src/lib/pair-record/pair-record.ts +133 -0
  34. package/src/lib/plist/binary-plist-creator.ts +571 -0
  35. package/src/lib/plist/binary-plist-parser.ts +587 -0
  36. package/src/lib/plist/constants.ts +53 -0
  37. package/src/lib/plist/index.ts +54 -0
  38. package/src/lib/plist/length-based-splitter.ts +326 -0
  39. package/src/lib/plist/plist-creator.ts +42 -0
  40. package/src/lib/plist/plist-decoder.ts +135 -0
  41. package/src/lib/plist/plist-encoder.ts +36 -0
  42. package/src/lib/plist/plist-parser.ts +144 -0
  43. package/src/lib/plist/plist-service.ts +231 -0
  44. package/src/lib/plist/unified-plist-creator.ts +19 -0
  45. package/src/lib/plist/unified-plist-parser.ts +25 -0
  46. package/src/lib/plist/utils.ts +376 -0
  47. package/src/lib/remote-xpc/constants.ts +22 -0
  48. package/src/lib/remote-xpc/handshake-frames.ts +377 -0
  49. package/src/lib/remote-xpc/handshake.ts +152 -0
  50. package/src/lib/remote-xpc/remote-xpc-connection.ts +461 -0
  51. package/src/lib/remote-xpc/xpc-protocol.ts +412 -0
  52. package/src/lib/tunnel/index.ts +253 -0
  53. package/src/lib/tunnel/packet-stream-client.ts +185 -0
  54. package/src/lib/tunnel/packet-stream-server.ts +133 -0
  55. package/src/lib/tunnel/tunnel-api-client.ts +234 -0
  56. package/src/lib/tunnel/tunnel-registry-server.ts +410 -0
  57. package/src/lib/types.ts +291 -0
  58. package/src/lib/usbmux/index.ts +630 -0
  59. package/src/lib/usbmux/usbmux-decoder.ts +66 -0
  60. package/src/lib/usbmux/usbmux-encoder.ts +55 -0
  61. package/src/service-connection.ts +79 -0
  62. package/src/services/index.ts +15 -0
  63. package/src/services/ios/base-service.ts +81 -0
  64. package/src/services/ios/diagnostic-service/index.ts +241 -0
  65. package/src/services/ios/diagnostic-service/keys.ts +770 -0
  66. package/src/services/ios/syslog-service/index.ts +387 -0
  67. package/src/services/ios/tunnel-service/index.ts +88 -0
  68. package/src/services.ts +81 -0
  69. package/test/integration/diagnostics-test.ts +44 -0
  70. package/test/integration/read-pair-record-test.ts +39 -0
  71. package/test/integration/tunnel-test.ts +104 -0
  72. package/test/unit/apple-tv/tlv/decoder.spec.ts +144 -0
  73. package/test/unit/apple-tv/tlv/encoder.spec.ts +91 -0
  74. package/test/unit/apple-tv/tlv/pairing-tlv.spec.ts +101 -0
  75. package/test/unit/apple-tv/tlv/tlv-integration.spec.ts +146 -0
  76. package/test/unit/apple-tv/utils/buffer-utils.spec.ts +74 -0
  77. package/test/unit/apple-tv/utils/uuid-generator.spec.ts +39 -0
  78. package/test/unit/fixtures/index.ts +88 -0
  79. package/test/unit/fixtures/usbmuxconnectmessage.bin +0 -0
  80. package/test/unit/fixtures/usbmuxlistdevicemessage.bin +0 -0
  81. package/test/unit/plist/error-handling.spec.ts +101 -0
  82. package/test/unit/plist/fixtures/sample.binary.plist +0 -0
  83. package/test/unit/plist/fixtures/sample.xml.plist +38 -0
  84. package/test/unit/plist/plist-parser.spec.ts +283 -0
  85. package/test/unit/plist/plist.spec.ts +205 -0
  86. package/test/unit/plist/tag-position-handling.spec.ts +90 -0
  87. package/test/unit/plist/unified-plist-parser.spec.ts +227 -0
  88. package/test/unit/plist/utils.spec.ts +249 -0
  89. package/test/unit/plist/xml-cleaning.spec.ts +60 -0
  90. package/test/unit/tunnel/tunnel-registry-server.spec.ts +194 -0
  91. package/test/unit/usbmux/usbmux-specs.ts +71 -0
  92. package/tsconfig.json +36 -0
@@ -0,0 +1,587 @@
1
+ /**
2
+ * Binary Property List (bplist) Parser
3
+ *
4
+ * This module provides functionality to parse binary property lists (bplists)
5
+ * commonly used in Apple's iOS and macOS systems.
6
+ */
7
+ import { logger } from '@appium/support';
8
+
9
+ import type { PlistArray, PlistDictionary, PlistValue } from '../types.js';
10
+ import {
11
+ APPLE_EPOCH_OFFSET,
12
+ BPLIST_MAGIC_AND_VERSION,
13
+ BPLIST_TRAILER_SIZE,
14
+ BPLIST_TYPE,
15
+ } from './constants.js';
16
+
17
+ const log = logger.getLogger('Plist');
18
+
19
+ /**
20
+ * Represents a temporary object during binary plist parsing
21
+ */
22
+ interface TempObject {
23
+ type: 'array' | 'dict';
24
+ objLength: number;
25
+ startOffset: number;
26
+ value: PlistArray | PlistDictionary;
27
+ }
28
+
29
+ /**
30
+ * Type for the object table during parsing
31
+ */
32
+ type ObjectTableItem = PlistValue | TempObject;
33
+
34
+ /**
35
+ * Class for parsing binary property lists
36
+ */
37
+ class BinaryPlistParser {
38
+ private _buffer: Buffer;
39
+ private _offsetSize: number;
40
+ private _objectRefSize: number;
41
+ private _numObjects: number;
42
+ private _topObject: number;
43
+ private _offsetTableOffset: number;
44
+ private readonly _objectTable: ObjectTableItem[];
45
+
46
+ /**
47
+ * Creates a new BinaryPlistParser
48
+ * @param buffer - The binary plist data as a Buffer
49
+ */
50
+ constructor(buffer: Buffer) {
51
+ this._buffer = buffer;
52
+ this._objectTable = [];
53
+
54
+ // Initialize with default values, will be set in parseTrailer
55
+ this._offsetSize = 0;
56
+ this._objectRefSize = 0;
57
+ this._numObjects = 0;
58
+ this._topObject = 0;
59
+ this._offsetTableOffset = 0;
60
+ }
61
+
62
+ /**
63
+ * Parses the binary plist
64
+ * @returns The parsed JavaScript object
65
+ */
66
+ parse(): PlistValue {
67
+ this._validateHeader();
68
+ this._parseTrailer();
69
+ this._parseObjects();
70
+ this._resolveReferences();
71
+ return this._handleTopObject();
72
+ }
73
+
74
+ /**
75
+ * Validates the binary plist header
76
+ * @throws Error if the buffer is not a valid binary plist
77
+ */
78
+ private _validateHeader(): void {
79
+ if (
80
+ this._buffer.length < 8 ||
81
+ !this._buffer.slice(0, 8).equals(BPLIST_MAGIC_AND_VERSION)
82
+ ) {
83
+ throw new Error('Not a binary plist. Expected bplist00 magic.');
84
+ }
85
+ }
86
+
87
+ /**
88
+ * Parses the trailer section of the binary plist
89
+ * @throws Error if the buffer is too small to contain a trailer
90
+ */
91
+ private _parseTrailer(): void {
92
+ if (this._buffer.length < BPLIST_TRAILER_SIZE) {
93
+ throw new Error('Binary plist is too small to contain a trailer.');
94
+ }
95
+
96
+ const trailer = this._buffer.slice(
97
+ this._buffer.length - BPLIST_TRAILER_SIZE,
98
+ );
99
+
100
+ // Extract trailer information
101
+ this._offsetSize = trailer.readUInt8(6);
102
+ this._objectRefSize = trailer.readUInt8(7);
103
+ this._numObjects = Number(trailer.readBigUInt64BE(8));
104
+ this._topObject = Number(trailer.readBigUInt64BE(16));
105
+ this._offsetTableOffset = Number(trailer.readBigUInt64BE(24));
106
+ }
107
+
108
+ /**
109
+ * Reads an object reference from the buffer
110
+ * @param offset - The offset to read from
111
+ * @returns The object reference index
112
+ */
113
+ /**
114
+ * Helper method to read multi-byte integers safely, handling potential overflow
115
+ * @param startOffset - The offset to start reading from
116
+ * @param byteCount - The number of bytes to read
117
+ * @param valueName - Name of the value type for error messages
118
+ * @returns The parsed integer value
119
+ */
120
+ private _readMultiByteInteger(
121
+ startOffset: number,
122
+ byteCount: number,
123
+ valueName: string,
124
+ ): number {
125
+ // Use BigInt for calculations if byteCount is large enough to potentially overflow
126
+ if (byteCount > 6) {
127
+ // 6 bytes = 48 bits, safely under MAX_SAFE_INTEGER
128
+ let result = 0n;
129
+ for (let i = 0; i < byteCount; i++) {
130
+ result =
131
+ (result << 8n) | BigInt(this._buffer.readUInt8(startOffset + i));
132
+ }
133
+
134
+ // Check if the value exceeds MAX_SAFE_INTEGER
135
+ if (result > BigInt(Number.MAX_SAFE_INTEGER)) {
136
+ throw new Error(
137
+ `${valueName} value ${result} exceeds MAX_SAFE_INTEGER. Cannot safely convert to number.`,
138
+ );
139
+ }
140
+
141
+ // Safe to convert to number without precision loss
142
+ return Number(result);
143
+ }
144
+
145
+ // Use regular number arithmetic for smaller values
146
+ let result = 0;
147
+ for (let i = 0; i < byteCount; i++) {
148
+ result = (result << 8) | this._buffer.readUInt8(startOffset + i);
149
+ }
150
+ return result;
151
+ }
152
+
153
+ private _readObjectRef(offset: number): number {
154
+ return this._readMultiByteInteger(
155
+ offset,
156
+ this._objectRefSize,
157
+ 'Object reference',
158
+ );
159
+ }
160
+
161
+ /**
162
+ * Reads an offset from the offset table
163
+ * @param index - The index in the offset table
164
+ * @returns The offset value
165
+ */
166
+ private _readOffset(index: number): number {
167
+ const offsetStart = this._offsetTableOffset + index * this._offsetSize;
168
+ return this._readMultiByteInteger(offsetStart, this._offsetSize, 'Offset');
169
+ }
170
+
171
+ /**
172
+ * Parses an integer value from the buffer
173
+ * @param startOffset - The offset to start reading from
174
+ * @param intByteCount - The number of bytes to read
175
+ * @returns The parsed integer value (number or bigint)
176
+ */
177
+ private _parseIntegerValue(
178
+ startOffset: number,
179
+ intByteCount: number,
180
+ ): number | bigint {
181
+ // Handle different integer sizes
182
+ switch (intByteCount) {
183
+ case 1:
184
+ return this._buffer.readInt8(startOffset);
185
+ case 2:
186
+ return this._buffer.readInt16BE(startOffset);
187
+ case 4:
188
+ return this._buffer.readInt32BE(startOffset);
189
+ case 8: {
190
+ // For 64-bit integers, we need to handle potential precision loss
191
+ const bigInt = this._buffer.readBigInt64BE(startOffset);
192
+ const intValue = Number(bigInt);
193
+
194
+ // Check if conversion to Number caused precision loss
195
+ if (BigInt(intValue) !== bigInt) {
196
+ log.warn(
197
+ 'Precision loss when converting 64-bit integer to Number. Returning BigInt value.',
198
+ );
199
+ return bigInt; // Return the BigInt directly to avoid precision loss
200
+ }
201
+
202
+ return intValue; // Return as number if no precision loss
203
+ }
204
+ default:
205
+ throw new TypeError(
206
+ `Unexpected integer byte count: ${intByteCount}. Cannot parse integer value.`,
207
+ );
208
+ }
209
+ }
210
+
211
+ /**
212
+ * Parses a real (floating point) value from the buffer
213
+ * @param startOffset - The offset to start reading from
214
+ * @param floatByteCount - The number of bytes to read
215
+ * @returns The parsed floating point value
216
+ */
217
+ private _parseRealValue(startOffset: number, floatByteCount: number): number {
218
+ switch (floatByteCount) {
219
+ case 4:
220
+ return this._buffer.readFloatBE(startOffset);
221
+ case 8:
222
+ return this._buffer.readDoubleBE(startOffset);
223
+ default:
224
+ throw new TypeError(
225
+ `Unexpected float byte count: ${floatByteCount}. Cannot parse real value.`,
226
+ );
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Parses a date value from the buffer
232
+ * @param startOffset - The offset to start reading from
233
+ * @returns The parsed Date object
234
+ */
235
+ private _parseDateValue(startOffset: number): Date {
236
+ // Date is stored as a float, seconds since 2001-01-01
237
+ const timestamp = this._buffer.readDoubleBE(startOffset);
238
+ // Convert Apple epoch (2001-01-01) to Unix epoch (1970-01-01)
239
+ return new Date((timestamp + APPLE_EPOCH_OFFSET) * 1000);
240
+ }
241
+
242
+ /**
243
+ * Parses a data value from the buffer
244
+ * @param startOffset - The offset to start reading from
245
+ * @param objLength - The length of the data
246
+ * @returns The parsed Buffer
247
+ */
248
+ private _parseDataValue(startOffset: number, objLength: number): Buffer {
249
+ return Buffer.from(
250
+ this._buffer.slice(startOffset, startOffset + objLength),
251
+ );
252
+ }
253
+
254
+ /**
255
+ * Parses an ASCII string from the buffer
256
+ * @param startOffset - The offset to start reading from
257
+ * @param objLength - The length of the string
258
+ * @returns The parsed string
259
+ */
260
+ private _parseAsciiString(startOffset: number, objLength: number): string {
261
+ return this._buffer
262
+ .slice(startOffset, startOffset + objLength)
263
+ .toString('ascii');
264
+ }
265
+
266
+ /**
267
+ * Parses a Unicode string from the buffer
268
+ * @param startOffset - The offset to start reading from
269
+ * @param objLength - The length of the string in characters
270
+ * @returns The parsed string
271
+ */
272
+ private _parseUnicodeString(startOffset: number, objLength: number): string {
273
+ // Unicode strings are stored as UTF-16BE
274
+ const utf16Buffer = Buffer.alloc(objLength * 2);
275
+ for (let j = 0; j < objLength; j++) {
276
+ utf16Buffer.writeUInt16BE(
277
+ this._buffer.readUInt16BE(startOffset + j * 2),
278
+ j * 2,
279
+ );
280
+ }
281
+ return utf16Buffer.toString('utf16le', 0, objLength * 2);
282
+ }
283
+
284
+ /**
285
+ * Parses a UID value from the buffer
286
+ * @param startOffset - The offset to start reading from
287
+ * @param uidByteCount - The number of bytes to read
288
+ * @returns The parsed UID value
289
+ */
290
+ private _parseUidValue(startOffset: number, uidByteCount: number): number {
291
+ return this._readMultiByteInteger(startOffset, uidByteCount, 'UID');
292
+ }
293
+
294
+ /**
295
+ * Parses all objects in the binary plist
296
+ */
297
+ private _parseObjects(): void {
298
+ for (let i = 0; i < this._numObjects; i++) {
299
+ const objOffset = this._readOffset(i);
300
+ const objType = this._buffer.readUInt8(objOffset) & 0xf0;
301
+ const objInfo = this._buffer.readUInt8(objOffset) & 0x0f;
302
+
303
+ let objLength = objInfo;
304
+ let startOffset = objOffset + 1;
305
+
306
+ // For objects with length > 15, the actual length follows
307
+ if (objInfo === 0x0f) {
308
+ const intType = this._buffer.readUInt8(startOffset) & 0xf0;
309
+ if (intType !== BPLIST_TYPE.INT) {
310
+ throw new TypeError(
311
+ `Expected integer type for length at offset ${startOffset}`,
312
+ );
313
+ }
314
+
315
+ const intInfo = this._buffer.readUInt8(startOffset) & 0x0f;
316
+ startOffset++;
317
+
318
+ // Read the length based on the integer size
319
+ const intByteCount = 1 << intInfo;
320
+ objLength = this._readMultiByteInteger(
321
+ startOffset,
322
+ intByteCount,
323
+ 'Object length',
324
+ );
325
+ startOffset += intByteCount;
326
+ }
327
+
328
+ // Parse the object based on its type
329
+ this._objectTable[i] = this._parseObjectByType(
330
+ objType,
331
+ objInfo,
332
+ startOffset,
333
+ objLength,
334
+ );
335
+ }
336
+ }
337
+
338
+ /**
339
+ * Parses an object based on its type
340
+ * @param objType - The object type
341
+ * @param objInfo - The object info
342
+ * @param startOffset - The start offset
343
+ * @param objLength - The object length
344
+ * @returns The parsed object
345
+ */
346
+ private _parseObjectByType(
347
+ objType: number,
348
+ objInfo: number,
349
+ startOffset: number,
350
+ objLength: number,
351
+ ): PlistValue | TempObject {
352
+ switch (objType) {
353
+ case BPLIST_TYPE.NULL:
354
+ return this._parseNullType(objInfo);
355
+
356
+ case BPLIST_TYPE.INT:
357
+ return this._parseIntegerValue(startOffset, 1 << objInfo);
358
+
359
+ case BPLIST_TYPE.REAL:
360
+ return this._parseRealValue(startOffset, 1 << objInfo);
361
+
362
+ case BPLIST_TYPE.DATE:
363
+ return this._parseDateValue(startOffset);
364
+
365
+ case BPLIST_TYPE.DATA:
366
+ return this._parseDataValue(startOffset, objLength);
367
+
368
+ case BPLIST_TYPE.STRING_ASCII:
369
+ return this._parseAsciiString(startOffset, objLength);
370
+
371
+ case BPLIST_TYPE.STRING_UNICODE:
372
+ return this._parseUnicodeString(startOffset, objLength);
373
+
374
+ case BPLIST_TYPE.UID:
375
+ return this._parseUidValue(startOffset, objInfo + 1);
376
+
377
+ case BPLIST_TYPE.ARRAY:
378
+ return this._createTempArray(objLength, startOffset);
379
+
380
+ case BPLIST_TYPE.DICT:
381
+ return this._createTempDict(objLength, startOffset);
382
+
383
+ default:
384
+ throw new TypeError(
385
+ `Unsupported binary plist object type: ${objType.toString(16)}`,
386
+ );
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Parses a null type object
392
+ * @param objInfo - The object info
393
+ * @returns The parsed value (null, false, or true)
394
+ */
395
+ private _parseNullType(objInfo: number): PlistValue {
396
+ switch (objInfo) {
397
+ case 0x00:
398
+ return null;
399
+ case 0x08:
400
+ return false;
401
+ case 0x09:
402
+ return true;
403
+ case 0x0f:
404
+ return null; // fill byte
405
+ default:
406
+ throw new TypeError(
407
+ `Unexpected null type object info: 0x${objInfo.toString(16)}. Cannot parse null value.`,
408
+ );
409
+ }
410
+ }
411
+
412
+ /**
413
+ * Creates a temporary array object
414
+ * @param objLength - The array length
415
+ * @param startOffset - The start offset
416
+ * @returns The temporary array object
417
+ */
418
+ private _createTempArray(objLength: number, startOffset: number): TempObject {
419
+ return {
420
+ type: 'array',
421
+ objLength,
422
+ startOffset,
423
+ value: [] as PlistArray,
424
+ };
425
+ }
426
+
427
+ /**
428
+ * Creates a temporary dictionary object
429
+ * @param objLength - The dictionary length
430
+ * @param startOffset - The start offset
431
+ * @returns The temporary dictionary object
432
+ */
433
+ private _createTempDict(objLength: number, startOffset: number): TempObject {
434
+ return {
435
+ type: 'dict',
436
+ objLength,
437
+ startOffset,
438
+ value: {} as PlistDictionary,
439
+ };
440
+ }
441
+
442
+ /**
443
+ * Type guard to check if an object is a TempObject
444
+ * @param obj - The object to check
445
+ * @returns True if the object is a TempObject
446
+ */
447
+ private _isTempObject(obj: ObjectTableItem): obj is TempObject {
448
+ return typeof obj === 'object' && obj !== null && 'type' in obj;
449
+ }
450
+
451
+ /**
452
+ * Resolves references for arrays and dictionaries
453
+ */
454
+ private _resolveReferences(): void {
455
+ for (let i = 0; i < this._numObjects; i++) {
456
+ const obj = this._objectTable[i];
457
+ if (this._isTempObject(obj)) {
458
+ if (obj.type === 'array') {
459
+ this._resolveArrayReferences(obj, i);
460
+ } else if (obj.type === 'dict') {
461
+ this._resolveDictReferences(obj, i);
462
+ }
463
+ }
464
+ }
465
+ }
466
+
467
+ /**
468
+ * Resolves references for an array
469
+ * @param obj - The temporary array object
470
+ * @param index - The index in the object table
471
+ */
472
+ private _resolveArrayReferences(obj: TempObject, index: number): void {
473
+ const array = obj.value as PlistArray;
474
+ for (let j = 0; j < obj.objLength; j++) {
475
+ const refIdx = this._readObjectRef(
476
+ obj.startOffset + j * this._objectRefSize,
477
+ );
478
+ const refValue = this._objectTable[refIdx];
479
+ // Ensure we're not adding a TempObject to the array
480
+ if (!this._isTempObject(refValue)) {
481
+ array.push(refValue);
482
+ }
483
+ }
484
+ this._objectTable[index] = array;
485
+ }
486
+
487
+ /**
488
+ * Resolves references for a dictionary
489
+ * @param obj - The temporary dictionary object
490
+ * @param index - The index in the object table
491
+ */
492
+ private _resolveDictReferences(obj: TempObject, index: number): void {
493
+ const dict = obj.value as PlistDictionary;
494
+ const keyCount = obj.objLength;
495
+
496
+ // Keys are stored first, followed by values
497
+ for (let j = 0; j < keyCount; j++) {
498
+ const keyRef = this._readObjectRef(
499
+ obj.startOffset + j * this._objectRefSize,
500
+ );
501
+ const valueRef = this._readObjectRef(
502
+ obj.startOffset + (keyCount + j) * this._objectRefSize,
503
+ );
504
+
505
+ const key = this._objectTable[keyRef];
506
+ const value = this._objectTable[valueRef];
507
+
508
+ if (typeof key !== 'string') {
509
+ throw new TypeError(
510
+ `Dictionary key must be a string, got ${typeof key}`,
511
+ );
512
+ }
513
+
514
+ // Ensure we're not adding a TempObject to the dictionary
515
+ if (!this._isTempObject(value)) {
516
+ dict[key] = value;
517
+ }
518
+ }
519
+ this._objectTable[index] = dict;
520
+ }
521
+
522
+ /**
523
+ * Handles special case for the top object
524
+ * @returns The final parsed value
525
+ */
526
+ private _handleTopObject(): PlistValue {
527
+ // If the top object is an empty object but we have key-value pairs in the array format,
528
+ // convert it to a proper object
529
+ if (
530
+ this._topObject === 0 &&
531
+ this._objectTable[0] &&
532
+ typeof this._objectTable[0] === 'object' &&
533
+ !this._isTempObject(this._objectTable[0]) &&
534
+ Object.keys(this._objectTable[0] as object).length === 0 &&
535
+ this._objectTable.length > 1
536
+ ) {
537
+ return this._convertArrayToDict();
538
+ }
539
+
540
+ // Ensure the top object is a PlistValue and not a TempObject
541
+ const topValue = this._objectTable[this._topObject];
542
+ if (this._isTempObject(topValue)) {
543
+ return topValue.value;
544
+ }
545
+ return topValue;
546
+ }
547
+
548
+ /**
549
+ * Converts an array format to a dictionary
550
+ * @returns The converted dictionary
551
+ */
552
+ private _convertArrayToDict(): PlistDictionary {
553
+ const result: PlistDictionary = {};
554
+ // Process the array in key-value pairs
555
+ for (let i = 1; i < this._objectTable.length; i += 2) {
556
+ const key = this._objectTable[i];
557
+ if (i + 1 < this._objectTable.length && typeof key === 'string') {
558
+ const value = this._objectTable[i + 1];
559
+ if (!this._isTempObject(value)) {
560
+ result[key] = value;
561
+ }
562
+ }
563
+ }
564
+ return result;
565
+ }
566
+ }
567
+
568
+ /**
569
+ * Parses a binary plist buffer into a JavaScript object
570
+ * @param buffer - The binary plist data as a Buffer
571
+ * @returns The parsed JavaScript object
572
+ */
573
+ export function parseBinaryPlist(buffer: Buffer): PlistValue {
574
+ const parser = new BinaryPlistParser(buffer);
575
+ return parser.parse();
576
+ }
577
+
578
+ /**
579
+ * Determines if a buffer is a binary plist
580
+ * @param buffer - The buffer to check
581
+ * @returns True if the buffer is a binary plist
582
+ */
583
+ export function isBinaryPlist(buffer: Buffer): boolean {
584
+ return (
585
+ buffer.length >= 8 && buffer.slice(0, 8).equals(BPLIST_MAGIC_AND_VERSION)
586
+ );
587
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Common constants for plist operations
3
+ */
4
+
5
+ // Constants for binary plist format
6
+ export const BPLIST_MAGIC = 'bplist';
7
+ export const BPLIST_VERSION = '00';
8
+ export const BPLIST_MAGIC_AND_VERSION = Buffer.from(
9
+ `${BPLIST_MAGIC}${BPLIST_VERSION}`,
10
+ );
11
+
12
+ // Binary plist header constants
13
+ export const BINARY_PLIST_MAGIC = 'bplist00';
14
+ export const IBINARY_PLIST_MAGIC = 'Ibplist00';
15
+ export const BINARY_PLIST_HEADER_LENGTH = 9;
16
+
17
+ // XML plist constants
18
+ export const XML_DECLARATION = '<?xml';
19
+ export const PLIST_CLOSING_TAG = '</plist>';
20
+
21
+ // Length field constants
22
+ export const LENGTH_FIELD_1_BYTE = 1;
23
+ export const LENGTH_FIELD_2_BYTES = 2;
24
+ export const LENGTH_FIELD_4_BYTES = 4;
25
+ export const LENGTH_FIELD_8_BYTES = 8;
26
+ export const UINT32_HIGH_MULTIPLIER = 0x100000000;
27
+
28
+ // Encoding constants
29
+ export const UTF8_ENCODING = 'utf8';
30
+
31
+ // Apple epoch offset (seconds between Unix epoch 1970-01-01 and Apple epoch 2001-01-01)
32
+ export const APPLE_EPOCH_OFFSET = 978307200;
33
+
34
+ // Binary plist trailer size (last 32 bytes of the file)
35
+ export const BPLIST_TRAILER_SIZE = 32;
36
+
37
+ // Object types in binary plist
38
+ export const BPLIST_TYPE = {
39
+ NULL: 0x00,
40
+ FALSE: 0x08,
41
+ TRUE: 0x09,
42
+ FILL: 0x0f,
43
+ INT: 0x10,
44
+ REAL: 0x20,
45
+ DATE: 0x30,
46
+ DATA: 0x40,
47
+ STRING_ASCII: 0x50,
48
+ STRING_UNICODE: 0x60,
49
+ UID: 0x80,
50
+ ARRAY: 0xa0,
51
+ SET: 0xc0,
52
+ DICT: 0xd0,
53
+ };
@@ -0,0 +1,54 @@
1
+ // Export all components for easy imports
2
+ import { createBinaryPlist } from './binary-plist-creator.js';
3
+ import { isBinaryPlist, parseBinaryPlist } from './binary-plist-parser.js';
4
+ import {
5
+ APPLE_EPOCH_OFFSET,
6
+ BPLIST_MAGIC_AND_VERSION,
7
+ BPLIST_TYPE,
8
+ UTF8_ENCODING,
9
+ } from './constants.js';
10
+ import { LengthBasedSplitter } from './length-based-splitter.js';
11
+ import { createPlist as createXmlPlist } from './plist-creator.js';
12
+ import { PlistServiceDecoder } from './plist-decoder.js';
13
+ import { PlistServiceEncoder } from './plist-encoder.js';
14
+ import { parsePlist as parseXmlPlist } from './plist-parser.js';
15
+ import { PlistService } from './plist-service.js';
16
+ import { createPlist } from './unified-plist-creator.js';
17
+ import { parsePlist } from './unified-plist-parser.js';
18
+ import {
19
+ ensureString,
20
+ escapeXml,
21
+ findFirstReplacementCharacter,
22
+ fixMultipleXmlDeclarations,
23
+ hasUnicodeReplacementCharacter,
24
+ isValidXml,
25
+ isXmlPlistContent,
26
+ trimBeforeXmlDeclaration,
27
+ } from './utils.js';
28
+
29
+ export {
30
+ createPlist,
31
+ createXmlPlist,
32
+ createBinaryPlist,
33
+ LengthBasedSplitter,
34
+ parsePlist,
35
+ parseXmlPlist,
36
+ parseBinaryPlist,
37
+ isBinaryPlist,
38
+ PlistService,
39
+ PlistServiceDecoder,
40
+ PlistServiceEncoder,
41
+ APPLE_EPOCH_OFFSET,
42
+ BPLIST_MAGIC_AND_VERSION,
43
+ BPLIST_TYPE,
44
+ UTF8_ENCODING,
45
+ // Utility functions
46
+ hasUnicodeReplacementCharacter,
47
+ findFirstReplacementCharacter,
48
+ ensureString,
49
+ trimBeforeXmlDeclaration,
50
+ fixMultipleXmlDeclarations,
51
+ isValidXml,
52
+ escapeXml,
53
+ isXmlPlistContent,
54
+ };