<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>jjgod / blog &#187; NSString</title>
	<atom:link href="http://blog.jjgod.org/tag/nsstring/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.jjgod.org</link>
	<description>Random notes &#38; thoughts by Jiang Jiang.</description>
	<lastBuildDate>Mon, 16 Jan 2012 11:08:59 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>Cocoa 的 NSString 解码错误处理</title>
		<link>http://blog.jjgod.org/2008/02/17/cocoa-nsstring-decoding-error/</link>
		<comments>http://blog.jjgod.org/2008/02/17/cocoa-nsstring-decoding-error/#comments</comments>
		<pubDate>Sat, 16 Feb 2008 22:37:21 +0000</pubDate>
		<dc:creator>jjgod</dc:creator>
				<category><![CDATA[Mac]]></category>
		<category><![CDATA[Programming]]></category>
		<category><![CDATA[ASCII]]></category>
		<category><![CDATA[cocoa]]></category>
		<category><![CDATA[NSString]]></category>
		<category><![CDATA[Safari]]></category>
		<category><![CDATA[UTF-8]]></category>

		<guid isPermaLink="false">http://blog.jjgod.org/2008/02/17/cocoa-nsstring-decoding-error/</guid>
		<description><![CDATA[在使用 Safari 的时候，我们会注意到一个很常见的乱码问题，如下图: 这是在打开 http://att.newsmth.net/att.php?p.719.214628.536.png 这样的图片链接时，Safari 错误的判断了这个图片文件的文件名造成的。而为什么会有这样的错误判断呢？ 其实 Safari 使用的是 Cocoa 框架 URL Loading 架构中的 NSURLResponse 类的 suggestedFilename 方法实现的。 而这个方法，其实就是解析 HTTP 首部中的 Content-Disposition 域里的 filename 部分完成的，比如下面这个首部: $ curl -I http://att.newsmth.net/att.php?p.719.214628.536.png HTTP/1.1 200 OK .... Content-Disposition: inline;filename=&#38;#892;&#38;#428;_6.png .... 显然这是乱码，可奇怪的是，这和我们在上面的图中看到的乱码又不一样，这是为什么呢？ 假如将它作为 GBK 来解码就清楚了: $ curl -I http://att.newsmth.net/att.php?p.719.214628.536.png &#124; iconv -f gbk -t utf-8 HTTP/1.1 200 OK .... [...]]]></description>
			<content:encoded><![CDATA[<p>在使用 <a href="http://www.apple.com/safari">Safari</a> 的时候，我们会注意到一个很常见的乱码问题，如下图:</p>

<p><img src="http://jjgod.org/document/images/safari-decode-error.png" alt="Safari Decode Error" /></p>

<p>这是在打开 <a href="">http://att.newsmth.net/att.php?p.719.214628.536.png</a> 这样的图片链接时，Safari 错误的判断了这个图片文件的文件名造成的。而为什么会有这样的错误判断呢？</p>

<p>其实 Safari 使用的是 <a href="http://developer.apple.com/cocoa">Cocoa</a> 框架 <a href="http://developer.apple.com/documentation/Cocoa/Conceptual/URLLoadingSystem/URLLoadingSystem.html">URL Loading</a> 架构中的 NSURLResponse 类的 <a href="http://developer.apple.com/documentation/Cocoa/Reference/Foundation/Classes/NSURLResponse_Class/Reference/Reference.html#//apple_ref/occ/instm/NSURLResponse/suggestedFilename">suggestedFilename</a> 方法实现的。</p>

<p>而这个方法，其实就是解析 HTTP 首部中的 <a href="http://www.ietf.org/rfc/rfc2183.txt">Content-Disposition</a> 域里的 filename 部分完成的，比如下面这个首部:</p>

<pre><code>$ curl -I http://att.newsmth.net/att.php?p.719.214628.536.png
HTTP/1.1 200 OK
....
Content-Disposition: inline;filename=&amp;#892;&amp;#428;_6.png
....
</code></pre>

<p>显然这是乱码，可奇怪的是，这和我们在上面的图中看到的乱码又不一样，这是为什么呢？</p>

<p>假如将它作为 GBK 来解码就清楚了:</p>

<pre><code>$ curl -I http://att.newsmth.net/att.php?p.719.214628.536.png | iconv -f gbk -t utf-8
HTTP/1.1 200 OK
....
Content-Disposition: inline;filename=图片_6.png
....
</code></pre>

<p>哦，原来是 GBK 编码的“图片_6.png”，可是这个文件名怎么会变成开头图片中那种形式的乱码呢？其实写一段 Cocoa 程序就可以发现:</p>

<pre><code>#import &lt;Foundation/Foundation.h&gt;

int main()
{
    const char *bytes = "图片_6.png";
    NSString *str = [[NSString alloc] initWithCString: bytes
                                             encoding: NSASCIIStringEncoding];
    NSLog(@"str: %@", str);
    [str release];

    return 0;
}
</code></pre>

<p>(用 GBK 编码保存) 这个程序的执行结果就是输出开头那段乱码，原来 NSURLResponse 把 Content-Disposition 中的 filename 当成 ASCII 处理了，怪不得会乱码。</p>

<p>可是也不能怪 NSURLResponse，毕竟服务器没有提供任何编码的信息，而 RFC 2183 中也明确说明，不应该在 filename 中使用任何 ASCII 以外的字符，用了就是后果自负了。</p>

<p>那假如我们要写一个自己的客户端 (或者尝试修正 Safari 的错误行为)，该怎么修正已经被按照 ASCII 错误解码的 NSString 呢？</p>

<p>因为 NSString 本身是按照 UTF-16 编码的，所以如果逐个字符地观察这个错误解码后的 NSString:</p>

<pre><code>int max = [str length];

int i;
for (i = 0; i &lt; max; i++)
{
    unichar ch = [str characterAtIndex: i];
    printf("%x ", ch);
}
</code></pre>

<p>我们可以得到:</p>

<pre><code>cd bc c6 ac 5f 36 2e 70 6e 67
</code></pre>

<p>这样一串输出，<code>5f 36 2e 70 6e 67</code> 就是 <code>_6.png</code>，比较好认，前面的 <code>cd bc c6 ac</code> 是什么呢？一查，原来是“图”和“片”这两个字的 GBK 编码。</p>

<p>这就好理解了：NSString 一开始把一段 GBK 编码的字节流<em>逐个字节地</em>按照 8bit-ASCII 处理了，原本 10 个字节对应的是 7 个字符，结果被错误地解码为了 10 个字符，所以我们要把它转换回去，首先是要还原回原来的那段字节流:</p>

<pre><code>int max = [str length];
char *nbytes = malloc(max + 1);

int i;
for (i = 0; i &lt; max; i++)
{
    unichar ch = [str characterAtIndex: i];
    nbytes[i] = (char) ch;
}
nbytes[i] = '\0';
</code></pre>

<p>然后再将这段字节流按照正确的编码 (GB18030) 处理:</p>

<pre><code>NSStringEncoding enc = CFStringConvertEncodingToNSStringEncoding(
    kCFStringEncodingGB_18030_2000);

NSLog(@"nstr: %@", [NSString stringWithCString: nbytes
                                      encoding: enc]);
</code></pre>

<p>结果果然得到了正确的输出:</p>

<pre><code>2008-02-17 06:09:46.408 test[14095:10b] nstr: 图片_6.png
</code></pre>

<p>同样的逻辑可以用在很多类似的乱码情形中。完整的代码可以在这里下载: <a href="http://jjgod.org/code/test-str-encoding.m">test-str-encoding.m</a></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.jjgod.org/2008/02/17/cocoa-nsstring-decoding-error/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>

