По той или иной причине некоторые из ваших пользователей пытаются отправить комментарии, содержащие то, что мы могли бы назвать "недопустимыми символами". Кодовые точки Unicode от \uD800
до \uDFFF
зарезервированы, чтобы текстовые кодировки UCS-2 и UTF-16 могли использовать их пары для идентификации других допустимых кодовых точек Unicode, которые в противном случае были бы вне допустимого диапазона для этих кодировок. Для большинства современных кодировок, включая UTF-16, эти кодовые точки допускаются только в допустимых парах, которые могут быть преобразованы в действительные кодовые точки символов при преобразовании в другое кодирование; они никогда не могут существовать как отдельные «персонажи».
К сожалению, JavaScript выбрал UCS-2 до стандартизации UTF-16, а UCS-2 не позволяет самостоятельно включать суррогатные символы, не создавая пары для получения правильной кодовой точки. Поскольку JavaScript это позволяет, браузеры также принимают его как ввод. Это осложнение, но в большинстве случаев это не так, как вы испытываете. Если ваша форма не использует JavaScript, ваши пользователи смогут без комментариев оставить комментарий, содержащий непарный суррогат. Как это будет работать?
Браузер применяет общий подход к несовместимости кодирования: любые символы, которые не могут быть переведены в целевую кодировку, заменяются �
заменяющим символом Unicode \uFFFD
. Браузер автоматически выполняет эту замену при кодировании типичных данных формы для отправки. Однако jQuery.serialize()
не имеет такой логики, как и встроенная функция encodeURIComponent
, которую он вызывает для кодирования значений формы. Вместо этого он просто выбрасывает URIError
, который вы видите. Вы можете найти эту ошибку, указанную в Раздел 18.2.6.1.1: Семантика времени выполнения: Encode
спецификации ECMAScript 9.
encodeURIComponent('\uD83D') // URIError: malformed URI sequence
Чтобы воспроизвести поведение браузероподобной формы в JavaScript, вам нужно найти и заменить любые случаи, когда в диапазоне от \uD800
до \uDBFF
встречается "высокий суррогат", а за ним не следует "низкий суррогат". \uDC00
до \uDFFF
или наоборот. Это может выглядеть примерно так:
const replaceUnpairedSurrogates = s => s
.replace(/[\uD800-\uDBFF]+([^\uDC00-\uDFFF]|$)/g, '�$1')
.replace(/(^|[^\uD800-\uDBFF])[\uDC00-\uDFFF]+/g, '$1�');
(Эта функция удовлетворяет «Ограничениям на процессы преобразования», требуемым стандартом Unicode, потому что она гарантирует, что следующие действительные символы не искажены заменой. Она не соответствует необязательному соглашению «Подстановка максимальных подчастей», потому что она может свернуть последовательные непарные суррогатные символы до одного заменяющего символа.)
В настоящее время вы используете jQuery.serialize(this)
для кодирования данных формы, что не позволяет нам преобразовывать значения формы до их кодирования. Но jQuery.serialize(this)
- это то же самое, что и jQuery.param(jQuery.serializeArray(this))
, что дает нам место для применения нашей замены:
jQuery(".post form.add-comment").on("submit", function(event) {
event.preventDefault();
const data = jQuery.param(
jQuery.serializeArray(this).map(
({name, value}) => {
name: replaceUnpairedSurrogates(name),
value: replaceUnpairedSurrogates(value),
})
)
);
jQuery.ajax({
type: "POST",
url: "/comment",
data: data
});
});
Ради тестирования вы можете запустить следующее, чтобы отобразить один "недопустимый символ" для копирования:
prompt('Copy this:', '\uD83D');