如何对 URL 字符串进行百分号编码

2,219 阅读3分钟
原文链接: www.cocoachina.com

47.png

在和web服务进行交互时,我们经常需要对URL中的特定字符和传输的表单数据进行百分号编码。例如,’&’在百分号编码时会变成’%26’。搞清楚 URL中哪部分的哪些字符应该进行百分号编码了并不是件易事。最好的资料好像是RFC 3986W3C HTML5。出于兴趣和教育目的,我创建了swift的String的扩展(和作为对比的Objective-C的分类)。

RFC3986 编码查询字符串

RFC3986 的第2.3节列出了你不需要百分号编码的字符,因为它们在URL中没有特殊的含义。

ALPHA / DIGIT / “-” / “.” / “_” / “~” 

α/数字/”-”/”.”/”_”

第3.4节也解释了因为查询往往会本身包含一个URL,最好不要百分号编码斜杠(“/”)和问号(“?”)。这也是受欢迎的iOS HTTP网络库Alamofire采取的方法,这给了我信心。

因此,用RFC 3986编码一个兼容性的查询,我们可以百分号编码如上所述以外的所有字符。这很简单,如果我们首先构建一组允许的字符,然后用stringByAddingPercentEncodingWithAllowedCharacters去编码剩余的。

注意:苹果已经在iOS 9中弃用了stringByAddingPercentEscapesUsingEncoding或CFURLCreateStringByAddingPercentEscapes这两个方法。

Swift

首先,swift String extension:

extension String {
    func stringByAddingPercentEncodingForRFC3986() -> String? {
        let unreserved = "-._~/?"
        let allowed = NSMutableCharacterSet.alphanumericCharacterSet()
        allowed.addCharactersInString(unreserved)
        return stringByAddingPercentEncodingWithAllowedCharacters(allowed)
    }
}

Object-C

我们可以用Object-C的NSString的分类来做相同的事。

@implementation NSString (URLEncoding)
- (nullable NSString *)stringByAddingPercentEncodingForRFC3986 {
    NSString *unreserved = @"-._~/?";
    NSMutableCharacterSet *allowed = [NSMutableCharacterSet alphanumericCharacterSet];
    [allowed addCharactersInString:unreserved];
    return [self stringByAddingPercentEncodingWithAllowedCharacters: allowed];
}
@end
// Swift 
let query = "one&two =three" 
let encoded = query.stringByAddingPercentEncodingForRFC3986() 
// "one%26two%20%3Dthree"  

// Objective-C 
NSString *query = @"one&two =three"; 
NSString *encoded = [query stringByAddingPercentEncodingForRFC3986]; 
// "one%26two%20%3Dthree"

对x-www-form-urlencoded进行编码

推荐W3C HTML5 对表单数据编码是相似的,但是和RFC 3986有一点不同。在第4.10.22.5节中告诉我们下列字符是不应该百分号编码:

ALPHA / DIGIT / “*” / “-” / “.” / “_” 

α/数字/”-”/”.”/”_”

你应该用“+”(0x2B)代替空格(“ ”)。它和RFC 3986 的不同在 Stack Overflow answer 里有描述。波浪号(“~”)被百分号编码了,但是星号(“*”)没有。该建议很好地总结了这种情况:这种编码的表单数据在很多方面是异常的,多年来的实践的问题和折中解决导致了互通性的一系列必要操作。但是绝不代表好的设计实践。

Swift

给String extension添加一个新的方法

public func stringByAddingPercentEncodingForFormData(plusForSpace: Bool=false) -> String? {
  let unreserved = "*-._"
  let allowed = NSMutableCharacterSet.alphanumericCharacterSet()
  allowed.addCharactersInString(unreserved)

  if plusForSpace {
    allowed.addCharactersInString(" ")
  }

  var encoded = stringByAddingPercentEncodingWithAllowedCharacters(allowed)
  if plusForSpace {
    encoded = encoded?.stringByReplacingOccurrencesOfString(" ", 
                       withString: "+")
  }
  return encoded
}

注意,由于很多 web服务好像不关心我用“+”或者百分号编码将空格做了可选的编码。

Object-C

Object-C的方法缺少一个可选参数

- (nullable NSString *)stringByAddingPercentEncodingForFormData:(BOOL)plusForSpace
{
    NSString *unreserved = @"*-._";
    NSMutableCharacterSet *allowed = [NSMutableCharacterSet                                     alphanumericCharacterSet];
    [allowed addCharactersInString:unreserved];
    if (plusForSpace) {
        [allowed addCharactersInString:@" "];
    }
    NSString *encoded = [self stringByAddingPercentEncodingWithAllowedCharacters:allowed];
    if (plusForSpace) {
        encoded = [encoded stringByReplacingOccurrencesOfString:@" " withString:@"+"];
    }
    return encoded;
}

用例:

// Swift
let query = "one two"
let space = query.stringByAddingPercentEncodingForFormData()
// "one%20two"

let plus = query.stringByAddingPercentEncodingForFormData(true)
// "one+two"

// Objective-C
NSString *query = @"one two";
NSString *encodedQuery = [query stringByAddingPercentEncodingForFormData:YES];
// "one+two"

源代码

Swift代码和一些测试用例你可以在我的Github代码实例库的Encode项目里找到,Object-C的分类和测试用例在TwitterSearch项目里。欢迎反馈和改进。

深入阅读

搜索CocoaChina微信公众号:CocoaChina

微信扫一扫

订阅每日移动开发及APP推广热点资讯
公众号:
CocoaChina