iOS UIWebView 广告拦截 js,css 拦截方法 (
刚刚开始写博客,可能描述的思路不是很清晰,还请各位看官担待,写的不好,如有纰漏,望请斧正,感觉不尽.
当我们做混合APP 或者是 纯网页的APP(就是只加一个UIWebView的套壳APP)时,经常会遇到讨厌的广告劫持!!!
特别是那些 营运商 的 DNS劫持,植入广告,真的是太恶心了.
来现在过滤DNS广告好像有2种:
-
HTTPS协议 --- 这个安全,可靠,推荐!!! 但是如果都能用这个那就没这篇文章什么事了.
2.设置过滤规则 ---这个也是Adblock (最强的广告拦截插件,没用的,我也不知道说什么了) 的拦截原理.
现在我们是要模仿它,为我们的UIWebView 加载的网页也加上拦截机制,过滤这些广告.
这个方法缺点也是很大的,需要维护过滤规则 比较麻烦.
- 如果你需要加载的网页都是从外网上拉下来的,那这个目前好像没有什么解决办法.(如果读者还有什么高招,还请留言告知!)只能 保持最新的过滤规则,哈哈哈~~
- 如果你加载的网页全部都是在你自己的服务器上,那就好办多了,直接不是你域名和你合作的域名内的请求都不通过就行了. 注意!! 像第三方登录之类的都会被拦截的,因为所有的系统请求都会经过你的过滤规则.(笔者就被坑过,哈哈哈哈)
众所周知,我们原生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是否相同,这里返回默认实现即可。它的主要应用场景是某些直接使用缓存而非再次请求网络的地方。
startLoading和stopLoading 实现请求和取消流程。
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 现在正在整理,之后再奉上.如有疑问,欢迎留言.
以上来源参考自网络,如有侵权请私信笔者.