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

问题描述 投票:4回答:1

我有一个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

此错误似乎阻止将注释发送到我们的服务器,因此我们无法分辨用户尝试发布的可能导致此错误的内容。

可能导致此错误发生的原因,我们如何防止它?

javascript jquery character-encoding urlencode
1个回答
4
投票

出于这样或那样的原因,some of your users正在尝试提交包含我们可能称之为“无效字符”的评论。从\uD800\uDFFF的Unicode代码点是保留的,因此UCS-2和UTF-16文本编码可以使用它们的对来识别其他有效的Unicode字符代码点,否则这些代码点将超出这些编码的范围。对于大多数现代编码,包括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');
© www.soinside.com 2019 - 2024. All rights reserved.