代码改变世界

iOS URL 编码

2017-01-08 01:26  v2m  阅读(773)  评论(0编辑  收藏  举报

一、iOS 中的NSURL编码

iOS 中,NSURL 的基本样式是

scheme://username:password@host:port/path?query#fragment

RFC 1738规定:

Thus, only alphanumerics, the special characters "$-_.+!*'(),", and

reserved characters used for their reserved purposes may be used

unencoded within a URL.

On the other hand, characters that are not required to be encoded

(including alphanumerics) may be encoded within the scheme-specific

part of a URL, as long as they are not being used for a reserved

purpose.

所以对一些不符合规定的字符我们需要进行编码。就是“URL编码”,也叫“百分号编码”。一般都是把不符合的字符转成UTF-8编码,然后每两位(2 BYTE)前面加上‘%’。

在以前,我们经常使用下面的函数去排除 @"!*'();😡&=+$,/?%#[]" 这些字符,

- (nullable NSString *)stringByAddingPercentEscapesUsingEncoding:(NSStringEncoding)enc;
CF_EXPORT CFStringRef CFURLCreateStringByAddingPercentEscapes(CFAllocatorRef allocator, CFStringRef originalString, CFStringRef charactersToLeaveUnescaped, CFStringRef legalURLCharactersToBeEscaped, CFStringEncoding encoding);

现在这两个函数已经 deprecate 了,建议使用新的方法

- (nullable NSString *)stringByAddingPercentEncodingWithAllowedCharacters:(NSCharacterSet *)allowedCharacters NS_AVAILABLE(10_9, 7_0);

文档上说这个函数默认使用 UTF-8 编码;而且可以根据特定的url 部分,使用不同的NSCharacter(如果字符虽然在allowedCharacters里面但是超过了 7-bit ASCII 范围,也会转码)。
上面的不同字符集指的是:

@property (class, readonly, copy) NSCharacterSet *URLUserAllowedCharacterSet NS_AVAILABLE(10_9, 7_0);
@property (class, readonly, copy) NSCharacterSet *URLPasswordAllowedCharacterSet NS_AVAILABLE(10_9, 7_0);
@property (class, readonly, copy) NSCharacterSet *URLHostAllowedCharacterSet NS_AVAILABLE(10_9, 7_0);
@property (class, readonly, copy) NSCharacterSet *URLPathAllowedCharacterSet NS_AVAILABLE(10_9, 7_0);
@property (class, readonly, copy) NSCharacterSet *URLQueryAllowedCharacterSet NS_AVAILABLE(10_9, 7_0);
@property (class, readonly, copy) NSCharacterSet *URLFragmentAllowedCharacterSet NS_AVAILABLE(10_9, 7_0);

二、NSURL 相关 NSCharacterSet

一个疑问是上面的 CharacterSet 到底包含了哪些字符,思路就是根据下面两个函数

- (BOOL)hasMemberInPlane:(uint8_t)thePlane;
- (BOOL)longCharacterIsMember:(UTF32Char)theLongChar;

先判断 set 是不是有 plane 里面的字符,然后再遍历 plane 里面的字符是不是在 set 里面(参考5)。最后得到了如下结果

URLUserAllowedCharacterSet 
[!$&\'()*+,-.0123456789;=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~]

URLPasswordAllowedCharacterSet 
[!$&\'()*+,-.0123456789;=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~]

URLHostAllowedCharacterSet 
[!$&\'()*+,-.0123456789:;=ABCDEFGHIJKLMNOPQRSTUVWXYZ[]_abcdefghijklmnopqrstuvwxyz~]

URLPathAllowedCharacterSet 
[!$&\'()*+,-./0123456789:=@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~]
 
URLQueryAllowedCharacterSet 
[!$&\'()*+,-./0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~]

URLFragmentAllowedCharacterSet 
[!$&\'()*+,-./0123456789:;=?@ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~]

参考6中对于 plane 的说明如下:

$$
目前的Unicode字符分为17组编排,每组称为平面(Plane),而每平面拥有65536(即2^{16})个代码点。然而目前只用了少数平面。
$$

其中整型转Unicode字符,用了下面的函数

UTF32Char c1 = OSSwapHostToLittleInt32(c); // To make it byte-order safe
NSString *s = [[NSString alloc] initWithBytes:&c1 length:4 encoding:NSUTF32LittleEndianStringEncoding];

其中 OSSwapHostToLittleInt32 定义在 <libkern/OSByteOrder.h> 中,用 NSSwapHostLongToLittle 这个函数也可以。

let uniChar = UnicodeScalar(unicode)

其实这里可以看到是有问题的,比如一个query 是 a==b,这个是不会被编码的,然而怎么断句就有问题了,所以最好还是要结合一下自己写的 Custom Character Set。

三、一些说明

  1. RFC 3986(参考7)的一些定义

保留字符= gen-delims / sub-delims

gen-delims  = ":" / "/" / "?" / "#" / "[" / "]" / "@"

sub-delims  = "!" / "$" / "&" / "'" / "(" / ")"

/ "*" / "+" / "," / ";" / "="

允许的字符= ALPHA / DIGIT / "-" / "." / "_" / "~"

  1. RFC 1738(参考1)的一些定义

不安全字符:

空格(SP) "<" and ">" """ "#" "%" "{", "}", "|", "", "^", "~",

"[", "]" "`"

这些都应该始终被encode的

保留字符:

scheme : ";", "/", "?", ":", "@", "=" and "&"

通常一个字节以字符形式或者编码形式呈现意思是一样的。

但是对scheme的保留字符编码会改变URL的语义。

所以只有 apphanumerics,特殊字符"$-_.+!*'(),",以及用在

保留目的地的保留字符或许不应该去编码。

另一方面,不需要编码的字符(包括字母数字),

只要它们不用于保留目的可以在URL 的 scheme 内编码。

  1. 被废弃的
CFURLCreateStringByAddingPercentEscapes

已经排除了 RFC 2396 中不合法的字符,所以最开始的那个 "!*'();😡&=+$,/?%#[]" 也是有问题的。

参考:

  1. http://www.ietf.org/rfc/rfc1738.txt
  2. https://zh.wikipedia.org/wiki/统一资源定位符
  3. https://zh.wikipedia.org/wiki/百分号编码
  4. http://stackoverflow.com/questions/32064754/how-to-use-stringbyaddingpercentencodingwithallowedcharacters-for-a-url-in-swi
  5. http://stackoverflow.com/questions/15741631/nsarray-from-nscharacterset
  6. https://zh.wikipedia.org/wiki/Unicode字符平面映射
  7. https://tools.ietf.org/html/rfc3986