fix: api.misc.http* functions mixed cookies of different hosts
Massimo Melina committed
Feb 12, 2026 at 12:05 UTC
182b8aefc9624c11b5f9123af3edbc442ebb7e3d
2 files changed
+25
-14
src/util-http.ts
+9
-7
@@ -25,7 +25,7 @@ export interface XRequestOptions extends https.RequestOptions {
25
body?: string | Buffer | Readable
26
proxy?: string // url format
27
// very basic cookie store
28
- jar?: Record<string, string>
28
+ jar?: { [host: string]: { [cookieName: string]: string } }
29
noRedirect?: boolean
30
// throw for http-level errors. Default is true.
31
httpThrow?: boolean
@@ -47,10 +47,12 @@ export function httpStream(url: string, { body, proxy, jar, noRedirect, httpThro
47
if (!(body instanceof Readable))
48
options.headers['content-length'] ??= Buffer.byteLength(body)
49
}
50
- if (jar)
51
- options.headers.cookie = _.map(jar, (v,k) => `${k}=${v}; `).join('')
52
- + (options.headers.cookie || '') // preserve parameter
50
const { auth, ...parsed } = parse(url)
51
+ const hostJar = jar && (jar[parsed.hostname || ''] ||= {})
52
+ if (hostJar) {
53
+ options.headers.cookie = _.map(hostJar, (v,k) => `${k}=${v}; `).join('')
54
+ + (options.headers.cookie || '') // preserve parameter
55
+ }
56
const proxyParsed = proxy ? parse(proxy) : null
57
Object.assign(options, _.pick(proxyParsed || parsed, ['hostname', 'port', 'path', 'protocol']))
58
if (auth) {
@@ -72,11 +74,11 @@ export function httpStream(url: string, { body, proxy, jar, noRedirect, httpThro
74
const proto = options.protocol === 'https:' ? https : http
75
const req = proto.request(options, res => {
76
console.debug("http responded", res.statusCode, "to", url)
75
- if (jar) for (const entry of res.headers['set-cookie'] || []) {
77
+ if (hostJar) for (const entry of res.headers['set-cookie'] || []) {
78
const [, k, v] = /(.+?)=([^;]+)/.exec(entry) || []
79
if (!k) continue
78
- if (v) jar[k] = v
79
- else delete jar[k]
80
+ if (v) hostJar[k] = v
81
+ else delete hostJar[k]
82
}
83
if (!res.statusCode || httpThrow && res.statusCode >= 400)
84
return reject(new Error(String(res.statusCode), { cause: res }))
tests/test.ts
+16
-7
@@ -210,13 +210,6 @@ describe('basics', () => {
210
test('delete.no perm', req('/for-admins/', 405, { method: 'delete' }))
211
test('delete.need account', req(UPLOAD_ROOT + 'alfa.txt', 401, { method: 'delete'}))
212
test('rename.no perm', reqApi('rename', { uri: '/for-admins', dest: 'any' }, 403))
213
- test('of_disabled.cantLogin', () => login('of_disabled').then(() => { throw "in" }, () => {}))
214
- test('allow_net.canLogin', () => login(username))
215
- test('allow_net.cantLogin', () => {
216
- defaultBaseUrl = BASE_URL_127 // 127.0.0.1 is not allowed for this account
217
- return login(username).then(() => { throw "in" }, () => {})
218
- .finally(() => defaultBaseUrl = BASE_URL)
219
- })
213
214
test('create_folder.bad encoding', reqApi('comment', { uri: '%a' }, 400))
215
test('comment.bad encoding', reqApi('comment', { uri: '%a', comment: 'anything' }, 400))
@@ -242,6 +235,22 @@ describe('limits', () => {
235
after(() => rm(fn))
236
})
237
238
+describe('sessions', () => {
239
+ test('of_disabled.cantLogin', () => login('of_disabled').then(() => { throw "in" }, () => {}))
240
+ test('allow_net.canLogin', () => login(username))
241
+ test('allow_net.cantLogin', () => {
242
+ defaultBaseUrl = BASE_URL_127 // 127.0.0.1 is not allowed for this account
243
+ return login(username).then(() => { throw "in" }, () => {})
244
+ .finally(() => defaultBaseUrl = BASE_URL)
245
+ })
246
+ test('httpStream.jar isolates host cookies', async () => {
247
+ const jar = {}
248
+ await reqApi('loginSrp1', { username }, res => Boolean(res?.salt && res?.pubKey), { jar })()
249
+ await reqApi('loginSrp2', { pubKey: '1', proof: '1' }, 409, { baseUrl: BASE_URL_127, jar })()
250
+ await reqApi('loginSrp2', { pubKey: '1', proof: '1' }, 401, { jar })()
251
+ })
252
+})
253
+
254
describe('accounts', () => {
255
before(() => login(username))
256
test('get_accounts', reqApi('get_accounts', {}, ({ list }) => _.find(list, { username }) && _.find(list, { username: 'admins' })))