├── 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 | 
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 |
--------------------------------------------------------------------------------