EMASCurlWeb/EMASCurlWebUrlSchemeHandler.m (167 lines of code) (raw):

// // EMASCurlUrlSchemeHandler.m // EMASCurl // // Created by xuyecan on 2025/2/3. // #import <Foundation/Foundation.h> #import <WebKit/WebKit.h> #import <objc/runtime.h> #import <objc/message.h> #import <os/lock.h> #import "EMASCurlWebUtils.h" #import "EMASCurlWebNetworkManager.h" #import "EMASCurlWebUrlSchemeHandler.h" #import "WKWebViewConfiguration+Loader.h" #import "EMASCurlWebLogger.h" @protocol EMASCurlResourceMatcherManagerDelegate <NSObject> - (void)redirectWithRequest:(NSURLRequest *)redirectRequest; @end @interface EMASCurlWebUrlSchemeHandler () { os_unfair_lock _taskMaplock; NSHashTable *_taskHashTable; } @property (nonatomic, strong) EMASCurlWebNetworkManager *networkSession; @end @implementation EMASCurlWebUrlSchemeHandler - (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration { self = [super init]; if (self) { _taskMaplock = OS_UNFAIR_LOCK_INIT; _taskHashTable = [NSHashTable weakObjectsHashTable]; _networkSession = [[EMASCurlWebNetworkManager alloc] initWithSessionConfiguration:configuration]; } return self; } - (void)dealloc { [_networkSession cancelAllTasks]; } #pragma mark - Network Resource Matcher Methods - (BOOL)canHandleWithRequest:(NSURLRequest *)request { return YES; } - (void)startWithRequest:(NSURLRequest *)request responseCallback:(EMASCurlNetResponseCallback)responseCallback dataCallback:(EMASCurlNetDataCallback)dataCallback failCallback:(EMASCurlNetFailCallback)failCallback successCallback:(EMASCurlNetSuccessCallback)successCallback redirectCallback:(EMASCurlNetRedirectCallback)redirectCallback { EMASCurlNetworkDataTask *dataTask = [self.networkSession dataTaskWithRequest:request responseCallback:responseCallback dataCallback:dataCallback successCallback:^{ successCallback(); EMASCurlCacheLog(@"WebContentLoader fetched data from network, url: %@", request.URL.absoluteString); } failCallback:failCallback redirectCallback:redirectCallback]; [dataTask resume]; } #pragma mark - WKURLSchemeHandler - (void)webView:(WKWebView *)webView startURLSchemeTask:(id<WKURLSchemeTask>)urlSchemeTask API_AVAILABLE(ios(LimitVersion)) { os_unfair_lock_lock(&_taskMaplock); [_taskHashTable addObject:urlSchemeTask]; os_unfair_lock_unlock(&_taskMaplock); EMASCurlCacheLog(@"WebContentLoader intercepted url: %@", urlSchemeTask.request.URL.absoluteString); EMASCurlWeak(self) [self startWithRequest:urlSchemeTask.request responseCallback:^(NSURLResponse * _Nonnull response) { EMASCurlStrong(self) [self didReceiveResponse:response urlSchemeTask:urlSchemeTask]; } dataCallback:^(NSData * _Nonnull data) { EMASCurlStrong(self) [self didReceiveData:data urlSchemeTask:urlSchemeTask]; } failCallback:^(NSError * _Nonnull error) { EMASCurlStrong(self) [self didFailWithError:error urlSchemeTask:urlSchemeTask]; } successCallback:^{ EMASCurlStrong(self) [self didFinishWithUrlSchemeTask:urlSchemeTask]; } redirectCallback:^(NSURLResponse * _Nonnull response, NSURLRequest * _Nonnull redirectRequest, EMASCurlNetRedirectDecisionCallback redirectDecisionCallback) { EMASCurlStrong(self) [self didRedirectWithResponse:response newRequest:redirectRequest redirectDecision:redirectDecisionCallback urlSchemeTask:urlSchemeTask]; }]; } - (void)webView:(WKWebView *)webView stopURLSchemeTask:(id<WKURLSchemeTask>)urlSchemeTask API_AVAILABLE(ios(LimitVersion)) { os_unfair_lock_lock(&_taskMaplock); [_taskHashTable removeObject:urlSchemeTask]; os_unfair_lock_unlock(&_taskMaplock); } #pragma mark - Task Callbacks - (void)didReceiveResponse:(NSURLResponse *)response urlSchemeTask:(id<WKURLSchemeTask>)urlSchemeTask { if (![self isAliveWithURLSchemeTask:urlSchemeTask]) { return; } @try { EMASCurlCacheLog(@"WebContentLoader received response, url: %@", urlSchemeTask.request.URL.absoluteString); [urlSchemeTask didReceiveResponse:response]; } @catch (NSException *exception) {} @finally {} } - (void)didReceiveData:(NSData *)data urlSchemeTask:(id<WKURLSchemeTask>)urlSchemeTask { if (![self isAliveWithURLSchemeTask:urlSchemeTask]) { return; } @try { EMASCurlCacheLog(@"WebContentLoader received data, length: %ld, url: %@", data.length, urlSchemeTask.request.URL.absoluteString); [urlSchemeTask didReceiveData:data]; } @catch (NSException *exception) {} @finally {} } - (void)didFinishWithUrlSchemeTask:(id<WKURLSchemeTask>)urlSchemeTask { if (![self isAliveWithURLSchemeTask:urlSchemeTask]) { return; } @try { EMASCurlCacheLog(@"WebContentLoader finished, url: %@", urlSchemeTask.request.URL.absoluteString); [urlSchemeTask didFinish]; } @catch (NSException *exception) {} @finally {} } - (void)didFailWithError:(NSError *)error urlSchemeTask:(id<WKURLSchemeTask>)urlSchemeTask { if (![self isAliveWithURLSchemeTask:urlSchemeTask]) { return; } @try { EMASCurlCacheLog(@"WebContentLoader encountered error, url: %@", urlSchemeTask.request.URL.absoluteString); [urlSchemeTask didFailWithError:error]; } @catch (NSException *exception) {} @finally {} } - (void)didRedirectWithResponse:(NSURLResponse *)response newRequest:(NSURLRequest *)redirectRequest redirectDecision:(EMASCurlNetRedirectDecisionCallback)redirectDecisionCallback urlSchemeTask:(id<WKURLSchemeTask>)urlSchemeTask { if (![EMASCurlWebUtils isEqualURLA:urlSchemeTask.request.mainDocumentURL.absoluteString withURLB:response.URL.absoluteString]) { redirectDecisionCallback(YES); return; } redirectDecisionCallback(NO); if ([self isAliveWithURLSchemeTask:urlSchemeTask]) { NSString *s1 = @"didPerform"; NSString *s2 = @"Redirection:"; NSString *s3 = @"newRequest:"; SEL sel = NSSelectorFromString([NSString stringWithFormat:@"_%@%@%@", s1, s2, s3]); if ([urlSchemeTask respondsToSelector:sel]) { @try { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" [urlSchemeTask performSelector:sel withObject:response withObject:redirectRequest]; #pragma clang diagnostic pop } @catch (NSException *exception) { } @finally {} } } [self redirectWithRequest:redirectRequest]; } #pragma mark - Utility Methods - (BOOL)isAliveWithURLSchemeTask:(id<WKURLSchemeTask>)urlSchemeTask { BOOL alive = NO; os_unfair_lock_lock(&_taskMaplock); alive = [_taskHashTable containsObject:urlSchemeTask]; os_unfair_lock_unlock(&_taskMaplock); EMASCurlCacheLog(@"isAliveWithURLSchemeTask encountered an exception"); return alive; } - (void)redirectWithRequest:(NSURLRequest *)redirectRequest { void *storeKey = (__bridge void*)[EMASCurlWebUrlSchemeHandler class]; EMASCurlWebWeakProxy *redirectDelegateProxy = objc_getAssociatedObject(self, storeKey); if ([redirectDelegateProxy respondsToSelector:@selector(redirectWithRequest:)]) { ((void (*)(id, SEL, NSURLRequest *))objc_msgSend)(redirectDelegateProxy, @selector(redirectWithRequest:), redirectRequest); } } @end