iOS UIWebView 广告拦截 js,css 拦截方法 (NSURLProtocol)

刚刚开始写博客,可能描述的思路不是很清晰,还请各位看官担待,写的不好,如有纰漏,望请斧正,感觉不尽.

当我们做混合APP 或者是 纯网页的APP(就是只加一个UIWebView的套壳APP)时,经常会遇到讨厌的广告劫持!!!
特别是那些 营运商 的 DNS劫持,植入广告,真的是太恶心了.

来现在过滤DNS广告好像有2种:
  1. HTTPS协议 --- 这个安全,可靠,推荐!!! 但是如果都能用这个那就没这篇文章什么事了.
    2.设置过滤规则 ---这个也是Adblock (最强的广告拦截插件,没用的,我也不知道说什么了) 的拦截原理.
    现在我们是要模仿它,为我们的UIWebView 加载的网页也加上拦截机制,过滤这些广告.
    这个方法缺点也是很大的,需要维护过滤规则 比较麻烦.
  1. 如果你需要加载的网页都是从外网上拉下来的,那这个目前好像没有什么解决办法.(如果读者还有什么高招,还请留言告知!)只能 保持最新的过滤规则,哈哈哈~~
  2. 如果你加载的网页全部都是在你自己的服务器上,那就好办多了,直接不是你域名和你合作的域名内的请求都不通过就行了. 注意!! 像第三方登录之类的都会被拦截的,因为所有的系统请求都会经过你的过滤规则.(笔者就被坑过,哈哈哈哈)

众所周知,我们原生iOS的UIWebView 要于html网页进行交互 有两个途径

1: 通过UIWebView 的 stringByEvaluatingJavaScriptFromString: 方法实现与HTML网页的交互.

注意: 这个方法必须在网页加载完成之后才会有效,也就是再 delegate 中的 webViewDidFinishLoad: 方法执行过之后

- (void)webViewDidFinishLoad:(UIWebView *)webView
{
     //获取某个id的标签中的内容 
    NSString *content = [webView stringByEvaluatingJavaScriptFromString:
        @"document.getElementById('你的某个标签的id').innerHTML"];
}
2: ios7 以上有了这个 神器!!<JavaScriptCore/JavaScriptCore.h> 框架.

使用这个框架进行交互的网上有这相当多的教程,我这就放几个传送门就好了.

这个有Swift 版 和 OC版的,协议模型注入 (JSExport )

这个是使用 stringByEvaluatingJavaScriptFromString 方法的

NSURLProtocol

概念
NSURLProtocol :它可以轻松地重定义整个URL Loading System。当你注册自定义NSURLProtocol后,就有机会对所有的请求进行统一的处理,基于这一点它可以让你实现以下的功能

·自定义请求和响应
·提供自定义的全局缓存支持
·重定向网络请求
·提供HTTP Mocking (方便前期测试)
·其他一些全局的网络请求修改需求

使用方法

继承NSURLPorotocl,并注册你的NSURLProtocol
完整源代码最后附上

[NSURLProtocol registerClass:[CCURLProtocol class]];

实现NSURLProtocol的相关方法
当遍历到我们自定义的NSURLProtocol时,系统先会调用canInitWithRequest:这个方法。顾名思义,这是整个流程的入口,只有这个方法返回YES我们才能够继续后续的处理。我们可以在这个方法的实现里面进行请求的过滤,筛选出需要进行处理的请求。

+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
  //只处理http和https请求
    NSString *scheme = [[request URL] scheme];
    if (  ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame )||
          ([scheme caseInsensitiveCompare:@"https"] == NSOrderedSame )   )
    {
        //看看是否已经处理过了,防止无限循环
        if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request]) {
            return NO;
        }
        
        return YES;//处理
        
    }
    return NO;

}

当筛选出需要处理的请求后,就可以进行后续的处理,需要至少实现如下4个方法

+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request
{
 //在这里网页出现任何变动(加载JS ,CSS 什么的都能拦截得到) ,发送个通知 do something
    //[[NSNotificationCenter defaultCenter] postNotificationName:PageChangeNotification object:self userInfo:nil]; 

    NSLog(@"canonicalRequestForRequest:%@",request.URL.absoluteString);
    NSMutableURLRequest *mutableReqeust = [request mutableCopy];
#warning --- 拦截URL 进行规则过滤 
   //在这个方法  redirectHostInRequset 里面去过滤你要过滤的东西
    mutableReqeust = [self redirectHostInRequset:mutableReqeust];
    return mutableReqeust;
}

+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b
{
    return [super requestIsCacheEquivalent:a toRequest:b];
}

- (void)startLoading
{   
    NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
    
    //打标签,防止无限循环
    [NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:mutableReqeust];
//    
    self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];
}
//
- (void)stopLoading
{
    [self.connection cancel];
}

canonicalRequestForRequest: 进行过滤 ,返回规范化后的request

**requestIsCacheEquivalent:toRequest: **用于判断你的自定义reqeust是否相同,这里返回默认实现即可。它的主要应用场景是某些直接使用缓存而非再次请求网络的地方。

startLoadingstopLoading 实现请求和取消流程。

redirectHostInRequset: 方法的实现

+(NSMutableURLRequest*)redirectHostInRequset:(NSMutableURLRequest*)request
{
  //没有域名的URL请求就原路返回,不能返回nil ,不然在跳转APP的时候会被拦截返回空出错(或者其他情况).
  //eg:  mqq://im/chat?chat_type=wpa&uin=1299101858&version=1&src_type=web  跳转到指定QQ用户的聊天窗口
    if ([request.URL host].length == 0) {
        return request;
    }
    NSString *originUrlString = request.URL.absoluteString;
    
    //获取主机名字,在这里执行正则匹配
    NSString *originHostString = [request.URL host];
    NSRange hostRange = [originUrlString rangeOfString:originHostString];
    //找不到主机名,返回
    if (hostRange.location == NSNotFound) {
        return request;
    }
    
    if (originUrlString != nil) {
        //获取拦截的黑白名单数据(过滤名单)
  //这个是自定义方法,你们自己随意发挥,哈哈哈.
#warning --- 思路实现
/*

这里的匹配黑白名单一般只是**匹配域名** 
思路 1:匹配白名单->匹配黑名单-> 如果两个都没有,就向服务器打印日志. (拉外网)
思路 2:匹配白名单 

以下代码运用思路1 实现

eg: 这个是过滤的规则的例子格式
.*(.qq.com|api.weibo.com|.weibo.com|.baidu.com|.weixin.qq.com|.sina.com|.sina.cn).*

*/
        NSDictionary *dic = [self getHoldUpDic];
        if (!dic)//如果为空不处理黑白名单
        {
            return request;
        }
        
        //白名单
        NSString *whiteList = dic[@"whiteList"];
        //黑名单
        NSString * blackList = dic[@"blackList"];
        
#pragma mark - 白名单匹配
        
        //1.1将正则表达式设置为OC规则
        if (![whiteList isEqualToString:@""])
        {
            NSRegularExpression *regular1 = [[NSRegularExpression alloc] initWithPattern:whiteList options:NSRegularExpressionCaseInsensitive error:nil];
            //2.利用规则测试字符串获取匹配结果
            NSArray *results1 = [regular1 matchesInString:originUrlString options:0 range:NSMakeRange(0, originUrlString.length)];
            if (results1.count > 0)//是白名单,允许访问
            {
                return request;
            }
            
        }
        
#pragma mark - 黑名单匹配
        if (![blackList isEqualToString:@""])
        {
            //1.1将正则表达式设置为OC规则
            NSRegularExpression *regular2 = [[NSRegularExpression alloc] initWithPattern:blackList options:NSRegularExpressionCaseInsensitive error:nil];
            //2.利用规则匹配字符串获取匹配结果
            NSArray *results2 = [regular2 matchesInString:originUrlString options:0 range:NSMakeRange(0, originUrlString.length)];
            
            if (results2.count > 0 ) //黑名单,返回nil;
            {
                return request;
            }
            
        }
        
        if (![whiteList isEqualToString:@""]&&![blackList isEqualToString:@""])
        {
#pragma mark - 发送到服务端打印日志
            
            //do something
            
        }
        
        
    }
    
    
    
    
    return request;
}

实现NSURLConnectionDelegate和NSURLConnectionDataDelegate



#warning  笔者声明:这里是有大坑的,最好是全部的代理方法都实现一遍,不然有可能会出现各种问题
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    [self.client URLProtocol:self
            didFailWithError:error];
}
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
{
//    if (response != nil)
//    {
//        [[self client] URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
//    }
#warning  lanjie wen ti 这里需要回传[self client] 消息,那么需要重定向的网页就会出现问题:host不对或者造成跨域调用导致资源无法加载
     [[self client] URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
//    return request; // 这里如果返回 request 会重新请求一次
    return nil;
}
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
{
    return YES;
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    [self.client URLProtocol:self didReceiveAuthenticationChallenge:challenge];
}
- (void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    [self.client URLProtocol:self didCancelAuthenticationChallenge:challenge];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    [self.client URLProtocol:self
          didReceiveResponse:response
          cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}
- (void)connection:(NSURLConnection *)connection  didReceiveData:(NSData *)data
{
    [self.client URLProtocol:self
                 didLoadData:data];
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
    return cachedResponse;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    [self.client URLProtocolDidFinishLoading:self];
}

继承的 CCURLProtocol 类的代码

CCURLProtocol.h 文件

#import <Foundation/Foundation.h>

@interface CCURLProtocol : NSURLProtocol

@end

CCURLProtocol.m 文件



#import "CCURLProtocol.h"
#import "AFNetWork.h"

static NSString * const URLProtocolHandledKey = @"URLProtocolHandledKey";
static NSDictionary *_holdUpDic;
@interface CCURLProtocol ()<NSURLConnectionDelegate>

@property (nonatomic, strong) NSURLConnection *connection;
@end


@implementation CCURLProtocol
+(NSDictionary *)getHoldUpDic
{
    if (!_holdUpDic)
    {
#pragma mark - 这里是获取黑白名单的数据
         /*
                 [AFNetWork postWithURL:@"" Params:@"" Success:^(NSURLSessionDataTask *task, id responseObject) {
            //获取广告拦截资料
            _holdUpDic = responseObject;
            
            //写入本地plist文件
            BOOL success = [_holdUpDic writeToFile:path atomically:YES];
            if (success )
            {
                NSLog(@"写入成功");
                
            }else
            {
                NSLog(@"写入失败");
            }

        }];
        
        _holdUpDic = [NSDictionary dictionaryWithContentsOfFile:path];
         */
    }
    return _holdUpDic;
}
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
    
    //只处理http和https请求
    NSString *scheme = [[request URL] scheme];
    if ( ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame )||([scheme caseInsensitiveCompare:@"https"] == NSOrderedSame ))
    {
        //看看是否已经处理过了,防止无限循环
        if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request]) {
            return NO;
        }
        
        return YES;//处理
        
    }
    return NO;
}

+ (NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request
{
    //网页发生变动
   // [[NSNotificationCenter defaultCenter] postNotificationName:PageChangeNotification object:self userInfo:nil];
   // NSLog(@"canonicalRequestForRequest:%@",request.URL.absoluteString);
    NSMutableURLRequest *mutableReqeust = [request mutableCopy];
    mutableReqeust = [self redirectHostInRequset:mutableReqeust];
    return mutableReqeust;
}

+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b
{
    return [super requestIsCacheEquivalent:a toRequest:b];
}

- (void)startLoading
{
    NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
    
    //打标签,防止无限循环
    [NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:mutableReqeust];
//    
    self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];
}
//
- (void)stopLoading
{

    [self.connection cancel];
}

#pragma mark - NSURLConnectionDelegate

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    [self.client URLProtocol:self
            didFailWithError:error];
}
- (NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)response
{
//    if (response != nil)
//    {
//        [[self client] URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
//    }
#warning  lanjie wen ti 这里需要回传[self client] 消息,那么需要重定向的网页就会出现问题:host不对或者造成跨域调用导致资源无法加载
     [[self client] URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
//    return request; // 这里如果返回 request 会重新请求一次
    return nil;
}
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
{
    return YES;
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    [self.client URLProtocol:self didReceiveAuthenticationChallenge:challenge];
}
- (void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    [self.client URLProtocol:self didCancelAuthenticationChallenge:challenge];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    [self.client URLProtocol:self
          didReceiveResponse:response
          cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}
- (void)connection:(NSURLConnection *)connection  didReceiveData:(NSData *)data
{
    [self.client URLProtocol:self
                 didLoadData:data];
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
    return cachedResponse;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    [self.client URLProtocolDidFinishLoading:self];
}

#pragma mark -- private

+(NSMutableURLRequest*)redirectHostInRequset:(NSMutableURLRequest*)request
{
  //没有域名的URL请求就原路返回,不能返回nil ,不然在跳转APP的时候会被拦截返回空出错(或者其他情况).
  //eg:  mqq://im/chat?chat_type=wpa&uin=1299101858&version=1&src_type=web  跳转到指定QQ用户的聊天窗口
    if ([request.URL host].length == 0) {
        return request;
    }
    NSString *originUrlString = request.URL.absoluteString;
    
    //获取主机名字,在这里执行正则匹配
    NSString *originHostString = [request.URL host];
    NSRange hostRange = [originUrlString rangeOfString:originHostString];
    //找不到主机名,返回
    if (hostRange.location == NSNotFound) {
        return request;
    }
    
    if (originUrlString != nil) {
        //获取拦截的黑白名单数据(过滤名单)
  //这个是自定义方法,你们自己随意发挥,哈哈哈.
#warning --- 思路实现
/*

这里的匹配黑白名单一般只是**匹配域名** 
思路 1:匹配白名单->匹配黑名单-> 如果两个都没有,就向服务器打印日志. (拉外网)
思路 2:匹配白名单 

以下代码运用思路1 实现

eg: 这个是过滤的规则的例子格式
.*(.qq.com|api.weibo.com|.weibo.com|.baidu.com|.weixin.qq.com|.sina.com|.sina.cn).*

*/
        NSDictionary *dic = [self getHoldUpDic];
        if (!dic)//如果为空不处理黑白名单
        {
            return request;
        }
        
        //白名单
        NSString *whiteList = dic[@"whiteList"];
        //黑名单
        NSString * blackList = dic[@"blackList"];
        
#pragma mark - 白名单匹配
        
        //1.1将正则表达式设置为OC规则
        if (![whiteList isEqualToString:@""])
        {
            NSRegularExpression *regular1 = [[NSRegularExpression alloc] initWithPattern:whiteList options:NSRegularExpressionCaseInsensitive error:nil];
            //2.利用规则测试字符串获取匹配结果
            NSArray *results1 = [regular1 matchesInString:originUrlString options:0 range:NSMakeRange(0, originUrlString.length)];
            if (results1.count > 0)//是白名单,允许访问
            {
                return request;
            }
            
        }
        
#pragma mark - 黑名单匹配
        if (![blackList isEqualToString:@""])
        {
            //1.1将正则表达式设置为OC规则
            NSRegularExpression *regular2 = [[NSRegularExpression alloc] initWithPattern:blackList options:NSRegularExpressionCaseInsensitive error:nil];
            //2.利用规则匹配字符串获取匹配结果
            NSArray *results2 = [regular2 matchesInString:originUrlString options:0 range:NSMakeRange(0, originUrlString.length)];
            
            if (results2.count > 0 ) //黑名单,返回nil;
            {
                return request;
            }
            
        }
        
        if (![whiteList isEqualToString:@""]&&![blackList isEqualToString:@""])
        {
#pragma mark - 发送到服务端打印日志
            
            //do something
            
        }
       
    }
    return request;
}


@end

demo 现在正在整理,之后再奉上.如有疑问,欢迎留言.

以上来源参考自网络,如有侵权请私信笔者.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 151,688评论 1 330
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 64,559评论 1 273
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 101,749评论 0 226
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 42,581评论 0 191
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 50,741评论 3 271
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 39,684评论 1 192
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,122评论 2 292
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 29,847评论 0 182
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 33,441评论 0 228
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 29,939评论 2 232
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,333评论 1 242
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 27,783评论 2 236
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,275评论 3 220
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 25,830评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,444评论 0 180
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 34,553评论 2 249
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 34,618评论 2 249

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,471评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,050评论 18 139
  • 本文是逐行翻译,便于参照原文,如有歧义或者疑问请阅读原文比较。于 2017.1.25===============...
    Auditore阅读 1,434评论 4 4
  • 姓名:王建勛 公司:思沃技术171期利他2组王建勋 211/230期志工 【知-学习】 诵《六项精进》大纲2遍,共...
    常修阅读 513评论 0 1
  • 彩虹有7种颜色 简谱有7个音阶 7年是一次细胞更替 7天是一周美好生活 而7也是我的有缘人 从小学到高中的12年我...
    晨阳Indra阅读 514评论 0 0