├── screen-01.PNG ├── WebViewProxyDemo ├── WebViewProxyDemo.xcodeproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcuserdata │ │ │ └── zhanlin.xcuserdatad │ │ │ └── UserInterfaceState.xcuserstate │ ├── xcuserdata │ │ └── zhanlin.xcuserdatad │ │ │ └── xcschemes │ │ │ ├── xcschememanagement.plist │ │ │ └── WebViewProxyDemo.xcscheme │ └── project.pbxproj ├── WebViewProxyDemo │ ├── ViewController.h │ ├── main.m │ ├── AppDelegate.h │ ├── ViewController.m │ ├── Images.xcassets │ │ └── AppIcon.appiconset │ │ │ └── Contents.json │ ├── Info.plist │ ├── Base.lproj │ │ ├── LaunchScreen.xib │ │ └── Main.storyboard │ └── AppDelegate.m ├── WebViewProxyKit │ ├── UUSSLWhiteList.h │ ├── UUSSLWhiteList.m │ ├── CredentialsManager.h │ ├── CacheStoragePolicy.h │ ├── CanonicalRequest.h │ ├── CredentialsManager.m │ ├── QNSURLSessionDemux.h │ ├── CacheStoragePolicy.m │ ├── CustomHTTPProtocol.h │ ├── QNSURLSessionDemux.m │ ├── CanonicalRequest.m │ └── CustomHTTPProtocol.m └── WebViewProxyDemoTests │ ├── Info.plist │ └── WebViewProxyDemoTests.m └── README.md /screen-01.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanleewo/iOS-WebViewProxyKit/HEAD/screen-01.PNG -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyDemo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyDemo.xcodeproj/project.xcworkspace/xcuserdata/zhanlin.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhanleewo/iOS-WebViewProxyKit/HEAD/WebViewProxyDemo/WebViewProxyDemo.xcodeproj/project.xcworkspace/xcuserdata/zhanlin.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyDemo/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // WebViewProxyDemo 4 | // 5 | // Created by Rommel on 14/11/28. 6 | // Copyright (c) 2014年 AOLC. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : UIViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyDemo/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // WebViewProxyDemo 4 | // 5 | // Created by Rommel on 14/11/28. 6 | // Copyright (c) 2014年 AOLC. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "AppDelegate.h" 11 | 12 | int main(int argc, char * argv[]) { 13 | @autoreleasepool { 14 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyDemo/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // WebViewProxyDemo 4 | // 5 | // Created by Rommel on 14/11/28. 6 | // Copyright (c) 2014年 AOLC. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "CredentialsManager.h" 12 | 13 | @interface AppDelegate : UIResponder 14 | 15 | @property (strong, nonatomic) UIWindow *window; 16 | 17 | @property (nonatomic, strong, readwrite) CredentialsManager * credentialsManager; 18 | @end 19 | 20 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyKit/UUSSLWhiteList.h: -------------------------------------------------------------------------------- 1 | // 2 | // UUSSLWhiteList.h 3 | // WebViewProxyDemo 4 | // 5 | // Created by Rommel on 14/11/28. 6 | // Copyright (c) 2014年 AOLC. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface UUSSLWhiteList : NSObject 12 | { 13 | @private 14 | NSMutableArray *_whiteList; 15 | } 16 | 17 | + (instancetype) sharedSSLWhiteList; 18 | 19 | - (void) addIgonerURL:(NSURL *) url; 20 | - (void) addIgonerHost:(NSString *) host; 21 | 22 | 23 | - (BOOL) hostIsContainedInWhiteList:(NSString *) host; 24 | - (BOOL) urlIsContainedInWhiteList:(NSURL *) url; 25 | 26 | @end -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyDemo.xcodeproj/xcuserdata/zhanlin.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | WebViewProxyDemo.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | 9C87340B1A281956001F7CB3 16 | 17 | primary 18 | 19 | 20 | 9C8734241A281956001F7CB3 21 | 22 | primary 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyDemoTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.aolc.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | iOS-WebViewProxyKit 2 | =================== 3 | 4 | ## ABOUT 5 | This software show you how to setup an proxy for UIWebview based on the 6 | [apple's official demo](https://developer.apple.com/library/ios/samplecode/CustomHTTPProtocol/Listings/Read_Me_About_CustomHTTPProtocol_txt.html) 7 | ![iOS-WebViewProxyKit](https://raw.githubusercontent.com/zhanleewo/iOS-WebViewProxyKit/master/screen-01.PNG) 8 | 9 | ## LICENSE 10 | ``` 11 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 12 | Version 2, December 2004 13 | 14 | Copyright (C) 2014 Edward Zhan 15 | 16 | Everyone is permitted to copy and distribute verbatim or modified 17 | copies of this license document, and changing it is allowed as long 18 | as the name is changed. 19 | 20 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE 21 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 22 | 23 | 0. You just DO WHAT THE FUCK YOU WANT TO. 24 | ``` 25 | 26 | ## USAGE 27 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyDemo/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // WebViewProxyDemo 4 | // 5 | // Created by Rommel on 14/11/28. 6 | // Copyright (c) 2014年 AOLC. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | 11 | @interface ViewController () 12 | 13 | @property (weak, nonatomic) IBOutlet UITextField *urlTextfiled; 14 | 15 | @property (weak, nonatomic) IBOutlet UIWebView *webView; 16 | 17 | @end 18 | 19 | @implementation ViewController 20 | 21 | - (void)viewDidLoad { 22 | [super viewDidLoad]; 23 | } 24 | 25 | - (void)didReceiveMemoryWarning { 26 | [super didReceiveMemoryWarning]; 27 | } 28 | 29 | - (IBAction)goAction:(id)sender { 30 | 31 | NSString *urlStr = self.urlTextfiled.text; 32 | 33 | if(![urlStr hasPrefix:@"http://"] && ![urlStr hasPrefix:@"https://"]) { 34 | urlStr = [NSString stringWithFormat:@"http://%@", urlStr]; 35 | } 36 | 37 | NSURL *url = [NSURL URLWithString:urlStr]; 38 | NSURLRequest *request = [NSURLRequest requestWithURL:url]; 39 | [self.webView loadRequest:request]; 40 | } 41 | @end 42 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyDemoTests/WebViewProxyDemoTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // WebViewProxyDemoTests.m 3 | // WebViewProxyDemoTests 4 | // 5 | // Created by Rommel on 14/11/28. 6 | // Copyright (c) 2014年 AOLC. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @interface WebViewProxyDemoTests : XCTestCase 13 | 14 | @end 15 | 16 | @implementation WebViewProxyDemoTests 17 | 18 | - (void)setUp { 19 | [super setUp]; 20 | // Put setup code here. This method is called before the invocation of each test method in the class. 21 | } 22 | 23 | - (void)tearDown { 24 | // Put teardown code here. This method is called after the invocation of each test method in the class. 25 | [super tearDown]; 26 | } 27 | 28 | - (void)testExample { 29 | // This is an example of a functional test case. 30 | XCTAssert(YES, @"Pass"); 31 | } 32 | 33 | - (void)testPerformanceExample { 34 | // This is an example of a performance test case. 35 | [self measureBlock:^{ 36 | // Put the code you want to measure the time of here. 37 | }]; 38 | } 39 | 40 | @end 41 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyDemo/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "ipad", 35 | "size" : "29x29", 36 | "scale" : "1x" 37 | }, 38 | { 39 | "idiom" : "ipad", 40 | "size" : "29x29", 41 | "scale" : "2x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "40x40", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "40x40", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "76x76", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "76x76", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyKit/UUSSLWhiteList.m: -------------------------------------------------------------------------------- 1 | // 2 | // UUSSLWhiteList.m 3 | // WebViewProxyDemo 4 | // 5 | // Created by Rommel on 14/11/28. 6 | // Copyright (c) 2014年 AOLC. All rights reserved. 7 | // 8 | 9 | #import "UUSSLWhiteList.h" 10 | 11 | @implementation UUSSLWhiteList 12 | - (instancetype) init { 13 | if(self = [super init]) { 14 | _whiteList = [[NSMutableArray alloc] initWithCapacity:10]; 15 | } 16 | return self; 17 | } 18 | 19 | + (instancetype) sharedSSLWhiteList { 20 | static UUSSLWhiteList *_sSSLWhiteList = nil; 21 | static dispatch_once_t once; 22 | dispatch_once(&once, ^{ 23 | _sSSLWhiteList = [[UUSSLWhiteList alloc] init]; 24 | }); 25 | return _sSSLWhiteList; 26 | } 27 | 28 | - (void) addIgonerURL:(NSURL *) url 29 | { 30 | if(![self urlIsContainedInWhiteList:url]) { 31 | if(url == nil) return; 32 | if(url.host == nil || url.host.length == 0) return; 33 | [_whiteList addObject:url.host]; 34 | } 35 | } 36 | 37 | - (void) addIgonerHost:(NSString *) host 38 | { 39 | if(![self hostIsContainedInWhiteList:host]) { 40 | if(host == nil || host.length == 0) return; 41 | [_whiteList addObject:host]; 42 | } 43 | } 44 | 45 | - (BOOL) hostIsContainedInWhiteList:(NSString *) host 46 | { 47 | if(host == nil || host.length == 0) return NO; 48 | return [_whiteList containsObject:host]; 49 | } 50 | 51 | 52 | - (BOOL) urlIsContainedInWhiteList:(NSURL *) url 53 | { 54 | if(url == nil) return NO; 55 | if(url.host == nil || url.host.length == 0) return NO; 56 | 57 | return [_whiteList containsObject:url.host]; 58 | } 59 | 60 | @end 61 | 62 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyDemo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | com.aolc.$(PRODUCT_NAME:rfc1034identifier) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | UIRequiredDeviceCapabilities 30 | 31 | armv7 32 | 33 | UISupportedInterfaceOrientations 34 | 35 | UIInterfaceOrientationPortrait 36 | UIInterfaceOrientationLandscapeLeft 37 | UIInterfaceOrientationLandscapeRight 38 | 39 | UISupportedInterfaceOrientations~ipad 40 | 41 | UIInterfaceOrientationPortrait 42 | UIInterfaceOrientationPortraitUpsideDown 43 | UIInterfaceOrientationLandscapeLeft 44 | UIInterfaceOrientationLandscapeRight 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyKit/CredentialsManager.h: -------------------------------------------------------------------------------- 1 | /* 2 | File: CredentialsManager.h 3 | Abstract: Manages the list of trusted anchor certificates. 4 | Version: 1.1 5 | 6 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 7 | Inc. ("Apple") in consideration of your agreement to the following 8 | terms, and your use, installation, modification or redistribution of 9 | this Apple software constitutes acceptance of these terms. If you do 10 | not agree with these terms, please do not use, install, modify or 11 | redistribute this Apple software. 12 | 13 | In consideration of your agreement to abide by the following terms, and 14 | subject to these terms, Apple grants you a personal, non-exclusive 15 | license, under Apple's copyrights in this original Apple software (the 16 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 17 | Software, with or without modifications, in source and/or binary forms; 18 | provided that if you redistribute the Apple Software in its entirety and 19 | without modifications, you must retain this notice and the following 20 | text and disclaimers in all such redistributions of the Apple Software. 21 | Neither the name, trademarks, service marks or logos of Apple Inc. may 22 | be used to endorse or promote products derived from the Apple Software 23 | without specific prior written permission from Apple. Except as 24 | expressly stated in this notice, no other rights or licenses, express or 25 | implied, are granted by Apple herein, including but not limited to any 26 | patent rights that may be infringed by your derivative works or by other 27 | works in which the Apple Software may be incorporated. 28 | 29 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 30 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 31 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 32 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 33 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 34 | 35 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 36 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 37 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 38 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 39 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 40 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 41 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 42 | POSSIBILITY OF SUCH DAMAGE. 43 | 44 | Copyright (C) 2014 Apple Inc. All Rights Reserved. 45 | 46 | */ 47 | 48 | @import Foundation; 49 | 50 | /*! Manages the list of trusted anchor certificates. This class is thread 51 | * safe. 52 | */ 53 | 54 | @interface CredentialsManager : NSObject 55 | 56 | - (instancetype)init; 57 | 58 | @property (atomic, copy, readonly ) NSArray * trustedAnchors; ///< The list of trusted anchor certificates; elements are of type SecCertificateRef; observable. 59 | 60 | /*! Adds a certificate to the end of the list of trusted anchor certificates. 61 | * Does nothing if the certificate is already in the list. 62 | * \param newAnchor The certificate to add; must not be NULL. 63 | */ 64 | 65 | - (void)addTrustedAnchor:(SecCertificateRef)newAnchor; 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyKit/CacheStoragePolicy.h: -------------------------------------------------------------------------------- 1 | /* 2 | File: CacheStoragePolicy.h 3 | Abstract: A function to determine the cache storage policy for a request. 4 | Version: 1.1 5 | 6 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 7 | Inc. ("Apple") in consideration of your agreement to the following 8 | terms, and your use, installation, modification or redistribution of 9 | this Apple software constitutes acceptance of these terms. If you do 10 | not agree with these terms, please do not use, install, modify or 11 | redistribute this Apple software. 12 | 13 | In consideration of your agreement to abide by the following terms, and 14 | subject to these terms, Apple grants you a personal, non-exclusive 15 | license, under Apple's copyrights in this original Apple software (the 16 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 17 | Software, with or without modifications, in source and/or binary forms; 18 | provided that if you redistribute the Apple Software in its entirety and 19 | without modifications, you must retain this notice and the following 20 | text and disclaimers in all such redistributions of the Apple Software. 21 | Neither the name, trademarks, service marks or logos of Apple Inc. may 22 | be used to endorse or promote products derived from the Apple Software 23 | without specific prior written permission from Apple. Except as 24 | expressly stated in this notice, no other rights or licenses, express or 25 | implied, are granted by Apple herein, including but not limited to any 26 | patent rights that may be infringed by your derivative works or by other 27 | works in which the Apple Software may be incorporated. 28 | 29 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 30 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 31 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 32 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 33 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 34 | 35 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 36 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 37 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 38 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 39 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 40 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 41 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 42 | POSSIBILITY OF SUCH DAMAGE. 43 | 44 | Copyright (C) 2014 Apple Inc. All Rights Reserved. 45 | 46 | */ 47 | 48 | @import Foundation; 49 | 50 | /*! Determines the cache storage policy for a response. 51 | * \details When we provide a response up to the client we need to tell the client whether 52 | * the response is cacheable or not. The default HTTP/HTTPS protocol has a reasonable 53 | * complex chunk of code to determine this, but we can't get at it. Thus, we have to 54 | * reimplement it ourselves. This is split off into a separate file to emphasise that 55 | * this is standard boilerplate that you probably don't need to look at. 56 | * \param request The request that generated the response; must not be nil. 57 | * \param response The response itself; must not be nil. 58 | * \returns A cache storage policy to use. 59 | */ 60 | 61 | extern NSURLCacheStoragePolicy CacheStoragePolicyForRequestAndResponse(NSURLRequest * request, NSHTTPURLResponse * response); 62 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyKit/CanonicalRequest.h: -------------------------------------------------------------------------------- 1 | /* 2 | File: CanonicalRequest.h 3 | Abstract: A function for creating canonical HTTP/HTTPS requests. 4 | Version: 1.1 5 | 6 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 7 | Inc. ("Apple") in consideration of your agreement to the following 8 | terms, and your use, installation, modification or redistribution of 9 | this Apple software constitutes acceptance of these terms. If you do 10 | not agree with these terms, please do not use, install, modify or 11 | redistribute this Apple software. 12 | 13 | In consideration of your agreement to abide by the following terms, and 14 | subject to these terms, Apple grants you a personal, non-exclusive 15 | license, under Apple's copyrights in this original Apple software (the 16 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 17 | Software, with or without modifications, in source and/or binary forms; 18 | provided that if you redistribute the Apple Software in its entirety and 19 | without modifications, you must retain this notice and the following 20 | text and disclaimers in all such redistributions of the Apple Software. 21 | Neither the name, trademarks, service marks or logos of Apple Inc. may 22 | be used to endorse or promote products derived from the Apple Software 23 | without specific prior written permission from Apple. Except as 24 | expressly stated in this notice, no other rights or licenses, express or 25 | implied, are granted by Apple herein, including but not limited to any 26 | patent rights that may be infringed by your derivative works or by other 27 | works in which the Apple Software may be incorporated. 28 | 29 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 30 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 31 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 32 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 33 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 34 | 35 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 36 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 37 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 38 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 39 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 40 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 41 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 42 | POSSIBILITY OF SUCH DAMAGE. 43 | 44 | Copyright (C) 2014 Apple Inc. All Rights Reserved. 45 | 46 | */ 47 | 48 | @import Foundation; 49 | 50 | /*! Returns a canonical form of the supplied request. 51 | * \details The Foundation URL loading system needs to be able to canonicalize URL 52 | * requests for various reasons (for example, to look for cache hits). The default 53 | * HTTP/HTTPS protocol has a complex chunk of code to perform this function. Unfortunately 54 | * there's no way for third party code to access this. Instead, we have to reimplement 55 | * it all ourselves. This is split off into a separate file to emphasise that this 56 | * is standard boilerplate that you probably don't need to look at. 57 | * 58 | * IMPORTANT: While you can take most of this code as read, you might want to tweak 59 | * the handling of the "Accept-Language" in the CanonicaliseHeaders routine. 60 | * \param request The request to canonicalise; must not be nil. 61 | * \returns The canonical request; should never be nil. 62 | */ 63 | 64 | extern NSMutableURLRequest * CanonicalRequestForRequest(NSURLRequest *request); 65 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyDemo/Base.lproj/LaunchScreen.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 20 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyKit/CredentialsManager.m: -------------------------------------------------------------------------------- 1 | /* 2 | File: CredentialsManager.m 3 | Abstract: Manages the list of trusted anchor certificates. 4 | Version: 1.1 5 | 6 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 7 | Inc. ("Apple") in consideration of your agreement to the following 8 | terms, and your use, installation, modification or redistribution of 9 | this Apple software constitutes acceptance of these terms. If you do 10 | not agree with these terms, please do not use, install, modify or 11 | redistribute this Apple software. 12 | 13 | In consideration of your agreement to abide by the following terms, and 14 | subject to these terms, Apple grants you a personal, non-exclusive 15 | license, under Apple's copyrights in this original Apple software (the 16 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 17 | Software, with or without modifications, in source and/or binary forms; 18 | provided that if you redistribute the Apple Software in its entirety and 19 | without modifications, you must retain this notice and the following 20 | text and disclaimers in all such redistributions of the Apple Software. 21 | Neither the name, trademarks, service marks or logos of Apple Inc. may 22 | be used to endorse or promote products derived from the Apple Software 23 | without specific prior written permission from Apple. Except as 24 | expressly stated in this notice, no other rights or licenses, express or 25 | implied, are granted by Apple herein, including but not limited to any 26 | patent rights that may be infringed by your derivative works or by other 27 | works in which the Apple Software may be incorporated. 28 | 29 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 30 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 31 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 32 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 33 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 34 | 35 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 36 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 37 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 38 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 39 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 40 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 41 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 42 | POSSIBILITY OF SUCH DAMAGE. 43 | 44 | Copyright (C) 2014 Apple Inc. All Rights Reserved. 45 | 46 | */ 47 | 48 | #import "CredentialsManager.h" 49 | 50 | @import Security; 51 | 52 | @interface CredentialsManager () 53 | 54 | @property (atomic, strong, readonly ) NSMutableArray * mutableTrustedAnchors; 55 | 56 | @end 57 | 58 | @implementation CredentialsManager 59 | 60 | - (instancetype)init 61 | { 62 | self = [super init]; 63 | if (self != nil) { 64 | self->_mutableTrustedAnchors = [[NSMutableArray alloc] init]; 65 | assert(self->_mutableTrustedAnchors != nil); 66 | } 67 | return self; 68 | } 69 | 70 | - (NSArray *)trustedAnchors 71 | { 72 | NSArray * result; 73 | 74 | @synchronized (self) { 75 | result = [self->_mutableTrustedAnchors copy]; 76 | assert(result != nil); 77 | } 78 | return result; 79 | } 80 | 81 | - (void)addTrustedAnchor:(SecCertificateRef)newAnchor 82 | { 83 | BOOL found; 84 | 85 | assert(newAnchor != NULL); 86 | 87 | @synchronized (self) { 88 | 89 | // Check to see if the certificate is already in the mutableTrustedAnchors 90 | // array. Somewhere along the line SecCertificate refs started supporting 91 | // CFEqual, so we can use -indexOfObject:, which is nice. 92 | 93 | found = [self->_mutableTrustedAnchors indexOfObject:(__bridge id) newAnchor] != NSNotFound; 94 | 95 | // If the new anchor isn't already in the array, add it. 96 | 97 | if ( ! found ) { 98 | NSIndexSet * indexSet; 99 | 100 | indexSet = [NSIndexSet indexSetWithIndex:[self->_mutableTrustedAnchors count]]; 101 | assert(indexSet != nil); 102 | 103 | [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexSet forKey:@"trustedAnchors"]; 104 | [self->_mutableTrustedAnchors addObject:(__bridge id)newAnchor]; 105 | [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexSet forKey:@"trustedAnchors"]; 106 | } 107 | } 108 | } 109 | 110 | @end 111 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyDemo.xcodeproj/xcuserdata/zhanlin.xcuserdatad/xcschemes/WebViewProxyDemo.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 75 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 94 | 100 | 101 | 102 | 103 | 105 | 106 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyKit/QNSURLSessionDemux.h: -------------------------------------------------------------------------------- 1 | /* 2 | File: QNSURLSessionDemux.h 3 | Abstract: A general class to demux NSURLSession delegate callbacks. 4 | Version: 1.1 5 | 6 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 7 | Inc. ("Apple") in consideration of your agreement to the following 8 | terms, and your use, installation, modification or redistribution of 9 | this Apple software constitutes acceptance of these terms. If you do 10 | not agree with these terms, please do not use, install, modify or 11 | redistribute this Apple software. 12 | 13 | In consideration of your agreement to abide by the following terms, and 14 | subject to these terms, Apple grants you a personal, non-exclusive 15 | license, under Apple's copyrights in this original Apple software (the 16 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 17 | Software, with or without modifications, in source and/or binary forms; 18 | provided that if you redistribute the Apple Software in its entirety and 19 | without modifications, you must retain this notice and the following 20 | text and disclaimers in all such redistributions of the Apple Software. 21 | Neither the name, trademarks, service marks or logos of Apple Inc. may 22 | be used to endorse or promote products derived from the Apple Software 23 | without specific prior written permission from Apple. Except as 24 | expressly stated in this notice, no other rights or licenses, express or 25 | implied, are granted by Apple herein, including but not limited to any 26 | patent rights that may be infringed by your derivative works or by other 27 | works in which the Apple Software may be incorporated. 28 | 29 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 30 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 31 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 32 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 33 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 34 | 35 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 36 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 37 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 38 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 39 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 40 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 41 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 42 | POSSIBILITY OF SUCH DAMAGE. 43 | 44 | Copyright (C) 2014 Apple Inc. All Rights Reserved. 45 | 46 | */ 47 | 48 | #import 49 | 50 | /*! A simple class for demultiplexing NSURLSession delegate callbacks to a per-task delegate object. 51 | 52 | You initialise the class with a session configuration. After that you can create data tasks 53 | within that session by calling -dataTaskWithRequest:delegate:modes:. Any delegate callbacks 54 | for that data task are redirected to the delegate on the thread that created the task in 55 | one of the specified run loop modes. That thread must run its run loop in order to get 56 | these callbacks. 57 | */ 58 | 59 | @interface QNSURLSessionDemux : NSObject 60 | 61 | /*! Create a demultiplex for the specified session configuration. 62 | * \param configuration The session configuration to use; if nil, a default session is created. 63 | * \returns An initialised instance. 64 | */ 65 | 66 | - (instancetype)initWithConfiguration:(NSURLSessionConfiguration *)configuration; 67 | 68 | @property (atomic, copy, readonly ) NSURLSessionConfiguration * configuration; ///< A copy of the configuration passed to -initWithConfiguration:. 69 | @property (atomic, strong, readonly ) NSURLSession * session; ///< The session created from the configuration passed to -initWithConfiguration:. 70 | 71 | /*! Creates a new data task whose delegate callbacks are routed to the supplied delegate. 72 | * \details The callbacks are run on the current thread (that is, the thread that called this 73 | * method) in the specified modes. 74 | * 75 | * The delegate is retained until the task completes, that is, until after your 76 | * -URLSession:task:didCompleteWithError: delegate callback returns. 77 | * 78 | * The returned task is suspend. You must resume the returned task for the task to 79 | * make progress. Furthermore, it's not safe to simply discard the returned task 80 | * because in that case the task's delegate is never released. 81 | * 82 | * \param request The request that the data task executes; must not be nil. 83 | * \param delegate The delegate to receive the data task's delegate callbacks; must not be nil. 84 | * \param modes The run loop modes in which to run the data task's delegate callbacks; if nil or 85 | * empty, the default run loop mode (NSDefaultRunLoopMode is used). 86 | * \returns A suspended data task that you must resume. 87 | */ 88 | 89 | - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request delegate:(id)delegate modes:(NSArray *)modes; 90 | 91 | @end 92 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyKit/CacheStoragePolicy.m: -------------------------------------------------------------------------------- 1 | /* 2 | File: CacheStoragePolicy.m 3 | Abstract: A function to determine the cache storage policy for a request. 4 | Version: 1.1 5 | 6 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 7 | Inc. ("Apple") in consideration of your agreement to the following 8 | terms, and your use, installation, modification or redistribution of 9 | this Apple software constitutes acceptance of these terms. If you do 10 | not agree with these terms, please do not use, install, modify or 11 | redistribute this Apple software. 12 | 13 | In consideration of your agreement to abide by the following terms, and 14 | subject to these terms, Apple grants you a personal, non-exclusive 15 | license, under Apple's copyrights in this original Apple software (the 16 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 17 | Software, with or without modifications, in source and/or binary forms; 18 | provided that if you redistribute the Apple Software in its entirety and 19 | without modifications, you must retain this notice and the following 20 | text and disclaimers in all such redistributions of the Apple Software. 21 | Neither the name, trademarks, service marks or logos of Apple Inc. may 22 | be used to endorse or promote products derived from the Apple Software 23 | without specific prior written permission from Apple. Except as 24 | expressly stated in this notice, no other rights or licenses, express or 25 | implied, are granted by Apple herein, including but not limited to any 26 | patent rights that may be infringed by your derivative works or by other 27 | works in which the Apple Software may be incorporated. 28 | 29 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 30 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 31 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 32 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 33 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 34 | 35 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 36 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 37 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 38 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 39 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 40 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 41 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 42 | POSSIBILITY OF SUCH DAMAGE. 43 | 44 | Copyright (C) 2014 Apple Inc. All Rights Reserved. 45 | 46 | */ 47 | 48 | #import "CacheStoragePolicy.h" 49 | 50 | extern NSURLCacheStoragePolicy CacheStoragePolicyForRequestAndResponse(NSURLRequest * request, NSHTTPURLResponse * response) 51 | // See comment in header. 52 | { 53 | BOOL cacheable; 54 | NSURLCacheStoragePolicy result; 55 | 56 | assert(request != NULL); 57 | assert(response != NULL); 58 | 59 | // First determine if the request is cacheable based on its status code. 60 | 61 | switch ([response statusCode]) { 62 | case 200: 63 | case 203: 64 | case 206: 65 | case 301: 66 | case 304: 67 | case 404: 68 | case 410: { 69 | cacheable = YES; 70 | } break; 71 | default: { 72 | cacheable = NO; 73 | } break; 74 | } 75 | 76 | // If the response might be cacheable, look at the "Cache-Control" header in 77 | // the response. 78 | 79 | // IMPORTANT: We can't rely on -rangeOfString: returning valid results if the target 80 | // string is nil, so we have to explicitly test for nil in the following two cases. 81 | 82 | if (cacheable) { 83 | NSString * responseHeader; 84 | 85 | responseHeader = [[response allHeaderFields][@"Cache-Control"] lowercaseString]; 86 | if ( (responseHeader != nil) && [responseHeader rangeOfString:@"no-store"].location != NSNotFound) { 87 | cacheable = NO; 88 | } 89 | } 90 | 91 | // If we still think it might be cacheable, look at the "Cache-Control" header in 92 | // the request. 93 | 94 | if (cacheable) { 95 | NSString * requestHeader; 96 | 97 | requestHeader = [[request allHTTPHeaderFields][@"Cache-Control"] lowercaseString]; 98 | if ( (requestHeader != nil) 99 | && ([requestHeader rangeOfString:@"no-store"].location != NSNotFound) 100 | && ([requestHeader rangeOfString:@"no-cache"].location != NSNotFound) ) { 101 | cacheable = NO; 102 | } 103 | } 104 | 105 | // Use the cacheable flag to determine the result. 106 | 107 | if (cacheable) { 108 | 109 | // This code only caches HTTPS data in memory. This is inline with earlier versions of 110 | // iOS. Modern versions of iOS use file protection to protect the cache, and thus are 111 | // happy to cache HTTPS on disk. I've not made the correspondencing change because 112 | // it's nice to see all three cache policies in action. 113 | 114 | if ([[[[request URL] scheme] lowercaseString] isEqual:@"https"]) { 115 | result = NSURLCacheStorageAllowedInMemoryOnly; 116 | } else { 117 | result = NSURLCacheStorageAllowed; 118 | } 119 | } else { 120 | result = NSURLCacheStorageNotAllowed; 121 | } 122 | 123 | return result; 124 | } 125 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyDemo/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyDemo/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // WebViewProxyDemo 4 | // 5 | // Created by Rommel on 14/11/28. 6 | // Copyright (c) 2014年 AOLC. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | 11 | #import "CustomHTTPProtocol.h" 12 | 13 | @interface AppDelegate () 14 | 15 | @end 16 | 17 | @implementation AppDelegate 18 | 19 | 20 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 21 | 22 | 23 | // NSDictionary *dict = @{ 24 | // @"SOCKSEnable" : @1, 25 | // @"SOCKSProxy" : @"127.0.0.1", 26 | // @"SOCKSPort" : @1080, 27 | // @"SOCKSProxyAuthenticated" : @0, 28 | // }; 29 | // 30 | // [CustomHTTPProtocol setProxyConfig:dict]; 31 | // self.credentialsManager = [[CredentialsManager alloc] init]; 32 | // [CustomHTTPProtocol setDelegate:self]; 33 | // if (YES) { 34 | // [CustomHTTPProtocol start]; 35 | // } 36 | 37 | return YES; 38 | } 39 | 40 | - (void)applicationWillResignActive:(UIApplication *)application { 41 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state. 42 | // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game. 43 | } 44 | 45 | - (void)applicationDidEnterBackground:(UIApplication *)application { 46 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 47 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits. 48 | } 49 | 50 | - (void)applicationWillEnterForeground:(UIApplication *)application { 51 | // Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background. 52 | } 53 | 54 | - (void)applicationDidBecomeActive:(UIApplication *)application { 55 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface. 56 | } 57 | 58 | - (void)applicationWillTerminate:(UIApplication *)application { 59 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. 60 | } 61 | 62 | 63 | 64 | /*! Called by an CustomHTTPProtocol instance to ask the delegate whether it's prepared to handle 65 | * a particular authentication challenge. Can be called on any thread. 66 | * \param protocol The protocol instance itself; will not be nil. 67 | * \param protectionSpace The protection space for the authentication challenge; will not be nil. 68 | * \returns Return YES if you want the -customHTTPProtocol:didReceiveAuthenticationChallenge: delegate 69 | * callback, or NO for the challenge to be handled in the default way. 70 | */ 71 | 72 | - (BOOL)customHTTPProtocol:(CustomHTTPProtocol *)protocol canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace { 73 | 74 | assert(protocol != nil); 75 | #pragma unused(protocol) 76 | assert(protectionSpace != nil); 77 | 78 | return [[protectionSpace authenticationMethod] isEqual:NSURLAuthenticationMethodServerTrust]; 79 | } 80 | 81 | /*! Called by an CustomHTTPProtocol instance to request that the delegate process on authentication 82 | * challenge. Will be called on the main thread. Unless the challenge is cancelled (see below) 83 | * the delegate must eventually resolve it by calling -resolveAuthenticationChallenge:withCredential:. 84 | * \param protocol The protocol instance itself; will not be nil. 85 | * \param challenge The authentication challenge; will not be nil. 86 | */ 87 | 88 | - (void)customHTTPProtocol:(CustomHTTPProtocol *)protocol didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { 89 | 90 | OSStatus err; 91 | NSURLCredential * credential; 92 | SecTrustRef trust; 93 | SecTrustResultType trustResult; 94 | 95 | // Given our implementation of -customHTTPProtocol:canAuthenticateAgainstProtectionSpace:, this method 96 | // is only called to handle server trust authentication challenges. It evaluates the trust based on 97 | // both the global set of trusted anchors and the list of trusted anchors returned by the CredentialsManager. 98 | 99 | assert(protocol != nil); 100 | assert(challenge != nil); 101 | assert([[[challenge protectionSpace] authenticationMethod] isEqual:NSURLAuthenticationMethodServerTrust]); 102 | assert([NSThread isMainThread]); 103 | 104 | credential = nil; 105 | 106 | // Extract the SecTrust object from the challenge, apply our trusted anchors to that 107 | // object, and then evaluate the trust. If it's OK, create a credential and use 108 | // that to resolve the authentication challenge. If anything goes wrong, resolve 109 | // the challenge with nil, which continues without a credential, which causes the 110 | // connection to fail. 111 | 112 | trust = [[challenge protectionSpace] serverTrust]; 113 | if (trust == NULL) { 114 | assert(NO); 115 | } else { 116 | err = SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef) self.credentialsManager.trustedAnchors); 117 | if (err != noErr) { 118 | assert(NO); 119 | } else { 120 | err = SecTrustSetAnchorCertificatesOnly(trust, false); 121 | if (err != noErr) { 122 | assert(NO); 123 | } else { 124 | err = SecTrustEvaluate(trust, &trustResult); 125 | if (err != noErr) { 126 | assert(NO); 127 | } else { 128 | if ( (trustResult == kSecTrustResultProceed) || (trustResult == kSecTrustResultUnspecified) ) { 129 | credential = [NSURLCredential credentialForTrust:trust]; 130 | assert(credential != nil); 131 | } 132 | //credential = [NSURLCredential credentialForTrust:trust]; 133 | } 134 | } 135 | } 136 | } 137 | 138 | [protocol resolveAuthenticationChallenge:challenge withCredential:credential]; 139 | } 140 | 141 | /*! Called by an CustomHTTPProtocol instance to cancel an issued authentication challenge. 142 | * Will be called on the main thread. 143 | * \param protocol The protocol instance itself; will not be nil. 144 | * \param challenge The authentication challenge; will not be nil; will match the challenge 145 | * previously issued by -customHTTPProtocol:canAuthenticateAgainstProtectionSpace:. 146 | */ 147 | 148 | - (void)customHTTPProtocol:(CustomHTTPProtocol *)protocol didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { 149 | 150 | } 151 | 152 | /*! Called by the CustomHTTPProtocol to log various bits of information. 153 | * Can be called on any thread. 154 | * \param protocol The protocol instance itself; nil to indicate log messages from the class itself. 155 | * \param format A standard NSString-style format string; will not be nil. 156 | * \param arguments Arguments for that format string. 157 | */ 158 | 159 | - (void)customHTTPProtocol:(CustomHTTPProtocol *)protocol logWithFormat:(NSString *)format arguments:(va_list)arguments { 160 | 161 | return; 162 | 163 | NSString * prefix; 164 | 165 | // protocol may be nil 166 | assert(format != nil); 167 | 168 | if (protocol == nil) { 169 | prefix = @"protocol "; 170 | } else { 171 | prefix = [NSString stringWithFormat:@"protocol %p ", protocol]; 172 | } 173 | [self logWithPrefix:prefix format:format arguments:arguments]; 174 | } 175 | 176 | 177 | - (void)logWithPrefix:(NSString *)prefix format:(NSString *)format arguments:(va_list)arguments 178 | { 179 | assert(prefix != nil); 180 | assert(format != nil); 181 | NSString *body = [[NSString alloc] initWithFormat:format arguments:arguments]; 182 | NSLog(@"%@ - %@", prefix, body); 183 | // if (sAppDelegateLoggingEnabled) { 184 | // NSTimeInterval now; 185 | // ThreadInfo * threadInfo; 186 | // NSString * str; 187 | // char elapsedStr[16]; 188 | // 189 | // now = [NSDate timeIntervalSinceReferenceDate]; 190 | // 191 | // threadInfo = [self threadInfoForCurrentThread]; 192 | // 193 | // str = [[NSString alloc] initWithFormat:format arguments:arguments]; 194 | // assert(str != nil); 195 | // 196 | // snprintf(elapsedStr, sizeof(elapsedStr), "+%.1f", (now - sAppStartTime)); 197 | // 198 | // fprintf(stderr, "%3zu %s %s%s\n", (size_t) threadInfo.number, elapsedStr, [prefix UTF8String], [str UTF8String]); 199 | // } 200 | } 201 | 202 | @end 203 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyKit/CustomHTTPProtocol.h: -------------------------------------------------------------------------------- 1 | /* 2 | File: CustomHTTPProtocol.h 3 | Abstract: An NSURLProtocol subclass that overrides the built-in HTTP/HTTPS protocol. 4 | Version: 1.1 5 | 6 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 7 | Inc. ("Apple") in consideration of your agreement to the following 8 | terms, and your use, installation, modification or redistribution of 9 | this Apple software constitutes acceptance of these terms. If you do 10 | not agree with these terms, please do not use, install, modify or 11 | redistribute this Apple software. 12 | 13 | In consideration of your agreement to abide by the following terms, and 14 | subject to these terms, Apple grants you a personal, non-exclusive 15 | license, under Apple's copyrights in this original Apple software (the 16 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 17 | Software, with or without modifications, in source and/or binary forms; 18 | provided that if you redistribute the Apple Software in its entirety and 19 | without modifications, you must retain this notice and the following 20 | text and disclaimers in all such redistributions of the Apple Software. 21 | Neither the name, trademarks, service marks or logos of Apple Inc. may 22 | be used to endorse or promote products derived from the Apple Software 23 | without specific prior written permission from Apple. Except as 24 | expressly stated in this notice, no other rights or licenses, express or 25 | implied, are granted by Apple herein, including but not limited to any 26 | patent rights that may be infringed by your derivative works or by other 27 | works in which the Apple Software may be incorporated. 28 | 29 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 30 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 31 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 32 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 33 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 34 | 35 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 36 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 37 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 38 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 39 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 40 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 41 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 42 | POSSIBILITY OF SUCH DAMAGE. 43 | 44 | Copyright (C) 2014 Apple Inc. All Rights Reserved. 45 | 46 | */ 47 | 48 | @import Foundation; 49 | 50 | @protocol CustomHTTPProtocolDelegate; 51 | 52 | /*! An NSURLProtocol subclass that overrides the built-in HTTP/HTTPS protocol to intercept 53 | * authentication challenges for subsystems, ilke UIWebView, that don't otherwise allow it. 54 | * To use this class you should set up your delegate (+setDelegate:) and then call +start. 55 | * If you don't call +start the class is completely benign. 56 | * 57 | * The really tricky stuff here is related to the authentication challenge delegate 58 | * callbacks; see the docs for CustomHTTPProtocolDelegate for the details. 59 | */ 60 | 61 | @interface CustomHTTPProtocol : NSURLProtocol 62 | + (void) setProxyConfig:(NSDictionary *) proxyConfig; 63 | /*! Call this to start the module. Prior to this the module is just dormant, and 64 | * all HTTP requests proceed as normal. After this all HTTP and HTTPS requests 65 | * go through this module. 66 | */ 67 | 68 | + (void)start; 69 | 70 | /*! Sets the delegate for the class. 71 | * \details Note that there's one delegate for the entire class, not one per 72 | * instance of the class as is more normal. The delegate is not retained in general, 73 | * but is retained for the duration of any given call. Once you set the delegate to nil 74 | * you can be assured that it won't be called unretained (that is, by the time that 75 | * -setDelegate: returns, we've already done all possible retains on the delegate). 76 | * \param newValue The new delegate to use; may be nil. 77 | */ 78 | 79 | + (void)setDelegate:(id)newValue; 80 | 81 | /*! Returns the class delegate. 82 | */ 83 | 84 | + (id)delegate; 85 | 86 | @property (atomic, strong, readonly ) NSURLAuthenticationChallenge * pendingChallenge; ///< The current authentication challenge; it's only safe to access this from the main thread. 87 | 88 | /*! Call this method to resolve an authentication challeng. This must be called on the main thread. 89 | * \param challenge The challenge to resolve. This must match the pendingChallenge property. 90 | * \param credential The credential to use, or nil to continue without a credential. 91 | */ 92 | 93 | - (void)resolveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge withCredential:(NSURLCredential *)credential; 94 | 95 | @end 96 | 97 | /*! The delegate for the CustomHTTPProtocol class (not its instances). 98 | * \details The delegate handles two different types of callbacks: 99 | * 100 | * - authentication challenges 101 | * 102 | * - logging 103 | * 104 | * The latter is very simple. The former is quite tricky. The basic idea is that each CustomHTTPProtocol 105 | * instance sends the delegate a serialised stream of authentication challenges, each of which it is 106 | * expected to resolve. The sequence is as follows: 107 | * 108 | * -# It calls -customHTTPProtocol:canAuthenticateAgainstProtectionSpace: to determine if the delegate 109 | * can handle the challenge. This can be call on an arbitrary background thread. 110 | * 111 | * -# If the delegate returns YES, it calls -customHTTPProtocol:didReceiveAuthenticationChallenge: to 112 | * actually process the challenge. This is always called on the main thread. The delegate can resolve 113 | * the challenge synchronously (that is, before returning from the call) or it can return from the call 114 | * and then, later on, resolve the challenge. Resolving the challenge involves calling 115 | * -[CustomHTTPProtocol resolveAuthenticationChallenge:withCredential:], which also must be called 116 | * on the main thread. Between the calls to -customHTTPProtocol:didReceiveAuthenticationChallenge: 117 | * and -[CustomHTTPProtocol resolveAuthenticationChallenge:withCredential:], the protocol's 118 | * pendingChallenge property will contain the challenge. 119 | * 120 | * -# While there is a pending challenge, the protocol may call -customHTTPProtocol:didCancelAuthenticationChallenge: 121 | * to cancel the challenge. This is always called on the main thread. 122 | * 123 | * Note that this design follows the original NSURLConnection model, not the newer NSURLConnection model 124 | * (introduced in OS X 10.7 / iOS 5) or the NSURLSession model, because of my concerns about performance. 125 | * Specifically, -customHTTPProtocol:canAuthenticateAgainstProtectionSpace: can be called on any thread 126 | * but -customHTTPProtocol:didReceiveAuthenticationChallenge: is called on the main thread. If I unified 127 | * them I'd end up calling the resulting single routine on the main thread, which meanings a lot more 128 | * bouncing between threads, much of which would be pointless in the common case where you don't want to 129 | * customise the default behaviour. Alternatively I could call the unified routine on an arbitrary thread, 130 | * but that would make it harder for clients and require a major rework of my implementation. 131 | */ 132 | 133 | @protocol CustomHTTPProtocolDelegate 134 | 135 | @optional 136 | 137 | /*! Called by an CustomHTTPProtocol instance to ask the delegate whether it's prepared to handle 138 | * a particular authentication challenge. Can be called on any thread. 139 | * \param protocol The protocol instance itself; will not be nil. 140 | * \param protectionSpace The protection space for the authentication challenge; will not be nil. 141 | * \returns Return YES if you want the -customHTTPProtocol:didReceiveAuthenticationChallenge: delegate 142 | * callback, or NO for the challenge to be handled in the default way. 143 | */ 144 | 145 | - (BOOL)customHTTPProtocol:(CustomHTTPProtocol *)protocol canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace; 146 | 147 | /*! Called by an CustomHTTPProtocol instance to request that the delegate process on authentication 148 | * challenge. Will be called on the main thread. Unless the challenge is cancelled (see below) 149 | * the delegate must eventually resolve it by calling -resolveAuthenticationChallenge:withCredential:. 150 | * \param protocol The protocol instance itself; will not be nil. 151 | * \param challenge The authentication challenge; will not be nil. 152 | */ 153 | 154 | - (void)customHTTPProtocol:(CustomHTTPProtocol *)protocol didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge; 155 | 156 | /*! Called by an CustomHTTPProtocol instance to cancel an issued authentication challenge. 157 | * Will be called on the main thread. 158 | * \param protocol The protocol instance itself; will not be nil. 159 | * \param challenge The authentication challenge; will not be nil; will match the challenge 160 | * previously issued by -customHTTPProtocol:canAuthenticateAgainstProtectionSpace:. 161 | */ 162 | 163 | - (void)customHTTPProtocol:(CustomHTTPProtocol *)protocol didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge; 164 | 165 | /*! Called by the CustomHTTPProtocol to log various bits of information. 166 | * Can be called on any thread. 167 | * \param protocol The protocol instance itself; nil to indicate log messages from the class itself. 168 | * \param format A standard NSString-style format string; will not be nil. 169 | * \param arguments Arguments for that format string. 170 | */ 171 | 172 | - (void)customHTTPProtocol:(CustomHTTPProtocol *)protocol logWithFormat:(NSString *)format arguments:(va_list)arguments; 173 | 174 | @end 175 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyKit/QNSURLSessionDemux.m: -------------------------------------------------------------------------------- 1 | /* 2 | File: QNSURLSessionDemux.m 3 | Abstract: A general class to demux NSURLSession delegate callbacks. 4 | Version: 1.1 5 | 6 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 7 | Inc. ("Apple") in consideration of your agreement to the following 8 | terms, and your use, installation, modification or redistribution of 9 | this Apple software constitutes acceptance of these terms. If you do 10 | not agree with these terms, please do not use, install, modify or 11 | redistribute this Apple software. 12 | 13 | In consideration of your agreement to abide by the following terms, and 14 | subject to these terms, Apple grants you a personal, non-exclusive 15 | license, under Apple's copyrights in this original Apple software (the 16 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 17 | Software, with or without modifications, in source and/or binary forms; 18 | provided that if you redistribute the Apple Software in its entirety and 19 | without modifications, you must retain this notice and the following 20 | text and disclaimers in all such redistributions of the Apple Software. 21 | Neither the name, trademarks, service marks or logos of Apple Inc. may 22 | be used to endorse or promote products derived from the Apple Software 23 | without specific prior written permission from Apple. Except as 24 | expressly stated in this notice, no other rights or licenses, express or 25 | implied, are granted by Apple herein, including but not limited to any 26 | patent rights that may be infringed by your derivative works or by other 27 | works in which the Apple Software may be incorporated. 28 | 29 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 30 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 31 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 32 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 33 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 34 | 35 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 36 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 37 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 38 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 39 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 40 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 41 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 42 | POSSIBILITY OF SUCH DAMAGE. 43 | 44 | Copyright (C) 2014 Apple Inc. All Rights Reserved. 45 | 46 | */ 47 | 48 | #import "QNSURLSessionDemux.h" 49 | 50 | @interface QNSURLSessionDemuxTaskInfo : NSObject 51 | 52 | - (instancetype)initWithTask:(NSURLSessionDataTask *)task delegate:(id)delegate modes:(NSArray *)modes; 53 | 54 | @property (atomic, strong, readonly ) NSURLSessionDataTask * task; 55 | @property (atomic, strong, readonly ) id delegate; 56 | @property (atomic, strong, readonly ) NSThread * thread; 57 | @property (atomic, copy, readonly ) NSArray * modes; 58 | 59 | - (void)performBlock:(dispatch_block_t)block; 60 | 61 | - (void)invalidate; 62 | 63 | @end 64 | 65 | @interface QNSURLSessionDemuxTaskInfo () 66 | 67 | @property (atomic, strong, readwrite) id delegate; 68 | @property (atomic, strong, readwrite) NSThread * thread; 69 | 70 | @end 71 | 72 | @implementation QNSURLSessionDemuxTaskInfo 73 | 74 | - (instancetype)initWithTask:(NSURLSessionDataTask *)task delegate:(id)delegate modes:(NSArray *)modes 75 | { 76 | assert(task != nil); 77 | assert(delegate != nil); 78 | assert(modes != nil); 79 | 80 | self = [super init]; 81 | if (self != nil) { 82 | self->_task = task; 83 | self->_delegate = delegate; 84 | self->_thread = [NSThread currentThread]; 85 | self->_modes = [modes copy]; 86 | } 87 | return self; 88 | } 89 | 90 | - (void)performBlock:(dispatch_block_t)block 91 | { 92 | assert(self.delegate != nil); 93 | assert(self.thread != nil); 94 | [self performSelector:@selector(performBlockOnClientThread:) onThread:self.thread withObject:[block copy] waitUntilDone:NO modes:self.modes]; 95 | } 96 | 97 | - (void)performBlockOnClientThread:(dispatch_block_t)block 98 | { 99 | assert([NSThread currentThread] == self.thread); 100 | block(); 101 | } 102 | 103 | - (void)invalidate 104 | { 105 | self.delegate = nil; 106 | self.thread = nil; 107 | } 108 | 109 | @end 110 | 111 | @interface QNSURLSessionDemux () 112 | 113 | @property (atomic, strong, readonly ) NSMutableDictionary * taskInfoByTaskID; // keys NSURLSessionTask taskIdentifier, values are SessionManager 114 | @property (atomic, strong, readonly ) NSOperationQueue * sessionDelegateQueue; 115 | 116 | @end 117 | 118 | @implementation QNSURLSessionDemux 119 | 120 | - (instancetype)init 121 | { 122 | return [self initWithConfiguration:nil]; 123 | } 124 | 125 | - (instancetype)initWithConfiguration:(NSURLSessionConfiguration *)configuration 126 | { 127 | // configuration may be nil 128 | self = [super init]; 129 | if (self != nil) { 130 | if (configuration == nil) { 131 | configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; 132 | } 133 | self->_configuration = [configuration copy]; 134 | 135 | self->_taskInfoByTaskID = [[NSMutableDictionary alloc] init]; 136 | 137 | self->_sessionDelegateQueue = [[NSOperationQueue alloc] init]; 138 | [self->_sessionDelegateQueue setMaxConcurrentOperationCount:1]; 139 | [self->_sessionDelegateQueue setName:@"QNSURLSessionDemux"]; 140 | 141 | self->_session = [NSURLSession sessionWithConfiguration:self->_configuration delegate:self delegateQueue:self->_sessionDelegateQueue]; 142 | self->_session.sessionDescription = @"QNSURLSessionDemux"; 143 | } 144 | return self; 145 | } 146 | 147 | - (NSURLSessionDataTask *)dataTaskWithRequest:(NSURLRequest *)request delegate:(id)delegate modes:(NSArray *)modes 148 | { 149 | NSURLSessionDataTask * task; 150 | QNSURLSessionDemuxTaskInfo * taskInfo; 151 | 152 | assert(request != nil); 153 | assert(delegate != nil); 154 | // modes may be nil 155 | 156 | if ([modes count] == 0) { 157 | modes = @[ NSDefaultRunLoopMode ]; 158 | } 159 | 160 | task = [self.session dataTaskWithRequest:request]; 161 | assert(task != nil); 162 | 163 | taskInfo = [[QNSURLSessionDemuxTaskInfo alloc] initWithTask:task delegate:delegate modes:modes]; 164 | 165 | @synchronized (self) { 166 | self.taskInfoByTaskID[@(task.taskIdentifier)] = taskInfo; 167 | } 168 | 169 | return task; 170 | } 171 | 172 | - (QNSURLSessionDemuxTaskInfo *)taskInfoForTask:(NSURLSessionTask *)task 173 | { 174 | QNSURLSessionDemuxTaskInfo * result; 175 | 176 | assert(task != nil); 177 | 178 | @synchronized (self) { 179 | result = self.taskInfoByTaskID[@(task.taskIdentifier)]; 180 | assert(result != nil); 181 | } 182 | return result; 183 | } 184 | 185 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)newRequest completionHandler:(void (^)(NSURLRequest *))completionHandler 186 | { 187 | QNSURLSessionDemuxTaskInfo * taskInfo; 188 | 189 | taskInfo = [self taskInfoForTask:task]; 190 | if ([taskInfo.delegate respondsToSelector:@selector(URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:)]) { 191 | [taskInfo performBlock:^{ 192 | [taskInfo.delegate URLSession:session task:task willPerformHTTPRedirection:response newRequest:newRequest completionHandler:completionHandler]; 193 | }]; 194 | } else { 195 | completionHandler(newRequest); 196 | } 197 | } 198 | 199 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler 200 | { 201 | QNSURLSessionDemuxTaskInfo * taskInfo; 202 | 203 | taskInfo = [self taskInfoForTask:task]; 204 | if ([taskInfo.delegate respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)]) { 205 | [taskInfo performBlock:^{ 206 | [taskInfo.delegate URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler]; 207 | }]; 208 | } else { 209 | completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); 210 | } 211 | } 212 | 213 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task needNewBodyStream:(void (^)(NSInputStream *bodyStream))completionHandler 214 | { 215 | QNSURLSessionDemuxTaskInfo * taskInfo; 216 | 217 | taskInfo = [self taskInfoForTask:task]; 218 | if ([taskInfo.delegate respondsToSelector:@selector(URLSession:task:needNewBodyStream:)]) { 219 | [taskInfo performBlock:^{ 220 | [taskInfo.delegate URLSession:session task:task needNewBodyStream:completionHandler]; 221 | }]; 222 | } else { 223 | completionHandler(nil); 224 | } 225 | } 226 | 227 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend 228 | { 229 | QNSURLSessionDemuxTaskInfo * taskInfo; 230 | 231 | taskInfo = [self taskInfoForTask:task]; 232 | if ([taskInfo.delegate respondsToSelector:@selector(URLSession:task:didSendBodyData:totalBytesSent:totalBytesExpectedToSend:)]) { 233 | [taskInfo performBlock:^{ 234 | [taskInfo.delegate URLSession:session task:task didSendBodyData:bytesSent totalBytesSent:totalBytesSent totalBytesExpectedToSend:totalBytesExpectedToSend]; 235 | }]; 236 | } 237 | } 238 | 239 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error 240 | { 241 | QNSURLSessionDemuxTaskInfo * taskInfo; 242 | 243 | taskInfo = [self taskInfoForTask:task]; 244 | 245 | // This is our last delegate callback so we remove our task info record. 246 | 247 | @synchronized (self) { 248 | [self.taskInfoByTaskID removeObjectForKey:@(taskInfo.task.taskIdentifier)]; 249 | } 250 | 251 | // Call the delegate if required. In that case we invalidate the task info on the client thread 252 | // after calling the delegate, otherwise the client thread side of the -performBlock: code can 253 | // find itself with an invalidated task info. 254 | 255 | if ([taskInfo.delegate respondsToSelector:@selector(URLSession:task:didCompleteWithError:)]) { 256 | [taskInfo performBlock:^{ 257 | [taskInfo.delegate URLSession:session task:task didCompleteWithError:error]; 258 | [taskInfo invalidate]; 259 | }]; 260 | } else { 261 | [taskInfo invalidate]; 262 | } 263 | } 264 | 265 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler 266 | { 267 | QNSURLSessionDemuxTaskInfo * taskInfo; 268 | 269 | taskInfo = [self taskInfoForTask:dataTask]; 270 | if ([taskInfo.delegate respondsToSelector:@selector(URLSession:dataTask:didReceiveResponse:completionHandler:)]) { 271 | [taskInfo performBlock:^{ 272 | [taskInfo.delegate URLSession:session dataTask:dataTask didReceiveResponse:response completionHandler:completionHandler]; 273 | }]; 274 | } else { 275 | completionHandler(NSURLSessionResponseAllow); 276 | } 277 | } 278 | 279 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didBecomeDownloadTask:(NSURLSessionDownloadTask *)downloadTask 280 | { 281 | QNSURLSessionDemuxTaskInfo * taskInfo; 282 | 283 | taskInfo = [self taskInfoForTask:dataTask]; 284 | if ([taskInfo.delegate respondsToSelector:@selector(URLSession:dataTask:didBecomeDownloadTask:)]) { 285 | [taskInfo performBlock:^{ 286 | [taskInfo.delegate URLSession:session dataTask:dataTask didBecomeDownloadTask:downloadTask]; 287 | }]; 288 | } 289 | } 290 | 291 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data 292 | { 293 | QNSURLSessionDemuxTaskInfo * taskInfo; 294 | 295 | taskInfo = [self taskInfoForTask:dataTask]; 296 | if ([taskInfo.delegate respondsToSelector:@selector(URLSession:dataTask:didReceiveData:)]) { 297 | [taskInfo performBlock:^{ 298 | [taskInfo.delegate URLSession:session dataTask:dataTask didReceiveData:data]; 299 | }]; 300 | } 301 | } 302 | 303 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *cachedResponse))completionHandler 304 | { 305 | QNSURLSessionDemuxTaskInfo * taskInfo; 306 | 307 | taskInfo = [self taskInfoForTask:dataTask]; 308 | if ([taskInfo.delegate respondsToSelector:@selector(URLSession:dataTask:willCacheResponse:completionHandler:)]) { 309 | [taskInfo performBlock:^{ 310 | [taskInfo.delegate URLSession:session dataTask:dataTask willCacheResponse:proposedResponse completionHandler:completionHandler]; 311 | }]; 312 | } else { 313 | completionHandler(proposedResponse); 314 | } 315 | } 316 | 317 | @end 318 | 319 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyKit/CanonicalRequest.m: -------------------------------------------------------------------------------- 1 | /* 2 | File: CanonicalRequest.m 3 | Abstract: A function for creating canonical HTTP/HTTPS requests. 4 | Version: 1.1 5 | 6 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 7 | Inc. ("Apple") in consideration of your agreement to the following 8 | terms, and your use, installation, modification or redistribution of 9 | this Apple software constitutes acceptance of these terms. If you do 10 | not agree with these terms, please do not use, install, modify or 11 | redistribute this Apple software. 12 | 13 | In consideration of your agreement to abide by the following terms, and 14 | subject to these terms, Apple grants you a personal, non-exclusive 15 | license, under Apple's copyrights in this original Apple software (the 16 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 17 | Software, with or without modifications, in source and/or binary forms; 18 | provided that if you redistribute the Apple Software in its entirety and 19 | without modifications, you must retain this notice and the following 20 | text and disclaimers in all such redistributions of the Apple Software. 21 | Neither the name, trademarks, service marks or logos of Apple Inc. may 22 | be used to endorse or promote products derived from the Apple Software 23 | without specific prior written permission from Apple. Except as 24 | expressly stated in this notice, no other rights or licenses, express or 25 | implied, are granted by Apple herein, including but not limited to any 26 | patent rights that may be infringed by your derivative works or by other 27 | works in which the Apple Software may be incorporated. 28 | 29 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 30 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 31 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 32 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 33 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 34 | 35 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 36 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 37 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 38 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 39 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 40 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 41 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 42 | POSSIBILITY OF SUCH DAMAGE. 43 | 44 | Copyright (C) 2014 Apple Inc. All Rights Reserved. 45 | 46 | */ 47 | 48 | #import "CanonicalRequest.h" 49 | 50 | #include 51 | 52 | #pragma mark * URL canonicalization steps 53 | 54 | /*! A step in the canonicalisation process. 55 | * \details The canonicalisation process is made up of a sequence of steps, each of which is 56 | * implemented by a function that matches this function pointer. The function gets a URL 57 | * and a mutable buffer holding that URL as bytes. The function can mutate the buffer as it 58 | * sees fit. It typically does this by calling CFURLGetByteRangeForComponent to find the range 59 | * of interest in the buffer. In that case bytesInserted is the amount to adjust that range, 60 | * and the function should modify that to account for any bytes it inserts or deletes. If 61 | * the function modifies the buffer too much, it can return kCFNotFound to force the system 62 | * to re-create the URL from the buffer. 63 | * \param url The original URL to work on. 64 | * \param urlData The URL as a mutable buffer; the routine modifies this. 65 | * \param bytesInserted The number of bytes that have been inserted so far the mutable buffer. 66 | * \returns An updated value of bytesInserted or kCFNotFound if the URL must be reparsed. 67 | */ 68 | 69 | typedef CFIndex (*CanonicalRequestStepFunction)(NSURL *url, NSMutableData *urlData, CFIndex bytesInserted); 70 | 71 | /*! The post-scheme separate should be "://"; if that's not the case, fix it. 72 | * \param url The original URL to work on. 73 | * \param urlData The URL as a mutable buffer; the routine modifies this. 74 | * \param bytesInserted The number of bytes that have been inserted so far the mutable buffer. 75 | * \returns An updated value of bytesInserted or kCFNotFound if the URL must be reparsed. 76 | */ 77 | 78 | static CFIndex FixPostSchemeSeparator(NSURL *url, NSMutableData *urlData, CFIndex bytesInserted) 79 | { 80 | CFRange range; 81 | uint8_t * urlDataBytes; 82 | NSUInteger urlDataLength; 83 | NSUInteger cursor; 84 | NSUInteger separatorLength; 85 | NSUInteger expectedSeparatorLength; 86 | 87 | assert(url != nil); 88 | assert(urlData != nil); 89 | assert(bytesInserted >= 0); 90 | 91 | range = CFURLGetByteRangeForComponent( (CFURLRef) url, kCFURLComponentScheme, NULL); 92 | if (range.location != kCFNotFound) { 93 | assert(range.location >= 0); 94 | assert(range.length >= 0); 95 | 96 | urlDataBytes = [urlData mutableBytes]; 97 | urlDataLength = [urlData length]; 98 | 99 | separatorLength = 0; 100 | cursor = (NSUInteger) range.location + (NSUInteger) bytesInserted + (NSUInteger) range.length; 101 | if ( (cursor < urlDataLength) && (urlDataBytes[cursor] == ':') ) { 102 | cursor += 1; 103 | separatorLength += 1; 104 | if ( (cursor < urlDataLength) && (urlDataBytes[cursor] == '/') ) { 105 | cursor += 1; 106 | separatorLength += 1; 107 | if ( (cursor < urlDataLength) && (urlDataBytes[cursor] == '/') ) { 108 | cursor += 1; 109 | separatorLength += 1; 110 | } 111 | } 112 | } 113 | #pragma unused(cursor) // quietens an analyser warning 114 | 115 | expectedSeparatorLength = strlen("://"); 116 | if (separatorLength != expectedSeparatorLength) { 117 | [urlData replaceBytesInRange:NSMakeRange((NSUInteger) range.location + (NSUInteger) bytesInserted + (NSUInteger) range.length, separatorLength) withBytes:"://" length:expectedSeparatorLength]; 118 | bytesInserted = kCFNotFound; // have to build everything now 119 | } 120 | } 121 | 122 | return bytesInserted; 123 | } 124 | 125 | /*! The scheme should be lower case; if it's not, make it so. 126 | * \param url The original URL to work on. 127 | * \param urlData The URL as a mutable buffer; the routine modifies this. 128 | * \param bytesInserted The number of bytes that have been inserted so far the mutable buffer. 129 | * \returns An updated value of bytesInserted or kCFNotFound if the URL must be reparsed. 130 | */ 131 | 132 | static CFIndex LowercaseScheme(NSURL *url, NSMutableData *urlData, CFIndex bytesInserted) 133 | { 134 | CFRange range; 135 | uint8_t * urlDataBytes; 136 | CFIndex i; 137 | 138 | assert(url != nil); 139 | assert(urlData != nil); 140 | assert(bytesInserted >= 0); 141 | 142 | range = CFURLGetByteRangeForComponent( (CFURLRef) url, kCFURLComponentScheme, NULL); 143 | if (range.location != kCFNotFound) { 144 | assert(range.location >= 0); 145 | assert(range.length >= 0); 146 | 147 | urlDataBytes = [urlData mutableBytes]; 148 | for (i = range.location + bytesInserted; i < (range.location + bytesInserted + range.length); i++) { 149 | urlDataBytes[i] = (uint8_t) tolower_l(urlDataBytes[i], NULL); 150 | } 151 | } 152 | return bytesInserted; 153 | } 154 | 155 | /*! The host should be lower case; if it's not, make it so. 156 | * \param url The original URL to work on. 157 | * \param urlData The URL as a mutable buffer; the routine modifies this. 158 | * \param bytesInserted The number of bytes that have been inserted so far the mutable buffer. 159 | * \returns An updated value of bytesInserted or kCFNotFound if the URL must be reparsed. 160 | */ 161 | 162 | static CFIndex LowercaseHost(NSURL *url, NSMutableData *urlData, CFIndex bytesInserted) 163 | // The host should be lower case; if it's not, make it so. 164 | { 165 | CFRange range; 166 | uint8_t * urlDataBytes; 167 | CFIndex i; 168 | 169 | assert(url != nil); 170 | assert(urlData != nil); 171 | assert(bytesInserted >= 0); 172 | 173 | range = CFURLGetByteRangeForComponent( (CFURLRef) url, kCFURLComponentHost, NULL); 174 | if (range.location != kCFNotFound) { 175 | assert(range.location >= 0); 176 | assert(range.length >= 0); 177 | 178 | urlDataBytes = [urlData mutableBytes]; 179 | for (i = range.location + bytesInserted; i < (range.location + bytesInserted + range.length); i++) { 180 | urlDataBytes[i] = (uint8_t) tolower_l(urlDataBytes[i], NULL); 181 | } 182 | } 183 | return bytesInserted; 184 | } 185 | 186 | /*! An empty host should be treated as "localhost" case; if it's not, make it so. 187 | * \param url The original URL to work on. 188 | * \param urlData The URL as a mutable buffer; the routine modifies this. 189 | * \param bytesInserted The number of bytes that have been inserted so far the mutable buffer. 190 | * \returns An updated value of bytesInserted or kCFNotFound if the URL must be reparsed. 191 | */ 192 | 193 | static CFIndex FixEmptyHost(NSURL *url, NSMutableData *urlData, CFIndex bytesInserted) 194 | { 195 | CFRange range; 196 | CFRange rangeWithSeparator; 197 | 198 | assert(url != nil); 199 | assert(urlData != nil); 200 | assert(bytesInserted >= 0); 201 | 202 | range = CFURLGetByteRangeForComponent( (CFURLRef) url, kCFURLComponentHost, &rangeWithSeparator); 203 | if (range.length == 0) { 204 | NSUInteger localhostLength; 205 | 206 | assert(range.location >= 0); 207 | assert(range.length >= 0); 208 | 209 | localhostLength = strlen("localhost"); 210 | if (range.location != kCFNotFound) { 211 | [urlData replaceBytesInRange:NSMakeRange( (NSUInteger) range.location + (NSUInteger) bytesInserted, 0) withBytes:"localhost" length:localhostLength]; 212 | bytesInserted += localhostLength; 213 | } else if ( (rangeWithSeparator.location != kCFNotFound) && (rangeWithSeparator.length == 0) ) { 214 | [urlData replaceBytesInRange:NSMakeRange((NSUInteger) rangeWithSeparator.location + (NSUInteger) bytesInserted, 0) withBytes:"localhost" length:localhostLength]; 215 | bytesInserted += localhostLength; 216 | } 217 | } 218 | return bytesInserted; 219 | } 220 | 221 | /*! Transform an empty URL path to "/". For example, "http://www.apple.com" becomes "http://www.apple.com/". 222 | * \param url The original URL to work on. 223 | * \param urlData The URL as a mutable buffer; the routine modifies this. 224 | * \param bytesInserted The number of bytes that have been inserted so far the mutable buffer. 225 | * \returns An updated value of bytesInserted or kCFNotFound if the URL must be reparsed. 226 | */ 227 | 228 | static CFIndex FixEmptyPath(NSURL *url, NSMutableData *urlData, CFIndex bytesInserted) 229 | { 230 | CFRange range; 231 | CFRange rangeWithSeparator; 232 | 233 | assert(url != nil); 234 | assert(urlData != nil); 235 | assert(bytesInserted >= 0); 236 | 237 | range = CFURLGetByteRangeForComponent( (CFURLRef) url, kCFURLComponentPath, &rangeWithSeparator); 238 | // The following is not a typo. We use rangeWithSeparator to find where to insert the 239 | // "/" and the range length to decide whether we /need/ to insert the "/". 240 | if ( (rangeWithSeparator.location != kCFNotFound) && (range.length == 0) ) { 241 | assert(range.location >= 0); 242 | assert(range.length >= 0); 243 | assert(rangeWithSeparator.location >= 0); 244 | assert(rangeWithSeparator.length >= 0); 245 | 246 | [urlData replaceBytesInRange:NSMakeRange( (NSUInteger) rangeWithSeparator.location + (NSUInteger) bytesInserted, 0) withBytes:"/" length:1]; 247 | bytesInserted += 1; 248 | } 249 | return bytesInserted; 250 | } 251 | 252 | /*! If the user specified the default port (80 for HTTP, 443 for HTTPS), remove it from the URL. 253 | * \details Actually this code is disabled because the equivalent code in the default protocol 254 | * handler has also been disabled; some setups depend on get the port number in the URL, even if it 255 | * is the default. 256 | * \param url The original URL to work on. 257 | * \param urlData The URL as a mutable buffer; the routine modifies this. 258 | * \param bytesInserted The number of bytes that have been inserted so far the mutable buffer. 259 | * \returns An updated value of bytesInserted or kCFNotFound if the URL must be reparsed. 260 | */ 261 | 262 | __attribute__((unused)) static CFIndex DeleteDefaultPort(NSURL *url, NSMutableData *urlData, CFIndex bytesInserted) 263 | { 264 | NSString * scheme; 265 | BOOL isHTTP; 266 | BOOL isHTTPS; 267 | CFRange range; 268 | uint8_t * urlDataBytes; 269 | NSString * portNumberStr; 270 | int portNumber; 271 | 272 | assert(url != nil); 273 | assert(urlData != nil); 274 | assert(bytesInserted >= 0); 275 | 276 | scheme = [[url scheme] lowercaseString]; 277 | assert(scheme != nil); 278 | 279 | isHTTP = [scheme isEqual:@"http" ]; 280 | isHTTPS = [scheme isEqual:@"https"]; 281 | 282 | range = CFURLGetByteRangeForComponent( (CFURLRef) url, kCFURLComponentPort, NULL); 283 | if (range.location != kCFNotFound) { 284 | assert(range.location >= 0); 285 | assert(range.length >= 0); 286 | 287 | urlDataBytes = [urlData mutableBytes]; 288 | 289 | portNumberStr = [[NSString alloc] initWithBytes:&urlDataBytes[range.location + bytesInserted] length:(NSUInteger) range.length encoding:NSUTF8StringEncoding]; 290 | if (portNumberStr != nil) { 291 | portNumber = [portNumberStr intValue]; 292 | if ( (isHTTP && (portNumber == 80)) || (isHTTPS && (portNumber == 443)) ) { 293 | // -1 and +1 to account for the leading ":" 294 | [urlData replaceBytesInRange:NSMakeRange((NSUInteger) range.location + (NSUInteger) bytesInserted - 1, (NSUInteger) range.length + 1) withBytes:NULL length:0]; 295 | bytesInserted -= (range.length + 1); 296 | } 297 | } 298 | } 299 | return bytesInserted; 300 | } 301 | 302 | #pragma mark * Other request canonicalization 303 | 304 | /*! Canonicalise the request headers. 305 | * \param request The request to canonicalise. 306 | */ 307 | 308 | static void CanonicaliseHeaders(NSMutableURLRequest * request) 309 | { 310 | // If there's no content type and the request is a POST with a body, add a default 311 | // content type of "application/x-www-form-urlencoded". 312 | 313 | if ( ([request valueForHTTPHeaderField:@"Content-Type"] == nil) 314 | && ([[request HTTPMethod] caseInsensitiveCompare:@"POST"] == NSOrderedSame) 315 | && (([request HTTPBody] != nil) || ([request HTTPBodyStream] != nil)) ) { 316 | [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; 317 | } 318 | 319 | // If there's no "Accept" header, add a default. 320 | 321 | if ([request valueForHTTPHeaderField:@"Accept"] == nil) { 322 | [request setValue:@"*/*" forHTTPHeaderField:@"Accept"]; 323 | } 324 | 325 | // If there's not "Accept-Encoding" header, add a default. 326 | 327 | if ([request valueForHTTPHeaderField:@"Accept-Encoding"] == nil) { 328 | [request setValue:@"gzip, deflate" forHTTPHeaderField:@"Accept-Encoding"]; 329 | } 330 | 331 | // If there's not an "Accept-Language" headre, add a default. This is quite bogus; ideally we 332 | // should derive the correct "Accept-Language" value from the langauge that the app is running 333 | // in. However, that's quite difficult to get right, so rather than show some general purpose 334 | // code that might fail in some circumstances, I've decided to just hardwire US English. 335 | // If you use this code in your own app you can customise it as you see fit. One option might be 336 | // to base this value on -[NSBundle preferredLocalizations], so that the web page comes back in 337 | // the language that the app is running in. 338 | 339 | if ([request valueForHTTPHeaderField:@"Accept-Language"] == nil) { 340 | [request setValue:@"en-us" forHTTPHeaderField:@"Accept-Language"]; 341 | } 342 | } 343 | 344 | #pragma mark * API 345 | 346 | extern NSMutableURLRequest * CanonicalRequestForRequest(NSURLRequest *request) 347 | { 348 | NSMutableURLRequest * result; 349 | NSString * scheme; 350 | 351 | assert(request != nil); 352 | 353 | // Make a mutable copy of the request. 354 | 355 | result = [request mutableCopy]; 356 | 357 | // First up check that we're dealing with HTTP or HTTPS. If not, do nothing (why were we 358 | // we even called?). 359 | 360 | scheme = [[[request URL] scheme] lowercaseString]; 361 | assert(scheme != nil); 362 | 363 | if ( ! [scheme isEqual:@"http" ] && ! [scheme isEqual:@"https"]) { 364 | assert(NO); 365 | } else { 366 | CFIndex bytesInserted; 367 | NSURL * requestURL; 368 | NSMutableData * urlData; 369 | static const CanonicalRequestStepFunction kStepFunctions[] = { 370 | FixPostSchemeSeparator, 371 | LowercaseScheme, 372 | LowercaseHost, 373 | FixEmptyHost, 374 | // DeleteDefaultPort, -- The built-in canonicalizer has stopped doing this, so we don't do it either. 375 | FixEmptyPath 376 | }; 377 | size_t stepIndex; 378 | size_t stepCount; 379 | 380 | // Canonicalise the URL by executing each of our step functions. 381 | 382 | bytesInserted = kCFNotFound; 383 | urlData = nil; 384 | requestURL = [request URL]; 385 | assert(requestURL != nil); 386 | 387 | stepCount = sizeof(kStepFunctions) / sizeof(*kStepFunctions); 388 | for (stepIndex = 0; stepIndex < stepCount; stepIndex++) { 389 | 390 | // If we don't have valid URL data, create it from the URL. 391 | 392 | assert(requestURL != nil); 393 | if (bytesInserted == kCFNotFound) { 394 | NSData * urlDataImmutable; 395 | 396 | urlDataImmutable = CFBridgingRelease( CFURLCreateData(NULL, (CFURLRef) requestURL, kCFStringEncodingUTF8, true) ); 397 | assert(urlDataImmutable != nil); 398 | 399 | urlData = [urlDataImmutable mutableCopy]; 400 | assert(urlData != nil); 401 | 402 | bytesInserted = 0; 403 | } 404 | assert(urlData != nil); 405 | 406 | // Run the step. 407 | 408 | bytesInserted = kStepFunctions[stepIndex](requestURL, urlData, bytesInserted); 409 | 410 | // Note: The following logging is useful when debugging this code. Change the 411 | // if expression to YES to enable it. 412 | 413 | if (NO) { 414 | fprintf(stderr, " [%zu] %.*s\n", stepIndex, (int) [urlData length], (const char *) [urlData bytes]); 415 | } 416 | 417 | // If the step invalidated our URL (or we're on the last step, whereupon we'll need 418 | // the URL outside of the loop), recreate the URL from the URL data. 419 | 420 | if ( (bytesInserted == kCFNotFound) || ((stepIndex + 1) == stepCount) ) { 421 | requestURL = CFBridgingRelease( CFURLCreateWithBytes(NULL, [urlData bytes], (CFIndex) [urlData length], kCFStringEncodingUTF8, NULL) ); 422 | assert(requestURL != nil); 423 | 424 | urlData = nil; 425 | } 426 | } 427 | 428 | [result setURL:requestURL]; 429 | 430 | // Canonicalise the headers. 431 | 432 | CanonicaliseHeaders(result); 433 | } 434 | 435 | return result; 436 | } 437 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyDemo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 9C8734121A281956001F7CB3 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C8734111A281956001F7CB3 /* main.m */; }; 11 | 9C8734151A281956001F7CB3 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C8734141A281956001F7CB3 /* AppDelegate.m */; }; 12 | 9C8734181A281956001F7CB3 /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C8734171A281956001F7CB3 /* ViewController.m */; }; 13 | 9C87341B1A281956001F7CB3 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9C8734191A281956001F7CB3 /* Main.storyboard */; }; 14 | 9C87341D1A281956001F7CB3 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9C87341C1A281956001F7CB3 /* Images.xcassets */; }; 15 | 9C8734201A281956001F7CB3 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9C87341E1A281956001F7CB3 /* LaunchScreen.xib */; }; 16 | 9C87342C1A281956001F7CB3 /* WebViewProxyDemoTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C87342B1A281956001F7CB3 /* WebViewProxyDemoTests.m */; }; 17 | 9C8734401A281D81001F7CB3 /* CacheStoragePolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C8734371A281D81001F7CB3 /* CacheStoragePolicy.m */; }; 18 | 9C8734411A281D81001F7CB3 /* CanonicalRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C8734391A281D81001F7CB3 /* CanonicalRequest.m */; }; 19 | 9C8734421A281D81001F7CB3 /* CredentialsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C87343B1A281D81001F7CB3 /* CredentialsManager.m */; }; 20 | 9C8734431A281D81001F7CB3 /* CustomHTTPProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C87343D1A281D81001F7CB3 /* CustomHTTPProtocol.m */; }; 21 | 9C8734441A281D81001F7CB3 /* QNSURLSessionDemux.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C87343F1A281D81001F7CB3 /* QNSURLSessionDemux.m */; }; 22 | 9C8734471A281E01001F7CB3 /* UUSSLWhiteList.m in Sources */ = {isa = PBXBuildFile; fileRef = 9C8734461A281E01001F7CB3 /* UUSSLWhiteList.m */; }; 23 | 9C8734491A282051001F7CB3 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9C8734481A282051001F7CB3 /* Security.framework */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXContainerItemProxy section */ 27 | 9C8734261A281956001F7CB3 /* PBXContainerItemProxy */ = { 28 | isa = PBXContainerItemProxy; 29 | containerPortal = 9C8734041A281956001F7CB3 /* Project object */; 30 | proxyType = 1; 31 | remoteGlobalIDString = 9C87340B1A281956001F7CB3; 32 | remoteInfo = WebViewProxyDemo; 33 | }; 34 | /* End PBXContainerItemProxy section */ 35 | 36 | /* Begin PBXFileReference section */ 37 | 9C87340C1A281956001F7CB3 /* WebViewProxyDemo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = WebViewProxyDemo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | 9C8734101A281956001F7CB3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 39 | 9C8734111A281956001F7CB3 /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 40 | 9C8734131A281956001F7CB3 /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 41 | 9C8734141A281956001F7CB3 /* AppDelegate.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; 42 | 9C8734161A281956001F7CB3 /* ViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ViewController.h; sourceTree = ""; }; 43 | 9C8734171A281956001F7CB3 /* ViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ViewController.m; sourceTree = ""; }; 44 | 9C87341A1A281956001F7CB3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 45 | 9C87341C1A281956001F7CB3 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; 46 | 9C87341F1A281956001F7CB3 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; 47 | 9C8734251A281956001F7CB3 /* WebViewProxyDemoTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = WebViewProxyDemoTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | 9C87342A1A281956001F7CB3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 49 | 9C87342B1A281956001F7CB3 /* WebViewProxyDemoTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = WebViewProxyDemoTests.m; sourceTree = ""; }; 50 | 9C8734361A281D81001F7CB3 /* CacheStoragePolicy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CacheStoragePolicy.h; sourceTree = ""; }; 51 | 9C8734371A281D81001F7CB3 /* CacheStoragePolicy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CacheStoragePolicy.m; sourceTree = ""; }; 52 | 9C8734381A281D81001F7CB3 /* CanonicalRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CanonicalRequest.h; sourceTree = ""; }; 53 | 9C8734391A281D81001F7CB3 /* CanonicalRequest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CanonicalRequest.m; sourceTree = ""; }; 54 | 9C87343A1A281D81001F7CB3 /* CredentialsManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CredentialsManager.h; sourceTree = ""; }; 55 | 9C87343B1A281D81001F7CB3 /* CredentialsManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CredentialsManager.m; sourceTree = ""; }; 56 | 9C87343C1A281D81001F7CB3 /* CustomHTTPProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CustomHTTPProtocol.h; sourceTree = ""; }; 57 | 9C87343D1A281D81001F7CB3 /* CustomHTTPProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CustomHTTPProtocol.m; sourceTree = ""; }; 58 | 9C87343E1A281D81001F7CB3 /* QNSURLSessionDemux.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = QNSURLSessionDemux.h; sourceTree = ""; }; 59 | 9C87343F1A281D81001F7CB3 /* QNSURLSessionDemux.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = QNSURLSessionDemux.m; sourceTree = ""; }; 60 | 9C8734451A281E01001F7CB3 /* UUSSLWhiteList.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UUSSLWhiteList.h; sourceTree = ""; }; 61 | 9C8734461A281E01001F7CB3 /* UUSSLWhiteList.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UUSSLWhiteList.m; sourceTree = ""; }; 62 | 9C8734481A282051001F7CB3 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 63 | /* End PBXFileReference section */ 64 | 65 | /* Begin PBXFrameworksBuildPhase section */ 66 | 9C8734091A281956001F7CB3 /* Frameworks */ = { 67 | isa = PBXFrameworksBuildPhase; 68 | buildActionMask = 2147483647; 69 | files = ( 70 | 9C8734491A282051001F7CB3 /* Security.framework in Frameworks */, 71 | ); 72 | runOnlyForDeploymentPostprocessing = 0; 73 | }; 74 | 9C8734221A281956001F7CB3 /* Frameworks */ = { 75 | isa = PBXFrameworksBuildPhase; 76 | buildActionMask = 2147483647; 77 | files = ( 78 | ); 79 | runOnlyForDeploymentPostprocessing = 0; 80 | }; 81 | /* End PBXFrameworksBuildPhase section */ 82 | 83 | /* Begin PBXGroup section */ 84 | 9C8734031A281956001F7CB3 = { 85 | isa = PBXGroup; 86 | children = ( 87 | 9C8734351A281D81001F7CB3 /* WebViewProxyKit */, 88 | 9C87340E1A281956001F7CB3 /* WebViewProxyDemo */, 89 | 9C8734281A281956001F7CB3 /* WebViewProxyDemoTests */, 90 | 9C87340D1A281956001F7CB3 /* Products */, 91 | ); 92 | sourceTree = ""; 93 | }; 94 | 9C87340D1A281956001F7CB3 /* Products */ = { 95 | isa = PBXGroup; 96 | children = ( 97 | 9C87340C1A281956001F7CB3 /* WebViewProxyDemo.app */, 98 | 9C8734251A281956001F7CB3 /* WebViewProxyDemoTests.xctest */, 99 | ); 100 | name = Products; 101 | sourceTree = ""; 102 | }; 103 | 9C87340E1A281956001F7CB3 /* WebViewProxyDemo */ = { 104 | isa = PBXGroup; 105 | children = ( 106 | 9C8734131A281956001F7CB3 /* AppDelegate.h */, 107 | 9C8734141A281956001F7CB3 /* AppDelegate.m */, 108 | 9C8734161A281956001F7CB3 /* ViewController.h */, 109 | 9C8734171A281956001F7CB3 /* ViewController.m */, 110 | 9C8734191A281956001F7CB3 /* Main.storyboard */, 111 | 9C87341C1A281956001F7CB3 /* Images.xcassets */, 112 | 9C87341E1A281956001F7CB3 /* LaunchScreen.xib */, 113 | 9C87340F1A281956001F7CB3 /* Supporting Files */, 114 | 9C87344A1A28206F001F7CB3 /* Frameworks */, 115 | ); 116 | path = WebViewProxyDemo; 117 | sourceTree = ""; 118 | }; 119 | 9C87340F1A281956001F7CB3 /* Supporting Files */ = { 120 | isa = PBXGroup; 121 | children = ( 122 | 9C8734101A281956001F7CB3 /* Info.plist */, 123 | 9C8734111A281956001F7CB3 /* main.m */, 124 | ); 125 | name = "Supporting Files"; 126 | sourceTree = ""; 127 | }; 128 | 9C8734281A281956001F7CB3 /* WebViewProxyDemoTests */ = { 129 | isa = PBXGroup; 130 | children = ( 131 | 9C87342B1A281956001F7CB3 /* WebViewProxyDemoTests.m */, 132 | 9C8734291A281956001F7CB3 /* Supporting Files */, 133 | ); 134 | path = WebViewProxyDemoTests; 135 | sourceTree = ""; 136 | }; 137 | 9C8734291A281956001F7CB3 /* Supporting Files */ = { 138 | isa = PBXGroup; 139 | children = ( 140 | 9C87342A1A281956001F7CB3 /* Info.plist */, 141 | ); 142 | name = "Supporting Files"; 143 | sourceTree = ""; 144 | }; 145 | 9C8734351A281D81001F7CB3 /* WebViewProxyKit */ = { 146 | isa = PBXGroup; 147 | children = ( 148 | 9C8734361A281D81001F7CB3 /* CacheStoragePolicy.h */, 149 | 9C8734371A281D81001F7CB3 /* CacheStoragePolicy.m */, 150 | 9C8734381A281D81001F7CB3 /* CanonicalRequest.h */, 151 | 9C8734391A281D81001F7CB3 /* CanonicalRequest.m */, 152 | 9C87343A1A281D81001F7CB3 /* CredentialsManager.h */, 153 | 9C87343B1A281D81001F7CB3 /* CredentialsManager.m */, 154 | 9C87343C1A281D81001F7CB3 /* CustomHTTPProtocol.h */, 155 | 9C87343D1A281D81001F7CB3 /* CustomHTTPProtocol.m */, 156 | 9C87343E1A281D81001F7CB3 /* QNSURLSessionDemux.h */, 157 | 9C87343F1A281D81001F7CB3 /* QNSURLSessionDemux.m */, 158 | 9C8734451A281E01001F7CB3 /* UUSSLWhiteList.h */, 159 | 9C8734461A281E01001F7CB3 /* UUSSLWhiteList.m */, 160 | ); 161 | path = WebViewProxyKit; 162 | sourceTree = ""; 163 | }; 164 | 9C87344A1A28206F001F7CB3 /* Frameworks */ = { 165 | isa = PBXGroup; 166 | children = ( 167 | 9C8734481A282051001F7CB3 /* Security.framework */, 168 | ); 169 | name = Frameworks; 170 | path = ..; 171 | sourceTree = ""; 172 | }; 173 | /* End PBXGroup section */ 174 | 175 | /* Begin PBXNativeTarget section */ 176 | 9C87340B1A281956001F7CB3 /* WebViewProxyDemo */ = { 177 | isa = PBXNativeTarget; 178 | buildConfigurationList = 9C87342F1A281956001F7CB3 /* Build configuration list for PBXNativeTarget "WebViewProxyDemo" */; 179 | buildPhases = ( 180 | 9C8734081A281956001F7CB3 /* Sources */, 181 | 9C8734091A281956001F7CB3 /* Frameworks */, 182 | 9C87340A1A281956001F7CB3 /* Resources */, 183 | ); 184 | buildRules = ( 185 | ); 186 | dependencies = ( 187 | ); 188 | name = WebViewProxyDemo; 189 | productName = WebViewProxyDemo; 190 | productReference = 9C87340C1A281956001F7CB3 /* WebViewProxyDemo.app */; 191 | productType = "com.apple.product-type.application"; 192 | }; 193 | 9C8734241A281956001F7CB3 /* WebViewProxyDemoTests */ = { 194 | isa = PBXNativeTarget; 195 | buildConfigurationList = 9C8734321A281956001F7CB3 /* Build configuration list for PBXNativeTarget "WebViewProxyDemoTests" */; 196 | buildPhases = ( 197 | 9C8734211A281956001F7CB3 /* Sources */, 198 | 9C8734221A281956001F7CB3 /* Frameworks */, 199 | 9C8734231A281956001F7CB3 /* Resources */, 200 | ); 201 | buildRules = ( 202 | ); 203 | dependencies = ( 204 | 9C8734271A281956001F7CB3 /* PBXTargetDependency */, 205 | ); 206 | name = WebViewProxyDemoTests; 207 | productName = WebViewProxyDemoTests; 208 | productReference = 9C8734251A281956001F7CB3 /* WebViewProxyDemoTests.xctest */; 209 | productType = "com.apple.product-type.bundle.unit-test"; 210 | }; 211 | /* End PBXNativeTarget section */ 212 | 213 | /* Begin PBXProject section */ 214 | 9C8734041A281956001F7CB3 /* Project object */ = { 215 | isa = PBXProject; 216 | attributes = { 217 | LastUpgradeCheck = 0610; 218 | ORGANIZATIONNAME = AOLC; 219 | TargetAttributes = { 220 | 9C87340B1A281956001F7CB3 = { 221 | CreatedOnToolsVersion = 6.1; 222 | }; 223 | 9C8734241A281956001F7CB3 = { 224 | CreatedOnToolsVersion = 6.1; 225 | TestTargetID = 9C87340B1A281956001F7CB3; 226 | }; 227 | }; 228 | }; 229 | buildConfigurationList = 9C8734071A281956001F7CB3 /* Build configuration list for PBXProject "WebViewProxyDemo" */; 230 | compatibilityVersion = "Xcode 3.2"; 231 | developmentRegion = English; 232 | hasScannedForEncodings = 0; 233 | knownRegions = ( 234 | en, 235 | Base, 236 | ); 237 | mainGroup = 9C8734031A281956001F7CB3; 238 | productRefGroup = 9C87340D1A281956001F7CB3 /* Products */; 239 | projectDirPath = ""; 240 | projectRoot = ""; 241 | targets = ( 242 | 9C87340B1A281956001F7CB3 /* WebViewProxyDemo */, 243 | 9C8734241A281956001F7CB3 /* WebViewProxyDemoTests */, 244 | ); 245 | }; 246 | /* End PBXProject section */ 247 | 248 | /* Begin PBXResourcesBuildPhase section */ 249 | 9C87340A1A281956001F7CB3 /* Resources */ = { 250 | isa = PBXResourcesBuildPhase; 251 | buildActionMask = 2147483647; 252 | files = ( 253 | 9C87341B1A281956001F7CB3 /* Main.storyboard in Resources */, 254 | 9C8734201A281956001F7CB3 /* LaunchScreen.xib in Resources */, 255 | 9C87341D1A281956001F7CB3 /* Images.xcassets in Resources */, 256 | ); 257 | runOnlyForDeploymentPostprocessing = 0; 258 | }; 259 | 9C8734231A281956001F7CB3 /* Resources */ = { 260 | isa = PBXResourcesBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | ); 264 | runOnlyForDeploymentPostprocessing = 0; 265 | }; 266 | /* End PBXResourcesBuildPhase section */ 267 | 268 | /* Begin PBXSourcesBuildPhase section */ 269 | 9C8734081A281956001F7CB3 /* Sources */ = { 270 | isa = PBXSourcesBuildPhase; 271 | buildActionMask = 2147483647; 272 | files = ( 273 | 9C8734401A281D81001F7CB3 /* CacheStoragePolicy.m in Sources */, 274 | 9C8734181A281956001F7CB3 /* ViewController.m in Sources */, 275 | 9C8734431A281D81001F7CB3 /* CustomHTTPProtocol.m in Sources */, 276 | 9C8734441A281D81001F7CB3 /* QNSURLSessionDemux.m in Sources */, 277 | 9C8734411A281D81001F7CB3 /* CanonicalRequest.m in Sources */, 278 | 9C8734151A281956001F7CB3 /* AppDelegate.m in Sources */, 279 | 9C8734421A281D81001F7CB3 /* CredentialsManager.m in Sources */, 280 | 9C8734471A281E01001F7CB3 /* UUSSLWhiteList.m in Sources */, 281 | 9C8734121A281956001F7CB3 /* main.m in Sources */, 282 | ); 283 | runOnlyForDeploymentPostprocessing = 0; 284 | }; 285 | 9C8734211A281956001F7CB3 /* Sources */ = { 286 | isa = PBXSourcesBuildPhase; 287 | buildActionMask = 2147483647; 288 | files = ( 289 | 9C87342C1A281956001F7CB3 /* WebViewProxyDemoTests.m in Sources */, 290 | ); 291 | runOnlyForDeploymentPostprocessing = 0; 292 | }; 293 | /* End PBXSourcesBuildPhase section */ 294 | 295 | /* Begin PBXTargetDependency section */ 296 | 9C8734271A281956001F7CB3 /* PBXTargetDependency */ = { 297 | isa = PBXTargetDependency; 298 | target = 9C87340B1A281956001F7CB3 /* WebViewProxyDemo */; 299 | targetProxy = 9C8734261A281956001F7CB3 /* PBXContainerItemProxy */; 300 | }; 301 | /* End PBXTargetDependency section */ 302 | 303 | /* Begin PBXVariantGroup section */ 304 | 9C8734191A281956001F7CB3 /* Main.storyboard */ = { 305 | isa = PBXVariantGroup; 306 | children = ( 307 | 9C87341A1A281956001F7CB3 /* Base */, 308 | ); 309 | name = Main.storyboard; 310 | sourceTree = ""; 311 | }; 312 | 9C87341E1A281956001F7CB3 /* LaunchScreen.xib */ = { 313 | isa = PBXVariantGroup; 314 | children = ( 315 | 9C87341F1A281956001F7CB3 /* Base */, 316 | ); 317 | name = LaunchScreen.xib; 318 | sourceTree = ""; 319 | }; 320 | /* End PBXVariantGroup section */ 321 | 322 | /* Begin XCBuildConfiguration section */ 323 | 9C87342D1A281956001F7CB3 /* Debug */ = { 324 | isa = XCBuildConfiguration; 325 | buildSettings = { 326 | ALWAYS_SEARCH_USER_PATHS = NO; 327 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 328 | CLANG_CXX_LIBRARY = "libc++"; 329 | CLANG_ENABLE_MODULES = YES; 330 | CLANG_ENABLE_OBJC_ARC = YES; 331 | CLANG_WARN_BOOL_CONVERSION = YES; 332 | CLANG_WARN_CONSTANT_CONVERSION = YES; 333 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 334 | CLANG_WARN_EMPTY_BODY = YES; 335 | CLANG_WARN_ENUM_CONVERSION = YES; 336 | CLANG_WARN_INT_CONVERSION = YES; 337 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 338 | CLANG_WARN_UNREACHABLE_CODE = YES; 339 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 340 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 341 | COPY_PHASE_STRIP = NO; 342 | ENABLE_STRICT_OBJC_MSGSEND = YES; 343 | GCC_C_LANGUAGE_STANDARD = gnu99; 344 | GCC_DYNAMIC_NO_PIC = NO; 345 | GCC_OPTIMIZATION_LEVEL = 0; 346 | GCC_PREPROCESSOR_DEFINITIONS = ( 347 | "DEBUG=1", 348 | "$(inherited)", 349 | ); 350 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 351 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 352 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 353 | GCC_WARN_UNDECLARED_SELECTOR = YES; 354 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 355 | GCC_WARN_UNUSED_FUNCTION = YES; 356 | GCC_WARN_UNUSED_VARIABLE = YES; 357 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 358 | MTL_ENABLE_DEBUG_INFO = YES; 359 | ONLY_ACTIVE_ARCH = YES; 360 | SDKROOT = iphoneos; 361 | TARGETED_DEVICE_FAMILY = "1,2"; 362 | }; 363 | name = Debug; 364 | }; 365 | 9C87342E1A281956001F7CB3 /* Release */ = { 366 | isa = XCBuildConfiguration; 367 | buildSettings = { 368 | ALWAYS_SEARCH_USER_PATHS = NO; 369 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 370 | CLANG_CXX_LIBRARY = "libc++"; 371 | CLANG_ENABLE_MODULES = YES; 372 | CLANG_ENABLE_OBJC_ARC = YES; 373 | CLANG_WARN_BOOL_CONVERSION = YES; 374 | CLANG_WARN_CONSTANT_CONVERSION = YES; 375 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 376 | CLANG_WARN_EMPTY_BODY = YES; 377 | CLANG_WARN_ENUM_CONVERSION = YES; 378 | CLANG_WARN_INT_CONVERSION = YES; 379 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 380 | CLANG_WARN_UNREACHABLE_CODE = YES; 381 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 382 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 383 | COPY_PHASE_STRIP = YES; 384 | ENABLE_NS_ASSERTIONS = NO; 385 | ENABLE_STRICT_OBJC_MSGSEND = YES; 386 | GCC_C_LANGUAGE_STANDARD = gnu99; 387 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 388 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 389 | GCC_WARN_UNDECLARED_SELECTOR = YES; 390 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 391 | GCC_WARN_UNUSED_FUNCTION = YES; 392 | GCC_WARN_UNUSED_VARIABLE = YES; 393 | IPHONEOS_DEPLOYMENT_TARGET = 8.1; 394 | MTL_ENABLE_DEBUG_INFO = NO; 395 | SDKROOT = iphoneos; 396 | TARGETED_DEVICE_FAMILY = "1,2"; 397 | VALIDATE_PRODUCT = YES; 398 | }; 399 | name = Release; 400 | }; 401 | 9C8734301A281956001F7CB3 /* Debug */ = { 402 | isa = XCBuildConfiguration; 403 | buildSettings = { 404 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 405 | INFOPLIST_FILE = WebViewProxyDemo/Info.plist; 406 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 407 | PRODUCT_NAME = "$(TARGET_NAME)"; 408 | }; 409 | name = Debug; 410 | }; 411 | 9C8734311A281956001F7CB3 /* Release */ = { 412 | isa = XCBuildConfiguration; 413 | buildSettings = { 414 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 415 | INFOPLIST_FILE = WebViewProxyDemo/Info.plist; 416 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 417 | PRODUCT_NAME = "$(TARGET_NAME)"; 418 | }; 419 | name = Release; 420 | }; 421 | 9C8734331A281956001F7CB3 /* Debug */ = { 422 | isa = XCBuildConfiguration; 423 | buildSettings = { 424 | BUNDLE_LOADER = "$(TEST_HOST)"; 425 | FRAMEWORK_SEARCH_PATHS = ( 426 | "$(SDKROOT)/Developer/Library/Frameworks", 427 | "$(inherited)", 428 | ); 429 | GCC_PREPROCESSOR_DEFINITIONS = ( 430 | "DEBUG=1", 431 | "$(inherited)", 432 | ); 433 | INFOPLIST_FILE = WebViewProxyDemoTests/Info.plist; 434 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 435 | PRODUCT_NAME = "$(TARGET_NAME)"; 436 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/WebViewProxyDemo.app/WebViewProxyDemo"; 437 | }; 438 | name = Debug; 439 | }; 440 | 9C8734341A281956001F7CB3 /* Release */ = { 441 | isa = XCBuildConfiguration; 442 | buildSettings = { 443 | BUNDLE_LOADER = "$(TEST_HOST)"; 444 | FRAMEWORK_SEARCH_PATHS = ( 445 | "$(SDKROOT)/Developer/Library/Frameworks", 446 | "$(inherited)", 447 | ); 448 | INFOPLIST_FILE = WebViewProxyDemoTests/Info.plist; 449 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 450 | PRODUCT_NAME = "$(TARGET_NAME)"; 451 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/WebViewProxyDemo.app/WebViewProxyDemo"; 452 | }; 453 | name = Release; 454 | }; 455 | /* End XCBuildConfiguration section */ 456 | 457 | /* Begin XCConfigurationList section */ 458 | 9C8734071A281956001F7CB3 /* Build configuration list for PBXProject "WebViewProxyDemo" */ = { 459 | isa = XCConfigurationList; 460 | buildConfigurations = ( 461 | 9C87342D1A281956001F7CB3 /* Debug */, 462 | 9C87342E1A281956001F7CB3 /* Release */, 463 | ); 464 | defaultConfigurationIsVisible = 0; 465 | defaultConfigurationName = Release; 466 | }; 467 | 9C87342F1A281956001F7CB3 /* Build configuration list for PBXNativeTarget "WebViewProxyDemo" */ = { 468 | isa = XCConfigurationList; 469 | buildConfigurations = ( 470 | 9C8734301A281956001F7CB3 /* Debug */, 471 | 9C8734311A281956001F7CB3 /* Release */, 472 | ); 473 | defaultConfigurationIsVisible = 0; 474 | }; 475 | 9C8734321A281956001F7CB3 /* Build configuration list for PBXNativeTarget "WebViewProxyDemoTests" */ = { 476 | isa = XCConfigurationList; 477 | buildConfigurations = ( 478 | 9C8734331A281956001F7CB3 /* Debug */, 479 | 9C8734341A281956001F7CB3 /* Release */, 480 | ); 481 | defaultConfigurationIsVisible = 0; 482 | }; 483 | /* End XCConfigurationList section */ 484 | }; 485 | rootObject = 9C8734041A281956001F7CB3 /* Project object */; 486 | } 487 | -------------------------------------------------------------------------------- /WebViewProxyDemo/WebViewProxyKit/CustomHTTPProtocol.m: -------------------------------------------------------------------------------- 1 | /* 2 | File: CustomHTTPProtocol.m 3 | Abstract: An NSURLProtocol subclass that overrides the built-in HTTP/HTTPS protocol. 4 | Version: 1.1 5 | 6 | Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple 7 | Inc. ("Apple") in consideration of your agreement to the following 8 | terms, and your use, installation, modification or redistribution of 9 | this Apple software constitutes acceptance of these terms. If you do 10 | not agree with these terms, please do not use, install, modify or 11 | redistribute this Apple software. 12 | 13 | In consideration of your agreement to abide by the following terms, and 14 | subject to these terms, Apple grants you a personal, non-exclusive 15 | license, under Apple's copyrights in this original Apple software (the 16 | "Apple Software"), to use, reproduce, modify and redistribute the Apple 17 | Software, with or without modifications, in source and/or binary forms; 18 | provided that if you redistribute the Apple Software in its entirety and 19 | without modifications, you must retain this notice and the following 20 | text and disclaimers in all such redistributions of the Apple Software. 21 | Neither the name, trademarks, service marks or logos of Apple Inc. may 22 | be used to endorse or promote products derived from the Apple Software 23 | without specific prior written permission from Apple. Except as 24 | expressly stated in this notice, no other rights or licenses, express or 25 | implied, are granted by Apple herein, including but not limited to any 26 | patent rights that may be infringed by your derivative works or by other 27 | works in which the Apple Software may be incorporated. 28 | 29 | The Apple Software is provided by Apple on an "AS IS" basis. APPLE 30 | MAKES NO WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION 31 | THE IMPLIED WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS 32 | FOR A PARTICULAR PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND 33 | OPERATION ALONE OR IN COMBINATION WITH YOUR PRODUCTS. 34 | 35 | IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL 36 | OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 37 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 38 | INTERRUPTION) ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, 39 | MODIFICATION AND/OR DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED 40 | AND WHETHER UNDER THEORY OF CONTRACT, TORT (INCLUDING NEGLIGENCE), 41 | STRICT LIABILITY OR OTHERWISE, EVEN IF APPLE HAS BEEN ADVISED OF THE 42 | POSSIBILITY OF SUCH DAMAGE. 43 | 44 | Copyright (C) 2014 Apple Inc. All Rights Reserved. 45 | 46 | */ 47 | 48 | #import "CustomHTTPProtocol.h" 49 | 50 | #import "CanonicalRequest.h" 51 | #import "CacheStoragePolicy.h" 52 | #import "QNSURLSessionDemux.h" 53 | #import "UUSSLWhiteList.h" 54 | 55 | // I use the following typedef to keep myself sane in the face of the wacky 56 | // Objective-C block syntax. 57 | 58 | typedef void (^ChallengeCompletionHandler)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * credential); 59 | 60 | static NSDictionary *sProxyConfig; 61 | 62 | @interface CustomHTTPProtocol () 63 | 64 | @property (atomic, strong, readwrite) NSThread * clientThread; ///< The thread on which we should call the client. 65 | 66 | /*! The run loop modes in which to call the client. 67 | * \details The concurrency control here is complex. It's set up on the client 68 | * thread in -startLoading and then never modified. It is, however, read by code 69 | * running on other threads (specifically the main thread), so we deallocate it in 70 | * -dealloc rather than in -stopLoading. We can be sure that it's not read before 71 | * it's set up because the main thread code that reads it can only be called after 72 | * -startLoading has started the connection running. 73 | */ 74 | 75 | @property (atomic, copy, readwrite) NSArray * modes; 76 | @property (atomic, assign, readwrite) NSTimeInterval startTime; ///< The start time of the request; written by client thread only; read by any thread. 77 | @property (atomic, strong, readwrite) NSURLSessionDataTask * task; ///< The NSURLSession task for that request; client thread only. 78 | @property (atomic, strong, readwrite) NSURLAuthenticationChallenge * pendingChallenge; 79 | @property (atomic, copy, readwrite) ChallengeCompletionHandler pendingChallengeCompletionHandler; ///< The completion handler that matches pendingChallenge; main thread only. 80 | 81 | @end 82 | 83 | @implementation CustomHTTPProtocol 84 | 85 | #pragma mark * Subclass specific additions 86 | 87 | /*! The backing store for the class delegate. This is protected by @synchronized on the class. 88 | */ 89 | 90 | static id sDelegate; 91 | 92 | + (void) setProxyConfig:(NSDictionary *) proxyConfig { 93 | sProxyConfig = proxyConfig; 94 | } 95 | 96 | + (void)start 97 | { 98 | [NSURLProtocol registerClass:self]; 99 | } 100 | 101 | + (id)delegate 102 | { 103 | id result; 104 | 105 | @synchronized (self) { 106 | result = sDelegate; 107 | } 108 | return result; 109 | } 110 | 111 | + (void)setDelegate:(id)newValue 112 | { 113 | @synchronized (self) { 114 | sDelegate = newValue; 115 | } 116 | } 117 | 118 | /*! Returns the session demux object used by all the protocol instances. 119 | * \details This object allows us to have a single NSURLSession, with a session delegate, 120 | * and have its delegate callbacks routed to the correct protocol instance on the correct 121 | * thread in the correct modes. Can be called on any thread. 122 | */ 123 | 124 | + (QNSURLSessionDemux *)sharedDemux 125 | { 126 | static dispatch_once_t sOnceToken; 127 | static QNSURLSessionDemux * sDemux; 128 | dispatch_once(&sOnceToken, ^{ 129 | 130 | NSURLSessionConfiguration * config; 131 | config = [NSURLSessionConfiguration defaultSessionConfiguration]; 132 | config.connectionProxyDictionary = sProxyConfig; 133 | 134 | // You have to explicitly configure the session to use your own protocol subclass here 135 | // otherwise you don't see redirects . 136 | config.protocolClasses = @[ self ]; 137 | sDemux = [[QNSURLSessionDemux alloc] initWithConfiguration:config]; 138 | 139 | }); 140 | return sDemux; 141 | } 142 | 143 | /*! Called by by both class code and instance code to log various bits of information. 144 | * Can be called on any thread. 145 | * \param protocol The protocol instance; nil if it's the class doing the logging. 146 | * \param format A standard NSString-style format string; will not be nil. 147 | */ 148 | 149 | + (void)customHTTPProtocol:(CustomHTTPProtocol *)protocol logWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(2, 3) 150 | // All internal logging calls this routine, which routes the log message to the 151 | // delegate. 152 | { 153 | // protocol may be nil 154 | id strongDelegate; 155 | 156 | strongDelegate = [self delegate]; 157 | if ([strongDelegate respondsToSelector:@selector(customHTTPProtocol:logWithFormat:arguments:)]) { 158 | va_list arguments; 159 | 160 | va_start(arguments, format); 161 | [strongDelegate customHTTPProtocol:protocol logWithFormat:format arguments:arguments]; 162 | va_end(arguments); 163 | } 164 | } 165 | 166 | #pragma mark * NSURLProtocol overrides 167 | 168 | /*! Used to mark our recursive requests so that we don't try to handle them (and thereby 169 | * suffer an infinite recursive death). 170 | */ 171 | 172 | static NSString * kOurRecursiveRequestFlagProperty = @"com.apple.dts.CustomHTTPProtocol"; 173 | 174 | + (BOOL)canInitWithRequest:(NSURLRequest *)request 175 | { 176 | BOOL shouldAccept; 177 | NSURL * url; 178 | NSString * scheme; 179 | 180 | // Check the basics. This routine is extremely defensive because experience has shown that 181 | // it can be called with some very odd requests . 182 | 183 | shouldAccept = (request != nil); 184 | if (shouldAccept) { 185 | url = [request URL]; 186 | shouldAccept = (url != nil); 187 | } 188 | if ( ! shouldAccept ) { 189 | [self customHTTPProtocol:nil logWithFormat:@"decline request (malformed)"]; 190 | } 191 | 192 | // Decline our recursive requests. 193 | 194 | if (shouldAccept) { 195 | shouldAccept = ([self propertyForKey:kOurRecursiveRequestFlagProperty inRequest:request] == nil); 196 | if ( ! shouldAccept ) { 197 | [self customHTTPProtocol:nil logWithFormat:@"decline request %@ (recursive)", url]; 198 | } 199 | } 200 | 201 | // Get the scheme. 202 | 203 | if (shouldAccept) { 204 | scheme = [[url scheme] lowercaseString]; 205 | shouldAccept = (scheme != nil); 206 | 207 | if ( ! shouldAccept ) { 208 | [self customHTTPProtocol:nil logWithFormat:@"decline request %@ (no scheme)", url]; 209 | } 210 | } 211 | 212 | // Look for "http" or "https". 213 | // 214 | // Flip either or both of the following to YESes to control which schemes go through this custom 215 | // NSURLProtocol subclass. 216 | 217 | if (shouldAccept) { 218 | shouldAccept = NO && [scheme isEqual:@"http"]; 219 | if ( ! shouldAccept ) { 220 | shouldAccept = YES && ([scheme isEqual:@"https"] || [scheme isEqual:@"http"]); 221 | } 222 | 223 | if ( ! shouldAccept ) { 224 | [self customHTTPProtocol:nil logWithFormat:@"decline request %@ (scheme mismatch)", url]; 225 | } else { 226 | [self customHTTPProtocol:nil logWithFormat:@"accept request %@", url]; 227 | } 228 | } 229 | 230 | return shouldAccept; 231 | } 232 | 233 | + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request 234 | { 235 | NSURLRequest * result; 236 | 237 | assert(request != nil); 238 | // can be called on any thread 239 | 240 | // Canonicalising a request is quite complex, so all the heavy lifting has 241 | // been shuffled off to a separate module. 242 | 243 | result = CanonicalRequestForRequest(request); 244 | 245 | [self customHTTPProtocol:nil logWithFormat:@"canonicalized %@ to %@", [request URL], [result URL]]; 246 | 247 | return result; 248 | } 249 | 250 | - (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id )client 251 | { 252 | assert(request != nil); 253 | // cachedResponse may be nil 254 | assert(client != nil); 255 | // can be called on any thread 256 | 257 | self = [super initWithRequest:request cachedResponse:cachedResponse client:client]; 258 | if (self != nil) { 259 | // All we do here is log the call. 260 | [[self class] customHTTPProtocol:self logWithFormat:@"init for %@ from <%@ %p>", [request URL], [client class], client]; 261 | } 262 | return self; 263 | } 264 | 265 | - (void)dealloc 266 | { 267 | // can be called on any thread 268 | [[self class] customHTTPProtocol:self logWithFormat:@"dealloc"]; 269 | assert(self->_task == nil); // we should have cleared it by now 270 | assert(self->_pendingChallenge == nil); // we should have cancelled it by now 271 | assert(self->_pendingChallengeCompletionHandler == nil); // we should have cancelled it by now 272 | } 273 | 274 | - (void)startLoading 275 | { 276 | NSMutableURLRequest * recursiveRequest; 277 | NSMutableArray * calculatedModes; 278 | NSString * currentMode; 279 | 280 | // At this point we kick off the process of loading the URL via NSURLSession. 281 | // The thread that calls this method becomes the client thread. 282 | 283 | assert(self.clientThread == nil); // you can't call -startLoading twice 284 | assert(self.task == nil); 285 | 286 | // Calculate our effective run loop modes. In some circumstances (yes I'm looking at 287 | // you UIWebView!) we can be called from a non-standard thread which then runs a 288 | // non-standard run loop mode waiting for the request to finish. We detect this 289 | // non-standard mode and add it to the list of run loop modes we use when scheduling 290 | // our callbacks. Exciting huh? 291 | // 292 | // For debugging purposes the non-standard mode is "WebCoreSynchronousLoaderRunLoopMode" 293 | // but it's better not to hard-code that here. 294 | 295 | assert(self.modes == nil); 296 | calculatedModes = [NSMutableArray array]; 297 | [calculatedModes addObject:NSDefaultRunLoopMode]; 298 | currentMode = [[NSRunLoop currentRunLoop] currentMode]; 299 | if ( (currentMode != nil) && ! [currentMode isEqual:NSDefaultRunLoopMode] ) { 300 | [calculatedModes addObject:currentMode]; 301 | } 302 | self.modes = calculatedModes; 303 | assert([self.modes count] > 0); 304 | 305 | // Create new request that's a clone of the request we were initialised with, 306 | // except that it has our 'recursive request flag' property set on it. 307 | 308 | recursiveRequest = [[self request] mutableCopy]; 309 | assert(recursiveRequest != nil); 310 | 311 | [[self class] setProperty:@YES forKey:kOurRecursiveRequestFlagProperty inRequest:recursiveRequest]; 312 | 313 | self.startTime = [NSDate timeIntervalSinceReferenceDate]; 314 | if (currentMode == nil) { 315 | [[self class] customHTTPProtocol:self logWithFormat:@"start %@", [recursiveRequest URL]]; 316 | } else { 317 | [[self class] customHTTPProtocol:self logWithFormat:@"start %@ (mode %@)", [recursiveRequest URL], currentMode]; 318 | } 319 | 320 | // Latch the thread we were called on, primarily for debugging purposes. 321 | 322 | self.clientThread = [NSThread currentThread]; 323 | 324 | // Once everything is ready to go, create a data task with the new request. 325 | 326 | self.task = [[[self class] sharedDemux] dataTaskWithRequest:recursiveRequest delegate:self modes:self.modes]; 327 | assert(self.task != nil); 328 | 329 | [self.task resume]; 330 | } 331 | 332 | - (void)stopLoading 333 | { 334 | // The implementation just cancels the current load (if it's still running). 335 | 336 | [[self class] customHTTPProtocol:self logWithFormat:@"stop (elapsed %.1f)", [NSDate timeIntervalSinceReferenceDate] - self.startTime]; 337 | 338 | assert(self.clientThread != nil); // someone must have called -startLoading 339 | 340 | // Check that we're being stopped on the same thread that we were started 341 | // on. Without this invariant things are going to go badly (for example, 342 | // run loop sources that got attached during -startLoading may not get 343 | // detached here). 344 | // 345 | // I originally had code here to bounce over to the client thread but that 346 | // actually gets complex when you consider run loop modes, so I've nixed it. 347 | // Rather, I rely on our client calling us on the right thread, which is what 348 | // the following assert is about. 349 | 350 | assert([NSThread currentThread] == self.clientThread); 351 | 352 | [self cancelPendingChallenge]; 353 | if (self.task != nil) { 354 | [self.task cancel]; 355 | self.task = nil; 356 | // The following ends up calling -URLSession:task:didCompleteWithError: with NSURLErrorDomain / NSURLErrorCancelled, 357 | // which specificallys traps and ignores the error. 358 | } 359 | // Don't nil out self.modes; see property declaration comments for a a discussion of this. 360 | } 361 | 362 | #pragma mark * Authentication challenge handling 363 | 364 | /*! Performs the block on the specified thread in one of specified modes. 365 | * \param thread The thread to target; nil implies the main thread. 366 | * \param modes The modes to target; nil or an empty array gets you the default run loop mode. 367 | * \param block The block to run. 368 | */ 369 | 370 | - (void)performOnThread:(NSThread *)thread modes:(NSArray *)modes block:(dispatch_block_t)block 371 | { 372 | // thread may be nil 373 | // modes may be nil 374 | assert(block != nil); 375 | 376 | if (thread == nil) { 377 | thread = [NSThread mainThread]; 378 | } 379 | if ([modes count] == 0) { 380 | modes = @[ NSDefaultRunLoopMode ]; 381 | } 382 | [self performSelector:@selector(onThreadPerformBlock:) onThread:thread withObject:[block copy] waitUntilDone:NO modes:modes]; 383 | } 384 | 385 | /*! A helper method used by -performOnThread:modes:block:. Runs in the specified context 386 | * and simply calls the block. 387 | * \param block The block to run. 388 | */ 389 | 390 | - (void)onThreadPerformBlock:(dispatch_block_t)block 391 | { 392 | assert(block != nil); 393 | block(); 394 | } 395 | 396 | /*! Called by our NSURLSession delegate callback to pass the challenge to our delegate. 397 | * \description This simply passes the challenge over to the main thread. 398 | * We do this so that all accesses to pendingChallenge are done from the main thread, 399 | * which avoids the need for extra synchronisation. 400 | * 401 | * By the time this runes, the NSURLSession delegate callback has already confirmed with 402 | * the delegate that it wants the challenge. 403 | * 404 | * Note that we use the default run loop mode here, not the common modes. We don't want 405 | * an authorisation dialog showing up on top of an active menu (-: 406 | * 407 | * Also, we implement our own 'perform block' infrastructure because Cocoa doesn't have 408 | * one and CFRunLoopPerformBlock is inadequate for the 409 | * return case (where we need to pass in an array of modes; CFRunLoopPerformBlock only takes 410 | * one mode). 411 | * \param challenge The authentication challenge to process; must not be nil. 412 | * \param completionHandler The associated completion handler; must not be nil. 413 | */ 414 | 415 | - (void)didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(ChallengeCompletionHandler)completionHandler 416 | { 417 | assert(challenge != nil); 418 | assert(completionHandler != nil); 419 | assert([NSThread currentThread] == self.clientThread); 420 | 421 | [[self class] customHTTPProtocol:self logWithFormat:@"challenge %@ received", [[challenge protectionSpace] authenticationMethod]]; 422 | 423 | [self performOnThread:nil modes:nil block:^{ 424 | [self mainThreadDidReceiveAuthenticationChallenge:challenge completionHandler:completionHandler]; 425 | }]; 426 | } 427 | 428 | /*! The main thread side of authentication challenge processing. 429 | * \details If there's already a pending challenge, something has gone wrong and 430 | * the routine simply cancels the new challenge. If our delegate doesn't implement 431 | * the -customHTTPProtocol:canAuthenticateAgainstProtectionSpace: delegate callback, 432 | * we also cancel the challenge. OTOH, if all goes well we simply call our delegate 433 | * with the challenge. 434 | * \param challenge The authentication challenge to process; must not be nil. 435 | * \param completionHandler The associated completion handler; must not be nil. 436 | */ 437 | 438 | - (void)mainThreadDidReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(ChallengeCompletionHandler)completionHandler 439 | { 440 | assert(challenge != nil); 441 | assert(completionHandler != nil); 442 | assert([NSThread isMainThread]); 443 | 444 | if (self.pendingChallenge != nil) { 445 | 446 | // Our delegate is not expecting a second authentication challenge before resolving the 447 | // first. Likewise, NSURLSession shouldn't send us a second authentication challenge 448 | // before we resolve the first. If this happens, assert, log, and cancel the challenge. 449 | // 450 | // Note that we have to cancel the challenge on the thread on which we received it, 451 | // namely, the client thread. 452 | 453 | [[self class] customHTTPProtocol:self logWithFormat:@"challenge %@ cancelled; other challenge pending", [[challenge protectionSpace] authenticationMethod]]; 454 | assert(NO); 455 | [self clientThreadCancelAuthenticationChallenge:challenge completionHandler:completionHandler]; 456 | } else { 457 | id strongDelegate; 458 | 459 | strongDelegate = [[self class] delegate]; 460 | 461 | // Tell the delegate about it. It would be weird if the delegate didn't support this 462 | // selector (it did return YES from -customHTTPProtocol:canAuthenticateAgainstProtectionSpace: 463 | // after all), but if it doesn't then we just cancel the challenge ourselves (or the client 464 | // thread, of course). 465 | 466 | if ( ! [strongDelegate respondsToSelector:@selector(customHTTPProtocol:canAuthenticateAgainstProtectionSpace:)] ) { 467 | [[self class] customHTTPProtocol:self logWithFormat:@"challenge %@ cancelled; no delegate method", [[challenge protectionSpace] authenticationMethod]]; 468 | assert(NO); 469 | [self clientThreadCancelAuthenticationChallenge:challenge completionHandler:completionHandler]; 470 | } else { 471 | 472 | // Remember that this challenge is in progress. 473 | 474 | self.pendingChallenge = challenge; 475 | self.pendingChallengeCompletionHandler = completionHandler; 476 | 477 | // Pass the challenge to the delegate. 478 | 479 | [[self class] customHTTPProtocol:self logWithFormat:@"challenge %@ passed to delegate", [[challenge protectionSpace] authenticationMethod]]; 480 | [strongDelegate customHTTPProtocol:self didReceiveAuthenticationChallenge:self.pendingChallenge]; 481 | } 482 | } 483 | } 484 | 485 | /*! Cancels an authentication challenge that hasn't made it to the pending challenge state. 486 | * \details This routine is called as part of various error cases in the challenge handling 487 | * code. It cancels a challenge that, for some reason, we've failed to pass to our delegate. 488 | * 489 | * The routine is always called on the main thread but bounces over to the client thread to 490 | * do the actual cancellation. 491 | * \param challenge The authentication challenge to cancel; must not be nil. 492 | * \param completionHandler The associated completion handler; must not be nil. 493 | */ 494 | 495 | - (void)clientThreadCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(ChallengeCompletionHandler)completionHandler 496 | { 497 | #pragma unused(challenge) 498 | assert(challenge != nil); 499 | assert(completionHandler != nil); 500 | assert([NSThread isMainThread]); 501 | 502 | [self performOnThread:self.clientThread modes:self.modes block:^{ 503 | completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil); 504 | }]; 505 | } 506 | 507 | /*! Cancels an authentication challenge that /has/ made to the pending challenge state. 508 | * \details This routine is called by -stopLoading to cancel any challenge that might be 509 | * pending when the load is cancelled. It's always called on the client thread but 510 | * immediately bounces over to the main thread (because .pendingChallenge is a main 511 | * thread only value). 512 | */ 513 | 514 | - (void)cancelPendingChallenge 515 | { 516 | assert([NSThread currentThread] == self.clientThread); 517 | 518 | // Just pass the work off to the main thread. We do this so that all accesses 519 | // to pendingChallenge are done from the main thread, which avoids the need for 520 | // extra synchronisation. 521 | 522 | [self performOnThread:nil modes:nil block:^{ 523 | if (self.pendingChallenge == nil) { 524 | // This is not only not unusual, it's actually very typical. It happens every time you shut down 525 | // the connection. Ideally I'd like to not even call -mainThreadCancelPendingChallenge when 526 | // there's no challenge outstanding, but the synchronisation issues are tricky. Rather than solve 527 | // those, I'm just not going to log in this case. 528 | // 529 | // [[self class] customHTTPProtocol:self logWithFormat:@"challenge not cancelled; no challenge pending"]; 530 | } else { 531 | id strongeDelegate; 532 | NSURLAuthenticationChallenge * challenge; 533 | 534 | strongeDelegate = [[self class] delegate]; 535 | 536 | challenge = self.pendingChallenge; 537 | self.pendingChallenge = nil; 538 | self.pendingChallengeCompletionHandler = nil; 539 | 540 | if ([strongeDelegate respondsToSelector:@selector(customHTTPProtocol:didCancelAuthenticationChallenge:)]) { 541 | [[self class] customHTTPProtocol:self logWithFormat:@"challenge %@ cancellation passed to delegate", [[challenge protectionSpace] authenticationMethod]]; 542 | [strongeDelegate customHTTPProtocol:self didCancelAuthenticationChallenge:challenge]; 543 | } else { 544 | [[self class] customHTTPProtocol:self logWithFormat:@"challenge %@ cancellation failed; no delegate method", [[challenge protectionSpace] authenticationMethod]]; 545 | // If we managed to send a challenge to the client but can't cancel it, that's bad. 546 | // There's nothing we can do at this point except log the problem. 547 | assert(NO); 548 | } 549 | } 550 | }]; 551 | } 552 | 553 | - (void)resolveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge withCredential:(NSURLCredential *)credential 554 | { 555 | assert(challenge == self.pendingChallenge); 556 | // credential may be nil 557 | assert([NSThread isMainThread]); 558 | assert(self.clientThread != nil); 559 | 560 | if (challenge != self.pendingChallenge) { 561 | [[self class] customHTTPProtocol:self logWithFormat:@"challenge resolution mismatch (%@ / %@)", challenge, self.pendingChallenge]; 562 | // This should never happen, and we want to know if it does, at least in the debug build. 563 | assert(NO); 564 | } else { 565 | ChallengeCompletionHandler completionHandler; 566 | 567 | // We clear out our record of the pending challenge and then pass the real work 568 | // over to the client thread (which ensures that the challenge is resolved on 569 | // the same thread we received it on). 570 | 571 | completionHandler = self.pendingChallengeCompletionHandler; 572 | self.pendingChallenge = nil; 573 | self.pendingChallengeCompletionHandler = nil; 574 | 575 | [self performOnThread:self.clientThread modes:self.modes block:^{ 576 | if (credential == nil) { 577 | [[self class] customHTTPProtocol:self logWithFormat:@"challenge %@ resolved without credential", [[challenge protectionSpace] authenticationMethod]]; 578 | completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); 579 | } else { 580 | [[self class] customHTTPProtocol:self logWithFormat:@"challenge %@ resolved with <%@ %p>", [[challenge protectionSpace] authenticationMethod], [credential class], credential]; 581 | completionHandler(NSURLSessionAuthChallengeUseCredential, credential); 582 | } 583 | }]; 584 | } 585 | } 586 | 587 | #pragma mark * NSURLSession delegate callbacks 588 | 589 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)newRequest completionHandler:(void (^)(NSURLRequest *))completionHandler 590 | { 591 | NSMutableURLRequest * redirectRequest; 592 | 593 | #pragma unused(session) 594 | #pragma unused(task) 595 | assert(task == self.task); 596 | assert(response != nil); 597 | assert(newRequest != nil); 598 | #pragma unused(completionHandler) 599 | assert(completionHandler != nil); 600 | assert([NSThread currentThread] == self.clientThread); 601 | 602 | [[self class] customHTTPProtocol:self logWithFormat:@"will redirect from %@ to %@", [response URL], [newRequest URL]]; 603 | 604 | // The new request was copied from our old request, so it has our magic property. We actually 605 | // have to remove that so that, when the client starts the new request, we see it. If we 606 | // don't do this then we never see the new request and thus don't get a chance to change 607 | // its caching behaviour. 608 | // 609 | // We also cancel our current connection because the client is going to start a new request for 610 | // us anyway. 611 | 612 | assert([[self class] propertyForKey:kOurRecursiveRequestFlagProperty inRequest:newRequest] != nil); 613 | 614 | redirectRequest = [newRequest mutableCopy]; 615 | [[self class] removePropertyForKey:kOurRecursiveRequestFlagProperty inRequest:redirectRequest]; 616 | 617 | // Tell the client about the redirect. 618 | 619 | [[self client] URLProtocol:self wasRedirectedToRequest:redirectRequest redirectResponse:response]; 620 | 621 | // Stop our load. The CFNetwork infrastructure will create a new NSURLProtocol instance to run 622 | // the load of the redirect. 623 | 624 | // The following ends up calling -URLSession:task:didCompleteWithError: with NSURLErrorDomain / NSURLErrorCancelled, 625 | // which specificallys traps and ignores the error. 626 | 627 | [self.task cancel]; 628 | 629 | [[self client] URLProtocol:self didFailWithError:[NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]]; 630 | } 631 | 632 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler 633 | { 634 | 635 | if([[UUSSLWhiteList sharedSSLWhiteList] urlIsContainedInWhiteList:task.originalRequest.URL]) { 636 | 637 | NSURLCredential * credential = nil; 638 | SecTrustRef trust = NULL; 639 | 640 | trust = [[challenge protectionSpace] serverTrust]; 641 | credential = [NSURLCredential credentialForTrust:trust]; 642 | completionHandler(NSURLSessionAuthChallengeUseCredential, credential); 643 | return; 644 | } 645 | 646 | 647 | BOOL result; 648 | id strongeDelegate; 649 | 650 | #pragma unused(session) 651 | #pragma unused(task) 652 | assert(task == self.task); 653 | assert(challenge != nil); 654 | assert(completionHandler != nil); 655 | assert([NSThread currentThread] == self.clientThread); 656 | 657 | // Ask our delegate whether it wants this challenge. We do this from this thread, not the main thread, 658 | // to avoid the overload of bouncing to the main thread for challenges that aren't going to be customised 659 | // anyway. 660 | 661 | strongeDelegate = [[self class] delegate]; 662 | 663 | result = NO; 664 | if ([strongeDelegate respondsToSelector:@selector(customHTTPProtocol:canAuthenticateAgainstProtectionSpace:)]) { 665 | result = [strongeDelegate customHTTPProtocol:self canAuthenticateAgainstProtectionSpace:[challenge protectionSpace]]; 666 | } 667 | 668 | // If the client wants the challenge, kick off that process. If not, resolve it by doing the default thing. 669 | 670 | if (result) { 671 | [[self class] customHTTPProtocol:self logWithFormat:@"can authenticate %@", [[challenge protectionSpace] authenticationMethod]]; 672 | 673 | [self didReceiveAuthenticationChallenge:challenge completionHandler:completionHandler]; 674 | } else { 675 | [[self class] customHTTPProtocol:self logWithFormat:@"cannot authenticate %@", [[challenge protectionSpace] authenticationMethod]]; 676 | 677 | completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); 678 | } 679 | } 680 | 681 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler 682 | { 683 | NSURLCacheStoragePolicy cacheStoragePolicy; 684 | NSInteger statusCode; 685 | 686 | #pragma unused(session) 687 | #pragma unused(dataTask) 688 | assert(dataTask == self.task); 689 | assert(response != nil); 690 | assert(completionHandler != nil); 691 | assert([NSThread currentThread] == self.clientThread); 692 | 693 | // Pass the call on to our client. The only tricky thing is that we have to decide on a 694 | // cache storage policy, which is based on the actual request we issued, not the request 695 | // we were given. 696 | 697 | if ([response isKindOfClass:[NSHTTPURLResponse class]]) { 698 | cacheStoragePolicy = CacheStoragePolicyForRequestAndResponse(self.task.originalRequest, (NSHTTPURLResponse *) response); 699 | statusCode = [((NSHTTPURLResponse *) response) statusCode]; 700 | } else { 701 | assert(NO); 702 | cacheStoragePolicy = NSURLCacheStorageNotAllowed; 703 | statusCode = 42; 704 | } 705 | 706 | [[self class] customHTTPProtocol:self logWithFormat:@"received response %zd / %@ with cache storage policy %zu", (ssize_t) statusCode, [response URL], (size_t) cacheStoragePolicy]; 707 | 708 | [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:cacheStoragePolicy]; 709 | 710 | completionHandler(NSURLSessionResponseAllow); 711 | } 712 | 713 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data 714 | { 715 | #pragma unused(session) 716 | #pragma unused(dataTask) 717 | assert(dataTask == self.task); 718 | assert(data != nil); 719 | assert([NSThread currentThread] == self.clientThread); 720 | 721 | // Just pass the call on to our client. 722 | 723 | [[self class] customHTTPProtocol:self logWithFormat:@"received %zu bytes of data", (size_t) [data length]]; 724 | 725 | [[self client] URLProtocol:self didLoadData:data]; 726 | } 727 | 728 | - (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *))completionHandler 729 | { 730 | #pragma unused(session) 731 | #pragma unused(dataTask) 732 | assert(dataTask == self.task); 733 | assert(proposedResponse != nil); 734 | assert(completionHandler != nil); 735 | assert([NSThread currentThread] == self.clientThread); 736 | 737 | // We implement this delegate callback purely for the purposes of logging. 738 | 739 | [[self class] customHTTPProtocol:self logWithFormat:@"will cache response"]; 740 | 741 | completionHandler(proposedResponse); 742 | } 743 | 744 | - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error 745 | // An NSURLSession delegate callback. We pass this on to the client. 746 | { 747 | #pragma unused(session) 748 | #pragma unused(task) 749 | assert( (self.task == nil) || (task == self.task) ); // can be nil in the 'cancel from -stopLoading' case 750 | assert([NSThread currentThread] == self.clientThread); 751 | 752 | // Just log and then, in most cases, pass the call on to our client. 753 | 754 | if (error == nil) { 755 | [[self class] customHTTPProtocol:self logWithFormat:@"success"]; 756 | 757 | [[self client] URLProtocolDidFinishLoading:self]; 758 | } else if ( [[error domain] isEqual:NSURLErrorDomain] && ([error code] == NSURLErrorCancelled) ) { 759 | // Do nothing. This happens in two cases: 760 | // 761 | // o during a redirect, in which case the redirect code has already told the client about 762 | // the failure 763 | // 764 | // o if the request is cancelled by a call to -stopLoading, in which case the client doesn't 765 | // want to know about the failure 766 | } else { 767 | [[self class] customHTTPProtocol:self logWithFormat:@"error %@ / %d", [error domain], (int) [error code]]; 768 | 769 | [[self client] URLProtocol:self didFailWithError:error]; 770 | } 771 | 772 | // We don't need to clean up the connection here; the system will call, or has already called, 773 | // -stopLoading to do that. 774 | } 775 | 776 | @end 777 | --------------------------------------------------------------------------------