Skip to content

Commit 78c3d6d

Browse files
committed
fix: Content-Length with UTF-8 2-byte characters
1 parent 23a0d4f commit 78c3d6d

File tree

3 files changed

+96
-12
lines changed

3 files changed

+96
-12
lines changed

.vscode/launch.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"configurations": [
3+
{
4+
// based on https://tsx.is/vscode
5+
"name": "Debug file with tsx",
6+
"type": "node",
7+
"request": "launch",
8+
"program": "${file}",
9+
"runtimeExecutable": "tsx",
10+
"console": "integratedTerminal",
11+
"internalConsoleOptions": "neverOpen",
12+
"skipFiles": [
13+
"<node_internals>/**",
14+
"${workspaceFolder}/node_modules/**",
15+
],
16+
}
17+
]
18+
}

src/handler.ts

Lines changed: 48 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -263,7 +263,10 @@ export const mapResponse = (
263263
set.headers['content-type'] = 'text/plain;charset=utf8'
264264

265265
if (res) {
266-
set.headers['content-length'] = (response as string).length
266+
set.headers['content-length'] = Buffer.byteLength(
267+
response as string,
268+
'utf8'
269+
)
267270
res.writeHead(set.status!, set.headers)
268271
res.end(response)
269272
}
@@ -275,7 +278,10 @@ export const mapResponse = (
275278
response = JSON.stringify(response)
276279

277280
set.headers['content-type'] = 'application/json;charset=utf8'
278-
set.headers['content-length'] = (response as string).length
281+
set.headers['content-length'] = Buffer.byteLength(
282+
response as string,
283+
'utf8'
284+
)
279285

280286
if (res) {
281287
res.writeHead(set.status!, set.headers)
@@ -349,7 +355,10 @@ export const mapResponse = (
349355
response = JSON.stringify(response)
350356

351357
set.headers['content-type'] = 'application/json;charset=utf8'
352-
set.headers['content-length'] = (response as string)?.length
358+
set.headers['content-length'] = Buffer.byteLength(
359+
response as string,
360+
'utf8'
361+
)
353362

354363
if (res) {
355364
res.writeHead(set.status!, set.headers)
@@ -392,7 +401,10 @@ export const mapResponse = (
392401
response = (response as number | boolean).toString()
393402

394403
set.headers['content-type'] = 'text/plain;charset=utf8'
395-
set.headers['content-length'] = (response as string).length
404+
set.headers['content-length'] = Buffer.byteLength(
405+
response as string,
406+
'utf8'
407+
)
396408

397409
if (res) {
398410
res.writeHead(set.status!, set.headers)
@@ -474,7 +486,10 @@ export const mapResponse = (
474486
'application/json;charset=utf8'
475487

476488
response = JSON.stringify(response)
477-
set.headers['content-length'] = (response as string).length
489+
set.headers['content-length'] = Buffer.byteLength(
490+
response as string,
491+
'utf8'
492+
)
478493

479494
if (res) {
480495
res.writeHead(set.status!, set.headers)
@@ -486,7 +501,10 @@ export const mapResponse = (
486501
}
487502

488503
set.headers['content-type'] = 'text/plain;charset=utf8'
489-
set.headers['content-length'] = (response as string).length
504+
set.headers['content-length'] = Buffer.byteLength(
505+
response as string,
506+
'utf8'
507+
)
490508

491509
if (res) {
492510
res.writeHead(set.status!, set.headers)
@@ -517,7 +535,10 @@ export const mapEarlyResponse = (
517535
switch (response?.constructor?.name) {
518536
case 'String':
519537
set.headers['content-type'] = 'text/plain;charset=utf8'
520-
set.headers['content-length'] = (response as string).length
538+
set.headers['content-length'] = Buffer.byteLength(
539+
response as string,
540+
'utf8'
541+
)
521542

522543
if (res) {
523544
res.writeHead(set.status!, set.headers)
@@ -531,7 +552,10 @@ export const mapEarlyResponse = (
531552
response = JSON.stringify(response)
532553

533554
set.headers['content-type'] = 'application/json;charset=utf8'
534-
set.headers['content-length'] = (response as string).length
555+
set.headers['content-length'] = Buffer.byteLength(
556+
response as string,
557+
'utf8'
558+
)
535559

536560
if (res) {
537561
res.writeHead(set.status!, set.headers)
@@ -599,7 +623,10 @@ export const mapEarlyResponse = (
599623
response = JSON.stringify(response)
600624

601625
set.headers['content-type'] = 'application/json;charset=utf8'
602-
set.headers['content-length'] = (response as string).length
626+
set.headers['content-length'] = Buffer.byteLength(
627+
response as string,
628+
'utf8'
629+
)
603630

604631
return [response, set as any]
605632

@@ -637,7 +664,10 @@ export const mapEarlyResponse = (
637664
response = (response as number | boolean).toString()
638665

639666
set.headers['content-type'] = 'text/plain;charset=utf8'
640-
set.headers['content-length'] = (response as string).length
667+
set.headers['content-length'] = Buffer.byteLength(
668+
response as string,
669+
'utf8'
670+
)
641671

642672
if (res) {
643673
res.writeHead(set.status!, set.headers)
@@ -716,7 +746,10 @@ export const mapEarlyResponse = (
716746
if (!set.headers['Content-Type'])
717747
set.headers['content-type'] =
718748
'application/json;charset=utf8'
719-
set.headers['content-length'] = (response as string).length
749+
set.headers['content-length'] = Buffer.byteLength(
750+
response as string,
751+
'utf8'
752+
)
720753

721754
if (res) {
722755
res.writeHead(set.status!, set.headers)
@@ -728,7 +761,10 @@ export const mapEarlyResponse = (
728761
}
729762

730763
set.headers['content-type'] = 'text/plain;charset=utf8'
731-
set.headers['content-length'] = (response as string).length
764+
set.headers['content-length'] = Buffer.byteLength(
765+
response as string,
766+
'utf8'
767+
)
732768

733769
if (res) {
734770
res.writeHead(set.status!, set.headers)

test/core/charset.test.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import Elysia from 'elysia'
2+
import { inject } from 'light-my-request'
3+
import { describe, it, expect } from 'vitest'
4+
5+
import node from '../../src'
6+
7+
const app = new Elysia({ adapter: node() })
8+
.get('/', () => ({ utf: 'ú' }))
9+
.compile()
10+
11+
// @ts-expect-error
12+
const handle = app._handle!
13+
14+
describe('Node - Charset', () => {
15+
it('handle UTF-8 2-byte characters', () => {
16+
inject(handle, { path: '/' }, (error, res) => {
17+
expect(error).toBeNull()
18+
19+
const expected = JSON.stringify({ utf: 'ú' })
20+
21+
expect(res?.body).toBe(expected)
22+
expect(res?.headers['content-type']).toBe(
23+
'application/json;charset=utf8'
24+
)
25+
expect(res?.headers['content-length']).toBe(
26+
new TextEncoder().encode(expected).byteLength.toString()
27+
)
28+
})
29+
})
30+
})

0 commit comments

Comments
 (0)