i18next-http-middleware 3.9.3 → 3.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cjs/index.js +11 -4
- package/cjs/utils.js +14 -3
- package/esm/index.js +11 -4
- package/esm/utils.js +11 -2
- package/lib/index.js +17 -3
- package/lib/utils.js +34 -11
- package/package.json +1 -1
package/cjs/index.js
CHANGED
|
@@ -173,7 +173,7 @@ function koaPlugin(i18next, options) {
|
|
|
173
173
|
function freshPlugin(i18next, options) {
|
|
174
174
|
return function () {
|
|
175
175
|
var _ref = _asyncToGenerator(_regenerator().m(function _callee2(ctx) {
|
|
176
|
-
var placeholder, resp;
|
|
176
|
+
var placeholder, resp, contentLanguage, setCookie;
|
|
177
177
|
return _regenerator().w(function (_context2) {
|
|
178
178
|
while (1) switch (_context2.n) {
|
|
179
179
|
case 0:
|
|
@@ -192,7 +192,14 @@ function freshPlugin(i18next, options) {
|
|
|
192
192
|
return ctx.next();
|
|
193
193
|
case 1:
|
|
194
194
|
resp = _context2.v;
|
|
195
|
-
|
|
195
|
+
contentLanguage = placeholder.headers.get('Content-Language');
|
|
196
|
+
if (contentLanguage) {
|
|
197
|
+
resp.headers.set('Content-Language', contentLanguage);
|
|
198
|
+
}
|
|
199
|
+
setCookie = placeholder.headers.get('Set-Cookie');
|
|
200
|
+
if (setCookie) {
|
|
201
|
+
resp.headers.set('Set-Cookie', setCookie);
|
|
202
|
+
}
|
|
196
203
|
return _context2.a(2, resp);
|
|
197
204
|
}
|
|
198
205
|
}, _callee2);
|
|
@@ -244,8 +251,8 @@ function getResourcesHandler(i18next) {
|
|
|
244
251
|
languages = options.getParams(req)[options.lngParam || 'lng'] ? options.getParams(req)[options.lngParam || 'lng'].split(' ') : [];
|
|
245
252
|
namespaces = options.getParams(req)[options.nsParam || 'ns'] ? options.getParams(req)[options.nsParam || 'ns'].split(' ') : [];
|
|
246
253
|
}
|
|
247
|
-
languages = languages.filter(utils.
|
|
248
|
-
namespaces = namespaces.filter(utils.
|
|
254
|
+
languages = languages.filter(utils.isSafeLangIdentifier);
|
|
255
|
+
namespaces = namespaces.filter(utils.isSafeNsIdentifier);
|
|
249
256
|
namespaces.forEach(function (ns) {
|
|
250
257
|
if (i18next.options.ns && i18next.options.ns.indexOf(ns) < 0) {
|
|
251
258
|
i18next.options.ns.push(ns);
|
package/cjs/utils.js
CHANGED
|
@@ -8,20 +8,31 @@ exports.defaults = defaults;
|
|
|
8
8
|
exports.escape = escape;
|
|
9
9
|
exports.extend = extend;
|
|
10
10
|
exports.hasXSS = hasXSS;
|
|
11
|
-
exports.isSafeIdentifier =
|
|
11
|
+
exports.isSafeIdentifier = void 0;
|
|
12
|
+
exports.isSafeLangIdentifier = isSafeLangIdentifier;
|
|
13
|
+
exports.isSafeNsIdentifier = isSafeNsIdentifier;
|
|
12
14
|
exports.removeLngFromUrl = removeLngFromUrl;
|
|
13
15
|
exports.sanitizeHeaderValue = sanitizeHeaderValue;
|
|
14
16
|
exports.setPath = setPath;
|
|
15
17
|
var UNSAFE_KEYS = exports.UNSAFE_KEYS = ['__proto__', 'constructor', 'prototype'];
|
|
16
|
-
function
|
|
18
|
+
function isSafeIdentifierBase(v) {
|
|
17
19
|
if (typeof v !== 'string') return false;
|
|
18
20
|
if (v.length === 0 || v.length > 128) return false;
|
|
19
21
|
if (UNSAFE_KEYS.indexOf(v) > -1) return false;
|
|
20
22
|
if (v.indexOf('..') > -1) return false;
|
|
21
|
-
if (v.indexOf('
|
|
23
|
+
if (v.indexOf('\\') > -1) return false;
|
|
22
24
|
if (/[\x00-\x1F\x7F]/.test(v)) return false;
|
|
23
25
|
return true;
|
|
24
26
|
}
|
|
27
|
+
function isSafeLangIdentifier(v) {
|
|
28
|
+
if (!isSafeIdentifierBase(v)) return false;
|
|
29
|
+
if (v.indexOf('/') > -1) return false;
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
function isSafeNsIdentifier(v) {
|
|
33
|
+
return isSafeIdentifierBase(v);
|
|
34
|
+
}
|
|
35
|
+
var isSafeIdentifier = exports.isSafeIdentifier = isSafeLangIdentifier;
|
|
25
36
|
function setPath(object, path, newValue) {
|
|
26
37
|
var stack;
|
|
27
38
|
if (typeof path !== 'string') stack = [].concat(path);
|
package/esm/index.js
CHANGED
|
@@ -156,7 +156,7 @@ export function koaPlugin(i18next, options) {
|
|
|
156
156
|
export function freshPlugin(i18next, options) {
|
|
157
157
|
return function () {
|
|
158
158
|
var _ref = _asyncToGenerator(_regenerator().m(function _callee2(ctx) {
|
|
159
|
-
var placeholder, resp;
|
|
159
|
+
var placeholder, resp, contentLanguage, setCookie;
|
|
160
160
|
return _regenerator().w(function (_context2) {
|
|
161
161
|
while (1) switch (_context2.n) {
|
|
162
162
|
case 0:
|
|
@@ -175,7 +175,14 @@ export function freshPlugin(i18next, options) {
|
|
|
175
175
|
return ctx.next();
|
|
176
176
|
case 1:
|
|
177
177
|
resp = _context2.v;
|
|
178
|
-
|
|
178
|
+
contentLanguage = placeholder.headers.get('Content-Language');
|
|
179
|
+
if (contentLanguage) {
|
|
180
|
+
resp.headers.set('Content-Language', contentLanguage);
|
|
181
|
+
}
|
|
182
|
+
setCookie = placeholder.headers.get('Set-Cookie');
|
|
183
|
+
if (setCookie) {
|
|
184
|
+
resp.headers.set('Set-Cookie', setCookie);
|
|
185
|
+
}
|
|
179
186
|
return _context2.a(2, resp);
|
|
180
187
|
}
|
|
181
188
|
}, _callee2);
|
|
@@ -227,8 +234,8 @@ export function getResourcesHandler(i18next) {
|
|
|
227
234
|
languages = options.getParams(req)[options.lngParam || 'lng'] ? options.getParams(req)[options.lngParam || 'lng'].split(' ') : [];
|
|
228
235
|
namespaces = options.getParams(req)[options.nsParam || 'ns'] ? options.getParams(req)[options.nsParam || 'ns'].split(' ') : [];
|
|
229
236
|
}
|
|
230
|
-
languages = languages.filter(utils.
|
|
231
|
-
namespaces = namespaces.filter(utils.
|
|
237
|
+
languages = languages.filter(utils.isSafeLangIdentifier);
|
|
238
|
+
namespaces = namespaces.filter(utils.isSafeNsIdentifier);
|
|
232
239
|
namespaces.forEach(function (ns) {
|
|
233
240
|
if (i18next.options.ns && i18next.options.ns.indexOf(ns) < 0) {
|
|
234
241
|
i18next.options.ns.push(ns);
|
package/esm/utils.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
export var UNSAFE_KEYS = ['__proto__', 'constructor', 'prototype'];
|
|
2
|
-
|
|
2
|
+
function isSafeIdentifierBase(v) {
|
|
3
3
|
if (typeof v !== 'string') return false;
|
|
4
4
|
if (v.length === 0 || v.length > 128) return false;
|
|
5
5
|
if (UNSAFE_KEYS.indexOf(v) > -1) return false;
|
|
6
6
|
if (v.indexOf('..') > -1) return false;
|
|
7
|
-
if (v.indexOf('
|
|
7
|
+
if (v.indexOf('\\') > -1) return false;
|
|
8
8
|
if (/[\x00-\x1F\x7F]/.test(v)) return false;
|
|
9
9
|
return true;
|
|
10
10
|
}
|
|
11
|
+
export function isSafeLangIdentifier(v) {
|
|
12
|
+
if (!isSafeIdentifierBase(v)) return false;
|
|
13
|
+
if (v.indexOf('/') > -1) return false;
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
export function isSafeNsIdentifier(v) {
|
|
17
|
+
return isSafeIdentifierBase(v);
|
|
18
|
+
}
|
|
19
|
+
export var isSafeIdentifier = isSafeLangIdentifier;
|
|
11
20
|
export function setPath(object, path, newValue) {
|
|
12
21
|
var stack;
|
|
13
22
|
if (typeof path !== 'string') stack = [].concat(path);
|
package/lib/index.js
CHANGED
|
@@ -162,7 +162,17 @@ export function freshPlugin (i18next, options) {
|
|
|
162
162
|
const placeholder = ctx.state.res
|
|
163
163
|
delete ctx.state.res
|
|
164
164
|
const resp = await ctx.next()
|
|
165
|
-
|
|
165
|
+
|
|
166
|
+
const contentLanguage = placeholder.headers.get('Content-Language')
|
|
167
|
+
if (contentLanguage) {
|
|
168
|
+
resp.headers.set('Content-Language', contentLanguage)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const setCookie = placeholder.headers.get('Set-Cookie')
|
|
172
|
+
if (setCookie) {
|
|
173
|
+
resp.headers.set('Set-Cookie', setCookie)
|
|
174
|
+
}
|
|
175
|
+
|
|
166
176
|
return resp
|
|
167
177
|
}
|
|
168
178
|
}
|
|
@@ -248,8 +258,12 @@ export function getResourcesHandler (i18next, options = {}) {
|
|
|
248
258
|
// connector. i18next itself permits arbitrary language codes, so we do
|
|
249
259
|
// not impose a BCP-47 shape — we only block known-dangerous patterns.
|
|
250
260
|
// See: https://www.i18next.com/how-to/faq#how-should-the-language-codes-be-formatted
|
|
251
|
-
|
|
252
|
-
|
|
261
|
+
// lng goes through the strict check (rejects `/`); ns goes through
|
|
262
|
+
// the loose check (allows `/` so nested-namespace layouts like `a/b`
|
|
263
|
+
// that map to `public/locales/en/a/b.json` keep working). Directory
|
|
264
|
+
// escape via `..` / `\` / control chars is still blocked on both.
|
|
265
|
+
languages = languages.filter(utils.isSafeLangIdentifier)
|
|
266
|
+
namespaces = namespaces.filter(utils.isSafeNsIdentifier)
|
|
253
267
|
|
|
254
268
|
// extend ns
|
|
255
269
|
namespaces.forEach(ns => {
|
package/lib/utils.js
CHANGED
|
@@ -1,25 +1,48 @@
|
|
|
1
1
|
export const UNSAFE_KEYS = ['__proto__', 'constructor', 'prototype']
|
|
2
2
|
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
// - no control characters (header injection downstream, log forging)
|
|
11
|
-
// - non-empty, length <= 128
|
|
12
|
-
export function isSafeIdentifier (v) {
|
|
3
|
+
// Shared denylist: the patterns that are always unsafe to forward to a
|
|
4
|
+
// backend connector, regardless of whether the value is a language code
|
|
5
|
+
// or a namespace name. Blocks directory escape (`..`), Windows path
|
|
6
|
+
// separators (`\`), control characters, prototype keys, empty strings,
|
|
7
|
+
// and oversized inputs. The one dimension that differs per-key is whether
|
|
8
|
+
// `/` is allowed — see the two per-key helpers below.
|
|
9
|
+
function isSafeIdentifierBase (v) {
|
|
13
10
|
if (typeof v !== 'string') return false
|
|
14
11
|
if (v.length === 0 || v.length > 128) return false
|
|
15
12
|
if (UNSAFE_KEYS.indexOf(v) > -1) return false
|
|
16
13
|
if (v.indexOf('..') > -1) return false
|
|
17
|
-
if (v.indexOf('
|
|
14
|
+
if (v.indexOf('\\') > -1) return false
|
|
18
15
|
// eslint-disable-next-line no-control-regex
|
|
19
16
|
if (/[\x00-\x1F\x7F]/.test(v)) return false
|
|
20
17
|
return true
|
|
21
18
|
}
|
|
22
19
|
|
|
20
|
+
// Strict — also rejects `/`. Applied to `lng`:
|
|
21
|
+
// no legitimate BCP-47 / i18next language-code shape
|
|
22
|
+
// (https://www.i18next.com/how-to/faq#how-should-the-language-codes-be-formatted)
|
|
23
|
+
// contains `/`, so allowing it would only enable attacks.
|
|
24
|
+
export function isSafeLangIdentifier (v) {
|
|
25
|
+
if (!isSafeIdentifierBase(v)) return false
|
|
26
|
+
if (v.indexOf('/') > -1) return false
|
|
27
|
+
return true
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Loose — allows `/`. Applied to `ns`:
|
|
31
|
+
// nested namespace names like `a/b` that map to subfolder locale layouts
|
|
32
|
+
// (`public/locales/en/a/b.json` on fs-backend, `/locales/en/a/b.json`
|
|
33
|
+
// on http-backend) are a documented i18next pattern and were rejected
|
|
34
|
+
// in 3.9.3 as an unintended regression. Directory escape is still
|
|
35
|
+
// prevented — `..` is still blocked, `\` is still blocked — so the
|
|
36
|
+
// security fix remains in force for every concrete attack pattern
|
|
37
|
+
// from the original advisory.
|
|
38
|
+
export function isSafeNsIdentifier (v) {
|
|
39
|
+
return isSafeIdentifierBase(v)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Backwards-compatible alias for the strict check. Kept because 3.9.3
|
|
43
|
+
// exported this symbol; external callers (if any) get the pre-fix behaviour.
|
|
44
|
+
export const isSafeIdentifier = isSafeLangIdentifier
|
|
45
|
+
|
|
23
46
|
export function setPath (object, path, newValue) {
|
|
24
47
|
let stack
|
|
25
48
|
if (typeof path !== 'string') stack = [].concat(path)
|