i18next-http-middleware 3.9.1 → 3.9.3
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/README.md +0 -1
- package/cjs/LanguageDetector.js +1 -2
- package/cjs/index.d.ts +31 -0
- package/cjs/index.js +33 -31
- package/cjs/languageLookups/cookie.js +2 -0
- package/cjs/utils.js +20 -1
- package/esm/index.d.ts +31 -0
- package/esm/index.js +31 -28
- package/esm/languageLookups/cookie.js +2 -0
- package/esm/utils.js +17 -1
- package/index.d.ts +31 -0
- package/lib/index.js +21 -8
- package/lib/languageLookups/cookie.js +7 -0
- package/lib/utils.js +35 -2
- package/licence +1 -1
- package/package.json +18 -18
package/README.md
CHANGED
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/i18next/i18next-http-middleware/actions?query=workflow%3Anode)
|
|
4
4
|
[](https://github.com/i18next/i18next-http-middleware/actions?query=workflow%3Adeno)
|
|
5
|
-
[](https://travis-ci.org/i18next/i18next-http-middleware)
|
|
6
5
|
[](https://www.npmjs.com/package/i18next-http-middleware)
|
|
7
6
|
|
|
8
7
|
This is a middleware to be used with Node.js web frameworks like express or Fastify and also for Deno.
|
package/cjs/LanguageDetector.js
CHANGED
|
@@ -12,8 +12,7 @@ var _header = _interopRequireDefault(require("./languageLookups/header.js"));
|
|
|
12
12
|
var _session = _interopRequireDefault(require("./languageLookups/session.js"));
|
|
13
13
|
var _httpFunctions = require("./httpFunctions.js");
|
|
14
14
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
15
|
-
function
|
|
16
|
-
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; }
|
|
15
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
|
|
17
16
|
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
18
17
|
function _classCallCheck(a, n) { if (!(a instanceof n)) throw new TypeError("Cannot call a class as a function"); }
|
|
19
18
|
function _defineProperties(e, r) { for (var t = 0; t < r.length; t++) { var o = r[t]; o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(e, _toPropertyKey(o.key), o); } }
|
package/cjs/index.d.ts
CHANGED
|
@@ -185,3 +185,34 @@ export class LanguageDetector implements i18next.Module {
|
|
|
185
185
|
caches: LanguageDetectorCaches
|
|
186
186
|
): void;
|
|
187
187
|
}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
export interface HapiPlugin<TOptions = any> {
|
|
191
|
+
name: string;
|
|
192
|
+
version: string;
|
|
193
|
+
register: (
|
|
194
|
+
server: {
|
|
195
|
+
ext: (
|
|
196
|
+
event: string,
|
|
197
|
+
method: (request: any, h: any) => any
|
|
198
|
+
) => void;
|
|
199
|
+
},
|
|
200
|
+
options: TOptions & HandleOptions & { i18next?: I18next }
|
|
201
|
+
) => void;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export const hapiPlugin: HapiPlugin;
|
|
205
|
+
|
|
206
|
+
declare const middleware: {
|
|
207
|
+
plugin: typeof plugin;
|
|
208
|
+
hapiPlugin: typeof hapiPlugin;
|
|
209
|
+
koaPlugin: typeof koaPlugin;
|
|
210
|
+
freshPlugin: typeof freshPlugin;
|
|
211
|
+
handle: typeof handle;
|
|
212
|
+
getResourcesHandler: typeof getResourcesHandler;
|
|
213
|
+
missingKeyHandler: typeof missingKeyHandler;
|
|
214
|
+
addRoute: typeof addRoute;
|
|
215
|
+
LanguageDetector: typeof LanguageDetector;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export default middleware;
|
package/cjs/index.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
|
-
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
4
3
|
Object.defineProperty(exports, "__esModule", {
|
|
5
4
|
value: true
|
|
6
5
|
});
|
|
@@ -18,14 +17,15 @@ var utils = _interopRequireWildcard(require("./utils.js"));
|
|
|
18
17
|
var _LanguageDetector = _interopRequireDefault(require("./LanguageDetector.js"));
|
|
19
18
|
var _httpFunctions = require("./httpFunctions.js");
|
|
20
19
|
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
|
21
|
-
function
|
|
22
|
-
function
|
|
20
|
+
function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); }
|
|
21
|
+
function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); }
|
|
23
22
|
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
24
23
|
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
25
24
|
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
26
25
|
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
|
|
27
26
|
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
28
|
-
function
|
|
27
|
+
function _regenerator() { var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i.return) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
|
|
28
|
+
function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
|
|
29
29
|
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
30
30
|
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
31
31
|
var LanguageDetector = exports.LanguageDetector = _LanguageDetector.default;
|
|
@@ -93,7 +93,7 @@ function handle(i18next) {
|
|
|
93
93
|
res.locals.resolvedLanguage = i18n.resolvedLanguage;
|
|
94
94
|
}
|
|
95
95
|
if (lng && options.getHeader(res, 'Content-Language') !== lng) {
|
|
96
|
-
options.setHeader(res, 'Content-Language', utils.
|
|
96
|
+
options.setHeader(res, 'Content-Language', utils.sanitizeHeaderValue(lng));
|
|
97
97
|
}
|
|
98
98
|
req.languages = i18next.services.languageUtils.toResolveHierarchy(lng);
|
|
99
99
|
req.resolvedLanguage = i18n.resolvedLanguage;
|
|
@@ -107,7 +107,7 @@ function handle(i18next) {
|
|
|
107
107
|
}
|
|
108
108
|
req.language = req.locale = req.lng = lng;
|
|
109
109
|
if (lng && options.getHeader(res, 'Content-Language') !== lng) {
|
|
110
|
-
options.setHeader(res, 'Content-Language', utils.
|
|
110
|
+
options.setHeader(res, 'Content-Language', utils.sanitizeHeaderValue(lng));
|
|
111
111
|
}
|
|
112
112
|
req.languages = i18next.services.languageUtils.toResolveHierarchy(lng);
|
|
113
113
|
req.resolvedLanguage = i18n.resolvedLanguage;
|
|
@@ -148,20 +148,19 @@ function plugin(instance, options, next) {
|
|
|
148
148
|
function koaPlugin(i18next, options) {
|
|
149
149
|
var middleware = handle(i18next, options);
|
|
150
150
|
return function () {
|
|
151
|
-
var _koaMiddleware = _asyncToGenerator(
|
|
152
|
-
return
|
|
153
|
-
while (1) switch (_context.
|
|
151
|
+
var _koaMiddleware = _asyncToGenerator(_regenerator().m(function _callee(ctx, next) {
|
|
152
|
+
return _regenerator().w(function (_context) {
|
|
153
|
+
while (1) switch (_context.n) {
|
|
154
154
|
case 0:
|
|
155
|
-
_context.
|
|
155
|
+
_context.n = 1;
|
|
156
156
|
return new Promise(function (resolve) {
|
|
157
157
|
return middleware(ctx, ctx, resolve);
|
|
158
158
|
});
|
|
159
|
-
case
|
|
160
|
-
_context.
|
|
159
|
+
case 1:
|
|
160
|
+
_context.n = 2;
|
|
161
161
|
return next();
|
|
162
|
-
case
|
|
163
|
-
|
|
164
|
-
return _context.stop();
|
|
162
|
+
case 2:
|
|
163
|
+
return _context.a(2);
|
|
165
164
|
}
|
|
166
165
|
}, _callee);
|
|
167
166
|
}));
|
|
@@ -173,10 +172,10 @@ function koaPlugin(i18next, options) {
|
|
|
173
172
|
}
|
|
174
173
|
function freshPlugin(i18next, options) {
|
|
175
174
|
return function () {
|
|
176
|
-
var _ref = _asyncToGenerator(
|
|
175
|
+
var _ref = _asyncToGenerator(_regenerator().m(function _callee2(ctx) {
|
|
177
176
|
var placeholder, resp;
|
|
178
|
-
return
|
|
179
|
-
while (1) switch (_context2.
|
|
177
|
+
return _regenerator().w(function (_context2) {
|
|
178
|
+
while (1) switch (_context2.n) {
|
|
180
179
|
case 0:
|
|
181
180
|
handle(i18next, _objectSpread(_objectSpread({}, options), {}, {
|
|
182
181
|
attachLocals: true
|
|
@@ -189,15 +188,12 @@ function freshPlugin(i18next, options) {
|
|
|
189
188
|
ctx.state.languages = ctx.req.languages;
|
|
190
189
|
placeholder = ctx.state.res;
|
|
191
190
|
delete ctx.state.res;
|
|
192
|
-
_context2.
|
|
191
|
+
_context2.n = 1;
|
|
193
192
|
return ctx.next();
|
|
194
|
-
case
|
|
195
|
-
resp = _context2.
|
|
193
|
+
case 1:
|
|
194
|
+
resp = _context2.v;
|
|
196
195
|
resp.headers.set('Content-Language', placeholder.headers.get('Content-Language'));
|
|
197
|
-
return _context2.
|
|
198
|
-
case 14:
|
|
199
|
-
case "end":
|
|
200
|
-
return _context2.stop();
|
|
196
|
+
return _context2.a(2, resp);
|
|
201
197
|
}
|
|
202
198
|
}, _callee2);
|
|
203
199
|
}));
|
|
@@ -248,6 +244,8 @@ function getResourcesHandler(i18next) {
|
|
|
248
244
|
languages = options.getParams(req)[options.lngParam || 'lng'] ? options.getParams(req)[options.lngParam || 'lng'].split(' ') : [];
|
|
249
245
|
namespaces = options.getParams(req)[options.nsParam || 'ns'] ? options.getParams(req)[options.nsParam || 'ns'].split(' ') : [];
|
|
250
246
|
}
|
|
247
|
+
languages = languages.filter(utils.isSafeIdentifier);
|
|
248
|
+
namespaces = namespaces.filter(utils.isSafeIdentifier);
|
|
251
249
|
namespaces.forEach(function (ns) {
|
|
252
250
|
if (i18next.options.ns && i18next.options.ns.indexOf(ns) < 0) {
|
|
253
251
|
i18next.options.ns.push(ns);
|
|
@@ -280,22 +278,26 @@ function missingKeyHandler(i18next) {
|
|
|
280
278
|
return options.send(res, 'i18next-express-middleware:: no backend configured');
|
|
281
279
|
}
|
|
282
280
|
var body = options.getBody(req);
|
|
281
|
+
var saveMissingKeys = function saveMissingKeys(src) {
|
|
282
|
+
if (!src || _typeof(src) !== 'object') return;
|
|
283
|
+
for (var _i = 0, _Object$keys = Object.keys(src); _i < _Object$keys.length; _i++) {
|
|
284
|
+
var m = _Object$keys[_i];
|
|
285
|
+
if (utils.UNSAFE_KEYS.indexOf(m) > -1) continue;
|
|
286
|
+
i18next.services.backendConnector.saveMissing([lng], ns, m, src[m]);
|
|
287
|
+
}
|
|
288
|
+
};
|
|
283
289
|
if (typeof body === 'function') {
|
|
284
290
|
var promise = body();
|
|
285
291
|
if (promise && typeof promise.then === 'function') {
|
|
286
292
|
return new Promise(function (resolve) {
|
|
287
293
|
promise.then(function (b) {
|
|
288
|
-
|
|
289
|
-
i18next.services.backendConnector.saveMissing([lng], ns, m, b[m]);
|
|
290
|
-
}
|
|
294
|
+
saveMissingKeys(b);
|
|
291
295
|
resolve(options.send(res, 'ok'));
|
|
292
296
|
});
|
|
293
297
|
});
|
|
294
298
|
}
|
|
295
299
|
}
|
|
296
|
-
|
|
297
|
-
i18next.services.backendConnector.saveMissing([lng], ns, m, body[m]);
|
|
298
|
-
}
|
|
300
|
+
saveMissingKeys(body);
|
|
299
301
|
return options.send(res, 'ok');
|
|
300
302
|
};
|
|
301
303
|
}
|
|
@@ -84,6 +84,8 @@ var _default = exports.default = {
|
|
|
84
84
|
sameSite: options.cookieSameSite
|
|
85
85
|
};
|
|
86
86
|
if (options.cookieSecure) cookieOptions.secure = options.cookieSecure;
|
|
87
|
+
var sameSiteNormalised = typeof cookieOptions.sameSite === 'string' ? cookieOptions.sameSite.toLowerCase() : cookieOptions.sameSite;
|
|
88
|
+
if (sameSiteNormalised === 'none') cookieOptions.secure = true;
|
|
87
89
|
var existingCookie = options.getHeader(res, 'set-cookie') || options.getHeader(res, 'Set-Cookie') || [];
|
|
88
90
|
if (typeof existingCookie === 'string') existingCookie = [existingCookie];
|
|
89
91
|
if (!Array.isArray(existingCookie)) existingCookie = [];
|
package/cjs/utils.js
CHANGED
|
@@ -3,12 +3,25 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
+
exports.UNSAFE_KEYS = void 0;
|
|
6
7
|
exports.defaults = defaults;
|
|
7
8
|
exports.escape = escape;
|
|
8
9
|
exports.extend = extend;
|
|
9
10
|
exports.hasXSS = hasXSS;
|
|
11
|
+
exports.isSafeIdentifier = isSafeIdentifier;
|
|
10
12
|
exports.removeLngFromUrl = removeLngFromUrl;
|
|
13
|
+
exports.sanitizeHeaderValue = sanitizeHeaderValue;
|
|
11
14
|
exports.setPath = setPath;
|
|
15
|
+
var UNSAFE_KEYS = exports.UNSAFE_KEYS = ['__proto__', 'constructor', 'prototype'];
|
|
16
|
+
function isSafeIdentifier(v) {
|
|
17
|
+
if (typeof v !== 'string') return false;
|
|
18
|
+
if (v.length === 0 || v.length > 128) return false;
|
|
19
|
+
if (UNSAFE_KEYS.indexOf(v) > -1) return false;
|
|
20
|
+
if (v.indexOf('..') > -1) return false;
|
|
21
|
+
if (v.indexOf('/') > -1 || v.indexOf('\\') > -1) return false;
|
|
22
|
+
if (/[\x00-\x1F\x7F]/.test(v)) return false;
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
12
25
|
function setPath(object, path, newValue) {
|
|
13
26
|
var stack;
|
|
14
27
|
if (typeof path !== 'string') stack = [].concat(path);
|
|
@@ -16,11 +29,13 @@ function setPath(object, path, newValue) {
|
|
|
16
29
|
while (stack.length > 1) {
|
|
17
30
|
var _key = stack.shift();
|
|
18
31
|
if (_key.indexOf('###') > -1) _key = _key.replace(/###/g, '.');
|
|
32
|
+
if (UNSAFE_KEYS.indexOf(_key) > -1) return;
|
|
19
33
|
if (!object[_key]) object[_key] = {};
|
|
20
34
|
object = object[_key];
|
|
21
35
|
}
|
|
22
36
|
var key = stack.shift();
|
|
23
37
|
if (key.indexOf('###') > -1) key = key.replace(/###/g, '.');
|
|
38
|
+
if (UNSAFE_KEYS.indexOf(key) > -1) return;
|
|
24
39
|
object[key] = newValue;
|
|
25
40
|
}
|
|
26
41
|
var arr = [];
|
|
@@ -62,9 +77,13 @@ function removeLngFromUrl(url, lookupFromPathIndex) {
|
|
|
62
77
|
function escape(str) {
|
|
63
78
|
return str.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(/</g, '<').replace(/>/g, '>').replace(/\//g, '/').replace(/\\/g, '\').replace(/`/g, '`');
|
|
64
79
|
}
|
|
80
|
+
function sanitizeHeaderValue(str) {
|
|
81
|
+
if (typeof str !== 'string') return str;
|
|
82
|
+
return str.replace(/[\r\n\x00-\x1F\x7F]/g, '');
|
|
83
|
+
}
|
|
65
84
|
function hasXSS(input) {
|
|
66
85
|
if (typeof input !== 'string') return false;
|
|
67
|
-
var xssPatterns = [/<\s*script.*?>/i, /<\s*\/\s*script\s*>/i, /<\s
|
|
86
|
+
var xssPatterns = [/<\s*script.*?>/i, /<\s*\/\s*script\s*>/i, /<\s*\w+\s+[^>]*?\bon\w+\s*=/i, /javascript\s*:/i, /vbscript\s*:/i, /expression\s*\(/i, /eval\s*\(/i, /alert\s*\(/i, /document\.cookie/i, /document\.write\s*\(/i, /window\.location/i, /innerHTML/i];
|
|
68
87
|
return xssPatterns.some(function (pattern) {
|
|
69
88
|
return pattern.test(input);
|
|
70
89
|
});
|
package/esm/index.d.ts
CHANGED
|
@@ -185,3 +185,34 @@ export class LanguageDetector implements i18next.Module {
|
|
|
185
185
|
caches: LanguageDetectorCaches
|
|
186
186
|
): void;
|
|
187
187
|
}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
export interface HapiPlugin<TOptions = any> {
|
|
191
|
+
name: string;
|
|
192
|
+
version: string;
|
|
193
|
+
register: (
|
|
194
|
+
server: {
|
|
195
|
+
ext: (
|
|
196
|
+
event: string,
|
|
197
|
+
method: (request: any, h: any) => any
|
|
198
|
+
) => void;
|
|
199
|
+
},
|
|
200
|
+
options: TOptions & HandleOptions & { i18next?: I18next }
|
|
201
|
+
) => void;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export const hapiPlugin: HapiPlugin;
|
|
205
|
+
|
|
206
|
+
declare const middleware: {
|
|
207
|
+
plugin: typeof plugin;
|
|
208
|
+
hapiPlugin: typeof hapiPlugin;
|
|
209
|
+
koaPlugin: typeof koaPlugin;
|
|
210
|
+
freshPlugin: typeof freshPlugin;
|
|
211
|
+
handle: typeof handle;
|
|
212
|
+
getResourcesHandler: typeof getResourcesHandler;
|
|
213
|
+
missingKeyHandler: typeof missingKeyHandler;
|
|
214
|
+
addRoute: typeof addRoute;
|
|
215
|
+
LanguageDetector: typeof LanguageDetector;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export default middleware;
|
package/esm/index.js
CHANGED
|
@@ -4,7 +4,8 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
|
|
|
4
4
|
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
5
5
|
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; }
|
|
6
6
|
function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
7
|
-
function
|
|
7
|
+
function _regenerator() { var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i.return) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
|
|
8
|
+
function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
|
|
8
9
|
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
9
10
|
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
10
11
|
import * as utils from './utils.js';
|
|
@@ -75,7 +76,7 @@ export function handle(i18next) {
|
|
|
75
76
|
res.locals.resolvedLanguage = i18n.resolvedLanguage;
|
|
76
77
|
}
|
|
77
78
|
if (lng && options.getHeader(res, 'Content-Language') !== lng) {
|
|
78
|
-
options.setHeader(res, 'Content-Language', utils.
|
|
79
|
+
options.setHeader(res, 'Content-Language', utils.sanitizeHeaderValue(lng));
|
|
79
80
|
}
|
|
80
81
|
req.languages = i18next.services.languageUtils.toResolveHierarchy(lng);
|
|
81
82
|
req.resolvedLanguage = i18n.resolvedLanguage;
|
|
@@ -89,7 +90,7 @@ export function handle(i18next) {
|
|
|
89
90
|
}
|
|
90
91
|
req.language = req.locale = req.lng = lng;
|
|
91
92
|
if (lng && options.getHeader(res, 'Content-Language') !== lng) {
|
|
92
|
-
options.setHeader(res, 'Content-Language', utils.
|
|
93
|
+
options.setHeader(res, 'Content-Language', utils.sanitizeHeaderValue(lng));
|
|
93
94
|
}
|
|
94
95
|
req.languages = i18next.services.languageUtils.toResolveHierarchy(lng);
|
|
95
96
|
req.resolvedLanguage = i18n.resolvedLanguage;
|
|
@@ -130,20 +131,19 @@ export function plugin(instance, options, next) {
|
|
|
130
131
|
export function koaPlugin(i18next, options) {
|
|
131
132
|
var middleware = handle(i18next, options);
|
|
132
133
|
return function () {
|
|
133
|
-
var _koaMiddleware = _asyncToGenerator(
|
|
134
|
-
return
|
|
135
|
-
while (1) switch (_context.
|
|
134
|
+
var _koaMiddleware = _asyncToGenerator(_regenerator().m(function _callee(ctx, next) {
|
|
135
|
+
return _regenerator().w(function (_context) {
|
|
136
|
+
while (1) switch (_context.n) {
|
|
136
137
|
case 0:
|
|
137
|
-
_context.
|
|
138
|
+
_context.n = 1;
|
|
138
139
|
return new Promise(function (resolve) {
|
|
139
140
|
return middleware(ctx, ctx, resolve);
|
|
140
141
|
});
|
|
141
|
-
case
|
|
142
|
-
_context.
|
|
142
|
+
case 1:
|
|
143
|
+
_context.n = 2;
|
|
143
144
|
return next();
|
|
144
|
-
case
|
|
145
|
-
|
|
146
|
-
return _context.stop();
|
|
145
|
+
case 2:
|
|
146
|
+
return _context.a(2);
|
|
147
147
|
}
|
|
148
148
|
}, _callee);
|
|
149
149
|
}));
|
|
@@ -155,10 +155,10 @@ export function koaPlugin(i18next, options) {
|
|
|
155
155
|
}
|
|
156
156
|
export function freshPlugin(i18next, options) {
|
|
157
157
|
return function () {
|
|
158
|
-
var _ref = _asyncToGenerator(
|
|
158
|
+
var _ref = _asyncToGenerator(_regenerator().m(function _callee2(ctx) {
|
|
159
159
|
var placeholder, resp;
|
|
160
|
-
return
|
|
161
|
-
while (1) switch (_context2.
|
|
160
|
+
return _regenerator().w(function (_context2) {
|
|
161
|
+
while (1) switch (_context2.n) {
|
|
162
162
|
case 0:
|
|
163
163
|
handle(i18next, _objectSpread(_objectSpread({}, options), {}, {
|
|
164
164
|
attachLocals: true
|
|
@@ -171,15 +171,12 @@ export function freshPlugin(i18next, options) {
|
|
|
171
171
|
ctx.state.languages = ctx.req.languages;
|
|
172
172
|
placeholder = ctx.state.res;
|
|
173
173
|
delete ctx.state.res;
|
|
174
|
-
_context2.
|
|
174
|
+
_context2.n = 1;
|
|
175
175
|
return ctx.next();
|
|
176
|
-
case
|
|
177
|
-
resp = _context2.
|
|
176
|
+
case 1:
|
|
177
|
+
resp = _context2.v;
|
|
178
178
|
resp.headers.set('Content-Language', placeholder.headers.get('Content-Language'));
|
|
179
|
-
return _context2.
|
|
180
|
-
case 14:
|
|
181
|
-
case "end":
|
|
182
|
-
return _context2.stop();
|
|
179
|
+
return _context2.a(2, resp);
|
|
183
180
|
}
|
|
184
181
|
}, _callee2);
|
|
185
182
|
}));
|
|
@@ -230,6 +227,8 @@ export function getResourcesHandler(i18next) {
|
|
|
230
227
|
languages = options.getParams(req)[options.lngParam || 'lng'] ? options.getParams(req)[options.lngParam || 'lng'].split(' ') : [];
|
|
231
228
|
namespaces = options.getParams(req)[options.nsParam || 'ns'] ? options.getParams(req)[options.nsParam || 'ns'].split(' ') : [];
|
|
232
229
|
}
|
|
230
|
+
languages = languages.filter(utils.isSafeIdentifier);
|
|
231
|
+
namespaces = namespaces.filter(utils.isSafeIdentifier);
|
|
233
232
|
namespaces.forEach(function (ns) {
|
|
234
233
|
if (i18next.options.ns && i18next.options.ns.indexOf(ns) < 0) {
|
|
235
234
|
i18next.options.ns.push(ns);
|
|
@@ -262,22 +261,26 @@ export function missingKeyHandler(i18next) {
|
|
|
262
261
|
return options.send(res, 'i18next-express-middleware:: no backend configured');
|
|
263
262
|
}
|
|
264
263
|
var body = options.getBody(req);
|
|
264
|
+
var saveMissingKeys = function saveMissingKeys(src) {
|
|
265
|
+
if (!src || _typeof(src) !== 'object') return;
|
|
266
|
+
for (var _i = 0, _Object$keys = Object.keys(src); _i < _Object$keys.length; _i++) {
|
|
267
|
+
var m = _Object$keys[_i];
|
|
268
|
+
if (utils.UNSAFE_KEYS.indexOf(m) > -1) continue;
|
|
269
|
+
i18next.services.backendConnector.saveMissing([lng], ns, m, src[m]);
|
|
270
|
+
}
|
|
271
|
+
};
|
|
265
272
|
if (typeof body === 'function') {
|
|
266
273
|
var promise = body();
|
|
267
274
|
if (promise && typeof promise.then === 'function') {
|
|
268
275
|
return new Promise(function (resolve) {
|
|
269
276
|
promise.then(function (b) {
|
|
270
|
-
|
|
271
|
-
i18next.services.backendConnector.saveMissing([lng], ns, m, b[m]);
|
|
272
|
-
}
|
|
277
|
+
saveMissingKeys(b);
|
|
273
278
|
resolve(options.send(res, 'ok'));
|
|
274
279
|
});
|
|
275
280
|
});
|
|
276
281
|
}
|
|
277
282
|
}
|
|
278
|
-
|
|
279
|
-
i18next.services.backendConnector.saveMissing([lng], ns, m, body[m]);
|
|
280
|
-
}
|
|
283
|
+
saveMissingKeys(body);
|
|
281
284
|
return options.send(res, 'ok');
|
|
282
285
|
};
|
|
283
286
|
}
|
|
@@ -78,6 +78,8 @@ export default {
|
|
|
78
78
|
sameSite: options.cookieSameSite
|
|
79
79
|
};
|
|
80
80
|
if (options.cookieSecure) cookieOptions.secure = options.cookieSecure;
|
|
81
|
+
var sameSiteNormalised = typeof cookieOptions.sameSite === 'string' ? cookieOptions.sameSite.toLowerCase() : cookieOptions.sameSite;
|
|
82
|
+
if (sameSiteNormalised === 'none') cookieOptions.secure = true;
|
|
81
83
|
var existingCookie = options.getHeader(res, 'set-cookie') || options.getHeader(res, 'Set-Cookie') || [];
|
|
82
84
|
if (typeof existingCookie === 'string') existingCookie = [existingCookie];
|
|
83
85
|
if (!Array.isArray(existingCookie)) existingCookie = [];
|
package/esm/utils.js
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
export var UNSAFE_KEYS = ['__proto__', 'constructor', 'prototype'];
|
|
2
|
+
export function isSafeIdentifier(v) {
|
|
3
|
+
if (typeof v !== 'string') return false;
|
|
4
|
+
if (v.length === 0 || v.length > 128) return false;
|
|
5
|
+
if (UNSAFE_KEYS.indexOf(v) > -1) return false;
|
|
6
|
+
if (v.indexOf('..') > -1) return false;
|
|
7
|
+
if (v.indexOf('/') > -1 || v.indexOf('\\') > -1) return false;
|
|
8
|
+
if (/[\x00-\x1F\x7F]/.test(v)) return false;
|
|
9
|
+
return true;
|
|
10
|
+
}
|
|
1
11
|
export function setPath(object, path, newValue) {
|
|
2
12
|
var stack;
|
|
3
13
|
if (typeof path !== 'string') stack = [].concat(path);
|
|
@@ -5,11 +15,13 @@ export function setPath(object, path, newValue) {
|
|
|
5
15
|
while (stack.length > 1) {
|
|
6
16
|
var _key = stack.shift();
|
|
7
17
|
if (_key.indexOf('###') > -1) _key = _key.replace(/###/g, '.');
|
|
18
|
+
if (UNSAFE_KEYS.indexOf(_key) > -1) return;
|
|
8
19
|
if (!object[_key]) object[_key] = {};
|
|
9
20
|
object = object[_key];
|
|
10
21
|
}
|
|
11
22
|
var key = stack.shift();
|
|
12
23
|
if (key.indexOf('###') > -1) key = key.replace(/###/g, '.');
|
|
24
|
+
if (UNSAFE_KEYS.indexOf(key) > -1) return;
|
|
13
25
|
object[key] = newValue;
|
|
14
26
|
}
|
|
15
27
|
var arr = [];
|
|
@@ -51,9 +63,13 @@ export function removeLngFromUrl(url, lookupFromPathIndex) {
|
|
|
51
63
|
export function escape(str) {
|
|
52
64
|
return str.replace(/&/g, '&').replace(/"/g, '"').replace(/'/g, ''').replace(/</g, '<').replace(/>/g, '>').replace(/\//g, '/').replace(/\\/g, '\').replace(/`/g, '`');
|
|
53
65
|
}
|
|
66
|
+
export function sanitizeHeaderValue(str) {
|
|
67
|
+
if (typeof str !== 'string') return str;
|
|
68
|
+
return str.replace(/[\r\n\x00-\x1F\x7F]/g, '');
|
|
69
|
+
}
|
|
54
70
|
export function hasXSS(input) {
|
|
55
71
|
if (typeof input !== 'string') return false;
|
|
56
|
-
var xssPatterns = [/<\s*script.*?>/i, /<\s*\/\s*script\s*>/i, /<\s
|
|
72
|
+
var xssPatterns = [/<\s*script.*?>/i, /<\s*\/\s*script\s*>/i, /<\s*\w+\s+[^>]*?\bon\w+\s*=/i, /javascript\s*:/i, /vbscript\s*:/i, /expression\s*\(/i, /eval\s*\(/i, /alert\s*\(/i, /document\.cookie/i, /document\.write\s*\(/i, /window\.location/i, /innerHTML/i];
|
|
57
73
|
return xssPatterns.some(function (pattern) {
|
|
58
74
|
return pattern.test(input);
|
|
59
75
|
});
|
package/index.d.ts
CHANGED
|
@@ -185,3 +185,34 @@ export class LanguageDetector implements i18next.Module {
|
|
|
185
185
|
caches: LanguageDetectorCaches
|
|
186
186
|
): void;
|
|
187
187
|
}
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
export interface HapiPlugin<TOptions = any> {
|
|
191
|
+
name: string;
|
|
192
|
+
version: string;
|
|
193
|
+
register: (
|
|
194
|
+
server: {
|
|
195
|
+
ext: (
|
|
196
|
+
event: string,
|
|
197
|
+
method: (request: any, h: any) => any
|
|
198
|
+
) => void;
|
|
199
|
+
},
|
|
200
|
+
options: TOptions & HandleOptions & { i18next?: I18next }
|
|
201
|
+
) => void;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export const hapiPlugin: HapiPlugin;
|
|
205
|
+
|
|
206
|
+
declare const middleware: {
|
|
207
|
+
plugin: typeof plugin;
|
|
208
|
+
hapiPlugin: typeof hapiPlugin;
|
|
209
|
+
koaPlugin: typeof koaPlugin;
|
|
210
|
+
freshPlugin: typeof freshPlugin;
|
|
211
|
+
handle: typeof handle;
|
|
212
|
+
getResourcesHandler: typeof getResourcesHandler;
|
|
213
|
+
missingKeyHandler: typeof missingKeyHandler;
|
|
214
|
+
addRoute: typeof addRoute;
|
|
215
|
+
LanguageDetector: typeof LanguageDetector;
|
|
216
|
+
};
|
|
217
|
+
|
|
218
|
+
export default middleware;
|
package/lib/index.js
CHANGED
|
@@ -69,7 +69,7 @@ export function handle (i18next, options = {}) {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
if (lng && options.getHeader(res, 'Content-Language') !== lng) {
|
|
72
|
-
options.setHeader(res, 'Content-Language', utils.
|
|
72
|
+
options.setHeader(res, 'Content-Language', utils.sanitizeHeaderValue(lng))
|
|
73
73
|
}
|
|
74
74
|
|
|
75
75
|
req.languages = i18next.services.languageUtils.toResolveHierarchy(lng)
|
|
@@ -88,7 +88,7 @@ export function handle (i18next, options = {}) {
|
|
|
88
88
|
// set locale
|
|
89
89
|
req.language = req.locale = req.lng = lng
|
|
90
90
|
if (lng && options.getHeader(res, 'Content-Language') !== lng) {
|
|
91
|
-
options.setHeader(res, 'Content-Language', utils.
|
|
91
|
+
options.setHeader(res, 'Content-Language', utils.sanitizeHeaderValue(lng))
|
|
92
92
|
}
|
|
93
93
|
req.languages = i18next.services.languageUtils.toResolveHierarchy(lng)
|
|
94
94
|
req.resolvedLanguage = i18n.resolvedLanguage
|
|
@@ -243,6 +243,14 @@ export function getResourcesHandler (i18next, options = {}) {
|
|
|
243
243
|
: []
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
+
// Drop user-supplied values containing patterns that could trigger path
|
|
247
|
+
// traversal / SSRF / prototype pollution when forwarded to the backend
|
|
248
|
+
// connector. i18next itself permits arbitrary language codes, so we do
|
|
249
|
+
// not impose a BCP-47 shape — we only block known-dangerous patterns.
|
|
250
|
+
// 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)
|
|
253
|
+
|
|
246
254
|
// extend ns
|
|
247
255
|
namespaces.forEach(ns => {
|
|
248
256
|
if (i18next.options.ns && i18next.options.ns.indexOf(ns) < 0) {
|
|
@@ -290,23 +298,28 @@ export function missingKeyHandler (i18next, options = {}) {
|
|
|
290
298
|
|
|
291
299
|
const body = options.getBody(req)
|
|
292
300
|
|
|
301
|
+
// iterate only over own, non-prototype-polluting keys
|
|
302
|
+
const saveMissingKeys = src => {
|
|
303
|
+
if (!src || typeof src !== 'object') return
|
|
304
|
+
for (const m of Object.keys(src)) {
|
|
305
|
+
if (utils.UNSAFE_KEYS.indexOf(m) > -1) continue
|
|
306
|
+
i18next.services.backendConnector.saveMissing([lng], ns, m, src[m])
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
293
310
|
if (typeof body === 'function') {
|
|
294
311
|
const promise = body()
|
|
295
312
|
if (promise && typeof promise.then === 'function') {
|
|
296
313
|
return new Promise(resolve => {
|
|
297
314
|
promise.then(b => {
|
|
298
|
-
|
|
299
|
-
i18next.services.backendConnector.saveMissing([lng], ns, m, b[m])
|
|
300
|
-
}
|
|
315
|
+
saveMissingKeys(b)
|
|
301
316
|
resolve(options.send(res, 'ok'))
|
|
302
317
|
})
|
|
303
318
|
})
|
|
304
319
|
}
|
|
305
320
|
}
|
|
306
321
|
|
|
307
|
-
|
|
308
|
-
i18next.services.backendConnector.saveMissing([lng], ns, m, body[m])
|
|
309
|
-
}
|
|
322
|
+
saveMissingKeys(body)
|
|
310
323
|
|
|
311
324
|
return options.send(res, 'ok')
|
|
312
325
|
}
|
|
@@ -86,6 +86,13 @@ export default {
|
|
|
86
86
|
|
|
87
87
|
if (options.cookieSecure) cookieOptions.secure = options.cookieSecure
|
|
88
88
|
|
|
89
|
+
// SameSite=None requires Secure — browsers reject the cookie otherwise.
|
|
90
|
+
// Force Secure in that case so misconfigured setups still function.
|
|
91
|
+
const sameSiteNormalised = typeof cookieOptions.sameSite === 'string'
|
|
92
|
+
? cookieOptions.sameSite.toLowerCase()
|
|
93
|
+
: cookieOptions.sameSite
|
|
94
|
+
if (sameSiteNormalised === 'none') cookieOptions.secure = true
|
|
95
|
+
|
|
89
96
|
let existingCookie = options.getHeader(res, 'set-cookie') || options.getHeader(res, 'Set-Cookie') || []
|
|
90
97
|
if (typeof existingCookie === 'string') existingCookie = [existingCookie]
|
|
91
98
|
if (!Array.isArray(existingCookie)) existingCookie = []
|
package/lib/utils.js
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
export const UNSAFE_KEYS = ['__proto__', 'constructor', 'prototype']
|
|
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) {
|
|
13
|
+
if (typeof v !== 'string') return false
|
|
14
|
+
if (v.length === 0 || v.length > 128) return false
|
|
15
|
+
if (UNSAFE_KEYS.indexOf(v) > -1) return false
|
|
16
|
+
if (v.indexOf('..') > -1) return false
|
|
17
|
+
if (v.indexOf('/') > -1 || v.indexOf('\\') > -1) return false
|
|
18
|
+
// eslint-disable-next-line no-control-regex
|
|
19
|
+
if (/[\x00-\x1F\x7F]/.test(v)) return false
|
|
20
|
+
return true
|
|
21
|
+
}
|
|
22
|
+
|
|
1
23
|
export function setPath (object, path, newValue) {
|
|
2
24
|
let stack
|
|
3
25
|
if (typeof path !== 'string') stack = [].concat(path)
|
|
@@ -6,12 +28,14 @@ export function setPath (object, path, newValue) {
|
|
|
6
28
|
while (stack.length > 1) {
|
|
7
29
|
let key = stack.shift()
|
|
8
30
|
if (key.indexOf('###') > -1) key = key.replace(/###/g, '.')
|
|
31
|
+
if (UNSAFE_KEYS.indexOf(key) > -1) return // guard against prototype pollution
|
|
9
32
|
if (!object[key]) object[key] = {}
|
|
10
33
|
object = object[key]
|
|
11
34
|
}
|
|
12
35
|
|
|
13
36
|
let key = stack.shift()
|
|
14
37
|
if (key.indexOf('###') > -1) key = key.replace(/###/g, '.')
|
|
38
|
+
if (UNSAFE_KEYS.indexOf(key) > -1) return // guard against prototype pollution
|
|
15
39
|
object[key] = newValue
|
|
16
40
|
}
|
|
17
41
|
|
|
@@ -70,6 +94,15 @@ export function escape (str) {
|
|
|
70
94
|
.replace(/`/g, '`'))
|
|
71
95
|
}
|
|
72
96
|
|
|
97
|
+
// Strip control characters (CR, LF, NUL, other C0/C1) from a value before
|
|
98
|
+
// writing it into an HTTP header. Prevents HTTP response splitting on older
|
|
99
|
+
// Node.js, and unhandled ERR_INVALID_CHAR crashes on newer Node.js.
|
|
100
|
+
export function sanitizeHeaderValue (str) {
|
|
101
|
+
if (typeof str !== 'string') return str
|
|
102
|
+
// eslint-disable-next-line no-control-regex
|
|
103
|
+
return str.replace(/[\r\n\x00-\x1F\x7F]/g, '')
|
|
104
|
+
}
|
|
105
|
+
|
|
73
106
|
export function hasXSS (input) {
|
|
74
107
|
if (typeof input !== 'string') return false
|
|
75
108
|
|
|
@@ -77,8 +110,8 @@ export function hasXSS (input) {
|
|
|
77
110
|
const xssPatterns = [
|
|
78
111
|
/<\s*script.*?>/i,
|
|
79
112
|
/<\s*\/\s*script\s*>/i,
|
|
80
|
-
|
|
81
|
-
/<\s*\w+\s
|
|
113
|
+
// event handlers on any tag position (not just first attribute)
|
|
114
|
+
/<\s*\w+\s+[^>]*?\bon\w+\s*=/i,
|
|
82
115
|
/javascript\s*:/i,
|
|
83
116
|
/vbscript\s*:/i,
|
|
84
117
|
/expression\s*\(/i,
|
package/licence
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "i18next-http-middleware",
|
|
3
|
-
"version": "3.9.
|
|
3
|
+
"version": "3.9.3",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./cjs/index.js",
|
|
@@ -28,15 +28,15 @@
|
|
|
28
28
|
},
|
|
29
29
|
"module": "./esm/index.js",
|
|
30
30
|
"devDependencies": {
|
|
31
|
-
"@babel/cli": "7.
|
|
32
|
-
"@babel/core": "7.
|
|
33
|
-
"@babel/preset-env": "7.
|
|
34
|
-
"@hapi/hapi": "^21.
|
|
35
|
-
"@opentelemetry/api": "^1.9.
|
|
36
|
-
"@preact/signals": "^2.
|
|
37
|
-
"@types/express-serve-static-core": "^5.
|
|
38
|
-
"@koa/router": "
|
|
39
|
-
"koa": "2.
|
|
31
|
+
"@babel/cli": "7.28.6",
|
|
32
|
+
"@babel/core": "7.29.0",
|
|
33
|
+
"@babel/preset-env": "7.29.2",
|
|
34
|
+
"@hapi/hapi": "^21.4.8",
|
|
35
|
+
"@opentelemetry/api": "^1.9.1",
|
|
36
|
+
"@preact/signals": "^2.9.0",
|
|
37
|
+
"@types/express-serve-static-core": "^5.1.1",
|
|
38
|
+
"@koa/router": "15.4.0",
|
|
39
|
+
"koa": "3.2.0",
|
|
40
40
|
"babel-plugin-add-module-exports": "1.0.4",
|
|
41
41
|
"eslint": "8.53.0",
|
|
42
42
|
"eslint-config-standard": "17.1.0",
|
|
@@ -46,14 +46,14 @@
|
|
|
46
46
|
"eslint-plugin-require-path-exists": "1.1.9",
|
|
47
47
|
"eslint-plugin-standard": "5.0.0",
|
|
48
48
|
"expect.js": "0.3.1",
|
|
49
|
-
"express": "
|
|
50
|
-
"fastify": "5.
|
|
51
|
-
"i18next": "
|
|
52
|
-
"mocha": "
|
|
53
|
-
"preact": "10.
|
|
54
|
-
"preact-render-to-string": "6.6.
|
|
55
|
-
"supertest": "7.
|
|
56
|
-
"tsd": "0.
|
|
49
|
+
"express": "5.2.1",
|
|
50
|
+
"fastify": "5.8.5",
|
|
51
|
+
"i18next": "26.0.5",
|
|
52
|
+
"mocha": "11.7.5",
|
|
53
|
+
"preact": "10.29.1",
|
|
54
|
+
"preact-render-to-string": "6.6.7",
|
|
55
|
+
"supertest": "7.2.2",
|
|
56
|
+
"tsd": "0.33.0",
|
|
57
57
|
"uglify-js": "3.19.3"
|
|
58
58
|
},
|
|
59
59
|
"description": "i18next-http-middleware is a middleware to be used with Node.js web frameworks like express or Fastify and also for Deno.",
|