WWWConnection.mm 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. #include "WWWConnection.h"
  2. // WARNING: this MUST be c decl (NSString ctor will be called after +load, so we cant really change its value)
  3. // If you need to communicate with HTTPS server with self signed certificate you might consider UnityWWWConnectionSelfSignedCertDelegate
  4. // Though use it on your own risk. Blindly accepting self signed certificate is prone to MITM attack
  5. //const char* WWWDelegateClassName = "UnityWWWConnectionSelfSignedCertDelegate";
  6. const char* WWWDelegateClassName = "UnityWWWConnectionDelegate";
  7. const char* WWWRequestProviderClassName = "UnityWWWRequestDefaultProvider";
  8. static NSOperationQueue *webOperationQueue;
  9. @interface UnityWWWConnectionDelegate ()
  10. @property (readwrite, nonatomic) void* udata;
  11. @property (readwrite, retain, nonatomic) NSURL* url;
  12. @property (readwrite, retain, nonatomic) NSString* user;
  13. @property (readwrite, retain, nonatomic) NSString* password;
  14. @property (readwrite, retain, atomic) NSMutableURLRequest* request;
  15. @property (readwrite, retain, atomic) NSURLConnection* connection;
  16. @property (nonatomic) BOOL manuallyHandleRedirect;
  17. @property (readwrite, retain, nonatomic) NSOutputStream* outputStream;
  18. @end
  19. @implementation UnityWWWConnectionDelegate
  20. {
  21. // link to unity WWW implementation
  22. void* _udata;
  23. // connection parameters
  24. NSMutableURLRequest* _request;
  25. // connection that we manage
  26. NSURLConnection* _connection;
  27. // NSURLConnection do not quite handle user:pass@host urls
  28. // so we need to extract user/pass ourselves
  29. NSURL* _url;
  30. NSString* _user;
  31. NSString* _password;
  32. // response
  33. NSInteger _status;
  34. size_t _estimatedLength;
  35. size_t _dataRecievd;
  36. int _retryCount;
  37. NSOutputStream* _outputStream;
  38. BOOL _connectionStarted;
  39. BOOL _connectionCancelled;
  40. }
  41. @synthesize url = _url;
  42. @synthesize user = _user;
  43. @synthesize password = _password;
  44. @synthesize request = _request;
  45. @synthesize connection = _connection;
  46. @synthesize udata = _udata;
  47. @synthesize outputStream = _outputStream;
  48. - (NSURL*)extractUserPassFromUrl:(NSURL*)url
  49. {
  50. self.user = url.user;
  51. self.password = url.password;
  52. // strip user/pass from url
  53. NSString* newUrl = [NSString stringWithFormat: @"%@://%@%s%s%@%s%s",
  54. url.scheme, url.host,
  55. url.port ? ":" : "", url.port ? [[url.port stringValue] UTF8String] : "",
  56. url.path,
  57. url.fragment ? "#" : "", url.fragment ? [url.fragment UTF8String] : ""
  58. ];
  59. return [NSURL URLWithString: newUrl];
  60. }
  61. - (id)initWithURL:(NSURL*)url udata:(void*)udata;
  62. {
  63. self->_retryCount = 0;
  64. if ((self = [super init]))
  65. {
  66. self.url = url.user != nil ? [self extractUserPassFromUrl: url] : url;
  67. self.udata = udata;
  68. if ([url.scheme caseInsensitiveCompare: @"http"] == NSOrderedSame)
  69. 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.");
  70. }
  71. return self;
  72. }
  73. + (id)newDelegateWithURL:(NSURL*)url udata:(void*)udata
  74. {
  75. Class target = NSClassFromString([NSString stringWithUTF8String: WWWDelegateClassName]);
  76. NSAssert([target isSubclassOfClass: [UnityWWWConnectionDelegate class]], @"You MUST subclass UnityWWWConnectionDelegate");
  77. return [[target alloc] initWithURL: url udata: udata];
  78. }
  79. + (id)newDelegateWithCStringURL:(const char*)url udata:(void*)udata
  80. {
  81. return [UnityWWWConnectionDelegate newDelegateWithURL: [NSURL URLWithString: [NSString stringWithUTF8String: url]] udata: udata];
  82. }
  83. + (NSMutableURLRequest*)newRequestForHTTPMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers
  84. {
  85. Class target = NSClassFromString([NSString stringWithUTF8String: WWWRequestProviderClassName]);
  86. NSAssert([target conformsToProtocol: @protocol(UnityWWWRequestProvider)], @"You MUST implement UnityWWWRequestProvider protocol");
  87. return [target allocRequestForHTTPMethod: method url: url headers: headers];
  88. }
  89. - (void)startConnection
  90. {
  91. if (!_connectionCancelled)
  92. [self.connection start];
  93. _connectionStarted = YES;
  94. }
  95. - (void)cancelConnection
  96. {
  97. if (_connectionStarted)
  98. [self.connection cancel];
  99. _connectionCancelled = YES;
  100. }
  101. - (void)abort
  102. {
  103. [self cancelConnection];
  104. }
  105. - (void)cleanup
  106. {
  107. [self cancelConnection];
  108. self.connection = nil;
  109. self.request = nil;
  110. }
  111. // NSURLConnection Delegate Methods
  112. - (NSURLRequest *)connection:(NSURLConnection *)connection
  113. willSendRequest:(NSURLRequest *)request
  114. redirectResponse:(NSURLResponse *)response;
  115. {
  116. if (response && self.manuallyHandleRedirect)
  117. {
  118. // notify TransportiPhone of the redirect and signal to process the next response.
  119. if ([response isKindOfClass: [NSHTTPURLResponse class]])
  120. {
  121. NSHTTPURLResponse *httpresponse = (NSHTTPURLResponse*)response;
  122. NSMutableDictionary *headers = [httpresponse.allHeaderFields mutableCopy];
  123. // grab the correct URL from the request that would have
  124. // automatically been called through NSURLConnection.
  125. // The reason we do this is that WebRequestProto's state needs to
  126. // get updated internally, so we intercept redirects, cancel the current
  127. // NSURLConnection, notify WebRequestProto and let it construct a new
  128. // request from the updated URL
  129. [headers setObject: [request.URL absoluteString] forKey: @"Location"];
  130. httpresponse = [[NSHTTPURLResponse alloc] initWithURL: response.URL statusCode: httpresponse.statusCode HTTPVersion: nil headerFields: headers];
  131. [self handleResponse: httpresponse];
  132. }
  133. else
  134. {
  135. [self handleResponse: response];
  136. }
  137. [self cancelConnection];
  138. return nil;
  139. }
  140. return request;
  141. }
  142. - (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response
  143. {
  144. [self handleResponse: response];
  145. }
  146. - (void)handleResponse:(NSURLResponse*)response
  147. {
  148. NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
  149. NSDictionary* respHeader = [httpResponse allHeaderFields];
  150. NSEnumerator* headerEnum = [respHeader keyEnumerator];
  151. self->_status = [httpResponse statusCode];
  152. UnityReportWWWStatus(self.udata, (int)self->_status);
  153. for (id headerKey = [headerEnum nextObject]; headerKey; headerKey = [headerEnum nextObject])
  154. UnityReportWWWResponseHeader(self.udata, [headerKey UTF8String], [[respHeader objectForKey: headerKey] UTF8String]);
  155. long long contentLength = [response expectedContentLength];
  156. // ignore any data that we might have recieved during a redirect
  157. self->_estimatedLength = contentLength > 0 && (self->_status / 100 != 3) ? contentLength : 0;
  158. self->_dataRecievd = 0;
  159. UnityReportWWWReceivedResponse(self.udata, (unsigned int)self->_estimatedLength);
  160. }
  161. - (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
  162. {
  163. UnityReportWWWReceivedData(self.udata, data.bytes, (unsigned int)[data length], (unsigned int)self->_estimatedLength);
  164. }
  165. - (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error
  166. {
  167. UnityReportWWWNetworkError(self.udata, (int)[error code]);
  168. UnityReportWWWFinishedLoadingData(self.udata);
  169. }
  170. - (void)connectionDidFinishLoading:(NSURLConnection*)connection
  171. {
  172. UnityReportWWWFinishedLoadingData(self.udata);
  173. }
  174. - (void)connection:(NSURLConnection*)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
  175. {
  176. UnityReportWWWSentData(self.udata, (unsigned int)totalBytesWritten, (unsigned int)totalBytesExpectedToWrite);
  177. if (_outputStream != nil)
  178. {
  179. unsigned dataSize;
  180. const UInt8* bytes = (const UInt8*)UnityWWWGetUploadData(_udata, &dataSize);
  181. unsigned transmitted = [_outputStream write: bytes maxLength: dataSize];
  182. UnityWWWConsumeUploadData(_udata, transmitted);
  183. if (transmitted >= dataSize)
  184. {
  185. [_outputStream close];
  186. _outputStream = nil;
  187. }
  188. }
  189. }
  190. - (BOOL)connection:(NSURLConnection*)connection handleAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge
  191. {
  192. return NO;
  193. }
  194. - (void)connection:(NSURLConnection*)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge
  195. {
  196. if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodServerTrust)
  197. {
  198. [challenge.sender performDefaultHandlingForAuthenticationChallenge: challenge];
  199. }
  200. else
  201. {
  202. BOOL authHandled = [self connection: connection handleAuthenticationChallenge: challenge];
  203. if (authHandled == NO)
  204. {
  205. self->_retryCount++;
  206. // Empty user or password
  207. if (self->_retryCount > 1 || self.user == nil || [self.user length] == 0 || self.password == nil || [self.password length] == 0)
  208. {
  209. [[challenge sender] cancelAuthenticationChallenge: challenge];
  210. return;
  211. }
  212. NSURLCredential* newCredential =
  213. [NSURLCredential credentialWithUser: self.user password: self.password persistence: NSURLCredentialPersistenceNone];
  214. [challenge.sender useCredential: newCredential forAuthenticationChallenge: challenge];
  215. }
  216. }
  217. }
  218. @end
  219. @implementation UnityWWWConnectionSelfSignedCertDelegate
  220. - (BOOL)connection:(NSURLConnection*)connection handleAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge
  221. {
  222. if ([[challenge.protectionSpace authenticationMethod] isEqualToString: @"NSURLAuthenticationMethodServerTrust"])
  223. {
  224. [challenge.sender useCredential: [NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust]
  225. forAuthenticationChallenge: challenge];
  226. return YES;
  227. }
  228. return [super connection: connection handleAuthenticationChallenge: challenge];
  229. }
  230. @end
  231. @implementation UnityWWWRequestDefaultProvider
  232. + (NSMutableURLRequest*)allocRequestForHTTPMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers
  233. {
  234. NSMutableURLRequest* request = [[NSMutableURLRequest alloc] init];
  235. [request setURL: url];
  236. [request setHTTPMethod: method];
  237. [request setAllHTTPHeaderFields: headers];
  238. [request setCachePolicy: NSURLRequestReloadIgnoringLocalCacheData];
  239. return request;
  240. }
  241. @end
  242. //
  243. // unity interface
  244. //
  245. extern "C" void UnitySendWWWConnection(void* connection, const void* data, unsigned length, bool blockImmediately, unsigned long timeoutSec)
  246. {
  247. UnityWWWConnectionDelegate* delegate = (__bridge UnityWWWConnectionDelegate*)connection;
  248. NSMutableURLRequest* request = delegate.request;
  249. if (length > 0)
  250. {
  251. if (data != nil)
  252. {
  253. [request setHTTPBody: [NSData dataWithBytes: data length: length]];
  254. [request setValue: [NSString stringWithFormat: @"%d", length] forHTTPHeaderField: @"Content-Length"];
  255. }
  256. else
  257. {
  258. CFReadStreamRef readStream;
  259. CFWriteStreamRef writeStream;
  260. CFStreamCreateBoundPair(kCFAllocatorDefault, &readStream, &writeStream, 1024);
  261. [request setHTTPBodyStream: (__bridge NSInputStream*)readStream];
  262. [request setValue: @"chunked" forHTTPHeaderField: @"Transfer-Encoding"];
  263. CFWriteStreamOpen(writeStream);
  264. unsigned dataSize;
  265. const void* bytes = UnityWWWGetUploadData(delegate.udata, &dataSize);
  266. unsigned transmitted = CFWriteStreamWrite(writeStream, (UInt8*)bytes, dataSize);
  267. UnityWWWConsumeUploadData(delegate.udata, transmitted);
  268. if (transmitted >= dataSize)
  269. CFWriteStreamClose(writeStream);
  270. else
  271. delegate.outputStream = (__bridge NSOutputStream*)writeStream;
  272. }
  273. }
  274. [request setTimeoutInterval: timeoutSec];
  275. static dispatch_once_t onceToken;
  276. dispatch_once(&onceToken, ^{
  277. webOperationQueue = [[NSOperationQueue alloc] init];
  278. webOperationQueue.maxConcurrentOperationCount = [NSProcessInfo processInfo].activeProcessorCount * 5;
  279. webOperationQueue.name = @"com.unity3d.WebOperationQueue";
  280. });
  281. delegate.connection = [[NSURLConnection alloc] initWithRequest: request delegate: delegate startImmediately: NO];
  282. delegate.manuallyHandleRedirect = YES;
  283. [delegate.connection setDelegateQueue: webOperationQueue];
  284. [delegate startConnection];
  285. }
  286. extern "C" void* UnityStartWWWConnectionCustom(void* udata, const char* methodString, const void* headerDict, const char* url)
  287. {
  288. UnityWWWConnectionDelegate* delegate = [UnityWWWConnectionDelegate newDelegateWithCStringURL: url udata: udata];
  289. delegate.request = [UnityWWWConnectionDelegate newRequestForHTTPMethod: [NSString stringWithUTF8String: methodString] url: delegate.url headers: (__bridge NSDictionary*)headerDict];
  290. return (__bridge_retained void*)delegate;
  291. }
  292. extern "C" bool UnityBlockWWWConnectionIsDone(void* connection)
  293. {
  294. UnityWWWConnectionDelegate* delegate = (__bridge UnityWWWConnectionDelegate*)connection;
  295. return (delegate.request == nil);
  296. }
  297. extern "C" void UnityDestroyWWWConnection(void* connection)
  298. {
  299. UnityWWWConnectionDelegate* delegate = (__bridge_transfer UnityWWWConnectionDelegate*)connection;
  300. [delegate cleanup];
  301. delegate = nil;
  302. }
  303. extern "C" void UnityShouldCancelWWW(const void* connection)
  304. {
  305. UnityWWWConnectionDelegate* delegate = (__bridge UnityWWWConnectionDelegate*)connection;
  306. [delegate cancelConnection];
  307. }