| /* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| */ |
| |
| #import "CMISHttpRequest.h" |
| #import "CMISHttpResponse.h" |
| #import "CMISErrors.h" |
| #import "CMISAuthenticationProvider.h" |
| #import "CMISLog.h" |
| |
| //Exception names as returned in the <!--exception> tag |
| NSString * const kCMISExceptionInvalidArgument = @"invalidArgument"; |
| NSString * const kCMISExceptionNotSupported = @"notSupported"; |
| NSString * const kCMISExceptionObjectNotFound = @"objectNotFound"; |
| NSString * const kCMISExceptionPermissionDenied = @"permissionDenied"; |
| NSString * const kCMISExceptionRuntime = @"runtime"; |
| NSString * const kCMISExceptionConstraint = @"constraint"; |
| NSString * const kCMISExceptionContentAlreadyExists = @"contentAlreadyExists"; |
| NSString * const kCMISExceptionFilterNotValid = @"filterNotValid"; |
| NSString * const kCMISExceptionNameConstraintViolation = @"nameConstraintViolation"; |
| NSString * const kCMISExceptionStorage = @"storage"; |
| NSString * const kCMISExceptionStreamNotSupported = @"streamNotSupported"; |
| NSString * const kCMISExceptionUpdateConflict = @"updateConflict"; |
| NSString * const kCMISExceptionVersioning = @"versioning"; |
| |
| |
| @implementation CMISHttpRequest |
| |
| |
| + (id)startRequest:(NSMutableURLRequest *)urlRequest |
| httpMethod:(CMISHttpRequestMethod)httpRequestMethod |
| requestBody:(NSData*)requestBody |
| headers:(NSDictionary*)additionalHeaders |
| authenticationProvider:(id<CMISAuthenticationProvider>) authenticationProvider |
| completionBlock:(void (^)(CMISHttpResponse *httpResponse, NSError *error))completionBlock |
| { |
| CMISHttpRequest *httpRequest = [[self alloc] initWithHttpMethod:httpRequestMethod |
| completionBlock:completionBlock]; |
| httpRequest.requestBody = requestBody; |
| httpRequest.additionalHeaders = additionalHeaders; |
| httpRequest.authenticationProvider = authenticationProvider; |
| |
| if ([httpRequest startRequest:urlRequest] == NO) { |
| httpRequest = nil; |
| } |
| |
| return httpRequest; |
| } |
| |
| |
| - (id)initWithHttpMethod:(CMISHttpRequestMethod)httpRequestMethod |
| completionBlock:(void (^)(CMISHttpResponse *httpResponse, NSError *error))completionBlock |
| { |
| self = [super init]; |
| if (self) { |
| _requestMethod = httpRequestMethod; |
| _completionBlock = completionBlock; |
| } |
| return self; |
| } |
| |
| |
| - (BOOL)startRequest:(NSMutableURLRequest*)urlRequest |
| { |
| if (self.requestBody) { |
| if ([CMISLog sharedInstance].logLevel == CMISLogLevelTrace) { |
| CMISLogTrace(@"Request body: %@", [[NSString alloc] initWithData:self.requestBody encoding:NSUTF8StringEncoding]); |
| } |
| |
| [urlRequest setHTTPBody:self.requestBody]; |
| } |
| |
| [self.authenticationProvider.httpHeadersToApply enumerateKeysAndObjectsUsingBlock:^(NSString *headerName, NSString *header, BOOL *stop) { |
| [urlRequest addValue:header forHTTPHeaderField:headerName]; |
| }]; |
| |
| [self.additionalHeaders enumerateKeysAndObjectsUsingBlock:^(NSString *headerName, NSString *header, BOOL *stop) { |
| [urlRequest addValue:header forHTTPHeaderField:headerName]; |
| }]; |
| |
| self.connection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self startImmediately:NO]; |
| if (self.connection) { |
| [self.connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; |
| [self.connection start]; |
| return YES; |
| } else { |
| if (self.completionBlock) { |
| NSString *detailedDescription = [NSString stringWithFormat:@"Could not create connection to %@", urlRequest.URL]; |
| NSError *cmisError = [CMISErrors createCMISErrorWithCode:kCMISErrorCodeConnection detailedDescription:detailedDescription]; |
| self.completionBlock(nil, cmisError); |
| } |
| return NO; |
| } |
| } |
| |
| - (void)cancel |
| { |
| if (self.connection) { |
| void (^completionBlock)(CMISHttpResponse *httpResponse, NSError *error); |
| completionBlock = self.completionBlock; // remember completion block in order to invoke it after the connection was cancelled |
| |
| self.completionBlock = nil; // prevent potential NSURLConnection delegate callbacks to invoke the completion block redundantly |
| |
| [self.connection cancel]; |
| |
| self.connection = nil; |
| |
| NSError *cmisError = [CMISErrors createCMISErrorWithCode:kCMISErrorCodeCancelled detailedDescription:@"Request was cancelled"]; |
| completionBlock(nil, cmisError); |
| } |
| } |
| |
| |
| - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace |
| { |
| return [self.authenticationProvider canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace]; |
| } |
| |
| |
| - (void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge |
| { |
| [self.authenticationProvider didCancelAuthenticationChallenge:challenge]; |
| } |
| |
| |
| - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge |
| { |
| [self.authenticationProvider didReceiveAuthenticationChallenge:challenge]; |
| } |
| |
| |
| - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response |
| { |
| self.responseBody = [[NSMutableData alloc] init]; |
| if ([response isKindOfClass:NSHTTPURLResponse.class]) { |
| self.response = (NSHTTPURLResponse*)response; |
| } |
| } |
| |
| |
| - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data |
| { |
| [self.responseBody appendData:data]; |
| } |
| |
| |
| - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error |
| { |
| [self.authenticationProvider updateWithHttpURLResponse:self.response]; |
| |
| if (self.completionBlock) { |
| CMISErrorCodes cmisErrorCode = (error.code == NSURLErrorCancelled) ? kCMISErrorCodeCancelled : kCMISErrorCodeConnection; |
| NSError *cmisError = [CMISErrors cmisError:error cmisErrorCode:cmisErrorCode]; |
| self.completionBlock(nil, cmisError); |
| } |
| |
| self.completionBlock = nil; |
| self.connection = nil; |
| } |
| |
| |
| - (void)connectionDidFinishLoading:(NSURLConnection *)connection |
| { |
| [self.authenticationProvider updateWithHttpURLResponse:self.response]; |
| |
| if (self.completionBlock) { |
| NSError *cmisError = nil; |
| CMISHttpResponse *httpResponse = [CMISHttpResponse responseUsingURLHTTPResponse:self.response data:self.responseBody]; |
| if ([self checkStatusCodeForResponse:httpResponse httpRequestMethod:self.requestMethod error:&cmisError]) { |
| self.completionBlock(httpResponse, nil); |
| } else { |
| self.completionBlock(nil, cmisError); |
| } |
| } |
| |
| self.completionBlock = nil; |
| self.connection = nil; |
| } |
| |
| - (BOOL)checkStatusCodeForResponse:(CMISHttpResponse *)response httpRequestMethod:(CMISHttpRequestMethod)httpRequestMethod error:(NSError **)error |
| { |
| if ([CMISLog sharedInstance].logLevel == CMISLogLevelTrace) { |
| CMISLogTrace(@"Response status code: %d", response.statusCode); |
| CMISLogTrace(@"Response body: %@", [[NSString alloc] initWithData:response.data encoding:NSUTF8StringEncoding]); |
| } |
| |
| if ( (httpRequestMethod == HTTP_GET && response.statusCode != 200) |
| || (httpRequestMethod == HTTP_POST && response.statusCode != 201) |
| || (httpRequestMethod == HTTP_DELETE && response.statusCode != 204) |
| || (httpRequestMethod == HTTP_PUT && ((response.statusCode < 200 || response.statusCode > 299)))) { |
| if (error) { |
| NSString *exception = response.exception; |
| NSString *errorMessage = response.errorMessage; |
| if (errorMessage == nil) { |
| errorMessage = response.statusCodeMessage; // fall back to HTTP error message |
| } |
| |
| switch (response.statusCode) { |
| case 400: |
| if ([exception isEqualToString:kCMISExceptionFilterNotValid]) { |
| *error = [CMISErrors createCMISErrorWithCode:kCMISErrorCodeFilterNotValid |
| detailedDescription:errorMessage]; |
| } else { |
| *error = [CMISErrors createCMISErrorWithCode:kCMISErrorCodeInvalidArgument |
| detailedDescription:errorMessage]; |
| } |
| break; |
| case 401: |
| *error = [CMISErrors createCMISErrorWithCode:kCMISErrorCodeUnauthorized |
| detailedDescription:errorMessage]; |
| break; |
| case 403: |
| if ([exception isEqualToString:kCMISExceptionStreamNotSupported]) { |
| *error = [CMISErrors createCMISErrorWithCode:kCMISErrorCodeStreamNotSupported |
| detailedDescription:errorMessage]; |
| } else { |
| *error = [CMISErrors createCMISErrorWithCode:kCMISErrorCodePermissionDenied |
| detailedDescription:errorMessage]; |
| } |
| break; |
| case 404: |
| *error = [CMISErrors createCMISErrorWithCode:kCMISErrorCodeObjectNotFound |
| detailedDescription:errorMessage]; |
| break; |
| case 405: |
| *error = [CMISErrors createCMISErrorWithCode:kCMISErrorCodeNotSupported |
| detailedDescription:errorMessage]; |
| break; |
| case 407: |
| *error = [CMISErrors createCMISErrorWithCode:kCMISErrorCodeProxyAuthentication |
| detailedDescription:errorMessage]; |
| break; |
| case 409: |
| if ([exception isEqualToString:kCMISExceptionContentAlreadyExists]) { |
| *error = [CMISErrors createCMISErrorWithCode:kCMISErrorCodeContentAlreadyExists |
| detailedDescription:errorMessage]; |
| } else if ([exception isEqualToString:kCMISExceptionVersioning]) { |
| *error = [CMISErrors createCMISErrorWithCode:kCMISErrorCodeVersioning |
| detailedDescription:errorMessage]; |
| } else if ([exception isEqualToString:kCMISExceptionUpdateConflict]) { |
| *error = [CMISErrors createCMISErrorWithCode:kCMISErrorCodeUpdateConflict |
| detailedDescription:errorMessage]; |
| } else if ([exception isEqualToString:kCMISExceptionNameConstraintViolation]) { |
| *error = [CMISErrors createCMISErrorWithCode:kCMISErrorCodeNameConstraintViolation |
| detailedDescription:errorMessage]; |
| } else { |
| *error = [CMISErrors createCMISErrorWithCode:kCMISErrorCodeConstraint |
| detailedDescription:errorMessage]; |
| } |
| break; |
| default: |
| if ([exception isEqualToString:kCMISExceptionStorage]) { |
| *error = [CMISErrors createCMISErrorWithCode:kCMISErrorCodeStorage |
| detailedDescription:errorMessage]; |
| } else { |
| *error = [CMISErrors createCMISErrorWithCode:kCMISErrorCodeRuntime |
| detailedDescription:response.errorMessage]; |
| } |
| } |
| } |
| return NO; |
| } |
| return YES; |
| } |
| |
| @end |