Skip to content

Commit e64933a

Browse files
authored
fix: correct UTF-16BE string parsing and TempObject handling in binary plist parser (#65)
1 parent 2a9c865 commit e64933a

File tree

3 files changed

+56
-18
lines changed

3 files changed

+56
-18
lines changed

src/lib/plist/binary-plist-creator.ts

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,11 @@ class BinaryPlistCreator {
5959
const objectData: Buffer[] = [];
6060

6161
for (const value of this._objectTable) {
62-
objectOffsets.push(this._calculateObjectDataLength(objectData));
62+
// Calculate offset including the header length
63+
objectOffsets.push(
64+
BPLIST_MAGIC_AND_VERSION.length +
65+
this._calculateObjectDataLength(objectData),
66+
);
6367
objectData.push(this._createObjectData(value));
6468
}
6569

@@ -341,11 +345,22 @@ class BinaryPlistCreator {
341345
// Check if string can be ASCII
342346
// eslint-disable-next-line no-control-regex
343347
const isAscii = /^[\x00-\x7F]*$/.test(value);
344-
const stringBuffer = isAscii
345-
? Buffer.from(value, 'ascii')
346-
: Buffer.from(value, 'utf16le');
347348

348-
// Fixed the typo here - using stringBuffer.length instead of value.length for Unicode strings
349+
let stringBuffer: Buffer;
350+
if (isAscii) {
351+
stringBuffer = Buffer.from(value, 'ascii');
352+
} else {
353+
// Unicode strings should be stored as UTF-16BE in binary plists
354+
const utf16leBuffer = Buffer.from(value, 'utf16le');
355+
stringBuffer = Buffer.alloc(utf16leBuffer.length);
356+
357+
// Convert UTF-16LE to UTF-16BE
358+
for (let i = 0; i < utf16leBuffer.length; i += 2) {
359+
stringBuffer[i] = utf16leBuffer[i + 1]; // High byte
360+
stringBuffer[i + 1] = utf16leBuffer[i]; // Low byte
361+
}
362+
}
363+
349364
const length = isAscii ? value.length : stringBuffer.length / 2;
350365
let header: Buffer;
351366

src/lib/plist/binary-plist-parser.ts

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -270,15 +270,21 @@ class BinaryPlistParser {
270270
* @returns The parsed string
271271
*/
272272
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-
);
273+
// Unicode strings are stored as UTF-16BE in binary plists
274+
const bytesToRead = objLength * 2;
275+
const stringBuffer = this._buffer.slice(
276+
startOffset,
277+
startOffset + bytesToRead,
278+
);
279+
280+
// Convert UTF-16BE to UTF-16LE for proper decoding
281+
const utf16leBuffer = Buffer.alloc(bytesToRead);
282+
for (let i = 0; i < bytesToRead; i += 2) {
283+
utf16leBuffer[i] = stringBuffer[i + 1]; // Low byte
284+
utf16leBuffer[i + 1] = stringBuffer[i]; // High byte
280285
}
281-
return utf16Buffer.toString('utf16le', 0, objLength * 2);
286+
287+
return utf16leBuffer.toString('utf16le');
282288
}
283289

284290
/**
@@ -476,8 +482,10 @@ class BinaryPlistParser {
476482
obj.startOffset + j * this._objectRefSize,
477483
);
478484
const refValue = this._objectTable[refIdx];
479-
// Ensure we're not adding a TempObject to the array
480-
if (!this._isTempObject(refValue)) {
485+
// Handle TempObjects correctly - they should be resolved by the time we get here
486+
if (this._isTempObject(refValue)) {
487+
array.push(refValue.value);
488+
} else {
481489
array.push(refValue);
482490
}
483491
}
@@ -511,8 +519,10 @@ class BinaryPlistParser {
511519
);
512520
}
513521

514-
// Ensure we're not adding a TempObject to the dictionary
515-
if (!this._isTempObject(value)) {
522+
// Handle TempObjects correctly - they should be resolved by the time we get here
523+
if (this._isTempObject(value)) {
524+
dict[key] = value.value;
525+
} else {
516526
dict[key] = value;
517527
}
518528
}

test/unit/plist/plist.spec.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ describe('Plist Module', function () {
4747
nestedArray: [1, 2],
4848
},
4949
specialChars: '<Hello & World>',
50+
emoji: '😀',
51+
unicode: '测试',
5052
};
5153
});
5254

@@ -104,7 +106,6 @@ describe('Plist Module', function () {
104106
const binaryPlist = createBinaryPlist(expectedPlistObject);
105107
expect(isBinaryPlist(binaryPlist)).to.be.true;
106108
expect(isBinaryPlist(Buffer.from(sampleXmlPlistContent))).to.be.false;
107-
108109
// Create and verify
109110
expect(Buffer.isBuffer(binaryPlist)).to.be.true;
110111
expect(binaryPlist.slice(0, 6).toString()).to.equal('bplist');
@@ -201,5 +202,17 @@ describe('Plist Module', function () {
201202
const parsedEmptyXml = parseXmlPlist(emptyXmlResult);
202203
expect(parsedEmptyXml).to.deep.equal({});
203204
});
205+
206+
it('should validate that sample data contains emoji and unicode', function () {
207+
// Test round-trip with the sample data
208+
const binary = createBinaryPlist(expectedPlistObject);
209+
const obj = parseBinaryPlist(binary) as Record<string, any>;
210+
const xmlResult = createXmlPlist(expectedPlistObject);
211+
expect(obj).to.have.property('emoji', '😀');
212+
expect(obj).to.have.property('unicode', '测试');
213+
// Verify the XML contains the encoded characters
214+
expect(xmlResult).to.include('😀');
215+
expect(xmlResult).to.include('测试');
216+
});
204217
});
205218
});

0 commit comments

Comments
 (0)