你真的了解URLencode吗?
最近因项⽬需要,需重写⽹络组件。在重写及review项⽬组的⽹络组件旧代码时,发现对URL编码有不严谨之处。当说到这种写法其实是有问题时,⼏个同事都表⽰⾮常诧异并⼏度辩驳。本⼈表⽰有点⼩⼼惊,在⽹上搜索时还真的很少有另外的写法。在此以⾃⼰的⼀些理解和经验,做⼀下URL编码的普及,希望对⼤家有所帮助,有问题也请不吝赐教。(参考RFC1738,3986,6874,7320)
⼀、了解URL编码以及编码时机、运算过程
URI包括URL和URN,常⽤说法URL encode实际是遵循URI的相关⽂件。在URI的最初设计时,希望能通过书⾯转录,⽐如写在餐⼱纸上告诉另外⼀⼈,因此URI的构成字符必须是可写的ASCII字符。在这些可书写的字符⾥,由于⼀些字符在不同操作系统的编码有不同的解析,被包含在Unsafe characters(图3)之中,要格外注意。最后,在URI的构成字符中,最安全的⽅案是正确使⽤Reserved Characters (图1)和 Unreserved Characters (图1)的并集。
在对⾮法字符编码到合法URI时,规定使⽤percent encode编码,对⾮法字符的编码结果为三个字节(%+16进制字符*2)。然⽽如何⽣成percent编码,没有明确的指导规定,这也是⼤部分开发者拷贝旧代码却不知其所以然的原因。
percent encode从字⾯上语义明确的指出其使⽤%做编码标识,URL encode的实质就是正确的使⽤percent encode.
正确完成URL encode的关键问题在于:什么时候,对哪些内容,采⽤何种过滤原则,以及如何⽣成percent编码?
在WWW最初时,做法是将字符流转换成字节流,按照ASCII字符与字节⼀⼀对应可相互转换,使⽤对应ASCII字符的整型值作为%的后两个16进制字符,构成percent编码。后来出现了多种percent编码⽣成⽅法,导致了URI的难以识别。
现下做法,包括iOS使⽤percent相关的函数时,指定或系统默认的使⽤UTF8转成字节流,每个字节编成⼀个percent编码,例如中⽂“⽹易”的URL编码为%e7%bd%91%e6%98%93,⽽其UTF8字节流为e7 bd 91 e6 93,可以看出其⼀⼀对应关系。
那么percent编码是在对⾮法字符采⽤某种编码(约定为UTF8)转成字节流后,逐字节加上%构成percent编码。
由于不同scheme或协议对URI格式有不同的要求,RFC关于对哪些内容编码,采⽤何种过滤原则不做硬性规定。⽽将决定权延后到执⾏时由开发者根据需要决定。通常遵循以下原则:
1.不要对Unreserved Characters做percent encode编码。
2.除了保留字符和⾮保留字符外的所有字符,必须使⽤percent encode进⾏编码。
3.保留字符不⽤于URI分隔符,⽽是⽤于其它位置,⽐如query部分的value时,要对这时⽤到的保留字符做percent encode编码。
4.当两个URI的字符⼏乎对等,区别只在于⼀个对某些字符⽤的原有字符,另⼀个URI对这些字符做了percent encode时。绝⼤部分情况下,这两个URI应当被认为是不同的两个URI。因此,不应当对保留字在作为保留字的使⽤场景时使⽤percent encode编码。
图1. 保留字和⾮保留字
图2 不安全字符
⼆、iOS开发中URL encode的⽅法编写
有三个函数或系统⽅法⽤于URL encode,CFURLCreateStringByAddingPercentEscapes(9.0废弃),stringByAddingPercentEscapesUsingencode(9.0废弃),
stringByAddingPercentencodeWithAllowedCharacters(系统推荐)。该系统推荐⽅法默认使⽤了UTF8编码然后再根据我们指定允许的字符集完成percent编码。通常我们在拼接GET请求URL时使⽤URL encode。
应⽤场景⼀般是传⼊URLString和⼀个参数NSDictionary, 这时需要传⼊⽅保证URLString是已正确编码的,然后遍历NSDictionary的key和value, 按需指定允许的字符集对key值和value做编码。结果输出为
URLString[& | ?] URLencode(key1)=URLencode(value1)&URLencode(key2)=URLencode(value2)….
笔者使⽤的允许字符集为Unreserved Characters, 包括Reserved Characters以及中⽂等⾮法字符均会被percent编码,⽰例⽅法如下
+ (NSURL *) createGETURLFromString:(NSString *)urlString params:(NSDictionary *)params {
NSURL *parsedURL = [NSURL URLWithString:urlString];
NSString* queryPrefix = parsedURL.query ? @"&" : @"?";
NSMutableArray* pairs = [NSMutableArray array];
for (NSString* key in [params keyEnumerator]) {
if (![[params objectForKey:key] isKindOfClass:[NSString class]]) {
continue;
}
NSString *value = (NSString *)[params objectForKey:key];
NSCharacterSet *allowedCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~"]; NSString *urlEncodingKey = [key stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
NSString *urlEncodingValue = [value stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
[pairs addObject:[NSString stringWithFormat:@"%@=%@", urlEncodingKey, urlEncodingValue]];
}
在线url网址编码解码NSString* query = [pairs componentsJoinedByString:@"&"];
return [NSURL URLWithString:[NSString stringWithFormat:@"%@%@%@", urlString, queryPrefix, query]];
}
三、URL encode中的 + 与空格
在使⽤过base64编码的童鞋多半会知道,基础base64衍⽣了web safe base64,更改了编码字符集,其中基本表中会出现+和/字符,这个⼀般会被浏览器理解成空格和路径分割符。所以为了让其⼯作正常,需要把索引表的最后两个字符+和/分别替换成点 . 和下划线 _ 。+号经过percent编码后为%20,⽽20正是空格的ASCII码,这⼤概是浏览器的设计者将+理解成空格的原因。
那么在URL encode中是不是需要针对性的指定许可字符集,对+号和空格做处理防⽌混淆呢。实际上我们在之前使⽤Unreserved Characters对内容做编码时,并没有允许+和/符号出现在内容项的编码结果中。
因此,正确的使⽤percent编码,当传⼊的参数字典中包含有+号和/字符也是可以放⼼的。在传⼊参数是base64的结果时,并不需要特别的将base64换成web safe base64。
四、不正确的URL encode可能导致的问题
有的iOS开发者拿到 GET请求URLString和参数字典后,先拼接参数,然后再对整个字符串做URL encode,造成不能区分某些字符是处于分割组件的作⽤,或者是作为组件的content。这是千万不可取的,可能发⽣如下问题:
1.如果没有正确过滤,⽐如www.baidu做编码后变成了http%3a%2f%2fwww.baidu%2findex.htm,将不能正常访问。也就是说,为了⽀持对拼接后的字符串作URL encode, 必须对整个拼接后的字符串禁⽌对所有URI的保留字作编码,⽐如&字符,这就造成了问题2和3。
2. 当构建参数传⼊{“name” : ”namepart1&namepart2”,“id” : "kk"}。此时拼接字符串编成了www.baidu?name=namepart1&namepart2&id=kk,那么如何解析得到"name"字段“namepart1&namepart2”的实际value值,以及id字段的值"kk"?
3. 当构建参数传⼊{“name” : ”Mitty&isLogin=true”}。此时拼接字符串编成了www.baidu?name=Mitty&isLogin=true,如果isLogin真的是有意义的queryKey时,直接造成服务器接收了额外的参数。当然关于URL的攻击有很多,⽐如semantic attack, 这⾥不做讨论。
另外,有童鞋担⼼⾮法字符,先对中⽂做base64再放到get参数,从担⼼⾮法字符来讲不是必要的,经过URL编码后,与base64的结果同样是ASCII字符集,在⽹络上是可以正常不丢失信息的传输的。
服务器接到请求时,以PHP为例,应针对每个$_GET[“key”]做URL做解码。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论