jQuery(form).serialize()失败,出现“ URIError:格式错误的URI序列”

时间:2019-04-13 00:28:30

标签: javascript jquery character-encoding urlencode

我有一个Web应用程序,可让用户对彼此的帖子发表评论。我们使用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 个答案:

答案 0 :(得分:4)

出于某种原因,some of your users试图提交包含我们可能称为“无效字符”的评论。保留从\uD800\uDFFF的Unicode代码点,以便UCS-2和UTF-16文本编码可以使用它们对来标识其他有效的Unicode字符代码点,否则它们将不在此范围内。 -range表示这些编码。对于大多数现代编码,包括UTF-16,这些代码点仅允许成对出现,并在转换为另一种编码时可以映射为有效的字符代码点。它们永远不能作为独立的“字符”存在。

不幸的是,JavaScript在UTF-16标准化之前选择了UCS-2,并且UCS-2 确实允许您自己包含替代字符,而无需配对以产生有效的代码点。因为JavaScript允许,所以浏览器也接受它作为输入。这很复杂,但是在大多数情况下,它实际上并不会像您所遇到的那样妨碍用户的使用。如果您的表单未使用JavaScript,则您的用户将能够提交包含未配对代理的注释,而不会出现错误。那怎么办?

浏览器采用一种通用的方法来编码不兼容性:将无法翻译为目标编码的任何字符替换为 Unicode替换字符\uFFFD。对典型表单数据进行编码时,浏览器会自动执行此替换。但是,jQuery.serialize()没有任何这种逻辑,它调用的内置encodeURIComponent函数也没有对表单值进行编码的逻辑。相反,它只会抛出您看到的URIError。您可以在ECMAScript 9规范的Section 18.2.6.1.1: Runtime Semantics: Encode中找到指定的错误。

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');