Санитизация данных — процесс очистки, нормализации и приведения пользовательского ввода к безопасному и предсказуемому виду перед дальнейшей обработкой, отображением или сохранением.
В контексте JavaScript и Intl API санитизация особенно
важна при работе с:
locale);Ошибки в этих значениях могут привести к:
Пользовательский ввод часто содержит:
"EN-us"
"ru_RU"
"de-de "
" english "
null
undefined
123
Intl API ожидает корректные BCP 47 locale-теги:
"en-US"
"ru-RU"
"de-DE"
Некорректный locale способен вызвать ошибку:
new Intl.DateTimeFormat("wrong-locale")
Метод выполняет канонизацию locale-значений.
Intl.getCanonicalLocales("EN-us")
Результат:
["en-US"]
Базовая санитизация:
function sanitizeLocale(locale) {
if (typeof locale !== "string") {
return "en-US";
}
const normalized = locale.trim();
try {
return Intl.getCanonicalLocales(normalized)[0];
} catch {
return "en-US";
}
}
Использование:
sanitizeLocale(" RU-ru ")
Результат:
"ru-RU"
Даже корректный locale может не поддерживаться окружением.
Проверка:
Intl.DateTimeFormat.supportedLocalesOf([
"ru-RU",
"fr-FR",
"invalid"
]);
Результат:
["ru-RU", "fr-FR"]
function getSupportedLocale(locale) {
const sanitized = sanitizeLocale(locale);
const supported = Intl.DateTimeFormat
.supportedLocalesOf([sanitized]);
return supported[0] || "en-US";
}
Intl.NumberFormat требует ISO 4217 валютный код:
new Intl.NumberFormat("en-US", {
style: "currency",
currency: "USD"
});
Некорректное значение:
currency: "dollar"
вызовет:
RangeError
function sanitizeCurrency(currency) {
if (typeof currency !== "string") {
return "USD";
}
return currency.trim().toUpperCase();
}
Надёжная проверка через Intl.NumberFormat:
function isValidCurrency(currency) {
try {
new Intl.NumberFormat("en-US", {
style: "currency",
currency
});
return true;
} catch {
return false;
}
}
function sanitizeCurrency(currency) {
if (typeof currency !== "string") {
return "USD";
}
const normalized = currency
.trim()
.toUpperCase();
if (!isValidCurrency(normalized)) {
return "USD";
}
return normalized;
}
Intl.DateTimeFormat принимает только валидные IANA
timezone-идентификаторы:
"Europe/Berlin"
"Asia/Tokyo"
"America/New_York"
Некорректное значение:
"moscow"
вызовет исключение.
function isValidTimeZone(timeZone) {
try {
new Intl.DateTimeFormat("en-US", {
timeZone
});
return true;
} catch {
return false;
}
}
function sanitizeTimeZone(timeZone) {
if (typeof timeZone !== "string") {
return "UTC";
}
const normalized = timeZone.trim();
if (!isValidTimeZone(normalized)) {
return "UTC";
}
return normalized;
}
Intl.NumberFormat поддерживает ограниченный набор
единиц:
"kilometer"
"liter"
"byte"
"celsius"
Некорректное значение:
"kilometers"
приведёт к ошибке.
function isValidUnit(unit) {
try {
new Intl.NumberFormat("en-US", {
style: "unit",
unit
});
return true;
} catch {
return false;
}
}
function sanitizeUnit(unit) {
if (typeof unit !== "string") {
return "kilometer";
}
const normalized = unit
.trim()
.toLowerCase();
if (!isValidUnit(normalized)) {
return "kilometer";
}
return normalized;
}
Данные часто приходят в виде строк:
"123"
" 45.6 "
"1,000"
"abc"
Перед передачей в Intl.NumberFormat значения необходимо
нормализовать.
function sanitizeNumber(value) {
if (typeof value === "number") {
return Number.isFinite(value)
? value
: 0;
}
if (typeof value !== "string") {
return 0;
}
const normalized = value
.trim()
.replace(/,/g, "");
const parsed = Number(normalized);
return Number.isFinite(parsed)
? parsed
: 0;
}
Intl.NumberFormat способен форматировать:
NaN
Infinity
Однако подобные значения обычно считаются нежелательными.
Проверка:
Number.isFinite(value)
Проблемные данные:
"invalid-date"
null
undefined
{}
[]
function isValidDate(date) {
return (
date instanceof Date &&
!Number.isNaN(date.getTime())
);
}
function sanitizeDate(value) {
const date = new Date(value);
if (!isValidDate(date)) {
return new Date();
}
return date;
}
Наиболее безопасный подход — разрешать только заранее определённые значения.
const ALLOWED_LOCALES = [
"en-US",
"ru-RU",
"de-DE"
];
function sanitizeLocale(locale) {
try {
const normalized =
Intl.getCanonicalLocales(locale)[0];
if (ALLOWED_LOCALES.includes(normalized)) {
return normalized;
}
} catch {}
return "en-US";
}
const ALLOWED_CURRENCIES = [
"USD",
"EUR",
"KZT",
"RUB"
];
function sanitizeCurrency(currency) {
const normalized = String(currency)
.trim()
.toUpperCase();
return ALLOWED_CURRENCIES.includes(normalized)
? normalized
: "USD";
}
Опасная практика:
new Intl.NumberFormat(locale, userOptions)
Пользователь способен передать:
{
style: "currency",
currency: "INVALID",
maximumFractionDigits: 999
}
function sanitizeNumberFormatOptions(options = {}) {
return {
style: sanitizeStyle(options.style),
currency: sanitizeCurrency(
options.currency
),
maximumFractionDigits:
sanitizeFractionDigits(
options.maximumFractionDigits
)
};
}
const ALLOWED_STYLES = [
"decimal",
"currency",
"percent",
"unit"
];
function sanitizeStyle(style) {
return ALLOWED_STYLES.includes(style)
? style
: "decimal";
}
function sanitizeFractionDigits(value) {
const number = Number(value);
if (!Number.isInteger(number)) {
return 2;
}
return Math.min(
Math.max(number, 0),
20
);
}
Если без проверки копировать пользовательский объект:
{
"__proto__": {
hacked: true
}
}
возможно загрязнение прототипа.
function safeObject(input) {
const result = Object.create(null);
for (const key of Object.keys(input)) {
result[key] = input[key];
}
return result;
}
Перед отправкой на сервер:
const payload = {
locale: sanitizeLocale(user.locale),
currency: sanitizeCurrency(user.currency),
timeZone: sanitizeTimeZone(user.timeZone)
};
function removeUndefined(obj) {
return Object.fromEntries(
Object.entries(obj)
.filter(([_, value]) =>
value !== undefined
)
);
}
Например:
[
"en-US",
"invalid",
null,
"ru-RU"
]
function sanitizeLocales(locales) {
if (!Array.isArray(locales)) {
return ["en-US"];
}
return locales
.map(sanitizeLocale)
.filter(Boolean);
}
Пример URL:
?locale=ru-RU¤cy=kzt
const params = new URLSearchParams(
location.search
);
const locale = sanitizeLocale(
params.get("locale")
);
const currency = sanitizeCurrency(
params.get("currency")
);
Любая санитизация должна возвращать предсказуемый результат.
Плохой вариант:
return null;
Хороший вариант:
return "en-US";
| Тип | Fallback |
|---|---|
| locale | "en-US" |
| currency | "USD" |
| timeZone | "UTC" |
| number | 0 |
| date | new Date() |
| unit | "kilometer" |
function sanitizeIntlConfig(config = {}) {
return {
locale: sanitizeLocale(
config.locale
),
currency: sanitizeCurrency(
config.currency
),
timeZone: sanitizeTimeZone(
config.timeZone
),
unit: sanitizeUnit(
config.unit
)
};
}
function formatPrice(value, config) {
const safeConfig =
sanitizeIntlConfig(config);
const amount =
sanitizeNumber(value);
return new Intl.NumberFormat(
safeConfig.locale,
{
style: "currency",
currency: safeConfig.currency
}
).format(amount);
}
Использование:
formatPrice("1,200.5", {
locale: " RU-ru ",
currency: "kzt"
});
Результат:
1 200,50 ₸
function safeIntl(callback, fallback) {
try {
return callback();
} catch {
return fallback;
}
}
const result = safeIntl(
() => {
return new Intl.DateTimeFormat(
locale,
options
).format(date);
},
"Invalid format"
);
Плохая практика:
options.currency = "USD";
Лучше:
return {
...options,
currency: "USD"
};
Типичная структура:
src/
├── intl/
│ ├── sanitizeLocale.js
│ ├── sanitizeCurrency.js
│ ├── sanitizeTimeZone.js
│ └── sanitizeOptions.js
export function sanitizeIntl(data) {
return {
locale: sanitizeLocale(data.locale),
currency: sanitizeCurrency(data.currency),
timeZone: sanitizeTimeZone(data.timeZone)
};
}
Плохо:
sanitizeLocale(locale)
sanitizeLocale(locale)
sanitizeLocale(locale)
Лучше:
const safeLocale =
sanitizeLocale(locale);
const localeCache = new Map();
function cachedLocale(locale) {
if (localeCache.has(locale)) {
return localeCache.get(locale);
}
const sanitized =
sanitizeLocale(locale);
localeCache.set(
locale,
sanitized
);
return sanitized;
}
Проверяются:
null;undefined;test("invalid locale fallback", () => {
expect(
sanitizeLocale("invalid")
).toBe("en-US");
});
Ошибка:
new Intl.NumberFormat(locale, options)
без обработки исключений.
Ошибка:
currency: userCurrency
без валидации.
Плохо:
if (currency !== "BAD")
Хорошо:
ALLOWED_CURRENCIES.includes(currency)
Ошибка:
return undefined;
Любое значение считается потенциально некорректным:
function format(value, locale) {
const safeLocale =
sanitizeLocale(locale);
const safeValue =
sanitizeNumber(value);
return new Intl.NumberFormat(
safeLocale
).format(safeValue);
}
Intl API защищаются от исключений;