1. 问题
当URL的多个参数中,部分参数已经编码,部分参数未编码,则在iOS端如何才能在WKWebview正常打开呢?
比如这个URL:
https://www.baidu.com/baidu?wd=URL编码&test=%E7%BC%96%E7%A0%81
iOS端WKWebview如果直接使用这个链接,是不能打开网页的,需要进行编码处理。
显然,如果我们对整个URL字符串进行编码,则key "test" 对应的value 因为本来已经进行过一次编码,最终会编码两次。造成浏览器不能正确解析。
2. 编解码基础知识
开头的问题你不懂我说的是个啥?编解码基础知识如果不是很了解,就百度一下吧(主要是我很懒)。以后有空我再补充,可以让这篇文章瞬间丰满起来,看起来更专业😆。
3. talk is cheap, show me the code
3.1、假设参数中不包含会引起歧义的URL字符(&=),就如开头示例的URL。
笔者的思路是:只对不符合URL字符规范的字符进行编码,避免二次编码。编码实现如下:
+ (NSString *)urlencode:(NSString *)str {
//首先将参数段拆分出来
if ([str containsString:@"?"]) {
NSRange temRange = [str rangeOfString:@"?"];//匹配第一个问号
NSString *params = [params substringFromIndex:temRange.location];
NSCharacterSet *allowedCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~&="];
params = [params stringByAddingPercentEncodingWithAllowedCharacters:allowedCharacterSet];
str = [[str substringToIndex:temRange.location] stringByAppendingString:params];
return str;
}
return str;
}
测试用例:
https://juejin.cn/search?query=URL编码&test=%E7%BC%96%E7%A0%81
运行结果:
https://juejin.cn/search?query=URL%E7%BC%96%E7%A0%81&test=%E7%BC%96%E7%A0%81
一般情况下,做到这一步就可以了,因为如果参数中包含了(&、=),引起了歧义,PC浏览器可能也会解析出错,这个链接在测试的时候就通不过。
3.2、假设参数中可能包含会引起歧义的URL保留字符(&、=),希望具有一定的纠错能力。
例:?key1
=a=b&c&key2
=d(问号前的部分从略,下同)
思路:首先获取键值对,然后对参数进行编码。编码过后的URL就能正常使用了。
3.2.1参数转字典
通常场景下,我们是已知键值对的key的,这时候我们截取相应区间的字符串作为其对应的value即可。显然,key1对应的值是"a=b&c",key2对应的值是"d"(我们假设这时候的key是唯一的,不与value值中的内容相同),由于实现方法不难,具体算法我就不贴出来了(主要是我很懒)。
你以为这样就够了?不,我们的追求不止于此!如果我们事先并不知道key呢?
仍拿刚才的举例:?key1
=a=b&c&key2
=d(这次事先并不已知key分别为"key1","key2")
要获取参数键值对,这时候就需要一个匹配算法了。
我实现了下面的这个算法,这个算法的前提条件:
(1)key中不含“&”、“=”
(2)key是唯一的
-(NSDictionary *)dictionaryWithUrlString:(NSString *)urlString {
NSString *curByte = @"";
NSString *tempStr = @"";
NSString *key = @"";
NSString *lastKey = @"";
NSString *lastValue = @"";
BOOL isKey = YES;
BOOL start = NO;
NSMutableDictionary *params = [NSMutableDictionary dictionary];
for(int i=0; i< urlString.length; i++) {
curByte = [urlString substringWithRange:NSMakeRange(i, 1)];
if (!start) {
if ([curByte isEqualToString:@"?"]) {//第一个问号
start = YES;
}
continue;
}
// 只有下一个键值对确定了,才能确定上一个键值对。
if (isKey) {
if (![curByte isEqualToString:@"="]) {//
if ([curByte isEqualToString:@"&"] ) {
lastValue = [lastValue stringByAppendingString:@"&"];
lastValue = [lastValue stringByAppendingString:key];
params[lastKey] = lastValue;
isKey = YES;
key = @"";
} else {
key = [key stringByAppendingString:curByte];
if (i==urlString.length-1) {
lastValue = [lastValue stringByAppendingString:@"&"];
lastValue = [lastValue stringByAppendingString:key];
params[lastKey] = lastValue;
}
}
} else {
if (!key.length) {
lastValue = [lastValue stringByAppendingString:@"&"];
lastValue = [lastValue stringByAppendingString:key];
lastValue = [lastValue stringByAppendingString:@"="];
params[lastKey] = lastValue;
} else{
}
isKey = NO;
if (i==urlString.length-1) {
params[key] = @"";
}
NSLog(@"key is %@",key);
}
} else {
if (![curByte isEqualToString:@"&"] && (i!=urlString.length-1) ) {//
tempStr = [tempStr stringByAppendingString:curByte];
} else {
if (key.length) {
lastKey = key;
lastValue = tempStr;
}
if (i==urlString.length-1) {
lastValue = [lastValue stringByAppendingString:curByte];
params[lastKey] = lastValue;
} else {
params[lastKey] = lastValue;
tempStr = @"";
key = @"";
}
isKey = YES;
}
}
}
NSLog(@"params is %@",params);
return params;
}
测试用例:
?a=a=b&c=a&b&x&ddd=&ccc=
运行结果:
params is {
"ccc" : "",
"c" : "a&b&x",
"a" : "a=b",
"ddd" : ""
}
但是,不可避免还是存在有歧义的情况,比如一个键值对有可能是另一个键值对参数的一部分(“c”的value值也可能是“a&b&x&ddd=&ccc=”)。不过这个概率已经很小了。
3.2.2参数编码
接下来,对所有参数值进行编码:
-(NSString *)urlencodeParams:(NSDictionary *)paramsDic {
//URL
NSMutableString *URL = [NSMutableString stringWithFormat:@"http://www.baidu.com"];
//获取字典的所有keys
NSArray * keys = [paramsDic allKeys];
//拼接字符串
for (int j = 0; j < keys.count; j ++)
{
NSString *string;
if (j == 0)
{
//拼接时加?
string = [NSString stringWithFormat:@"?%@=%@", keys[j], [self urlencodeWithStr: paramsDic[keys[j]]]];
}
else
{
//拼接时加&
string = [NSString stringWithFormat:@"&%@=%@", keys[j], [self urlencodeWithStr: paramsDic[keys[j]]]];
}
//拼接字符串
[URL appendString:string];
}
return URL;
}
//对参数进行编码
-(NSString *)urlencodeWithStr:(NSString *)str {
NSCharacterSet *allowedCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~"];
return [str stringByAddingPercentEncodingWithAllowedCharacters: allowedCharacterSet];
}
3.2.3完整方案
每个步骤都已经基本完成,现在我们拼凑成完整的实现方案
-(void)test {
NSString *urlBase;//不带参数的前面部分
NSDictionary *params = [self dictionaryWithUrlString:urlString returnURLBase:&urlBase];
NSString *str = [self urlencode:urlBase withParams: params];
}
-(NSDictionary *)dictionaryWithUrlString:(NSString *)urlString returnURLBase:(NSString **)URLBase{
if (!(*URLBase)) {
*URLBase = @"";
}
NSString *curByte = @"";
NSString *tempStr = @"";
NSString *key = @"";
NSString *lastKey = @"";
NSString *lastValue = @"";
BOOL isKey = YES;
BOOL start = NO;
NSMutableDictionary *params = [NSMutableDictionary dictionary];
for(int i=0; i< urlString.length; i++) {
curByte = [urlString substringWithRange:NSMakeRange(i, 1)];
if (!start) {
if ([curByte isEqualToString:@"?"]) {//第一个问号
start = YES;
} else {
*URLBase = [(*URLBase) stringByAppendingString:curByte];
}
continue;
}
// 只有下一个键值对确定了,才能确定上一个键值对。
if (isKey) {
if (![curByte isEqualToString:@"="]) {//
if ([curByte isEqualToString:@"&"] ) {
lastValue = [lastValue stringByAppendingString:@"&"];
lastValue = [lastValue stringByAppendingString:key];
params[lastKey] = lastValue;
isKey = YES;
key = @"";
} else {
key = [key stringByAppendingString:curByte];
if (i==urlString.length-1) {
lastValue = [lastValue stringByAppendingString:@"&"];
lastValue = [lastValue stringByAppendingString:key];
params[lastKey] = lastValue;
}
}
} else {
if (!key.length) {
lastValue = [lastValue stringByAppendingString:@"&"];
lastValue = [lastValue stringByAppendingString:key];
lastValue = [lastValue stringByAppendingString:@"="];
params[lastKey] = lastValue;
} else{
}
isKey = NO;
if (i==urlString.length-1) {
params[key] = @"";
}
NSLog(@"key is %@",key);
}
} else {
if (![curByte isEqualToString:@"&"] && (i!=urlString.length-1) ) {//
tempStr = [tempStr stringByAppendingString:curByte];
} else {
if (key.length) {
lastKey = key;
lastValue = tempStr;
}
if (i==urlString.length-1) {
lastValue = [lastValue stringByAppendingString:curByte];
params[lastKey] = lastValue;
} else {
params[lastKey] = lastValue;
tempStr = @"";
key = @"";
}
isKey = YES;
}
}
}
NSLog(@"params is %@",params);
return params;
}
-(NSString *)urlencode:(NSString *)url withParams:(NSDictionary *)paramsDic {
if (!paramsDic) {
return url;
}
//URL
NSMutableString *URL = [NSMutableString stringWithFormat:@"%@", url];
//获取字典的所有keys
NSArray * keys = [paramsDic allKeys];
//拼接字符串
for (int j = 0; j < keys.count; j ++)
{
NSString *string;
if (j == 0)
{
//拼接时加?
string = [NSString stringWithFormat:@"?%@=%@", keys[j], [self urlencodeWithStr: paramsDic[keys[j]]]];
}
else
{
//拼接时加&
string = [NSString stringWithFormat:@"&%@=%@", keys[j], [self urlencodeWithStr: paramsDic[keys[j]]]];
}
//拼接字符串
[URL appendString:string];
}
return URL;
}
//对参数进行编码
-(NSString *)urlencodeWithStr:(NSString *)str {
NSCharacterSet *allowedCharacterSet = [NSCharacterSet characterSetWithCharactersInString:@"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~"];
return [str stringByAddingPercentEncodingWithAllowedCharacters: allowedCharacterSet];
}
测试用例:
https://www.nihaopiaoliang.com?a=a=b&c=a&b&x&ddd=&ccc=
编码后:
https://www.nihaopiaoliang.com?ccc=&c=a%26b%26x&a=a%3Db&ddd=
4、说明
本文方法只用作补救措施,尽可能还原没有按规范编码的参数。如果不是第三方链接,我们应该在源头就按规范编码URL。
网友评论