支持IDN的工具用于编码/解码可读IRI与有效URI

3 投票
1 回答
2060 浏览
提问于 2025-04-15 22:42

假设用户输入了某个资源的地址,我们需要把它转换成:

<a href="valid URI here">human readable form</a>

HTML4的规范提到了一种叫做RFC 3986的标准,这个标准只允许在主机部分使用ASCII字母数字字符和短横线,而其他部分的非ASCII字符需要用百分号编码。这就是我想放在链接的href属性里,以确保在所有浏览器中链接都能正常工作。国际化域名(IDN)需要用Punycode进行编码。

HTML5草案提到的标准是RFC 3987,这个标准也允许在主机部分使用百分号编码的Unicode字符,并且在主机和其他部分都可以使用大量的Unicode字符而不需要编码。用户可以以这些形式输入地址。为了提供人类可读的形式,我需要解码所有可打印的字符。需要注意的是,地址的某些部分可能不对应有效的UTF-8序列,通常是因为目标网站使用了其他字符编码。

我想要得到的一个例子是:

<a href="http://xn--80aswg.xn--p1ai/%D0%BF%D1%83%D1%82%D1%8C?%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81">
http://сайт.рф/путь?запрос</a>

有没有什么工具可以解决这些问题?我特别想知道Python和JavaScript的库。

更新:我知道在Python和JavaScript中有办法进行百分号和Punycode编码/解码(虽然没有进行适当的规范化,但我可以接受)。整个任务需要更多的工作,并且有一些陷阱(某些字符在特定情况下应该总是编码或从不编码)。我在想是否有现成的库可以解决整个问题,因为这似乎是一个相当常见的需求,现代浏览器已经能做到这样的转换(试着在Google Chrome中输入http://%D1%81%D0%B0%D0%B9%D1%82.%D1%80%D1%84/,它会被替换为http://сайт.рф/,但在HTTP请求中使用Host: xn--80aswg.xn--p1ai)。

更新2:Vinay Sajip指出Werkzeug有处理大部分情况的iri_to_uri和uri_to_iri函数。我目前只发现了两个它处理不好的情况:百分号编码的主机(这个比较好修复)和无效的UTF-8序列(这个稍微复杂一点,但应该不成问题)。

我仍在寻找JavaScript的库。虽然写起来不难,但我更希望能避免重复造轮子。

1 个回答

2

如果我理解得没错的话,你可以使用Python自带的功能:

# -*- coding: utf-8 -*-

import urllib
import urlparse

URL1 = u'http://сайт.рф/путь?запрос'
URL2 = 'http://%D1%81%D0%B0%D0%B9%D1%82.%D1%80%D1%84/'

def to_idn(url):
    parts = list(urlparse.urlparse(url))
    parts[1] = parts[1].encode('idna')
    parts[2:] = [urllib.quote(s.encode('utf-8')) for s in parts[2:]]
    return urlparse.urlunparse(parts)

def from_idn(url):
    return urllib.unquote(url)

print to_idn(URL1)
print from_idn(URL2)
print to_idn(from_idn(URL2).decode('utf-8'))

这段代码会输出:

http://xn--80aswg.xn--p1ai/%D0%BF%D1%83%D1%82%D1%8C?%D0%B7%D0%B0%D0%BF%D1%80%D0%BE%D1%81
http://сайт.рф/
http://xn--80aswg.xn--p1ai/

看起来正是你想要的。我不太确定你提到的特殊情况是什么,能不能给一些你所说的陷阱的例子呢?

更新:我刚想起来,Werkzeug在0.6及以后的版本中有iri_to_uriuri_to_iri这两个函数(链接指向相关文档部分)。

进一步更新:抱歉,我之前没有注意到你也在寻找JavaScript的实现。现有的公共领域的JavaScript实现punycode可以在这里找到。不过我不能保证它的可靠性。当然,你也可以使用JavaScript自带的encodeURI/decodeURI API。

撰写回答