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 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
- resp.headers.set('Content-Language', placeholder.headers.get('Content-Language'));
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.isSafeIdentifier);
248
- namespaces = namespaces.filter(utils.isSafeIdentifier);
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 = 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 isSafeIdentifier(v) {
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('/') > -1 || v.indexOf('\\') > -1) return false;
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
- resp.headers.set('Content-Language', placeholder.headers.get('Content-Language'));
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.isSafeIdentifier);
231
- namespaces = namespaces.filter(utils.isSafeIdentifier);
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
- export function isSafeIdentifier(v) {
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('/') > -1 || v.indexOf('\\') > -1) return false;
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
- resp.headers.set('Content-Language', placeholder.headers.get('Content-Language'))
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
- languages = languages.filter(utils.isSafeIdentifier)
252
- namespaces = namespaces.filter(utils.isSafeIdentifier)
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
- // Returns true if `v` can be safely forwarded as a language code or namespace
4
- // identifier to a backend connector. Denylist approach (i18next permits any
5
- // language-code shape — https://www.i18next.com/how-to/faq#how-should-the-language-codes-be-formatted)
6
- // that blocks the concrete attack patterns without restricting legitimate use:
7
- // - not one of `__proto__` / `constructor` / `prototype` (prototype pollution)
8
- // - no path separators `/` or `\` (path traversal / SSRF in fs/http backends)
9
- // - no `..` sequence (relative path traversal)
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('/') > -1 || v.indexOf('\\') > -1) return false
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)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-http-middleware",
3
- "version": "3.9.3",
3
+ "version": "3.9.5",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./cjs/index.js",