// // MBIMHTTPConnection.m // CocoaHTTPServer // // Created by James Magahern on 11/16/18. // Copyright © 2018 James Magahern. All rights reserved. // #import "MBIMHTTPConnection.h" #import "MBIMBridge.h" #import "MBIMBridge_Private.h" #import "MBIMBridgeOperation.h" #import "MBIMAuthToken.h" #import "MBIMUpdateQueue.h" #import "MBIMURLUtilities.h" #import #import "HTTPMessage.h" #import "GCDAsyncSocket.h" @interface HTTPConnection (/* INTERNAL */) - (BOOL)isAuthenticated; @end @implementation MBIMHTTPConnection { NSMutableData *_bodyData; MBIMBridgeOperation *_currentOperation; } - (BOOL)isSecureServer { return [[MBIMBridge sharedInstance] usesSSL]; } - (NSArray *)sslIdentityAndCertificates { return [[MBIMBridge sharedInstance] sslCertificateAndIdentity]; } - (BOOL)isPasswordProtected:(NSString *)path { if ([[MBIMBridge sharedInstance] usesAccessControl]) { NSURL *url = [NSURL URLWithString:path]; NSString *endpointName = [url lastPathComponent]; Class operationClass = [MBIMBridgeOperation operationClassForEndpointName:endpointName]; return [operationClass requiresAuthentication]; } return NO; } - (NSString *)passwordForUser:(NSString *)username { MBIMBridge *bridge = [MBIMBridge sharedInstance]; if ([username isEqualToString:bridge.authUsername]) { return bridge.authPassword; } // nil means "user not in system" return nil; } - (BOOL)isAuthenticated { NSString *authInfo = [request headerField:@"Authorization"]; if ([authInfo hasPrefix:@"Bearer"]) { NSArray *bearerAuthTuple = [authInfo componentsSeparatedByString:@" "]; if ([bearerAuthTuple count] == 2) { NSString *jwtToken = [bearerAuthTuple objectAtIndex:1]; MBIMAuthToken *authToken = [[MBIMAuthToken alloc] initWithTokenString:jwtToken]; return [authToken isValid]; } } return [super isAuthenticated]; } - (BOOL)useDigestAccessAuthentication { // TODO: should use digest all the time, but it's a bit more complicated. Allow this to be NO if // SSL is on, because then at least basic auth is encrypted return ![[MBIMBridge sharedInstance] usesSSL]; } - (BOOL)supportsMethod:(NSString *)method atPath:(NSString *)path { if ([method isEqualToString:@"GET"] || [method isEqualToString:@"POST"]) { return YES; } return NO; } - (NSObject *)httpResponseForMethod:(NSString *)method URI:(NSString *)path { __block NSObject *response = nil; dispatch_semaphore_t sema = dispatch_semaphore_create(0); MBIMBridgeOperationCompletionBlock completion = ^(NSObject *incomingResponse) { response = incomingResponse; dispatch_semaphore_signal(sema); }; NSURL *url = [NSURL URLWithString:path]; NSString *endpointName = [url lastPathComponent]; BOOL requestTimedOut = NO; Class operationClass = [MBIMBridgeOperation operationClassForEndpointName:endpointName]; if (operationClass != nil) { _currentOperation = [[operationClass alloc] initWithRequestURL:url completion:completion]; _currentOperation.requestBodyData = _bodyData; _currentOperation.request = self->request; [[[MBIMBridge sharedInstance] operationQueue] addOperation:_currentOperation]; long status = dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(60.0 * NSEC_PER_SEC))); if (status != 0) { requestTimedOut = YES; } } else { response = [[HTTPErrorResponse alloc] initWithErrorCode:404]; } if (requestTimedOut) { response = [_currentOperation cancelAndReturnTimeoutResponse]; } return response; } - (WebSocket *)webSocketForURI:(NSString *)path { NSURL *url = [NSURL URLWithString:path]; NSString *endpointName = [url lastPathComponent]; NSString *authTokenString = [url valueForQueryItemWithName:@"token"]; MBIMAuthToken *queryAuthToken = [[MBIMAuthToken alloc] initWithTokenString:authTokenString]; NSLog(@"Websocket for URI: %@ | authenticated request: %@", path, [self isAuthenticated] ? @"YES" : @"NO"); if ([endpointName isEqualToString:@"updates"]) { if (![self isAuthenticated] && ![queryAuthToken isValid]) { NSLog(@"Websocket: auth invalid, rejecting."); NSLog(@"Query Token: %@, raw: %@", queryAuthToken, authTokenString); // Respond with 401 unauthorized HTTPMessage *response = [[HTTPMessage alloc] initResponseWithStatusCode:401 description:nil version:HTTPVersion1_1]; [response setHeaderField:@"Content-Length" value:@"0"]; NSData *responseData = [self preprocessErrorResponse:response]; [asyncSocket writeData:responseData withTimeout:30 tag:90]; return nil; } NSLog(@"Vending websocket for consumer"); return [[MBIMUpdateQueue sharedInstance] vendUpdateWebSocketConsumerForRequest:request socket:asyncSocket]; } return [super webSocketForURI:path]; } - (BOOL)expectsRequestBodyFromMethod:(NSString *)method atPath:(NSString *)path { if ([method isEqualToString:@"POST"]) { return YES; } return NO; } - (void)prepareForBodyWithSize:(UInt64)contentLength { _bodyData = [[NSMutableData alloc] initWithCapacity:contentLength]; } - (void)processBodyData:(NSData *)postDataChunk { [_bodyData appendData:postDataChunk]; } - (void)die { [_currentOperation cancel]; [super die]; } @end