Skip to content

Commit fcd9e14

Browse files
feedthejimclaude
andcommitted
fix: use byte-level search for split flight boundary
The previous implementation used string.indexOf() which returns a character index, then used that index to slice a Uint8Array buffer. This caused data corruption when the buffer contained multi-byte UTF-8 characters (emoji, non-ASCII, etc.) because character indices don't match byte indices. Added findBoundaryIndex() helper that searches for the boundary bytes directly in the buffer, ensuring correct splitting regardless of UTF-8 encoding. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent fa0fd78 commit fcd9e14

File tree

1 file changed

+28
-4
lines changed

1 file changed

+28
-4
lines changed

packages/next/src/client/components/router-reducer/fetch-server-response.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,32 @@ function createUnclosingPrefetchStream(
504504
})
505505
}
506506

507+
/**
508+
* Finds the byte index of a byte sequence in a Uint8Array.
509+
* This is used instead of string.indexOf() because converting to string and searching
510+
* would use character indices instead of byte indices, which breaks with multi-byte UTF-8 chars.
511+
*
512+
* @param buffer - The buffer to search in
513+
* @param searchBytes - The byte sequence to find
514+
* @returns The byte index of the sequence, or -1 if not found
515+
*/
516+
function findBoundaryIndex(
517+
buffer: Uint8Array,
518+
searchBytes: Uint8Array
519+
): number {
520+
for (let i = 0; i <= buffer.length - searchBytes.length; i++) {
521+
let match = true
522+
for (let j = 0; j < searchBytes.length; j++) {
523+
if (buffer[i + j] !== searchBytes[j]) {
524+
match = false
525+
break
526+
}
527+
}
528+
if (match) return i
529+
}
530+
return -1
531+
}
532+
507533
/**
508534
* Splits a Flight stream that contains two concatenated streams separated by a boundary.
509535
* Used for split flight responses where action result and page data are sent together.
@@ -520,7 +546,6 @@ export async function splitFlightStreams(
520546
pageStream: ReadableStream<Uint8Array>
521547
}> {
522548
const reader = body.getReader()
523-
const decoder = new TextDecoder()
524549
const encoder = new TextEncoder()
525550
const boundaryBytes = encoder.encode(boundary)
526551

@@ -540,9 +565,8 @@ export async function splitFlightStreams(
540565
newBuffer.set(value, buffer.length)
541566
buffer = newBuffer
542567

543-
// Search for boundary in the accumulated buffer
544-
const text = decoder.decode(buffer)
545-
boundaryIndex = text.indexOf(boundary)
568+
// Search for boundary in the accumulated buffer (by searching raw bytes)
569+
boundaryIndex = findBoundaryIndex(buffer, boundaryBytes)
546570
}
547571

548572
// Split buffer at boundary

0 commit comments

Comments
 (0)