fix: (regression 3.0.3) ERR_UNESCAPED_CHARACTERS #1175
Massimo Melina committed
Feb 28, 2026 at 18:54 UTC
e85820b0cbcc967483a0c6f91308deedaa12f124
2 files changed
+8
-2
src/util-http.ts
+2
-1
@@ -144,9 +144,10 @@ export function parseHttpUrl(url: string) {
144
const options = urlToHttpOptions(parsed)
145
const withoutHash = url.split('#', 1)[0]!
146
const authority = /^[a-z][a-z\d+.-]*:\/\/[^/?#]*/i.exec(withoutHash)?.[0]
147
+ const unresolvedPath = !authority ? '/' : enforceStarting('/', withoutHash.slice(authority.length))
148
return {
149
...options,
150
host: parsed.host,
150
- path: !authority ? '/' : enforceStarting('/', withoutHash.slice(authority.length)), // unresolved paths are useful in our tests
151
+ path: unresolvedPath.replace(/[\u0000-\u0020\u0100-\u{10FFFF}]/gu, encodeURIComponent), // keep unresolved paths for tests, but escape characters that Node request rejects as unescaped
152
}
153
}
tests/test.ts
+6
-1
@@ -7,7 +7,7 @@ import { exec } from 'child_process'
7
import _ from 'lodash'
8
import yaml from 'yaml'
9
import { findDefined, pathEncode, randomId, try_, tryJson, UPLOAD_TEMP_HASH, wait } from '../src/cross'
10
-import { httpStream, stream2string, XRequestOptions } from '../src/util-http'
10
+import { httpStream, parseHttpUrl, stream2string, XRequestOptions } from '../src/util-http'
11
import { ThrottledStream, ThrottleGroup } from '../src/ThrottledStream'
12
import { mkdir, rm, rename, writeFile, access } from 'fs/promises'
13
import { Readable } from 'stream'
@@ -44,6 +44,11 @@ let defaultBaseUrl = BASE_URL
44
const execP = (cmd: string) => promisify(exec)(cmd).then(x => x.stdout)
45
46
describe('basics', () => {
47
+ test('parseHttpUrl.path escapes invalid chars and keeps unresolved segments', () => {
48
+ const parsedPath = parseHttpUrl('https://example.com/a/../репо with space/%2e%2e/file').path
49
+ if (parsedPath !== '/a/../%D1%80%D0%B5%D0%BF%D0%BE%20with%20space/%2e%2e/file')
50
+ throw Error('unexpected path: ' + parsedPath)
51
+ })
52
//before(async () => appStarted)
53
test('frontend', req('/', /<body>/, { headers: { accept: '*/*' } })) // workaround: 'accept' is necessary when running server-for-test-dev, still don't know why
54
test('force slash', req('/f1', 302, { noRedirect: true }))