i18next-http-middleware 3.9.11 → 3.10.1

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
@@ -132,7 +132,7 @@ function handle(i18next) {
132
132
  i18next.services.languageDetector.cacheUserLanguage(req, res, lng);
133
133
  }
134
134
  if (!req.lng) return next();
135
- i18next.loadLanguages(req.lng, function () {
135
+ i18n.loadLanguages(req.lng, function () {
136
136
  return next();
137
137
  });
138
138
  };
@@ -253,17 +253,16 @@ function getResourcesHandler(i18next) {
253
253
  }
254
254
  languages = languages.filter(utils.isSafeLangIdentifier);
255
255
  namespaces = namespaces.filter(utils.isSafeNsIdentifier);
256
- namespaces.forEach(function (ns) {
257
- if (i18next.options.ns && i18next.options.ns.indexOf(ns) < 0) {
258
- i18next.options.ns.push(ns);
259
- }
260
- });
261
256
  var loaded = false;
262
257
  var onLoaded;
263
258
  i18next.services.backendConnector.load(languages, namespaces, function () {
264
259
  languages.forEach(function (lng) {
265
260
  namespaces.forEach(function (ns) {
266
- utils.setPath(resources, [lng, ns], i18next.getResourceBundle(lng, ns));
261
+ var bundle = i18next.getResourceBundle(lng, ns);
262
+ if (bundle && i18next.options.ns && i18next.options.ns.indexOf(ns) < 0) {
263
+ i18next.options.ns.push(ns);
264
+ }
265
+ utils.setPath(resources, [lng, ns], bundle);
267
266
  });
268
267
  });
269
268
  loaded = true;
@@ -300,12 +299,14 @@ function missingKeyHandler(i18next) {
300
299
  }
301
300
  var body = options.getBody(req);
302
301
  var keySeparator = i18next.options && i18next.options.keySeparator;
302
+ var utilsOfRequest = req.i18n && req.i18n.services && req.i18n.services.utils;
303
+ var hasLoadedNamespace = utilsOfRequest && utilsOfRequest.hasLoadedNamespace;
303
304
  var saveMissingKeys = function saveMissingKeys(src) {
304
305
  if (!src || _typeof(src) !== 'object') return;
305
306
  for (var _i = 0, _Object$keys = Object.keys(src); _i < _Object$keys.length; _i++) {
306
307
  var m = _Object$keys[_i];
307
308
  if (utils.hasUnsafeKeySegment(m, keySeparator)) continue;
308
- i18next.services.backendConnector.saveMissing([lng], ns, m, src[m]);
309
+ i18next.services.backendConnector.saveMissing([lng], ns, m, src[m], undefined, undefined, undefined, hasLoadedNamespace);
309
310
  }
310
311
  };
311
312
  if (typeof body === 'function') {
package/cjs/utils.js CHANGED
@@ -30,8 +30,7 @@ function isSafeIdentifierBase(v) {
30
30
  }
31
31
  function isSafeLangIdentifier(v) {
32
32
  if (!isSafeIdentifierBase(v)) return false;
33
- if (v.indexOf('/') > -1) return false;
34
- return true;
33
+ return /^[A-Za-z0-9_@.-]+$/.test(v);
35
34
  }
36
35
  function isSafeNsIdentifier(v) {
37
36
  return isSafeIdentifierBase(v);
package/esm/index.js CHANGED
@@ -115,7 +115,7 @@ export function handle(i18next) {
115
115
  i18next.services.languageDetector.cacheUserLanguage(req, res, lng);
116
116
  }
117
117
  if (!req.lng) return next();
118
- i18next.loadLanguages(req.lng, function () {
118
+ i18n.loadLanguages(req.lng, function () {
119
119
  return next();
120
120
  });
121
121
  };
@@ -236,17 +236,16 @@ export function getResourcesHandler(i18next) {
236
236
  }
237
237
  languages = languages.filter(utils.isSafeLangIdentifier);
238
238
  namespaces = namespaces.filter(utils.isSafeNsIdentifier);
239
- namespaces.forEach(function (ns) {
240
- if (i18next.options.ns && i18next.options.ns.indexOf(ns) < 0) {
241
- i18next.options.ns.push(ns);
242
- }
243
- });
244
239
  var loaded = false;
245
240
  var onLoaded;
246
241
  i18next.services.backendConnector.load(languages, namespaces, function () {
247
242
  languages.forEach(function (lng) {
248
243
  namespaces.forEach(function (ns) {
249
- utils.setPath(resources, [lng, ns], i18next.getResourceBundle(lng, ns));
244
+ var bundle = i18next.getResourceBundle(lng, ns);
245
+ if (bundle && i18next.options.ns && i18next.options.ns.indexOf(ns) < 0) {
246
+ i18next.options.ns.push(ns);
247
+ }
248
+ utils.setPath(resources, [lng, ns], bundle);
250
249
  });
251
250
  });
252
251
  loaded = true;
@@ -283,12 +282,14 @@ export function missingKeyHandler(i18next) {
283
282
  }
284
283
  var body = options.getBody(req);
285
284
  var keySeparator = i18next.options && i18next.options.keySeparator;
285
+ var utilsOfRequest = req.i18n && req.i18n.services && req.i18n.services.utils;
286
+ var hasLoadedNamespace = utilsOfRequest && utilsOfRequest.hasLoadedNamespace;
286
287
  var saveMissingKeys = function saveMissingKeys(src) {
287
288
  if (!src || _typeof(src) !== 'object') return;
288
289
  for (var _i = 0, _Object$keys = Object.keys(src); _i < _Object$keys.length; _i++) {
289
290
  var m = _Object$keys[_i];
290
291
  if (utils.hasUnsafeKeySegment(m, keySeparator)) continue;
291
- i18next.services.backendConnector.saveMissing([lng], ns, m, src[m]);
292
+ i18next.services.backendConnector.saveMissing([lng], ns, m, src[m], undefined, undefined, undefined, hasLoadedNamespace);
292
293
  }
293
294
  };
294
295
  if (typeof body === 'function') {
package/esm/utils.js CHANGED
@@ -13,8 +13,7 @@ function isSafeIdentifierBase(v) {
13
13
  }
14
14
  export function isSafeLangIdentifier(v) {
15
15
  if (!isSafeIdentifierBase(v)) return false;
16
- if (v.indexOf('/') > -1) return false;
17
- return true;
16
+ return /^[A-Za-z0-9_@.-]+$/.test(v);
18
17
  }
19
18
  export function isSafeNsIdentifier(v) {
20
19
  return isSafeIdentifierBase(v);
package/lib/index.js CHANGED
@@ -127,9 +127,11 @@ export function handle (i18next, options = {}) {
127
127
  if (i18next.services.languageDetector) {
128
128
  i18next.services.languageDetector.cacheUserLanguage(req, res, lng)
129
129
  }
130
- // load resources
130
+ // load resources: through the request's clone, as loadLanguages() adds
131
+ // the language to options.preload, and every request on the shared
132
+ // instance pays for the length of that list until the process restarts
131
133
  if (!req.lng) return next()
132
- i18next.loadLanguages(req.lng, () => next())
134
+ i18n.loadLanguages(req.lng, () => next())
133
135
  }
134
136
  }
135
137
 
@@ -265,23 +267,19 @@ export function getResourcesHandler (i18next, options = {}) {
265
267
  languages = languages.filter(utils.isSafeLangIdentifier)
266
268
  namespaces = namespaces.filter(utils.isSafeNsIdentifier)
267
269
 
268
- // extend ns
269
- namespaces.forEach(ns => {
270
- if (i18next.options.ns && i18next.options.ns.indexOf(ns) < 0) {
271
- i18next.options.ns.push(ns)
272
- }
273
- })
274
-
275
270
  let loaded = false
276
271
  let onLoaded
277
272
  i18next.services.backendConnector.load(languages, namespaces, function () {
278
273
  languages.forEach(lng => {
279
274
  namespaces.forEach(ns => {
280
- utils.setPath(
281
- resources,
282
- [lng, ns],
283
- i18next.getResourceBundle(lng, ns)
284
- )
275
+ const bundle = i18next.getResourceBundle(lng, ns)
276
+ // extend ns only with a namespace the backend has: every request
277
+ // pays for the length of this shared list, and a made-up ns from
278
+ // the request would stay in it until the process restarts
279
+ if (bundle && i18next.options.ns && i18next.options.ns.indexOf(ns) < 0) {
280
+ i18next.options.ns.push(ns)
281
+ }
282
+ utils.setPath(resources, [lng, ns], bundle)
285
283
  })
286
284
  })
287
285
  loaded = true
@@ -335,6 +333,8 @@ export function missingKeyHandler (i18next, options = {}) {
335
333
 
336
334
  const body = options.getBody(req)
337
335
  const keySeparator = i18next.options && i18next.options.keySeparator
336
+ const utilsOfRequest = req.i18n && req.i18n.services && req.i18n.services.utils
337
+ const hasLoadedNamespace = utilsOfRequest && utilsOfRequest.hasLoadedNamespace
338
338
 
339
339
  // iterate only over own, non-prototype-polluting keys. The check also
340
340
  // rejects dotted variants like `__proto__.polluted` whose segments under
@@ -343,7 +343,9 @@ export function missingKeyHandler (i18next, options = {}) {
343
343
  if (!src || typeof src !== 'object') return
344
344
  for (const m of Object.keys(src)) {
345
345
  if (utils.hasUnsafeKeySegment(m, keySeparator)) continue
346
- i18next.services.backendConnector.saveMissing([lng], ns, m, src[m])
346
+ // i18next >= 26.7 takes the check of this request's instance (one clone per
347
+ // request) as the 8th argument; without it, it checks the newest clone
348
+ i18next.services.backendConnector.saveMissing([lng], ns, m, src[m], undefined, undefined, undefined, hasLoadedNamespace)
347
349
  }
348
350
  }
349
351
 
package/lib/utils.js CHANGED
@@ -17,14 +17,16 @@ function isSafeIdentifierBase (v) {
17
17
  return true
18
18
  }
19
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.
20
+ // Strict, applied to `lng`: on top of the base checks, the shape i18next accepts
21
+ // for a language code from outside since 26.7 (the rule of its exported
22
+ // `isSafeLanguageCode`): letters, digits, `-`, `_`, `@` and `.`. It is not a
23
+ // BCP-47 check (https://www.i18next.com/how-to/faq#how-should-the-language-codes-be-formatted),
24
+ // so `en_US`, `zh_Hant_TW` and `sr@latin` pass. `/`, quotes, spaces and `=` do
25
+ // not: a detected language ends up in `<html lang="...">`, and a value like
26
+ // `" onmouseover="x()` passed the base checks and the XSS patterns.
24
27
  export function isSafeLangIdentifier (v) {
25
28
  if (!isSafeIdentifierBase(v)) return false
26
- if (v.indexOf('/') > -1) return false
27
- return true
29
+ return /^[A-Za-z0-9_@.-]+$/.test(v)
28
30
  }
29
31
 
30
32
  // Loose — allows `/`. Applied to `ns`:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "i18next-http-middleware",
3
- "version": "3.9.11",
3
+ "version": "3.10.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "main": "./cjs/index.js",