65.9K
CodeProject 正在变化。 阅读更多。
Home

IDNA 解析器客户端

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.33/5 (3投票s)

2004年3月31日

BSD

7分钟阅读

viewsIcon

107362

downloadIcon

849

使用非 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-3490RFC-3492

题外话:Windows-2000/XP 会发送 UTF-8 编码的查询来处理 IDN(参见 RFC-2044RFC-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".

  • ”将所有转换后的标签拼接在一起,并为生成的名称发送查询。

    即,将“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] 中提取(当然,前提是 heNULL)。这会擦除原始的别名(如果存在)。但这是我当时能做的最好的。

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ø.noA记录

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()

参考文献

© . All rights reserved.