Files
OrderScheduling/Pods/CocoaDebug/Sources/CustomHTTPProtocol/_CustomHTTPProtocol.m
DDIsFriend 63ca919ed5 update
2023-08-23 09:24:40 +08:00

1190 lines
50 KiB
Objective-C

//
// Example
// man
//
// Created by man 11/11/2018.
// Copyright © 2020 man. All rights reserved.
//
#import "_CustomHTTPProtocol.h"
#import "_CanonicalRequest.h"
#import "_CacheStoragePolicy.h"
#import "_QNSURLSessionDemux.h"
//liman
#import "_Swizzling.h"
#import "_NetworkHelper.h"
#import "_HttpDatasource.h"
#import "NSObject+CocoaDebug.h"
// https://stackoverflow.com/questions/27604052/nsurlsessiontask-authentication-challenge-completionhandler-and-nsurlauthenticat
@interface CPURLSessionChallengeSender : NSObject <NSURLAuthenticationChallengeSender>
- (instancetype)initWithSessionCompletionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler;
@end
@implementation CPURLSessionChallengeSender
{
void (^_sessionCompletionHandler)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential);
}
- (instancetype)initWithSessionCompletionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
self = [super init];
if (self)
{
_sessionCompletionHandler = [completionHandler copy];
}
return self;
}
- (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
_sessionCompletionHandler(NSURLSessionAuthChallengeUseCredential, credential);
}
- (void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
_sessionCompletionHandler(NSURLSessionAuthChallengeUseCredential, nil);
}
- (void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
{
_sessionCompletionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
- (void)performDefaultHandlingForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
_sessionCompletionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
}
- (void)rejectProtectionSpaceAndContinueWithChallenge:(NSURLAuthenticationChallenge *)challenge
{
_sessionCompletionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
}
@end
//liman
typedef NSURLSessionConfiguration *(*SessionConfigConstructor)(id,SEL);
static SessionConfigConstructor orig_defaultSessionConfiguration;
static SessionConfigConstructor orig_ephemeralSessionConfiguration;
static NSURLSessionConfiguration *replaced_defaultSessionConfiguration(id self, SEL _cmd)
{
NSURLSessionConfiguration *config = orig_defaultSessionConfiguration(self,_cmd);
if ([config respondsToSelector:@selector(protocolClasses)] && [config respondsToSelector:@selector(setProtocolClasses:)]) {
NSMutableArray *urlProtocolClasses = [NSMutableArray arrayWithArray:config.protocolClasses];
Class protoCls = _CustomHTTPProtocol.class;
if (![urlProtocolClasses containsObject:protoCls]) {
[urlProtocolClasses insertObject:protoCls atIndex:0];
}
config.protocolClasses = urlProtocolClasses;
}
return config;
}
static NSURLSessionConfiguration *replaced_ephemeralSessionConfiguration(id self, SEL _cmd)
{
NSURLSessionConfiguration *config = orig_ephemeralSessionConfiguration(self,_cmd);
if ([config respondsToSelector:@selector(protocolClasses)] && [config respondsToSelector:@selector(setProtocolClasses:)]) {
NSMutableArray *urlProtocolClasses = [NSMutableArray arrayWithArray:config.protocolClasses];
Class protoCls = _CustomHTTPProtocol.class;
if (![urlProtocolClasses containsObject:protoCls]) {
[urlProtocolClasses insertObject:protoCls atIndex:0];
}
config.protocolClasses = urlProtocolClasses;
}
return config;
}
// I use the following typedef to keep myself sane in the face of the wacky
// Objective-C block syntax.
typedef void (^_ChallengeCompletionHandler)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * credential);
@interface _CustomHTTPProtocol () <NSURLSessionDataDelegate>
@property (atomic, strong, readwrite) NSThread * clientThread; ///< The thread on which we should call the client.
/*! The run loop modes in which to call the client.
* \details The concurrency control here is complex. It's set up on the client
* thread in -startLoading and then never modified. It is, however, read by code
* running on other threads (specifically the main thread), so we deallocate it in
* -dealloc rather than in -stopLoading. We can be sure that it's not read before
* it's set up because the main thread code that reads it can only be called after
* -startLoading has started the connection running.
*/
@property (atomic, copy, readwrite) NSArray * modes;
@property (atomic, assign, readwrite) NSTimeInterval startTime; ///< The start time of the request; written by client thread only; read by any thread.
@property (atomic, strong, readwrite) NSURLSessionDataTask * task; ///< The NSURLSession task for that request; client thread only.
@property (atomic, strong, readwrite) NSURLAuthenticationChallenge * pendingChallenge;
@property (atomic, copy, readwrite) _ChallengeCompletionHandler pendingChallengeCompletionHandler; ///< The completion handler that matches pendingChallenge; main thread only.
//liman
@property (atomic, strong) NSURLResponse *response;
@property (atomic, strong) NSMutableData *data;
@property (atomic, strong) NSError *error;
@end
@implementation _CustomHTTPProtocol
#pragma mark * Subclass specific additions
/*! The backing store for the class delegate. This is protected by @synchronized on the class.
*/
static id<_CustomHTTPProtocolDelegate> sDelegate;
+ (void)start
{
[NSURLProtocol registerClass:self];
}
//liman
+ (void)stop
{
[NSURLProtocol unregisterClass:self];
}
+ (id<_CustomHTTPProtocolDelegate>)delegate
{
id<_CustomHTTPProtocolDelegate> result;
@synchronized (self) {
result = sDelegate;
}
return result;
}
+ (void)setDelegate:(id<_CustomHTTPProtocolDelegate>)newValue
{
@synchronized (self) {
sDelegate = newValue;
}
}
/*! Returns the session demux object used by all the protocol instances.
* \details This object allows us to have a single NSURLSession, with a session delegate,
* and have its delegate callbacks routed to the correct protocol instance on the correct
* thread in the correct modes. Can be called on any thread.
*/
+ (_QNSURLSessionDemux *)sharedDemux
{
static dispatch_once_t sOnceToken;
static _QNSURLSessionDemux * sDemux;
dispatch_once(&sOnceToken, ^{
NSURLSessionConfiguration * config;
config = [NSURLSessionConfiguration defaultSessionConfiguration];
// You have to explicitly configure the session to use your own protocol subclass here
// otherwise you don't see redirects <rdar://problem/17384498>.
config.protocolClasses = @[ self ];
sDemux = [[_QNSURLSessionDemux alloc] initWithConfiguration:config];
});
return sDemux;
}
/*! Called by by both class code and instance code to log various bits of information.
* Can be called on any thread.
* \param protocol The protocol instance; nil if it's the class doing the logging.
* \param format A standard NSString-style format string; will not be nil.
*/
#pragma mark * NSURLProtocol overrides
/*! Used to mark our recursive requests so that we don't try to handle them (and thereby
* suffer an infinite recursive death).
*/
static NSString * kOurRecursiveRequestFlagProperty = @"com.apple.dts.CustomHTTPProtocol";
//liman
//+ (BOOL)canInitWithRequest:(NSURLRequest *)request
//{
// BOOL shouldAccept;
// NSURL * url;
// NSString * scheme;
//
// // Check the basics. This routine is extremely defensive because experience has shown that
// // it can be called with some very odd requests <rdar://problem/15197355>.
//
// shouldAccept = (request != nil);
// if (shouldAccept) {
// url = [request URL];
// shouldAccept = (url != nil);
// }
// if ( ! shouldAccept ) {
// [self customHTTPProtocol:nil logWithFormat:@"decline request (malformed)"];
// }
//
// // Decline our recursive requests.
//
// if (shouldAccept) {
// shouldAccept = ([self propertyForKey:kOurRecursiveRequestFlagProperty inRequest:request] == nil);
// if ( ! shouldAccept ) {
// [self customHTTPProtocol:nil logWithFormat:@"decline request %@ (recursive)", url];
// }
// }
//
// // Get the scheme.
//
// if (shouldAccept) {
// scheme = [[url scheme] lowercaseString];
// shouldAccept = (scheme != nil);
//
// if ( ! shouldAccept ) {
// [self customHTTPProtocol:nil logWithFormat:@"decline request %@ (no scheme)", url];
// }
// }
//
// // Look for "http" or "https".
// //
// // Flip either or both of the following to YESes to control which schemes go through this custom
// // NSURLProtocol subclass.
//
// if (shouldAccept) {
// shouldAccept = /* DISABLES CODE */ (NO) && [scheme isEqual:@"http"];
// if ( ! shouldAccept ) {
// shouldAccept = YES && [scheme isEqual:@"https"];
// }
//
// if ( ! shouldAccept ) {
// [self customHTTPProtocol:nil logWithFormat:@"decline request %@ (scheme mismatch)", url];
// } else {
// [self customHTTPProtocol:nil logWithFormat:@"accept request %@", url];
// }
// }
//
// return shouldAccept;
//}
//liman
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
if (![request.URL.scheme isEqualToString:@"http"] &&
![request.URL.scheme isEqualToString:@"https"]) {
return NO;
}
if ([NSURLProtocol propertyForKey:kOurRecursiveRequestFlagProperty inRequest:request] ) {
return NO;
}
if ([[_NetworkHelper shared] onlyURLs].count > 0) {
NSString* url = [request.URL.absoluteString lowercaseString];
for (NSString* _url in [_NetworkHelper shared].onlyURLs) {
if ([url rangeOfString:[_url lowercaseString]].location != NSNotFound)
return YES;
}
return NO;
}
return YES;
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
NSURLRequest * result;
//assert(request != nil);
// can be called on any thread
// Canonicalising a request is quite complex, so all the heavy lifting has
// been shuffled off to a separate module.
result = CanonicalRequestForRequest(request);
return result;
}
- (id)initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id <NSURLProtocolClient>)client
{
//assert(request != nil);
// cachedResponse may be nil
//assert(client != nil);
// can be called on any thread
self = [super initWithRequest:request cachedResponse:cachedResponse client:client];
return self;
}
- (void)dealloc
{
// can be called on any thread
//assert(self->_task == nil); // we should have cleared it by now
//assert(self->_pendingChallenge == nil); // we should have cancelled it by now
//assert(self->_pendingChallengeCompletionHandler == nil); // we should have cancelled it by now
}
- (void)startLoading
{
NSMutableURLRequest * recursiveRequest;
NSMutableArray * calculatedModes;
NSString * currentMode;
// At this point we kick off the process of loading the URL via NSURLSession.
// The thread that calls this method becomes the client thread.
//assert(self.clientThread == nil); // you can't call -startLoading twice
//assert(self.task == nil);
// Calculate our effective run loop modes. In some circumstances (yes I'm looking at
// you UIWebView!) we can be called from a non-standard thread which then runs a
// non-standard run loop mode waiting for the request to finish. We detect this
// non-standard mode and add it to the list of run loop modes we use when scheduling
// our callbacks. Exciting huh?
//
// For debugging purposes the non-standard mode is "WebCoreSynchronousLoaderRunLoopMode"
// but it's better not to hard-code that here.
//assert(self.modes == nil);
calculatedModes = [NSMutableArray array];
[calculatedModes addObject:NSDefaultRunLoopMode];
currentMode = [[NSRunLoop currentRunLoop] currentMode];
if ( (currentMode != nil) && ! [currentMode isEqual:NSDefaultRunLoopMode] ) {
[calculatedModes addObject:currentMode];
}
self.modes = calculatedModes;
//assert([self.modes count] > 0);
// Create new request that's a clone of the request we were initialised with,
// except that it has our 'recursive request flag' property set on it.
recursiveRequest = [[self request] mutableCopy];
//assert(recursiveRequest != nil);
[[self class] setProperty:@YES forKey:kOurRecursiveRequestFlagProperty inRequest:recursiveRequest];
//liman
self.startTime = [[NSDate date] timeIntervalSince1970];
self.data = [NSMutableData data];
// Latch the thread we were called on, primarily for debugging purposes.
self.clientThread = [NSThread currentThread];
// Once everything is ready to go, create a data task with the new request.
self.task = [[[self class] sharedDemux] dataTaskWithRequest:recursiveRequest delegate:self modes:self.modes];
//assert(self.task != nil);
[self.task resume];
}
- (void)stopLoading
{
// The implementation just cancels the current load (if it's still running).
//assert(self.clientThread != nil); // someone must have called -startLoading
// Check that we're being stopped on the same thread that we were started
// on. Without this invariant things are going to go badly (for example,
// run loop sources that got attached during -startLoading may not get
// detached here).
//
// I originally had code here to bounce over to the client thread but that
// actually gets complex when you consider run loop modes, so I've nixed it.
// Rather, I rely on our client calling us on the right thread, which is what
// the following //assert is about.
//assert([NSThread currentThread] == self.clientThread);
[self cancelPendingChallenge];
if (self.task != nil) {
[self.task cancel];
self.task = nil;
// The following ends up calling -URLSession:task:didCompleteWithError: with NSURLErrorDomain / NSURLErrorCancelled,
// which specificallys traps and ignores the error.
}
// Don't nil out self.modes; see property declaration comments for a a discussion of this.
//liman
if (![_NetworkHelper shared].isNetworkEnable) {
return;
}
_HttpModel* model = [[_HttpModel alloc] init];
model.url = self.request.URL;
model.method = self.request.HTTPMethod;
model.mineType = self.response.MIMEType;
if (self.request.HTTPBody) {
model.requestData = self.request.HTTPBody;
}
if (self.request.HTTPBodyStream) {//liman
NSData* data = [NSData dataWithInputStream:self.request.HTTPBodyStream];
model.requestData = data;
}
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)self.response;
model.statusCode = [NSString stringWithFormat:@"%d",(int)httpResponse.statusCode];
model.responseData = self.data;
model.size = [[NSByteCountFormatter new] stringFromByteCount:self.data.length];
model.isImage = [self.response.MIMEType rangeOfString:@"image"].location != NSNotFound;
//时间
NSTimeInterval startTimeDouble = self.startTime;
NSTimeInterval endTimeDouble = [[NSDate date] timeIntervalSince1970];
NSTimeInterval durationDouble = fabs(endTimeDouble - startTimeDouble);
model.startTime = [NSString stringWithFormat:@"%f", startTimeDouble];
model.endTime = [NSString stringWithFormat:@"%f", endTimeDouble];
model.totalDuration = [NSString stringWithFormat:@"%f (s)", durationDouble];
model.errorDescription = self.error.description;
model.errorLocalizedDescription = self.error.localizedDescription;
model.requestHeaderFields = self.request.allHTTPHeaderFields;
if ([self.response isKindOfClass:[NSHTTPURLResponse class]]) {
model.responseHeaderFields = ((NSHTTPURLResponse *)self.response).allHeaderFields;
}
if (self.response.MIMEType == nil) {
model.isImage = NO;
}
if ([model.url.absoluteString length] > 4) {
NSString *str = [model.url.absoluteString substringFromIndex: [model.url.absoluteString length] - 4];
if ([str isEqualToString:@".png"] || [str isEqualToString:@".PNG"] || [str isEqualToString:@".jpg"] || [str isEqualToString:@".JPG"] || [str isEqualToString:@".gif"] || [str isEqualToString:@".GIF"]) {
model.isImage = YES;
}
}
if ([model.url.absoluteString length] > 5) {
NSString *str = [model.url.absoluteString substringFromIndex: [model.url.absoluteString length] - 5];
if ([str isEqualToString:@".jpeg"] || [str isEqualToString:@".JPEG"]) {
model.isImage = YES;
}
}
//Handling errors 404...
model = [self handleError:self.error model:model];
if ([[_HttpDatasource shared] addHttpRequset:model])
{
[[NSNotificationCenter defaultCenter] postNotificationName:@"reloadHttp_CocoaDebug" object:nil userInfo:@{@"statusCode":model.statusCode}];
}
}
#pragma mark * Authentication challenge handling
/*! Performs the block on the specified thread in one of specified modes.
* \param thread The thread to target; nil implies the main thread.
* \param modes The modes to target; nil or an empty array gets you the default run loop mode.
* \param block The block to run.
*/
- (void)performOnThread:(NSThread *)thread modes:(NSArray *)modes block:(dispatch_block_t)block
{
// thread may be nil
// modes may be nil
//assert(block != nil);
if (thread == nil) {
thread = [NSThread mainThread];
}
if ([modes count] == 0) {
modes = @[ NSDefaultRunLoopMode ];
}
[self performSelector:@selector(onThreadPerformBlock:) onThread:thread withObject:[block copy] waitUntilDone:NO modes:modes];
}
/*! A helper method used by -performOnThread:modes:block:. Runs in the specified context
* and simply calls the block.
* \param block The block to run.
*/
- (void)onThreadPerformBlock:(dispatch_block_t)block
{
//assert(block != nil);
block();
}
/*! Called by our NSURLSession delegate callback to pass the challenge to our delegate.
* \description This simply passes the challenge over to the main thread.
* We do this so that all accesses to pendingChallenge are done from the main thread,
* which avoids the need for extra synchronisation.
*
* By the time this runes, the NSURLSession delegate callback has already confirmed with
* the delegate that it wants the challenge.
*
* Note that we use the default run loop mode here, not the common modes. We don't want
* an authorisation dialog showing up on top of an active menu (-:
*
* Also, we implement our own 'perform block' infrastructure because Cocoa doesn't have
* one <rdar://problem/17232344> and CFRunLoopPerformBlock is inadequate for the
* return case (where we need to pass in an array of modes; CFRunLoopPerformBlock only takes
* one mode).
* \param challenge The authentication challenge to process; must not be nil.
* \param completionHandler The associated completion handler; must not be nil.
*/
- (void)didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(_ChallengeCompletionHandler)completionHandler
{
//assert(challenge != nil);
//assert(completionHandler != nil);
//assert([NSThread currentThread] == self.clientThread);
[self performOnThread:nil modes:nil block:^{
[self mainThreadDidReceiveAuthenticationChallenge:challenge completionHandler:completionHandler];
}];
}
/*! The main thread side of authentication challenge processing.
* \details If there's already a pending challenge, something has gone wrong and
* the routine simply cancels the new challenge. If our delegate doesn't implement
* the -customHTTPProtocol:canAuthenticateAgainstProtectionSpace: delegate callback,
* we also cancel the challenge. OTOH, if all goes well we simply call our delegate
* with the challenge.
* \param challenge The authentication challenge to process; must not be nil.
* \param completionHandler The associated completion handler; must not be nil.
*/
- (void)mainThreadDidReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(_ChallengeCompletionHandler)completionHandler
{
//assert(challenge != nil);
//assert(completionHandler != nil);
//assert([NSThread isMainThread]);
if (self.pendingChallenge != nil) {
// Our delegate is not expecting a second authentication challenge before resolving the
// first. Likewise, NSURLSession shouldn't send us a second authentication challenge
// before we resolve the first. If this happens, //assert, log, and cancel the challenge.
//
// Note that we have to cancel the challenge on the thread on which we received it,
// namely, the client thread.
//assert(NO);
[self clientThreadCancelAuthenticationChallenge:challenge completionHandler:completionHandler];
} else {
id<_CustomHTTPProtocolDelegate> strongDelegate;
strongDelegate = [[self class] delegate];
// Tell the delegate about it. It would be weird if the delegate didn't support this
// selector (it did return YES from -customHTTPProtocol:canAuthenticateAgainstProtectionSpace:
// after all), but if it doesn't then we just cancel the challenge ourselves (or the client
// thread, of course).
if ( ! [strongDelegate respondsToSelector:@selector(customHTTPProtocol:canAuthenticateAgainstProtectionSpace:)] ) {
//assert(NO);
[self clientThreadCancelAuthenticationChallenge:challenge completionHandler:completionHandler];
} else {
// Remember that this challenge is in progress.
self.pendingChallenge = challenge;
self.pendingChallengeCompletionHandler = completionHandler;
// Pass the challenge to the delegate.
[strongDelegate customHTTPProtocol:self didReceiveAuthenticationChallenge:self.pendingChallenge];
}
}
}
/*! Cancels an authentication challenge that hasn't made it to the pending challenge state.
* \details This routine is called as part of various error cases in the challenge handling
* code. It cancels a challenge that, for some reason, we've failed to pass to our delegate.
*
* The routine is always called on the main thread but bounces over to the client thread to
* do the actual cancellation.
* \param challenge The authentication challenge to cancel; must not be nil.
* \param completionHandler The associated completion handler; must not be nil.
*/
- (void)clientThreadCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(_ChallengeCompletionHandler)completionHandler
{
#pragma unused(challenge)
//assert(challenge != nil);
//assert(completionHandler != nil);
//assert([NSThread isMainThread]);
[self performOnThread:self.clientThread modes:self.modes block:^{
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}];
}
/*! Cancels an authentication challenge that /has/ made to the pending challenge state.
* \details This routine is called by -stopLoading to cancel any challenge that might be
* pending when the load is cancelled. It's always called on the client thread but
* immediately bounces over to the main thread (because .pendingChallenge is a main
* thread only value).
*/
- (void)cancelPendingChallenge
{
//assert([NSThread currentThread] == self.clientThread);
// Just pass the work off to the main thread. We do this so that all accesses
// to pendingChallenge are done from the main thread, which avoids the need for
// extra synchronisation.
[self performOnThread:nil modes:nil block:^{
if (self.pendingChallenge == nil) {
// This is not only not unusual, it's actually very typical. It happens every time you shut down
// the connection. Ideally I'd like to not even call -mainThreadCancelPendingChallenge when
// there's no challenge outstanding, but the synchronisation issues are tricky. Rather than solve
// those, I'm just not going to log in this case.
//
// [[self class] customHTTPProtocol:self logWithFormat:@"challenge not cancelled; no challenge pending"];
} else {
id<_CustomHTTPProtocolDelegate> strongeDelegate;
NSURLAuthenticationChallenge * challenge;
strongeDelegate = [[self class] delegate];
challenge = self.pendingChallenge;
self.pendingChallenge = nil;
self.pendingChallengeCompletionHandler = nil;
if ([strongeDelegate respondsToSelector:@selector(customHTTPProtocol:didCancelAuthenticationChallenge:)]) {
[strongeDelegate customHTTPProtocol:self didCancelAuthenticationChallenge:challenge];
} else {
// If we managed to send a challenge to the client but can't cancel it, that's bad.
// There's nothing we can do at this point except log the problem.
//assert(NO);
}
}
}];
}
- (void)resolveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge withCredential:(NSURLCredential *)credential
{
//assert(challenge == self.pendingChallenge);
// credential may be nil
//assert([NSThread isMainThread]);
//assert(self.clientThread != nil);
if (challenge != self.pendingChallenge) {
// This should never happen, and we want to know if it does, at least in the debug build.
//assert(NO);
} else {
_ChallengeCompletionHandler completionHandler;
// We clear out our record of the pending challenge and then pass the real work
// over to the client thread (which ensures that the challenge is resolved on
// the same thread we received it on).
completionHandler = self.pendingChallengeCompletionHandler;
self.pendingChallenge = nil;
self.pendingChallengeCompletionHandler = nil;
[self performOnThread:self.clientThread modes:self.modes block:^{
if (credential == nil) {
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
} else {
completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
}
}];
}
}
#pragma mark * NSURLSession delegate callbacks
//liman
//- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)newRequest completionHandler:(void (^)(NSURLRequest *))completionHandler
//{
// NSMutableURLRequest * redirectRequest;
//
// #pragma unused(session)
// #pragma unused(task)
// //assert(task == self.task);
// //assert(response != nil);
// //assert(newRequest != nil);
// #pragma unused(completionHandler)
// //assert(completionHandler != nil);
// //assert([NSThread currentThread] == self.clientThread);
//
//
// // The new request was copied from our old request, so it has our magic property. We actually
// // have to remove that so that, when the client starts the new request, we see it. If we
// // don't do this then we never see the new request and thus don't get a chance to change
// // its caching behaviour.
// //
// // We also cancel our current connection because the client is going to start a new request for
// // us anyway.
//
// //assert([[self class] propertyForKey:kOurRecursiveRequestFlagProperty inRequest:newRequest] != nil);
//
// redirectRequest = [newRequest mutableCopy];
// [[self class] removePropertyForKey:kOurRecursiveRequestFlagProperty inRequest:redirectRequest];
//
// // Tell the client about the redirect.
//
// [[self client] URLProtocol:self wasRedirectedToRequest:redirectRequest redirectResponse:response];
//
// // Stop our load. The CFNetwork infrastructure will create a new NSURLProtocol instance to run
// // the load of the redirect.
//
// // The following ends up calling -URLSession:task:didCompleteWithError: with NSURLErrorDomain / NSURLErrorCancelled,
// // which specificallys traps and ignores the error.
//
// [self.task cancel];
//
// [[self client] URLProtocol:self didFailWithError:[NSError errorWithDomain:NSCocoaErrorDomain code:NSUserCancelledError userInfo:nil]];
//}
//liman
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest * _Nullable))completionHandler
{
//Redirect: code >=300 && < 400
if (response && [response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSInteger status = httpResponse.statusCode;
if (status >= 300 && status < 400) {
[[self client] URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
//Remember to set to nil, otherwise the normal request will be requested twice
request = nil;
}
}
completionHandler(request);
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{
BOOL result;
id<_CustomHTTPProtocolDelegate> strongeDelegate;
#pragma unused(session)
#pragma unused(task)
//assert(task == self.task);
//assert(challenge != nil);
//assert(completionHandler != nil);
//assert([NSThread currentThread] == self.clientThread);
// Ask our delegate whether it wants this challenge. We do this from this thread, not the main thread,
// to avoid the overload of bouncing to the main thread for challenges that aren't going to be customised
// anyway.
strongeDelegate = [[self class] delegate];
result = NO;
if ([strongeDelegate respondsToSelector:@selector(customHTTPProtocol:canAuthenticateAgainstProtectionSpace:)]) {
result = [strongeDelegate customHTTPProtocol:self canAuthenticateAgainstProtectionSpace:[challenge protectionSpace]];
}
// If the client wants the challenge, kick off that process. If not, resolve it by doing the default thing.
if (result) {
[self didReceiveAuthenticationChallenge:challenge completionHandler:completionHandler];
} else {
// completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
// Callback the original method
NSURLAuthenticationChallenge* challengeWrapper = [[NSURLAuthenticationChallenge alloc] initWithAuthenticationChallenge:challenge sender:[[CPURLSessionChallengeSender alloc] initWithSessionCompletionHandler:completionHandler]];
[self.client URLProtocol:self didReceiveAuthenticationChallenge:challengeWrapper];
}
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
NSURLCacheStoragePolicy cacheStoragePolicy;
NSInteger statusCode;
#pragma unused(session)
#pragma unused(dataTask)
//assert(dataTask == self.task);
//assert(response != nil);
//assert(completionHandler != nil);
//assert([NSThread currentThread] == self.clientThread);
// Pass the call on to our client. The only tricky thing is that we have to decide on a
// cache storage policy, which is based on the actual request we issued, not the request
// we were given.
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
cacheStoragePolicy = CacheStoragePolicyForRequestAndResponse(self.task.originalRequest, (NSHTTPURLResponse *) response);
statusCode = [((NSHTTPURLResponse *) response) statusCode];
} else {
//assert(NO);
cacheStoragePolicy = NSURLCacheStorageNotAllowed;
statusCode = 42;
}
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:cacheStoragePolicy];
self.response = response;//liman
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
#pragma unused(session)
#pragma unused(dataTask)
//assert(dataTask == self.task);
//assert(data != nil);
//assert([NSThread currentThread] == self.clientThread);
// Just pass the call on to our client.
[[self client] URLProtocol:self didLoadData:data];
[self.data appendData:data];//liman
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask willCacheResponse:(NSCachedURLResponse *)proposedResponse completionHandler:(void (^)(NSCachedURLResponse *))completionHandler
{
#pragma unused(session)
#pragma unused(dataTask)
//assert(dataTask == self.task);
//assert(proposedResponse != nil);
//assert(completionHandler != nil);
//assert([NSThread currentThread] == self.clientThread);
// We implement this delegate callback purely for the purposes of logging.
completionHandler(proposedResponse);
}
//liman
//- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
// // An NSURLSession delegate callback. We pass this on to the client.
//{
// #pragma unused(session)
// #pragma unused(task)
// //assert( (self.task == nil) || (task == self.task) ); // can be nil in the 'cancel from -stopLoading' case
// //assert([NSThread currentThread] == self.clientThread);
//
// // Just log and then, in most cases, pass the call on to our client.
//
// if (error == nil) {
//
// [[self client] URLProtocolDidFinishLoading:self];
// } else if ( [[error domain] isEqual:NSURLErrorDomain] && ([error code] == NSURLErrorCancelled) ) {
// // Do nothing. This happens in two cases:
// //
// // o during a redirect, in which case the redirect code has already told the client about
// // the failure
// //
// // o if the request is cancelled by a call to -stopLoading, in which case the client doesn't
// // want to know about the failure
// } else {
//
// [[self client] URLProtocol:self didFailWithError:error];
// }
//
// // We don't need to clean up the connection here; the system will call, or has already called,
// // -stopLoading to do that.
//}
//liman
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (error) {
[[self client] URLProtocol:self didFailWithError:error];
self.error = error;
} else {
[[self client] URLProtocolDidFinishLoading:self];
}
}
#pragma mark -
//liman
+ (void)load {
if (![[NSUserDefaults standardUserDefaults] boolForKey:@"disableNetworkMonitoring_CocoaDebug"]) {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
orig_defaultSessionConfiguration = (SessionConfigConstructor)replaceMethod(@selector(defaultSessionConfiguration), (IMP)replaced_defaultSessionConfiguration, [NSURLSessionConfiguration class], YES);
orig_ephemeralSessionConfiguration = (SessionConfigConstructor)replaceMethod(@selector(ephemeralSessionConfiguration), (IMP)replaced_ephemeralSessionConfiguration, [NSURLSessionConfiguration class], YES);
});
}
}
//Handling errors 404...
- (_HttpModel *)handleError:(NSError *)error model:(_HttpModel *)model {
if (!error) {
//https://httpcodes.co/status/
switch (model.statusCode.integerValue) {
case 100:
model.errorDescription = @"Informational :\nClient should continue with request";
model.errorLocalizedDescription = @"Continue";
break;
case 101:
model.errorDescription = @"Informational :\nServer is switching protocols";
model.errorLocalizedDescription = @"Switching Protocols";
break;
case 102:
model.errorDescription = @"Informational :\nServer has received and is processing the request";
model.errorLocalizedDescription = @"Processing";
break;
case 103:
model.errorDescription = @"Informational :\nresume aborted PUT or POST requests";
model.errorLocalizedDescription = @"Checkpoint";
break;
case 122:
model.errorDescription = @"Informational :\nURI is longer than a maximum of 2083 characters";
model.errorLocalizedDescription = @"Request-URI too long";
break;
case 300:
model.errorDescription = @"Redirection :\nMultiple options for the resource delivered";
model.errorLocalizedDescription = @"Multiple Choices";
break;
case 301:
model.errorDescription = @"Redirection :\nThis and all future requests directed to the given URI";
model.errorLocalizedDescription = @"Moved Permanently";
break;
case 302:
model.errorDescription = @"Redirection :\nTemporary response to request found via alternative URI";
model.errorLocalizedDescription = @"Found";
break;
case 303:
model.errorDescription = @"Redirection :\nPermanent response to request found via alternative URI";
model.errorLocalizedDescription = @"See Other";
break;
case 304:
model.errorDescription = @"Redirection :\nResource has not been modified since last requested";
model.errorLocalizedDescription = @"Not Modified";
break;
case 305:
model.errorDescription = @"Redirection :\nContent located elsewhere, retrieve from there";
model.errorLocalizedDescription = @"Use Proxy";
break;
case 306:
model.errorDescription = @"Redirection :\nSubsequent requests should use the specified proxy";
model.errorLocalizedDescription = @"Switch Proxy";
break;
case 307:
model.errorDescription = @"Redirection :\nConnect again to different URI as provided";
model.errorLocalizedDescription = @"Temporary Redirect";
break;
case 308:
model.errorDescription = @"Redirection :\nConnect again to a different URI using the same method";
model.errorLocalizedDescription = @"Permanent Redirect";
break;
case 400:
model.errorDescription = @"Client Error :\nRequest cannot be fulfilled due to bad syntax";
model.errorLocalizedDescription = @"Bad Request";
break;
case 401:
model.errorDescription = @"Client Error :\nAuthentication is possible but has failed";
model.errorLocalizedDescription = @"Unauthorized";
break;
case 402:
model.errorDescription = @"Client Error :\nPayment required, reserved for future use";
model.errorLocalizedDescription = @"Payment Required";
break;
case 403:
model.errorDescription = @"Client Error :\nServer refuses to respond to request";
model.errorLocalizedDescription = @"Forbidden";
break;
case 404:
model.errorDescription = @"Client Error :\nRequested resource could not be found";
model.errorLocalizedDescription = @"Not Found";
break;
case 405:
model.errorDescription = @"Client Error :\nRequest method not supported by that resource";
model.errorLocalizedDescription = @"Method Not Allowed";
break;
case 406:
model.errorDescription = @"Client Error :\nContent not acceptable according to the Accept headers";
model.errorLocalizedDescription = @"Not Acceptable";
break;
case 407:
model.errorDescription = @"Client Error :\nClient must first authenticate itself with the proxy";
model.errorLocalizedDescription = @"Proxy Authentication Required";
break;
case 408:
model.errorDescription = @"Client Error :\nServer timed out waiting for the request";
model.errorLocalizedDescription = @"Request Timeout";
break;
case 409:
model.errorDescription = @"Client Error :\nRequest could not be processed because of conflict";
model.errorLocalizedDescription = @"Conflict";
break;
case 410:
model.errorDescription = @"Client Error :\nResource is no longer available and will not be available again";
model.errorLocalizedDescription = @"Gone";
break;
case 411:
model.errorDescription = @"Client Error :\nRequest did not specify the length of its content";
model.errorLocalizedDescription = @"Length Required";
break;
case 412:
model.errorDescription = @"Client Error :\nServer does not meet request preconditions";
model.errorLocalizedDescription = @"Precondition Failed";
break;
case 413:
model.errorDescription = @"Client Error :\nRequest is larger than the server is willing or able to process";
model.errorLocalizedDescription = @"Request Entity Too Large";
break;
case 414:
model.errorDescription = @"Client Error :\nURI provided was too long for the server to process";
model.errorLocalizedDescription = @"Request-URI Too Long";
break;
case 415:
model.errorDescription = @"Client Error :\nServer does not support media type";
model.errorLocalizedDescription = @"Unsupported Media Type";
break;
case 416:
model.errorDescription = @"Client Error :\nClient has asked for unprovidable portion of the file";
model.errorLocalizedDescription = @"Requested Range Not Satisfiable";
break;
case 417:
model.errorDescription = @"Client Error :\nServer cannot meet requirements of Expect request-header field";
model.errorLocalizedDescription = @"Expectation Failed";
break;
case 418:
model.errorDescription = @"Client Error :\nI'm a teapot";
model.errorLocalizedDescription = @"I'm a Teapot";
break;
case 420:
model.errorDescription = @"Client Error :\nTwitter rate limiting";
model.errorLocalizedDescription = @"Enhance Your Calm";
break;
case 421:
model.errorDescription = @"Client Error :\nMisdirected Request";
model.errorLocalizedDescription = @"Misdirected Request";
break;
case 422:
model.errorDescription = @"Client Error :\nRequest unable to be followed due to semantic errors";
model.errorLocalizedDescription = @"Unprocessable Entity";
break;
case 423:
model.errorDescription = @"Client Error :\nResource that is being accessed is locked";
model.errorLocalizedDescription = @"Locked";
break;
case 424:
model.errorDescription = @"Client Error :\nRequest failed due to failure of a previous request";
model.errorLocalizedDescription = @"Failed Dependency";
break;
case 426:
model.errorDescription = @"Client Error :\nClient should switch to a different protocol";
model.errorLocalizedDescription = @"Upgrade Required";
break;
case 428:
model.errorDescription = @"Client Error :\nOrigin server requires the request to be conditional";
model.errorLocalizedDescription = @"Precondition Required";
break;
case 429:
model.errorDescription = @"Client Error :\nUser has sent too many requests in a given amount of time";
model.errorLocalizedDescription = @"Too Many Requests";
break;
case 431:
model.errorDescription = @"Client Error :\nServer is unwilling to process the request";
model.errorLocalizedDescription = @"Request Header Fields Too Large";
break;
case 444:
model.errorDescription = @"Client Error :\nServer returns no information and closes the connection";
model.errorLocalizedDescription = @"No Response";
break;
case 449:
model.errorDescription = @"Client Error :\nRequest should be retried after performing action";
model.errorLocalizedDescription = @"Retry With";
break;
case 450:
model.errorDescription = @"Client Error :\nWindows Parental Controls blocking access to webpage";
model.errorLocalizedDescription = @"Blocked by Windows Parental Controls";
break;
case 451:
model.errorDescription = @"Client Error :\nThe server cannot reach the client's mailbox";
model.errorLocalizedDescription = @"Wrong Exchange server";
break;
case 499:
model.errorDescription = @"Client Error :\nConnection closed by client while HTTP server is processing";
model.errorLocalizedDescription = @"Client Closed Request";
break;
case 500:
model.errorDescription = @"Server Error :\ngeneric error message";
model.errorLocalizedDescription = @"Internal Server Error";
break;
case 501:
model.errorDescription = @"Server Error :\nserver does not recognise method or lacks ability to fulfill";
model.errorLocalizedDescription = @"Not Implemented";
break;
case 502:
model.errorDescription = @"Server Error :\nserver received an invalid response from upstream server";
model.errorLocalizedDescription = @"Bad Gateway";
break;
case 503:
model.errorDescription = @"Server Error :\nserver is currently unavailable";
model.errorLocalizedDescription = @"Service Unavailable";
break;
case 504:
model.errorDescription = @"Server Error :\ngateway did not receive response from upstream server";
model.errorLocalizedDescription = @"Gateway Timeout";
break;
case 505:
model.errorDescription = @"Server Error :\nserver does not support the HTTP protocol version";
model.errorLocalizedDescription = @"HTTP Version Not Supported";
break;
case 506:
model.errorDescription = @"Server Error :\ncontent negotiation for the request results in a circular reference";
model.errorLocalizedDescription = @"Variant Also Negotiates";
break;
case 507:
model.errorDescription = @"Server Error :\nserver is unable to store the representation";
model.errorLocalizedDescription = @"Insufficient Storage";
break;
case 508:
model.errorDescription = @"Server Error :\nserver detected an infinite loop while processing the request";
model.errorLocalizedDescription = @"Loop Detected";
break;
case 509:
model.errorDescription = @"Server Error :\nbandwidth limit exceeded";
model.errorLocalizedDescription = @"Bandwidth Limit Exceeded";
break;
case 510:
model.errorDescription = @"Server Error :\nfurther extensions to the request are required";
model.errorLocalizedDescription = @"Not Extended";
break;
case 511:
model.errorDescription = @"Server Error :\nclient needs to authenticate to gain network access";
model.errorLocalizedDescription = @"Network Authentication Required";
break;
case 526:
model.errorDescription = @"Server Error :\nThe origin web server does not have a valid SSL certificate";
model.errorLocalizedDescription = @"Invalid SSL certificate";
break;
case 598:
model.errorDescription = @"Server Error :\nnetwork read timeout behind the proxy";
model.errorLocalizedDescription = @"Network Read Timeout Error";
break;
case 599:
model.errorDescription = @"Server Error :\nnetwork connect timeout behind the proxy";
model.errorLocalizedDescription = @"Network Connect Timeout Error";
break;
default:
break;
}
}
return model;
}
@end