IDNA 解析器客户端






3.33/5 (3投票s)
使用非 ASCII 字符进行 DNS 名称查找。
引言
本文介绍了一个 IDNA 客户端的代码(国际化域名应用)。
随着互联网对社会的影响越来越大,以母语正确显示品牌名称等变得越来越重要。对大多数人来说,使用正确的名称而不是某个 ASCII 化版本来访问网页或发送电子邮件更加直接。而且,随着各种顶级域名(尤其是.COM)中的域名枯竭,支持包含非 ASCII 字符的名称的需求变得更加迫切。
大多数顶级域名现在都开放注册包含国际字符的域名。但你可能无法在丹麦域名.dk中注册一个包含匈牙利语重音字符的名称。而.com, .org, .net和.biz域名很可能接受 ISO-Latin 和所有东亚字符集。请咨询您的本地注册商了解详情。
出于多种原因,IETF 设计者希望将 UTF-8 或任何其他编码的字符串排除在域名协议(端口 53,RFC-1035)之外。主要是为了向后兼容现有的协议和 DNS 服务器。支持 IDNA 不需要任何新的服务器端软件。因此,需要纯粹的 US-ASCII 来表示 IDN(国际化域名)中的国家字符。于是他们提出了一个非常巧妙的方案,称为Punycode。详细信息请参阅 RFC-3490 和 RFC-3492。
题外话:Windows-2000/XP 会发送 UTF-8 编码的查询来处理 IDN(参见 RFC-2044 和 RFC-2181)。而 Windows/2003 Server 似乎支持 Stuart Kwan 的 草案。这些方法很可能无效,因为很少有 DNS 服务器直接支持 UTF-8。但对于 IE/OE 用户来说,有一个解决方法。请参阅下面的参考资料。
域名转换
为了查询 IDN 的名称服务器,必须将名称转换为 ACE(ASCII 兼容编码)。示例如下。假设您想解析主机名www.blåbærsyltetøy.no (www.blueberryjam.no(是的,这个名字确实存在)。
算法如下:
- 将名称拆分成其标签的各个部分;“www", "blåbærsyltetøy”和“否".
- 对所有标签执行以下操作:
- 如果标签包含非 ASCII 字符(即 0x80 ... 0xFF),则使用 Punycode 将标签转换为 ACE 格式。
转换结果为“blbrsyltety-y8aO3x".
”并在前面加上一个“xn--”标签。因此变为“xn--blbrsyltety-y8aO3x".
- 如果标签包含非 ASCII 字符(即 0x80 ... 0xFF),则使用 Punycode 将标签转换为 ACE 格式。
- ”将所有转换后的标签拼接在一起,并为生成的名称发送查询。
即,将“www.xn--blbrsyltety-y8aO3x.no”传递给
gethostbyname()
。
可以看到,转换后的名称比原始名称长。我的代码使用固定大小的缓冲区,并保守地猜测最大可能的结果大小。MAX_HOST_LEN
的两倍(2*256)应该足以满足大多数名称。我不知道将东亚字符转换为 ACE 会发生什么。例如,Big5、GBK 或 Shift-JIS。我无法轻松测试这一点,因为我的 Windows 机器不支持这些编码。
使用代码
转换函数的公共接口在 idna.h 中。punycode.* 文件仅供内部使用。punycode.* 直接来自 RFC-3492,并针对 Windows 进行了一些改编。getopt.* 仅由演示源代码文件用于解析命令行。我选择不使其成为纯 C++ 实现,因为那样会排除 C 用户。它可以从任何类型的 Windows 程序(基于 MFC、控制台模式等)调用。后续版本可能会包含一个选项,除了 Windows 的 NLS 函数外,还使用 libiconv
。
主函数
IDNA_init()
BOOL IDNA_init (WORD code_page);
使用请求的 code_page
初始化 IDNA 转换器。此值可以为 0,以使用系统的默认 ANSI 代码页(CP_ACP
)。如果使用 C++ 包装类,目前无法指定其他代码页。
IDNA_convert_to_ACE()
BOOL IDNA_convert_to_ACE (char *name, size_t *size);
尝试使用 IDNA_init()
中指定的代码页将 name
转换为 ACE 格式。name
指向要转换的缓冲区。输入时 *size
必须指定要转换的缓冲区的最大大小。输出时 *size
会告知 ACE 转换后缓冲区的实际大小。
注意:如果 name
只包含 US-ASCII(小于或等于0x7F),则不进行转换,name
将保持不变。
IDNA_convert_from_ACE()
BOOL IDNA_convert_from_ACE (char *name, size_t *size);
尝试使用 IDNA_init()
中指定的代码页将 name
转换为字符串。输入时 *size
必须指定要转换的缓冲区的最大大小。输出时 *size
会告知 ACE 到 ASCII 转换后缓冲区的实际大小。
注意:如果 name
不包含带有“xn--”前缀的标签,则不进行转换,name
将保持不变。
IDNA_strerror()
返回指定错误号的错误字符串。在大多数情况下是 _idna_errno
。
状态码
上述函数(IDNA_strerror()
除外)成功时返回 TRUE
,任何错误时返回 FALSE
(这并不奇怪)。使用 IDNA_strerror(_idna_errno)
来检查原因。如果 _idna_errno == IDNAERR_WINNLS
,则错误发生在 WinNLS 函数之一中。使用 GetLastError()
获取错误。_idna_errno
在后续成功调用时不会被清除。
C++ 包装类
CIDNA_resolver
类是 C 代码实现的简单包装器。使用它类似于 ::gethostbyname()
函数。
最小示例
CIDNA_resolver idna;
struct hostent *he = idna.gethostbyname (name);
如果您想知道所提供的 name
的 ACE 名称,可以从 he->h_aliases[0]
中提取(当然,前提是 he
非 NULL
)。这会擦除原始的别名(如果存在)。但这是我当时能做的最好的。
CIDNA_resolver::gethostbyaddr()
用于将 IPv4 地址转换为您的代码页中的名称。我没有找到任何带有 PTR 记录的 ACE 域名,因此此函数仅通过 hosts 文件进行了测试。
随附的 Makefile 支持 MSVC、MingW 和 CygWin。执行以下其中一个命令
make msvc
make gcc
分别使用 Visual-C 或 MingW/CygWin 来构建演示。是的,Makefile 需要 GNU 的 make(因为我厌倦了 nmake 的限制)。我太懒了,无法提供 VC6/7 项目文件(因为我沉迷于 MingW)。将代码添加到您的项目中应该很简单,只需将 punycode.* 和 idna.* 添加到您的项目中即可。该代码仅在 Win-XP 上进行了测试。如果它在您的操作系统上不起作用,我很乐意听取原因。但首先,请使用完全调试模式运行一个演示程序。例如,“demo-1-vc.exe -c850 -dddd www.tromsø.no”,并仔细研究打印出的跟踪输出。
考虑因素
一些在应用程序层公开主机名的协议会遇到 IDN 的问题。最值得注意的是 HTTP 1.1。如果您尝试获取 URL http://www.blåbærsyltetøy.no/some/document,并且您能够解析它(通过 hosts 文件等),则会发送以下内容:
GET /some/document HTTP/1.1
User-Agent: whatever
Host: www.blåbærsyltetøy.no
如果 Web 服务器托管多个域,这很可能不起作用。它根本不会将“宿主”标头行与它托管的域匹配。因此,应用程序应该改用主机名的 ACE 格式发送。
GET /some/document HTTP/1.1
User-Agent: whatever
Host: www.xn--blbrsyltety-y8aO3x.no
至少 Apache 1.3 会根据发送的 Host 标头返回不同的结果。在这些情况下,使用 HTTP 1.0 可能是一种解决方案,但我尚未对此进行测试(并且也不是非常理解虚拟服务器)。随着 IDN 的普及,可以预期此类问题会更频繁地出现。
DNS 系统在匹配普通域名时通常不区分大小写。IDNA 的情况并非如此。ACE 格式的域名通常只以小写形式存储。例如,DNS 系统在 www.tromsø.no 的A记录
www.xn--troms-zuA.no. IN A 195.159.151.136
中有一个条目。为了处理大写形式 www.tromsØ.no,也会存储一个名为“www.xn--troms-ipA.no”的 A 记录。ACE 格式的组合(以及 MX 记录)将太多,因此通常不这样做。
注意:此代码不会对正确显示转换后的名称(在 IDNA_convert_from_ACE
中)做任何事情。它只是假设您正在为使用的任何代码页使用正确的字体。demo-1.c 有一个代码页(-c
)选项。使用它进行实验或使用适当的代码页调用 IDNA_init()
。
参考文献
- Windows 5.x 中的 DNS 命名限制.
- 在线 IDN 转换工具.
- 一些执行与我的代码相同或更多功能的库
- GNU 的 libidn.
- IBM 的 icu 库.
- VeriSign 的 IDN SDK.
但这些对于如此相对简单的任务来说是过度了。它们需要大量的字符表数据,而 libidn 仅假设您已安装 libiconv。Windows 用户最好使用内置的 NLS 函数(
MultiByteToWideChar()
和WideCharToMultiByte()
等)。
- VeriSign 的 i-Nav 插件,适用于 Internet Explorer 和 Outlook.
我没有尝试过,但据称 Mozilla 7.1+、FireFox 0.8+ 和 Opera 7.20+ 直接支持 IDN。只有 Opera 7.22+ 中的邮件客户端能正确处理 IDN。也就是说,根据需要转换为 ACE 格式或从 ACE 格式转换。