WWWConnection.mm 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454
  1. #include "WWWConnection.h"
  2. #if 0 // old UnityWebRequest backend
  3. // WARNING: this MUST be c decl (NSString ctor will be called after +load, so we cant really change its value)
  4. // If you need to communicate with HTTPS server with self signed certificate you might consider UnityWWWConnectionSelfSignedCertDelegate
  5. // Though use it on your own risk. Blindly accepting self signed certificate is prone to MITM attack
  6. //const char* WWWDelegateClassName = "UnityWWWConnectionSelfSignedCertDelegate";
  7. const char* WWWDelegateClassName = "UnityWWWConnectionDelegate";
  8. const char* WWWRequestProviderClassName = "UnityWWWRequestDefaultProvider";
  9. const CFIndex streamSize = 1024;
  10. static NSOperationQueue *webOperationQueue;
  11. @interface UnityWWWConnectionDelegate ()
  12. @property (readwrite, nonatomic) void* udata;
  13. @property (readwrite, retain, nonatomic) NSURL* url;
  14. @property (readwrite, retain, nonatomic) NSString* user;
  15. @property (readwrite, retain, nonatomic) NSString* password;
  16. @property (readwrite, retain, atomic) NSMutableURLRequest* request;
  17. @property (readwrite, retain, atomic) NSURLConnection* connection;
  18. @property (nonatomic) BOOL manuallyHandleRedirect;
  19. @property (nonatomic) BOOL wantCertificateCallback;
  20. @property (readwrite, retain, nonatomic) NSOutputStream* outputStream;
  21. @end
  22. @implementation UnityWWWConnectionDelegate
  23. {
  24. // link to unity WWW implementation
  25. void* _udata;
  26. // connection parameters
  27. NSMutableURLRequest* _request;
  28. // connection that we manage
  29. NSURLConnection* _connection;
  30. // NSURLConnection do not quite handle user:pass@host urls
  31. // so we need to extract user/pass ourselves
  32. NSURL* _url;
  33. NSString* _user;
  34. NSString* _password;
  35. // response
  36. NSInteger _status;
  37. size_t _estimatedLength;
  38. size_t _dataRecievd;
  39. int _retryCount;
  40. NSOutputStream* _outputStream;
  41. BOOL _connectionStarted;
  42. BOOL _connectionCancelled;
  43. }
  44. @synthesize url = _url;
  45. @synthesize user = _user;
  46. @synthesize password = _password;
  47. @synthesize request = _request;
  48. @synthesize connection = _connection;
  49. @synthesize udata = _udata;
  50. @synthesize outputStream = _outputStream;
  51. - (NSURL*)extractUserPassFromUrl:(NSURL*)url
  52. {
  53. self.user = url.user;
  54. self.password = url.password;
  55. // strip user/pass from url
  56. NSString* newUrl = [NSString stringWithFormat: @"%@://%@%s%s%@%s%s",
  57. url.scheme, url.host,
  58. url.port ? ":" : "", url.port ? [[url.port stringValue] UTF8String] : "",
  59. url.path,
  60. url.fragment ? "#" : "", url.fragment ? [url.fragment UTF8String] : ""
  61. ];
  62. return [NSURL URLWithString: newUrl];
  63. }
  64. - (id)initWithURL:(NSURL*)url udata:(void*)udata;
  65. {
  66. self->_retryCount = 0;
  67. if ((self = [super init]))
  68. {
  69. self.url = url.user != nil ? [self extractUserPassFromUrl: url] : url;
  70. self.udata = udata;
  71. if ([url.scheme caseInsensitiveCompare: @"http"] == NSOrderedSame)
  72. 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.");
  73. }
  74. return self;
  75. }
  76. + (id)newDelegateWithURL:(NSURL*)url udata:(void*)udata
  77. {
  78. Class target = NSClassFromString([NSString stringWithUTF8String: WWWDelegateClassName]);
  79. NSAssert([target isSubclassOfClass: [UnityWWWConnectionDelegate class]], @"You MUST subclass UnityWWWConnectionDelegate");
  80. return [[target alloc] initWithURL: url udata: udata];
  81. }
  82. + (id)newDelegateWithCStringURL:(const char*)url udata:(void*)udata
  83. {
  84. return [UnityWWWConnectionDelegate newDelegateWithURL: [NSURL URLWithString: [NSString stringWithUTF8String: url]] udata: udata];
  85. }
  86. + (NSMutableURLRequest*)newRequestForHTTPMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers
  87. {
  88. Class target = NSClassFromString([NSString stringWithUTF8String: WWWRequestProviderClassName]);
  89. NSAssert([target conformsToProtocol: @protocol(UnityWWWRequestProvider)], @"You MUST implement UnityWWWRequestProvider protocol");
  90. return [target allocRequestForHTTPMethod: method url: url headers: headers];
  91. }
  92. - (void)startConnection
  93. {
  94. if (!_connectionCancelled)
  95. [self.connection start];
  96. _connectionStarted = YES;
  97. }
  98. - (void)cancelConnection
  99. {
  100. if (_connectionStarted)
  101. [self.connection cancel];
  102. _connectionCancelled = YES;
  103. }
  104. - (void)abort
  105. {
  106. [self cancelConnection];
  107. }
  108. - (void)cleanup
  109. {
  110. [self cancelConnection];
  111. self.connection = nil;
  112. self.request = nil;
  113. }
  114. // NSURLConnection Delegate Methods
  115. - (NSURLRequest *)connection:(NSURLConnection *)connection
  116. willSendRequest:(NSURLRequest *)request
  117. redirectResponse:(NSURLResponse *)response;
  118. {
  119. if (response && self.manuallyHandleRedirect)
  120. {
  121. // notify TransportiPhone of the redirect and signal to process the next response.
  122. if ([response isKindOfClass: [NSHTTPURLResponse class]])
  123. {
  124. NSHTTPURLResponse *httpresponse = (NSHTTPURLResponse*)response;
  125. NSMutableDictionary *headers = [httpresponse.allHeaderFields mutableCopy];
  126. // grab the correct URL from the request that would have
  127. // automatically been called through NSURLConnection.
  128. // The reason we do this is that WebRequestProto's state needs to
  129. // get updated internally, so we intercept redirects, cancel the current
  130. // NSURLConnection, notify WebRequestProto and let it construct a new
  131. // request from the updated URL
  132. [headers setObject: [request.URL absoluteString] forKey: @"Location"];
  133. httpresponse = [[NSHTTPURLResponse alloc] initWithURL: response.URL statusCode: httpresponse.statusCode HTTPVersion: nil headerFields: headers];
  134. [self handleResponse: httpresponse];
  135. }
  136. else
  137. {
  138. [self handleResponse: response];
  139. }
  140. [self cancelConnection];
  141. return nil;
  142. }
  143. return request;
  144. }
  145. - (void)connection:(NSURLConnection*)connection didReceiveResponse:(NSURLResponse*)response
  146. {
  147. [self handleResponse: response];
  148. }
  149. - (void)handleResponse:(NSURLResponse*)response
  150. {
  151. NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
  152. NSDictionary* respHeader = [httpResponse allHeaderFields];
  153. NSEnumerator* headerEnum = [respHeader keyEnumerator];
  154. self->_status = [httpResponse statusCode];
  155. UnityReportWebRequestStatus(self.udata, (int)self->_status);
  156. for (id headerKey = [headerEnum nextObject]; headerKey; headerKey = [headerEnum nextObject])
  157. UnityReportWebRequestResponseHeader(self.udata, [headerKey UTF8String], [[respHeader objectForKey: headerKey] UTF8String]);
  158. long long contentLength = [response expectedContentLength];
  159. // ignore any data that we might have recieved during a redirect
  160. self->_estimatedLength = contentLength > 0 && (self->_status / 100 != 3) ? contentLength : 0;
  161. self->_dataRecievd = 0;
  162. UnityReportWebRequestReceivedResponse(self.udata, (unsigned int)self->_estimatedLength);
  163. }
  164. - (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
  165. {
  166. UnityReportWebRequestReceivedData(self.udata, data.bytes, (unsigned int)[data length], (unsigned int)self->_estimatedLength);
  167. }
  168. - (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error
  169. {
  170. UnityReportWebRequestNetworkError(self.udata, (int)[error code]);
  171. UnityReportWebRequestFinishedLoadingData(self.udata);
  172. }
  173. - (void)connectionDidFinishLoading:(NSURLConnection*)connection
  174. {
  175. UnityReportWebRequestFinishedLoadingData(self.udata);
  176. }
  177. - (void)connection:(NSURLConnection*)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
  178. {
  179. UnityReportWebRequestSentData(self.udata, (unsigned int)totalBytesWritten, (unsigned int)totalBytesExpectedToWrite);
  180. if (_outputStream != nil)
  181. {
  182. unsigned dataSize = streamSize;
  183. unsigned transmitted = 0;
  184. const UInt8* bytes = (const UInt8*)UnityWebRequestGetUploadData(_udata, &dataSize);
  185. if (dataSize > 0)
  186. {
  187. transmitted = [_outputStream write: bytes maxLength: dataSize];
  188. UnityWebRequestConsumeUploadData(_udata, transmitted);
  189. }
  190. if (dataSize < streamSize && transmitted >= dataSize)
  191. {
  192. [_outputStream close];
  193. _outputStream = nil;
  194. }
  195. }
  196. }
  197. - (BOOL)connection:(NSURLConnection*)connection handleAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge
  198. {
  199. return NO;
  200. }
  201. - (void)connection:(NSURLConnection*)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge
  202. {
  203. if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodServerTrust)
  204. {
  205. if (!self.wantCertificateCallback)
  206. {
  207. [challenge.sender performDefaultHandlingForAuthenticationChallenge: challenge];
  208. return;
  209. }
  210. #if !defined(DISABLE_WEBREQUEST_CERTIFICATE_CALLBACK)
  211. SecTrustResultType systemResult;
  212. SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
  213. if (serverTrust == nil || errSecSuccess != SecTrustEvaluate(serverTrust, &systemResult))
  214. {
  215. systemResult = kSecTrustResultOtherError;
  216. }
  217. switch (systemResult)
  218. {
  219. case kSecTrustResultUnspecified:
  220. case kSecTrustResultProceed:
  221. case kSecTrustResultRecoverableTrustFailure:
  222. break;
  223. default:
  224. [challenge.sender performDefaultHandlingForAuthenticationChallenge: challenge];
  225. return;
  226. }
  227. SecCertificateRef serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0);
  228. if (serverCertificate != nil)
  229. {
  230. CFDataRef serverCertificateData = SecCertificateCopyData(serverCertificate);
  231. const UInt8* const data = CFDataGetBytePtr(serverCertificateData);
  232. const CFIndex size = CFDataGetLength(serverCertificateData);
  233. bool trust = UnityReportWebRequestValidateCertificate(self.udata, (const char*)data, (unsigned)size);
  234. CFRelease(serverCertificateData);
  235. if (trust)
  236. {
  237. NSURLCredential *credential = [NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust];
  238. [challenge.sender useCredential: credential forAuthenticationChallenge: challenge];
  239. return;
  240. }
  241. }
  242. #endif
  243. [challenge.sender cancelAuthenticationChallenge: challenge];
  244. return;
  245. }
  246. else
  247. {
  248. BOOL authHandled = [self connection: connection handleAuthenticationChallenge: challenge];
  249. if (authHandled == NO)
  250. {
  251. self->_retryCount++;
  252. // Empty user or password
  253. if (self->_retryCount > 1 || self.user == nil || [self.user length] == 0 || self.password == nil || [self.password length] == 0)
  254. {
  255. [[challenge sender] cancelAuthenticationChallenge: challenge];
  256. return;
  257. }
  258. NSURLCredential* newCredential =
  259. [NSURLCredential credentialWithUser: self.user password: self.password persistence: NSURLCredentialPersistenceNone];
  260. [challenge.sender useCredential: newCredential forAuthenticationChallenge: challenge];
  261. }
  262. }
  263. }
  264. @end
  265. @implementation UnityWWWConnectionSelfSignedCertDelegate
  266. - (BOOL)connection:(NSURLConnection*)connection handleAuthenticationChallenge:(NSURLAuthenticationChallenge*)challenge
  267. {
  268. if ([[challenge.protectionSpace authenticationMethod] isEqualToString: @"NSURLAuthenticationMethodServerTrust"])
  269. {
  270. [challenge.sender useCredential: [NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust]
  271. forAuthenticationChallenge: challenge];
  272. return YES;
  273. }
  274. return [super connection: connection handleAuthenticationChallenge: challenge];
  275. }
  276. @end
  277. @implementation UnityWWWRequestDefaultProvider
  278. + (NSMutableURLRequest*)allocRequestForHTTPMethod:(NSString*)method url:(NSURL*)url headers:(NSDictionary*)headers
  279. {
  280. NSMutableURLRequest* request = [[NSMutableURLRequest alloc] init];
  281. [request setURL: url];
  282. [request setHTTPMethod: method];
  283. [request setAllHTTPHeaderFields: headers];
  284. [request setCachePolicy: NSURLRequestReloadIgnoringLocalCacheData];
  285. return request;
  286. }
  287. @end
  288. //
  289. // unity interface
  290. //
  291. extern "C" void UnitySendWebRequest(void* connection, unsigned length, unsigned long timeoutSec, bool wantCertificateCallback)
  292. {
  293. UnityWWWConnectionDelegate* delegate = (__bridge UnityWWWConnectionDelegate*)connection;
  294. NSMutableURLRequest* request = delegate.request;
  295. if (length > 0)
  296. {
  297. unsigned dataSize = streamSize;
  298. const void* bytes = UnityWebRequestGetUploadData(delegate.udata, &dataSize);
  299. if (dataSize > 0)
  300. {
  301. CFReadStreamRef readStream;
  302. CFWriteStreamRef writeStream;
  303. CFStreamCreateBoundPair(kCFAllocatorDefault, &readStream, &writeStream, streamSize);
  304. [request setHTTPBodyStream: (__bridge NSInputStream*)readStream];
  305. CFWriteStreamOpen(writeStream);
  306. unsigned transmitted = CFWriteStreamWrite(writeStream, (UInt8*)bytes, dataSize);
  307. UnityWebRequestConsumeUploadData(delegate.udata, transmitted);
  308. if (dataSize < streamSize && transmitted >= dataSize)
  309. CFWriteStreamClose(writeStream);
  310. else
  311. delegate.outputStream = (__bridge NSOutputStream*)writeStream;
  312. }
  313. }
  314. [request setTimeoutInterval: timeoutSec];
  315. static dispatch_once_t onceToken;
  316. dispatch_once(&onceToken, ^{
  317. webOperationQueue = [[NSOperationQueue alloc] init];
  318. webOperationQueue.maxConcurrentOperationCount = [NSProcessInfo processInfo].activeProcessorCount * 5;
  319. webOperationQueue.name = @"com.unity3d.WebOperationQueue";
  320. });
  321. if (wantCertificateCallback)
  322. {
  323. delegate.wantCertificateCallback = YES;
  324. }
  325. delegate.connection = [[NSURLConnection alloc] initWithRequest: request delegate: delegate startImmediately: NO];
  326. delegate.manuallyHandleRedirect = YES;
  327. [delegate.connection setDelegateQueue: webOperationQueue];
  328. [delegate startConnection];
  329. }
  330. extern "C" void* UnityCreateWebRequestBackend(void* udata, const char* methodString, const void* headerDict, const char* url)
  331. {
  332. UnityWWWConnectionDelegate* delegate = [UnityWWWConnectionDelegate newDelegateWithCStringURL: url udata: udata];
  333. delegate.request = [UnityWWWConnectionDelegate newRequestForHTTPMethod: [NSString stringWithUTF8String: methodString] url: delegate.url headers: (__bridge NSDictionary*)headerDict];
  334. return (__bridge_retained void*)delegate;
  335. }
  336. extern "C" bool UnityWebRequestIsDone(void* connection)
  337. {
  338. UnityWWWConnectionDelegate* delegate = (__bridge UnityWWWConnectionDelegate*)connection;
  339. return (delegate.request == nil);
  340. }
  341. extern "C" void UnityDestroyWebRequestBackend(void* connection)
  342. {
  343. UnityWWWConnectionDelegate* delegate = (__bridge_transfer UnityWWWConnectionDelegate*)connection;
  344. [delegate cleanup];
  345. delegate = nil;
  346. }
  347. extern "C" void UnityCancelWebRequest(const void* connection)
  348. {
  349. UnityWWWConnectionDelegate* delegate = (__bridge UnityWWWConnectionDelegate*)connection;
  350. [delegate cancelConnection];
  351. }
  352. #endif
  353. extern "C" void UnityWebRequestClearCookieCache(const char* domain)
  354. {
  355. NSArray<NSHTTPCookie*>* cookies;
  356. NSHTTPCookieStorage* cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
  357. if (domain == NULL)
  358. cookies = [cookieStorage cookies];
  359. else
  360. {
  361. NSURL* url = [NSURL URLWithString: [NSString stringWithUTF8String: domain]];
  362. if (url.path == nil || [url.path isEqualToString: [NSString string]])
  363. {
  364. NSMutableArray<NSHTTPCookie*>* hostCookies = [[NSMutableArray<NSHTTPCookie *> alloc] init];
  365. cookies = [cookieStorage cookies];
  366. NSUInteger cookieCount = [cookies count];
  367. for (unsigned i = 0; i < cookieCount; ++i)
  368. if ([cookies[i].domain isEqualToString: url.host])
  369. [hostCookies addObject: cookies[i]];
  370. cookies = hostCookies;
  371. }
  372. else
  373. cookies = [cookieStorage cookiesForURL: url];
  374. }
  375. NSUInteger cookieCount = [cookies count];
  376. for (int i = 0; i < cookieCount; ++i)
  377. [cookieStorage deleteCookie: cookies[i]];
  378. }