Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/connection/UndiciConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -221,14 +221,14 @@ export default class Connection extends BaseConnection {
} else {
const payload: Buffer[] = []
let currentLength = 0
response.body.setEncoding('utf8')
for await (const chunk of response.body) {
currentLength += Buffer.byteLength(chunk)
const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
currentLength += buf.byteLength
if (currentLength > maxResponseSize) {
response.body.destroy()
throw new RequestAbortedError(`The content length (${currentLength}) is bigger than the maximum allowed string (${maxResponseSize})`)
}
payload.push(chunk)
payload.push(buf)
}
return {
statusCode: response.statusCode,
Expand Down
39 changes: 39 additions & 0 deletions test/unit/undici-connection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1346,3 +1346,42 @@ test('limit max open connections using Undici Agent', async t => {

after.forEach(fn => fn())
})

test('UTF-8 multi-byte characters not corrupted when split across chunk boundaries', async t => {
t.plan(2)

// CJK character '傳' (U+50B3) is 3 bytes: 0xe5 0x82 0xb3
// Build a response where this character is split at a chunk boundary
const prefix = 'a'.repeat(10) // 10 ASCII bytes
const cjkText = '傳送中文測試'
const text = `{"message":"${prefix}${cjkText}"}`
const buf = Buffer.from(text)

// Split in the middle of the first CJK character:
// '{"message":"' (14 bytes) + 'a' * 10 (10 bytes) = 24 bytes of ASCII
// Then first byte of '傳' (0xe5) at position 24, split after byte 25
const splitPoint = 25 // after first byte of '傳'

function handler (req: http.IncomingMessage, res: http.ServerResponse) {
res.writeHead(200, { 'content-type': 'application/json; charset=utf-8' })
res.write(buf.subarray(0, splitPoint))
globalThis.setTimeout(() => {
res.end(buf.subarray(splitPoint))
}, 50)
}

const [{ port }, server] = await buildServer(handler)
const connection = new UndiciConnection({
url: new URL(`http://localhost:${port}`)
})
const res = await connection.request({
path: '/hello',
method: 'GET'
}, options)

// Must not contain U+FFFD replacement characters
t.notOk((res.body as string).includes('\ufffd'), 'should not contain U+FFFD replacement characters')
t.equal(res.body, text, 'decoded text should match original')

server.stop()
})