Ошибка jQuery (form) .serialize () с «URIError: неверно сформированная последовательность URI» - PullRequest
4 голосов
/ 13 апреля 2019

У меня есть веб-приложение, которое позволяет пользователям комментировать посты друг друга.Мы используем jQuery.ajax() для отправки новых комментариев на сервер, и в нашем тестировании это работает надежно.

jQuery(".post form.add-comment").on("submit", function(event) {
  event.preventDefault();
  jQuery.ajax({
    type: "POST",
    url: "/comment",
    data: jQuery(this).serialize()
  });
});

Однако мы автоматически собираем клиентские журналы ошибок JavaScript от наших пользователей (используя Sentry), и иногда возникают ошибки, которые выглядят так:

URIError: malformed URI sequence jquery.min.js:4:25041

Эта ошибка, похоже, препятствует отправке комментариев на наш сервер, поэтому мы не можем сказать, что пользователи пытались опубликовать, что могло бывызвали эту ошибку.

Что может быть причиной возникновения этой ошибки и как мы можем предотвратить ее?

1 Ответ

4 голосов
/ 13 апреля 2019

По той или иной причине некоторые из ваших пользователей пытаются отправить комментарии, содержащие то, что мы могли бы назвать "недопустимыми символами". Кодовые точки 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');
...