美文网首页
iOS URL编码兼容方案

iOS URL编码兼容方案

作者: 小而白 | 来源:发表于2021-02-07 16:53 被阅读0次

    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。

    相关文章

      网友评论

          本文标题:iOS URL编码兼容方案

          本文链接:https://www.haomeiwen.com/subject/vwjdtltx.html