123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376 |
- #include "WWWConnection.h"
- // WARNING: this MUST be c decl (NSString ctor will be called after +load, so we cant really change its value)
- // If you need to communicate with HTTPS server with self signed certificate you might consider UnityWWWConnectionSelfSignedCertDelegate
- // Though use it on your own risk. Blindly accepting self signed certificate is prone to MITM attack
- //const char* WWWDelegateClassName = "UnityWWWConnectionSelfSignedCertDelegate";
- const char* WWWDelegateClassName = "UnityWWWConnectionDelegate";
- const char* WWWRequestProviderClassName = "UnityWWWRequestDefaultProvider";
- static NSOperationQueue *webOperationQueue;
- @interface UnityWWWConnectionDelegate ()
- @property (readwrite, nonatomic) void* udata;
- @property (readwrite, retain, nonatomic) NSURL* url;
- @property (readwrite, retain, nonatomic) NSString* user;
- @property (readwrite, retain, nonatomic) NSString* password;
- @property (readwrite, retain, atomic) NSMutableURLRequest* request;
- @property (readwrite, retain, atomic) NSURLConnection* connection;
- @property (nonatomic) BOOL manuallyHandleRedirect;
- @property (readwrite, retain, nonatomic) NSOutputStream* outputStream;
- @end
- @implementation UnityWWWConnectionDelegate
- {
- // link to unity WWW implementation
- void* _udata;
- // connection parameters
- NSMutableURLRequest* _request;
- // connection that we manage
- NSURLConnection* _connection;
- // NSURLConnection do not quite handle user:pass@host urls
- // so we need to extract user/pass ourselves
- NSURL* _url;
- NSString* _user;
- NSString* _password;
- // response
- NSInteger _status;
- size_t _estimatedLength;
- size_t _dataRecievd;
- int _retryCount;
- NSOutputStream* _outputStream;
- BOOL _connectionStarted;
- BOOL _connectionCancelled;
- }
- @synthesize url = _url;
- @synthesize user = _user;
- @synthesize password = _password;
- @synthesize request = _request;
- @synthesize connection = _connection;
- @synthesize udata = _udata;
- @synthesize outputStream = _outputStream;
- - (NSURL*)extractUserPassFromUrl:(NSURL*)url
- {
- self.user = url.user;
- self.password = url.password;
- // strip user/pass from url
- NSString* newUrl = [NSString stringWithFormat: @"%@://%@%s%s%@%s%s",
- url.scheme, url.host,
- url.port ? ":" : "", url.port ? [[url.port stringValue] UTF8String] : "",
- url.path,
- url.fragment ? "#" : "", url.fragment ? [url.fragment UTF8String] : ""
- ];
- return [NSURL URLWithString: newUrl];
- }
- - (id)initWithURL:(NSURL*)url udata:(void*)udata;
- {
- self->_retryCount = 0;
- if ((self = [super init]))
- {
- self.url = url.user != nil ? [self extractUserPassFromUrl: url] : url;
- self.udata = udata;
- if ([url.scheme caseInsensitiveCompare: @"http"] == NSOrderedSame)
- NSLog(@"You are using download over http. Currently Unity adds NSAllowsArbitraryLoads to Info.plist to simplify transition, but it will be removed soon. Please consider updating to https.");
- }
- return self;
- }
- + (id)newDelegateWithURL:(NSURL*)url udata:(void*)udata
- {
- Class target = NSClassFromString([NSString stringWithUTF8String: WWWDelegateClassName]);
- NSAssert([target isSubclassOfClass: [UnityWWWConnectionDelegate class]], @"You MUST subclass UnityWWWConnectionDelegate");
- return [[target alloc] initWithURL: url udata: udata];
- }
- + (id)newDelegateWithCStringURL:(const char*)url udata:(void*)udata
- {
- return [UnityWWWConnectionDelegate newDelegateWithURL: [NSURL URLWithString: [NSString stringWithUTF8String: url]] udata: udata];
- }
- + (NSMutableURLRequest*)newRequestForHTTPMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers
- {
- Class target = NSClassFromString([NSString stringWithUTF8String: WWWRequestProviderClassName]);
- NSAssert([target conformsToProtocol: @protocol(UnityWWWRequestProvider)], @"You MUST implement UnityWWWRequestProvider protocol");
- return [target allocRequestForHTTPMethod: method url: url headers: headers];
- }
- - (void)startConnection
- {
- if (!_connectionCancelled)
- [self.connection start];
- _connectionStarted = YES;
- }
- - (void)cancelConnection
- {
- if (_connectionStarted)
- [self.connection cancel];
- _connectionCancelled = YES;
- }
- - (void)abort
- {
- [self cancelConnection];
- }
- - (void)cleanup
- {
- [self cancelConnection];
- self.connection = nil;
- self.request = nil;
- }
- // NSURLConnection Delegate Methods
- - (NSURLRequest *)connection:(NSURLConnection *)connection
- willSendRequest:(NSURLRequest *)request
- redirectResponse:(NSURLResponse *)response;
- {
- if (response && self.manuallyHandleRedirect)
- {
- // notify TransportiPhone of the redirect and signal to process the next response.
- if ([response isKindOfClass: [NSHTTPURLResponse class]])
- {
- NSHTTPURLResponse *httpresponse = (NSHTTPURLResponse*)response;
- NSMutableDictionary *headers = [httpresponse.allHeaderFields mutableCopy];
- // grab the correct URL from the request that would have
- // automatically been called through NSURLConnection.
- // The reason we do this is that WebRequestProto's state needs to
- // get updated internally, so we intercept redirects, cancel the current
- // NSURLConnection, notify WebRequestProto and let it construct a new
- // request from the updated URL
- [headers setObject: [request.URL absoluteString] forKey: @"Location"];
- httpresponse = [[NSHTTPURLResponse alloc] initWithURL: response.URL statusCode: httpresponse.statusCode HTTPVersion: nil headerFields: headers];
- [self handleResponse: httpresponse];
- }
- else
- {
- [self handleResponse: response];
- }
- [self cancelConnection];
- return nil;
- }
- return request;
- }
- - (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response
- {
- [self handleResponse: response];
- }
- - (void)handleResponse:(NSURLResponse*)response
- {
- NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
- NSDictionary* respHeader = [httpResponse allHeaderFields];
- NSEnumerator* headerEnum = [respHeader keyEnumerator];
- self->_status = [httpResponse statusCode];
- UnityReportWWWStatus(self.udata, (int)self->_status);
- for (id headerKey = [headerEnum nextObject]; headerKey; headerKey = [headerEnum nextObject])
- UnityReportWWWResponseHeader(self.udata, [headerKey UTF8String], [[respHeader objectForKey: headerKey] UTF8String]);
- long long contentLength = [response expectedContentLength];
- // ignore any data that we might have recieved during a redirect
- self->_estimatedLength = contentLength > 0 && (self->_status / 100 != 3) ? contentLength : 0;
- self->_dataRecievd = 0;
- UnityReportWWWReceivedResponse(self.udata, (unsigned int)self->_estimatedLength);
- }
- - (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
- {
- UnityReportWWWReceivedData(self.udata, data.bytes, (unsigned int)[data length], (unsigned int)self->_estimatedLength);
- }
- - (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error
- {
- UnityReportWWWNetworkError(self.udata, (int)[error code]);
- UnityReportWWWFinishedLoadingData(self.udata);
- }
- - (void)connectionDidFinishLoading:(NSURLConnection*)connection
- {
- UnityReportWWWFinishedLoadingData(self.udata);
- }
- - (void)connection:(NSURLConnection*)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
- {
- UnityReportWWWSentData(self.udata, (unsigned int)totalBytesWritten, (unsigned int)totalBytesExpectedToWrite);
- if (_outputStream != nil)
- {
- unsigned dataSize;
- const UInt8* bytes = (const UInt8*)UnityWWWGetUploadData(_udata, &dataSize);
- unsigned transmitted = [_outputStream write: bytes maxLength: dataSize];
- UnityWWWConsumeUploadData(_udata, transmitted);
- if (transmitted >= dataSize)
- {
- [_outputStream close];
- _outputStream = nil;
- }
- }
- }
- - (BOOL)connection:(NSURLConnection*)connection handleAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge
- {
- return NO;
- }
- - (void)connection:(NSURLConnection*)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge
- {
- if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodServerTrust)
- {
- [challenge.sender performDefaultHandlingForAuthenticationChallenge: challenge];
- }
- else
- {
- BOOL authHandled = [self connection: connection handleAuthenticationChallenge: challenge];
- if (authHandled == NO)
- {
- self->_retryCount++;
- // Empty user or password
- if (self->_retryCount > 1 || self.user == nil || [self.user length] == 0 || self.password == nil || [self.password length] == 0)
- {
- [[challenge sender] cancelAuthenticationChallenge: challenge];
- return;
- }
- NSURLCredential* newCredential =
- [NSURLCredential credentialWithUser: self.user password: self.password persistence: NSURLCredentialPersistenceNone];
- [challenge.sender useCredential: newCredential forAuthenticationChallenge: challenge];
- }
- }
- }
- @end
- @implementation UnityWWWConnectionSelfSignedCertDelegate
- - (BOOL)connection:(NSURLConnection*)connection handleAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge
- {
- if ([[challenge.protectionSpace authenticationMethod] isEqualToString: @"NSURLAuthenticationMethodServerTrust"])
- {
- [challenge.sender useCredential: [NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust]
- forAuthenticationChallenge: challenge];
- return YES;
- }
- return [super connection: connection handleAuthenticationChallenge: challenge];
- }
- @end
- @implementation UnityWWWRequestDefaultProvider
- + (NSMutableURLRequest*)allocRequestForHTTPMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers
- {
- NSMutableURLRequest* request = [[NSMutableURLRequest alloc] init];
- [request setURL: url];
- [request setHTTPMethod: method];
- [request setAllHTTPHeaderFields: headers];
- [request setCachePolicy: NSURLRequestReloadIgnoringLocalCacheData];
- return request;
- }
- @end
- //
- // unity interface
- //
- extern "C" void UnitySendWWWConnection(void* connection, const void* data, unsigned length, bool blockImmediately, unsigned long timeoutSec)
- {
- UnityWWWConnectionDelegate* delegate = (__bridge UnityWWWConnectionDelegate*)connection;
- NSMutableURLRequest* request = delegate.request;
- if (length > 0)
- {
- if (data != nil)
- {
- [request setHTTPBody: [NSData dataWithBytes: data length: length]];
- [request setValue: [NSString stringWithFormat: @"%d", length] forHTTPHeaderField: @"Content-Length"];
- }
- else
- {
- CFReadStreamRef readStream;
- CFWriteStreamRef writeStream;
- CFStreamCreateBoundPair(kCFAllocatorDefault, &readStream, &writeStream, 1024);
- [request setHTTPBodyStream: (__bridge NSInputStream*)readStream];
- [request setValue: @"chunked" forHTTPHeaderField: @"Transfer-Encoding"];
- CFWriteStreamOpen(writeStream);
- unsigned dataSize;
- const void* bytes = UnityWWWGetUploadData(delegate.udata, &dataSize);
- unsigned transmitted = CFWriteStreamWrite(writeStream, (UInt8*)bytes, dataSize);
- UnityWWWConsumeUploadData(delegate.udata, transmitted);
- if (transmitted >= dataSize)
- CFWriteStreamClose(writeStream);
- else
- delegate.outputStream = (__bridge NSOutputStream*)writeStream;
- }
- }
- [request setTimeoutInterval: timeoutSec];
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- webOperationQueue = [[NSOperationQueue alloc] init];
- webOperationQueue.maxConcurrentOperationCount = [NSProcessInfo processInfo].activeProcessorCount * 5;
- webOperationQueue.name = @"com.unity3d.WebOperationQueue";
- });
- delegate.connection = [[NSURLConnection alloc] initWithRequest: request delegate: delegate startImmediately: NO];
- delegate.manuallyHandleRedirect = YES;
- [delegate.connection setDelegateQueue: webOperationQueue];
- [delegate startConnection];
- }
- extern "C" void* UnityStartWWWConnectionCustom(void* udata, const char* methodString, const void* headerDict, const char* url)
- {
- UnityWWWConnectionDelegate* delegate = [UnityWWWConnectionDelegate newDelegateWithCStringURL: url udata: udata];
- delegate.request = [UnityWWWConnectionDelegate newRequestForHTTPMethod: [NSString stringWithUTF8String: methodString] url: delegate.url headers: (__bridge NSDictionary*)headerDict];
- return (__bridge_retained void*)delegate;
- }
- extern "C" bool UnityBlockWWWConnectionIsDone(void* connection)
- {
- UnityWWWConnectionDelegate* delegate = (__bridge UnityWWWConnectionDelegate*)connection;
- return (delegate.request == nil);
- }
- extern "C" void UnityDestroyWWWConnection(void* connection)
- {
- UnityWWWConnectionDelegate* delegate = (__bridge_transfer UnityWWWConnectionDelegate*)connection;
- [delegate cleanup];
- delegate = nil;
- }
- extern "C" void UnityShouldCancelWWW(const void* connection)
- {
- UnityWWWConnectionDelegate* delegate = (__bridge UnityWWWConnectionDelegate*)connection;
- [delegate cancelConnection];
- }
|