【问题标题】:Canonicalize URL to lowercase without breaking file system or culture?在不破坏文件系统或文化的情况下将 URL 规范化为小写?
【发布时间】:2012-01-24 08:00:04
【问题描述】:

将 URL 规范化为小写

我希望编写一个将 URL 转换为小写的 HTTP 模块。我的第一次尝试忽略了国际字符集并且效果很好:

// Convert URL virtual path to lowercase
string lowercase = context.Request.FilePath.ToLowerInvariant();

// If anything changed then issue 301 Permanent Redirect
if (!lowercase.Equals(context.Request.FilePath, StringComparison.Ordinal))
{
    context.Response.RedirectPermanent(...lowercase URL...);
}

土耳其测试(国际文化):

但是除了美国以外的文化呢?我参考了Turkey Test 想出了一个测试网址:

http://example.com/Iıİi

这个阴险的小宝石破坏了 URL 中大小写转换很简单的任何观念!它的小写和大写版本分别是:

http://example.com/ııii
http://example.com/IIİİ

为了使用土耳其语 URL 进行大小写转换,我首先必须将 ASP.NET 的当前文化设置为土耳其语:

<system.web>
    <globalization culture="tr-TR" />
</system.web>

接下来,我必须更改我的代码以使用当前文化进行大小写转换:

// Convert URL virtual path to lowercase
string lowercase = context.Request.FilePath.ToLower(CultureInfo.CurrentCulture);

// If anything changed then issue 301 Permanent Redirect
if (!lowercase.Equals(context.Request.FilePath, StringComparison.Ordinal))
{
    context.Response.RedirectPermanent(...);
}

但是等等! StringComparison.Ordinal 还能用吗?还是我应该使用StringComparison.CurrentCulture?我真的不确定!

文件名:变得更糟了!

即使上述方法有效,使用当前区域性进行大小写转换会破坏 NTFS 文件系统!假设我有一个名为 Iıİi.html 的静态文件:

http://example.com/Iıİi.html

即使 Windows 文件系统不区分大小写,它也不使用语言文化。将上述 URL 转换为小写会导致 404 Not Found,因为文件系统不认为这两个名称相等:

http://example.com/ııii.html

文件名的正确大小写转换?谁知道?!

MSDN 文章Best Practices for Using Strings in the .NET Framework 有注释(大约在文章中途):

注意: 文件系统、注册表项和值以及环境变量的字符串行为最好用 StringComparison.OrdinalIgnoreCase 表示。

嗯? 最好的表现???这是我们在 C# 中能做到的最好的吗?那么,匹配文件系统的正确大小写转换是什么? 谁知道?!!?我们只能说,使用上面的字符串比较可能在大多数情况下都有效。

总结:两种情况转换:静态/动态网址

  1. 所以我们已经看到 静态 URLs---文件路径与文件系统中的真实目录/文件匹配的 URL---必须使用仅“最好的代表“由StringComparison.OrdinalIgnoreCase。请注意,没有string.ToLowerOrdinal() 方法,因此很难确切知道什么大小写转换等同于OrdinalIgnoreCase 字符串比较。使用string.ToLowerInvariant() 可能是最好的选择,但它破坏了语言文化。
  2. 另一方面,动态网址---文件路径与磁盘上的真实文件(映射到您的应用程序)不匹配的网址---可以使用string.ToLower(CultureInfo.CurrentCulture) ,但它会破坏文件系统匹配,而且还不清楚存在哪些边缘情况可能会破坏此策略。

因此,大小写转换似乎首先需要检测 URL 是静态还是动态,然后再选择两种转换方法之一。对于静态 URL,不确定如何在不破坏 Windows 文件系统的情况下更改大小写。对于动态 URL,如果使用区域性进行大小写转换同样会破坏 URL,则值得怀疑。

哇!有人有解决这个烂摊子的办法吗?还是我应该闭上眼睛假装一切都是ASCII?

【问题讨论】:

  • 据我了解,小写/大写转换的安全案例可能只是基本的拉丁字母。 Unicode 包括许多语言,有些甚至可能没有大写字母之类的东西。这样,您可能只能对 127 以上的任何代码点使用完全匹配。
  • @John 转换基本拉丁语将是一个保守的解决方案,但 .NET 没有提供仅影响这些字符的 .ToLowerASCII().ToLowerInvariant() 走得更远,摧毁了许多国际角色。我确定目前没有 100% 的解决方案。
  • @KevinR 考虑尝试手动枚举文件并使用Accept-Language 中的值? String.ToLower 当您使用它进行比较时,它也是一个令人讨厌的蠕虫包。让我弄清楚如何进行不区分大小写的文化比较。
  • @JonathanDickinson 我没有使用Accept-LanguageCurrentCulture 仅指服务器端应用程序的配置(除非您制作该轨道 Accept-Language)。人们会期望将具有土耳其语 URL 的服务器设置为该文化,尽管我认为在一台机器上可能存在多个文化 URL 的噩梦场景。也许 John 只转换 ASCII 字符是对的。
  • @KevinR 我会避免每个 URL 文化都具有文化意识。即使它是用英语编写的,用户可能仍然是土耳其人,并且会应用他们自己的大小写规则。在这种情况下,当前用户胜过创建者。

标签: c# asp.net winapi


【解决方案1】:

我将挑战这里的前提,即尝试将 URL 自动转换为小写的任何实用程序。

完整的 URL 是否区分大小写完全取决于 Web 服务器、Web 应用程序框架和底层文件系统。

您只保证在 URL 的方案(http:// 等)和主机名部分不区分大小写。请记住,并非所有 URL 方案(例如 filenews)都包含主机名。

服务器的其他所有内容都可以区分大小写,包括路径 (/)、文件名、查询 (?)、片段 (#) 和权限信息(@ 之前的用户名/密码在mailtohttpftp 和其他一些方案中)。

【讨论】:

  • 我已经强烈考虑过这个前提。然而,SEO 最佳实践鼓励使用单个小写 URL 以及对无意的大写等效项的响应。必须有一个令人愉快的中间立场。请注意,我只处理路径,而不是 URL 的任何其他部分。此外,在我的测试中,片段 (#) 不会传输到服务器,而是包含在用户代理中。
  • 作为一个法国人(这些奇怪的人之一,他们使用很多重音字符,甚至更多异国情调的字符作为“ç”),我一点也不惊讶有没有自动转换为 ASCII 的 URL小写。我想土耳其人也会有同样的感觉,所以拥有这个功能没有什么意义,尤其是因为你详述的所有原因,把它做好真的很痛苦。
  • @Falanwe 让我明确一点,我并不提倡将重音字符转换为纯 ASCII。让我问一下:您是否更喜欢法语 URL 来响应大写和小写的法语重音等效项? StackOverflow 进行小写规范化。 W.W.S.O.D.?
  • 作为法国人,我习惯于让 url 不带重音,不大写。事实上,当一个网站对重音字符做出响应时,我往往会感到惊讶!特殊字符通常被编码成 %number,这会造成一些难看的 url。
  • @KévinR:我希望fleurs.fr/EXPÉDITIONfleurs.fr/expéditionfleurs.fr/expedition 的别名。但是如果由于编码而转向fleurs/exp%C3%A9dition,我不会感到惊讶。这就是为什么法国人不习惯在 URL 中使用重音符号(并且几乎不会大写重音字符,因为我们几乎不使用它们:要制作“É”,我必须复制粘贴它,因为它不在我键盘上的任何位置,而我没有'不知道它的 utf8 编号 ^^)
【解决方案2】:

你有一些不相容的目标。

  1. 具有区分区域性的小写字母。如果土耳其语看起来不好,你不想知道一些格鲁吉亚文字,别介意ß要么大写成SS,要么不常见到SZ——在任何一种情况下都有一个完整的大小写-folding where lower("ß") 将匹配 lower(upper("ß")) 您需要认为它至少等同于那些两个字符序列中的一个。一般来说,如果可能,我们的目标是折叠而不是降低大小写(这里不可能)。

  2. 在非文化敏感的上下文中使用它。 URI 最终是不透明的字符串。他们可能具有人类可读的理解对于编码人员、用户、搜索引擎和营销人员等都是有用的,但他们的最终工作是通过直接区分大小写的比较来识别资源。

  3. 将此映射到 NTFS,它具有基于 $UpCase 文件中映射的保留大小写敏感性,它通过比较 大写 形式的单词 (至少它不必以文化不敏感的方式决定 Σ 是小写到 σ 还是 ς

  4. 大概在 SEO 和人类可读性方面做得很好。这很可能是您最初目标的一部分,但是虽然这不是很容易阅读或解析它,但对于人和机器来说都比这更容易。折叠案例会丢失信息。

我建议一种不同的方法。

  1. 从您的起始字符串开始,无论它是什么以及它来自何处(NTFS 文件名、数据库条目、web.config 中的 HttpHandler 绑定)。把它作为你的规范形式。无论如何都有规则,人们应该根据某种规范形式创建这些字符串,并可能在可能的地方强制执行它,但是如果有什么东西违反了你的规则,那么不管有多少,都接受它作为该资源的官方规范名称你不喜欢它。

  2. 规范名称应该尽可能是外界“看到”的唯一名称。这可以通过编程方式强制执行,也可以仅作为最佳实践执行,因为使用 301 进行事后规范化并不能解决外部实体在取消引用 URI 之前不知道您这样做的事实。

    李>
  3. 收到请求后,根据它的使用方式对其进行测试。因此,虽然您可以选择使用(或不使用)特定的文化来处理您自己执行资源查找的情况,使用所谓的“静态”URI,您的逻辑可以通过简单地使用 NTFS 来故意遵循 NTFS 的逻辑来执行工作:

    1. 暂时忽略区分大小写的问题查找映射文件。
    2. 如果不匹配则 404,谁在乎大小写?
    3. 如果找到,则进行区分大小写的序数比较,如果不匹配则 301 到区分大小写的映射。
    4. 否则,照常进行。

编辑:

在某些方面,域名问题更为复杂。 IDN 的规则必须以更小的回旋余地来涵盖更多问题。但是,至少就案例规范化而言,它也更简单。

(我将忽略是否使用 www. 等的规范化。虽然我猜它在这里是同一项工作的一部分,但它正在推动范围,如果我们最终可能会在我们之间写一本书我们不会停在某个地方:)

IDN 在 RFC 3491 中定义了自己的大小写规范化(以及一些其他形式的规范化)规则。如果您要根据大小写规范化域名,请遵循该规则。

回答起来很简单,不是吗? :)

在某种程度上压力也较小,因为虽然搜索引擎必须认识到 http://example.net/thisisapathhttp://example.net/thisIsAPath 可能是同一个资源,但他们也必须认识到它们可能不同,这就是所有 SEO对其中一个进行规范化的优势(不管是哪一个)来自。

但是,他们知道 example.netEXAMPLE.NET 不可能是不同的站点,因此确保它们相同几乎没有 SEO 优势(对于缓存和历史列表之类的东西仍然很好自己跳)。当然,问题仍然在于 www.example.net 甚至 maAndPasExampleEmporium.us 可能是同一个站点,但同样,这与案例问题无关。

还有一个简单的问题,大多数时候我们不必处理超过几十个不同的域,所以有时更努力而不是更聪明地工作(即,只要确保它们都设置正确并且不要' t 以编程方式做任何事情!)可以解决问题。

最后一点,重要的是不要规范化第三方 URI。如果您更改路径(他们可能不会不区分大小写),您最终可能会破坏事物,并且您至少可能最终会破坏它们略有不同的规范化。最好始终保持原样。

【讨论】:

  • 确实,在追求精确解决方案时,目标不兼容变得很明显。有趣的是,ASCII 字符集中不存在不兼容性,这使得目标看起来很容易实现。我的意图是生成代码以规范化主机名(www 和 .org/net/us)、字母大小写、斜杠等,主要用于 SEO。可以禁用个别功能。您建议的方法和适度规范化的组合似乎可以合理地为不良传入链接、CAPS-LOCK 打字员、规范域等提供 SEO 和 404 避免。不是吗?我喜欢你的论点。谢谢。
  • 由于 ASCII 在处理此类情况时的局限性(除非您使用一些过时的控制代码,这会将它们带回来等等),因此 ASCII 中缺乏不兼容的大部分存在。我可以想到一个例子,nathairNATHAIR 在爱尔兰语中的意思是 snake,但 nAthair 是“父亲” ",因为它会出现在某些上下文中(大写为 nATHAIRN-ATHAIR)。我忽略了有不同问题的域名。有时间我会再补充。
  • 你世间的知识最能说明问题。我期待您的域名 cmets。通常,只需将多个 TLD 重定向到“.com”即可。我非常喜欢 S.O. 提供唯一文章 ID 后跟 SEO 标题的方法,这对于访问页面并不重要。无论您在文章 ID 后键入什么内容,都会找到正确的页面。然而,S.O.仍然重定向到规范 URL。我很好奇什么 S.O.与任何非 ASCII 字符一样,因为实现了小写。
【解决方案3】:

首先从不使用案例转换来比较字符串。它不必要地分配了一个字符串,它对性能产生了不必要的小影响,如果值为 null,可能会导致 ObjectReferenceException 并且可能导致不正确的比较。

如果这对您来说足够重要,我将手动遍历文件系统并使用您自己的比较来与每个文件/目录名称进行比较。您应该能够使用Accept-LanguageAccept-Encoding(如果它包含文化)HTTP 标头来找到要使用的合适文化。拥有CultureInfo 后,您可以使用它来执行字符串比较:

var ci = CultureInfo.CurrentCulture; // Use Accept-Language to derive this.
ci.CompareInfo.Compare("The URL", "the url", CompareOptions.IgnoreCase);

我只会在 HTTP 404 上执行此操作; HTTP 404 处理程序将搜索匹配的文件,然后对用户进行 HTTP 301 到正确大小写的 URL(因为手动文件系统遍历可能会很昂贵)。

【讨论】:

  • 大小写转换代表规范路径。没有案例转换,就没有什么可比的;并且,如果比较不匹配,则大小写转换是用于构建 301 URL 的字符串。如果没有大小写转换,唯一的其他选择是迭代 URL 路径字符以检查 char.IsUpper()。如果你发现了一个真实的案例,你仍然必须改变这个案例。
  • @KevinR 我知道您的文件在磁盘上(而不是数据库记录)——这使得第二段的第一句话非常重要。您的规范路径是文件系统描述的路径。
  • >>“您的规范路径是文件系统描述的路径”是的,我已经想到了。我可以对文件系统的字母大小写 301 或遵循鼓励小写并忽略文件系统的 SEO 实践。或者,可以编写一个将磁盘上的所有文件名转换为小写的应用程序。我有静态文件和动态 URL 的应用程序,因此 !File.Exists() 会触发动态 URL 大小写规则。
  • @KevinR 不要使用 File.Exists() 根据 URL 手动迭代文件并使用 CompareInfo.Compare - 这就是我的意思。
猜你喜欢
  • 1970-01-01
  • 2010-11-04
  • 1970-01-01
  • 1970-01-01
  • 2017-11-18
  • 2020-04-06
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多