fix: decode large safari cookie pages

This commit is contained in:
Neil
2026-05-30 05:04:27 -07:00
parent c66995c427
commit 998b9056b8
2 changed files with 68 additions and 2 deletions
@@ -0,0 +1,62 @@
import { describe, expect, it } from 'vitest'
import { decodeSafariBinaryCookies } from './browser-cookie-import'
function buildSafariCookieRecord(index: number): Buffer {
const domain = '.example.com'
const name = `cookie-${index}`
const path = '/'
const value = 'value'
const strings = [domain, name, path, value]
const stringBytes = strings.map((entry) => Buffer.byteLength(entry) + 1)
const size = 48 + stringBytes.reduce((sum, length) => sum + length, 0)
const record = Buffer.alloc(size)
record.writeUInt32LE(size, 0)
record.writeUInt32LE(0, 8)
let cursor = 48
const offsets = strings.map((entry, stringIndex) => {
const offset = cursor
record.write(entry, offset, 'utf8')
cursor += stringBytes[stringIndex]
return offset
})
record.writeUInt32LE(offsets[0], 16)
record.writeUInt32LE(offsets[1], 20)
record.writeUInt32LE(offsets[2], 24)
record.writeUInt32LE(offsets[3], 28)
record.writeDoubleLE(0, 40)
return record
}
function buildSafariBinaryCookies(count: number): Buffer {
const records = Array.from({ length: count }, (_, index) => buildSafariCookieRecord(index))
const totalRecordBytes = records.reduce((sum, record) => sum + record.length, 0)
const pageHeaderBytes = 8 + count * 4
const page = Buffer.alloc(pageHeaderBytes + totalRecordBytes)
page.writeUInt32BE(0x00000100, 0)
page.writeUInt32LE(count, 4)
let recordOffset = pageHeaderBytes
for (let index = 0; index < records.length; index += 1) {
const record = records[index]
page.writeUInt32LE(recordOffset, 8 + index * 4)
record.copy(page, recordOffset)
recordOffset += record.length
}
const file = Buffer.alloc(12 + page.length)
file.write('cook', 0, 'utf8')
file.writeUInt32BE(1, 4)
file.writeUInt32BE(page.length, 8)
page.copy(file, 12)
return file
}
describe('decodeSafariBinaryCookies', () => {
it('decodes Safari binary cookie pages with very large cookie counts', () => {
const cookies = decodeSafariBinaryCookies(buildSafariBinaryCookies(125_000))
expect(cookies).toHaveLength(125_000)
expect(cookies[0]?.name).toBe('cookie-0')
expect(cookies.at(-1)?.name).toBe('cookie-124999')
})
})
+6 -2
View File
@@ -1096,7 +1096,7 @@ function decryptAes256Gcm(payload: Buffer, key: Buffer): Buffer | null {
// Safari binary cookie parser
// ---------------------------------------------------------------------------
function decodeSafariBinaryCookies(buffer: Buffer): ValidatedCookie[] {
export function decodeSafariBinaryCookies(buffer: Buffer): ValidatedCookie[] {
if (buffer.length < 8) {
return []
}
@@ -1119,7 +1119,11 @@ function decodeSafariBinaryCookies(buffer: Buffer): ValidatedCookie[] {
for (const pageSize of pageSizes) {
const page = buffer.subarray(cursor, cursor + pageSize)
cursor += pageSize
cookies.push(...decodeSafariPage(page))
// Why: a single Safari cookie page can contain enough entries to exceed
// V8's argument limit if appended via spread.
for (const cookie of decodeSafariPage(page)) {
cookies.push(cookie)
}
}
return cookies
}