Skip to content

Commit c7a7df4

Browse files
Apply review suggestions (#9256)
1 parent b8d6459 commit c7a7df4

5 files changed

Lines changed: 140 additions & 20 deletions

File tree

b1.sh

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
2+
pnpm install && npx nx build
3+
4+

b2.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
pnpm test:unit src/scripts/metadata/ogg.test.ts --coverage --reporter=verbose

b3.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
cp -Rf dist/* ../ComfyUI_windows_portable/python_embeded/Lib/site-packages/comfyui_frontend_package/static/

src/scripts/metadata/ogg.test.ts

Lines changed: 98 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,18 @@ describe('getOggMetadata', () => {
187187
return buffer.buffer
188188
}
189189
},
190+
{
191+
name: 'segment data exceeds buffer boundary',
192+
createBuffer: () => {
193+
// Page header declares 1 segment of length 200, but only 10 bytes of data follow
194+
const buffer = new Uint8Array(27 + 1 + 10)
195+
buffer.set(new TextEncoder().encode('OggS'), 0)
196+
buffer[26] = 1 // 1 segment
197+
buffer[27] = 200 // segment length = 200 (exceeds remaining 10 bytes)
198+
// Only 10 bytes of data follow; segment exceeds boundary
199+
return buffer.buffer
200+
}
201+
},
190202
{
191203
name: 'truncated user comment in OpusTags',
192204
createBuffer: () => {
@@ -207,7 +219,6 @@ describe('getOggMetadata', () => {
207219
name: 'file smaller than the Ogg magic number',
208220
createBuffer: () => new Uint8Array([1, 2]).buffer
209221
},
210-
211222
{
212223
name: 'page has an invalid magic number (stop parsing)',
213224
createBuffer: () => {
@@ -222,6 +233,73 @@ describe('getOggMetadata', () => {
222233
combined.set(page2, page1.length)
223234
return combined.buffer
224235
}
236+
},
237+
{
238+
name: 'OpusTags packet truncated before vendorLength field',
239+
createBuffer: () => {
240+
// Packet is only 10 bytes: 'OpusTags'(8) + 2 extra bytes (not enough for 4-byte vendorLength)
241+
const packet = new Uint8Array(10)
242+
packet.set(new TextEncoder().encode('OpusTags'), 0)
243+
const buffer = new Uint8Array(27 + 1 + packet.length)
244+
buffer.set(new TextEncoder().encode('OggS'), 0)
245+
buffer[26] = 1
246+
buffer[27] = packet.length
247+
buffer.set(packet, 28)
248+
return buffer.buffer
249+
}
250+
},
251+
{
252+
name: 'OpusTags packet with oversized vendorLength',
253+
createBuffer: () => {
254+
// Packet: 'OpusTags'(8) + vendorLength=9999(4) + no actual vendor bytes
255+
const packet = new Uint8Array(12)
256+
packet.set(new TextEncoder().encode('OpusTags'), 0)
257+
new DataView(packet.buffer).setUint32(8, 9999, true) // vendor string claims 9999 bytes
258+
const buffer = new Uint8Array(27 + 1 + packet.length)
259+
buffer.set(new TextEncoder().encode('OggS'), 0)
260+
buffer[26] = 1
261+
buffer[27] = packet.length
262+
buffer.set(packet, 28)
263+
return buffer.buffer
264+
}
265+
},
266+
{
267+
name: 'OpusTags packet truncated before userCommentListLength',
268+
createBuffer: () => {
269+
// Packet: 'OpusTags'(8) + vendorLength=0(4) + no userCommentListLength
270+
const packet = new Uint8Array(12) // exactly 8 + 4, no bytes for userCommentListLength
271+
packet.set(new TextEncoder().encode('OpusTags'), 0)
272+
new DataView(packet.buffer).setUint32(8, 0, true) // vendorLength=0
273+
// packet ends here — no room for userCommentListLength (needs 4 more bytes)
274+
const buffer = new Uint8Array(27 + 1 + packet.length)
275+
buffer.set(new TextEncoder().encode('OggS'), 0)
276+
buffer[26] = 1
277+
buffer[27] = packet.length
278+
buffer.set(packet, 28)
279+
return buffer.buffer
280+
}
281+
},
282+
{
283+
name: 'OpusTags packet truncated mid-comment-list (missing comment length field)',
284+
createBuffer: () => {
285+
const commentBytes = new TextEncoder().encode('key=value')
286+
// Packet: 'OpusTags'(8) + vendorLength=0(4) + userCommentListLength=2(4) +
287+
// commentLength=9(4) + commentData(9) — second comment length field is missing
288+
const packet = new Uint8Array(8 + 4 + 4 + 4 + commentBytes.length)
289+
const view = new DataView(packet.buffer)
290+
packet.set(new TextEncoder().encode('OpusTags'), 0)
291+
view.setUint32(8, 0, true) // vendorLength = 0
292+
view.setUint32(12, 2, true) // userCommentListLength = 2 (but only 1 follows)
293+
view.setUint32(16, commentBytes.length, true) // first comment length
294+
packet.set(commentBytes, 20) // first comment data
295+
// second comment's length field is absent — packet ends here
296+
const buffer = new Uint8Array(27 + 1 + packet.length)
297+
buffer.set(new TextEncoder().encode('OggS'), 0)
298+
buffer[26] = 1
299+
buffer[27] = packet.length
300+
buffer.set(packet, 28)
301+
return buffer.buffer
302+
}
225303
}
226304
]
227305

@@ -231,14 +309,12 @@ describe('getOggMetadata', () => {
231309
[createBuffer()],
232310
`test_${name.replace(/\s+/g, '_')}.ogg`
233311
)
234-
try {
235-
const result = await getOggMetadata(file)
236-
expect(result.prompt).toBeUndefined()
237-
expect(result.workflow).toBeUndefined()
238-
} catch (e) {
239-
// If it throws (e.g. RangeError from DataView out of bounds), it's successfully handled gracefully by the caller
240-
expect(e).toBeInstanceOf(Error)
241-
}
312+
await expect(
313+
getOggMetadata(file).then((result) => {
314+
expect(result.prompt).toBeUndefined()
315+
expect(result.workflow).toBeUndefined()
316+
})
317+
).resolves.not.toThrow()
242318
})
243319
}
244320
})
@@ -247,7 +323,7 @@ describe('getOggMetadata', () => {
247323
const scenarios: {
248324
name: string
249325
createBuffer: () => ArrayBuffer
250-
expectedPrompt?: any
326+
expectedPrompt?: Record<string, unknown>
251327
}[] = [
252328
{
253329
name: 'unrelated Vorbis comments',
@@ -258,6 +334,14 @@ describe('getOggMetadata', () => {
258334
}),
259335
expectedPrompt: { valid: true } // Workflow is undefined, Prompt is valid
260336
},
337+
{
338+
name: 'invalid JSON in prompt/workflow comments (plain text)',
339+
createBuffer: () =>
340+
createOggWithOpusTags({
341+
prompt: 'hello world',
342+
workflow: 'plain text workflow'
343+
})
344+
},
261345
{
262346
name: 'user comments without an equal sign',
263347
createBuffer: () => {
@@ -287,18 +371,15 @@ describe('getOggMetadata', () => {
287371
[createBuffer()],
288372
`test_${name.replace(/\s+/g, '_')}.ogg`
289373
)
290-
try {
291-
const result = await getOggMetadata(file)
374+
await expect(getOggMetadata(file)).resolves.toSatisfy((result) => {
292375
if (expectedPrompt) {
293-
expect(result.prompt).toEqual(expectedPrompt) // either undefined or { valid: true }
376+
expect(result.prompt).toEqual(expectedPrompt)
294377
} else {
295378
expect(result.prompt).toBeUndefined()
296379
}
297380
expect(result.workflow).toBeUndefined()
298-
} catch (e) {
299-
// Handled via thrown exception
300-
expect(e).toBeInstanceOf(Error)
301-
}
381+
return true
382+
})
302383
})
303384
}
304385
})

src/scripts/metadata/ogg.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ function extractOpusTags(data: Uint8Array, decoder: TextDecoder): Uint8Array[] {
5050

5151
for (let i = 0; i < pageSegmentsCount; i++) {
5252
const segmentLength = data[lengthsOffset + i]
53+
54+
// Bounds check: ensure the segment data lies within the available data
55+
if (dataOffset + segmentLength > data.length) break
56+
5357
const segment = data.subarray(dataOffset, dataOffset + segmentLength)
5458
dataOffset += segmentLength
5559

@@ -86,20 +90,39 @@ function parseVorbisComments(packetData: Uint8Array, decoder: TextDecoder) {
8690
packetData.byteLength
8791
)
8892

93+
// Bounds check: ensure vendor length field is within packet
94+
if (readIndex + 4 > packetData.length)
95+
return { prompt: undefined, workflow: undefined }
96+
8997
// Skip vendor string length (4 bytes) + vendor string
9098
const vendorLength = packetView.getUint32(readIndex, true)
91-
readIndex += 4 + vendorLength
99+
readIndex += 4
100+
101+
// Bounds check: ensure vendor string is within packet
102+
if (readIndex + vendorLength > packetData.length)
103+
return { prompt: undefined, workflow: undefined }
104+
readIndex += vendorLength
105+
106+
// Bounds check: ensure user comment list length field is within packet
107+
if (readIndex + 4 > packetData.length)
108+
return { prompt: undefined, workflow: undefined }
92109

93110
// Get the number of user comments
94111
const userCommentListLength = packetView.getUint32(readIndex, true)
95112
readIndex += 4
96113

97114
let prompt, workflow
98115
for (let i = 0; i < userCommentListLength; i++) {
116+
// Bounds check: ensure comment length field is within packet
117+
if (readIndex + 4 > packetData.length) break
118+
99119
// Vorbis Comments spec: Get comment length (32-bit little-endian)
100120
const commentLength = packetView.getUint32(readIndex, true)
101121
readIndex += 4
102122

123+
// Bounds check: ensure comment data is within packet
124+
if (readIndex + commentLength > packetData.length) break
125+
103126
// Extract and decode the comment string (UTF-8)
104127
const text = decoder.decode(
105128
packetData.subarray(readIndex, readIndex + commentLength)
@@ -111,9 +134,17 @@ function parseVorbisComments(packetData: Uint8Array, decoder: TextDecoder) {
111134
const key = text.substring(0, separatorIndex)
112135
const value = text.substring(separatorIndex + 1)
113136
if (key === 'prompt') {
114-
prompt = JSON.parse(value)
137+
try {
138+
prompt = JSON.parse(value)
139+
} catch (e) {
140+
console.warn('Ogg metadata parsing failed for prompt:', e)
141+
}
115142
} else if (key === 'workflow') {
116-
workflow = JSON.parse(value)
143+
try {
144+
workflow = JSON.parse(value)
145+
} catch (e) {
146+
console.warn('Ogg metadata parsing failed for workflow:', e)
147+
}
117148
}
118149
}
119150

0 commit comments

Comments
 (0)