WWWConnection.mm
16.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
#include "WWWConnection.h"
#if 0 // old UnityWebRequest backend
// 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";
const CFIndex streamSize = 1024;
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 (nonatomic) BOOL wantCertificateCallback;
@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];
UnityReportWebRequestStatus(self.udata, (int)self->_status);
for (id headerKey = [headerEnum nextObject]; headerKey; headerKey = [headerEnum nextObject])
UnityReportWebRequestResponseHeader(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;
UnityReportWebRequestReceivedResponse(self.udata, (unsigned int)self->_estimatedLength);
}
- (void)connection:(NSURLConnection*)connection didReceiveData:(NSData*)data
{
UnityReportWebRequestReceivedData(self.udata, data.bytes, (unsigned int)[data length], (unsigned int)self->_estimatedLength);
}
- (void)connection:(NSURLConnection*)connection didFailWithError:(NSError*)error
{
UnityReportWebRequestNetworkError(self.udata, (int)[error code]);
UnityReportWebRequestFinishedLoadingData(self.udata);
}
- (void)connectionDidFinishLoading:(NSURLConnection*)connection
{
UnityReportWebRequestFinishedLoadingData(self.udata);
}
- (void)connection:(NSURLConnection*)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
{
UnityReportWebRequestSentData(self.udata, (unsigned int)totalBytesWritten, (unsigned int)totalBytesExpectedToWrite);
if (_outputStream != nil)
{
unsigned dataSize = streamSize;
unsigned transmitted = 0;
const UInt8* bytes = (const UInt8*)UnityWebRequestGetUploadData(_udata, &dataSize);
if (dataSize > 0)
{
transmitted = [_outputStream write: bytes maxLength: dataSize];
UnityWebRequestConsumeUploadData(_udata, transmitted);
}
if (dataSize < streamSize && 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)
{
if (!self.wantCertificateCallback)
{
[challenge.sender performDefaultHandlingForAuthenticationChallenge: challenge];
return;
}
#if !defined(DISABLE_WEBREQUEST_CERTIFICATE_CALLBACK)
SecTrustResultType systemResult;
SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
if (serverTrust == nil || errSecSuccess != SecTrustEvaluate(serverTrust, &systemResult))
{
systemResult = kSecTrustResultOtherError;
}
switch (systemResult)
{
case kSecTrustResultUnspecified:
case kSecTrustResultProceed:
case kSecTrustResultRecoverableTrustFailure:
break;
default:
[challenge.sender performDefaultHandlingForAuthenticationChallenge: challenge];
return;
}
SecCertificateRef serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0);
if (serverCertificate != nil)
{
CFDataRef serverCertificateData = SecCertificateCopyData(serverCertificate);
const UInt8* const data = CFDataGetBytePtr(serverCertificateData);
const CFIndex size = CFDataGetLength(serverCertificateData);
bool trust = UnityReportWebRequestValidateCertificate(self.udata, (const char*)data, (unsigned)size);
CFRelease(serverCertificateData);
if (trust)
{
NSURLCredential *credential = [NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust];
[challenge.sender useCredential: credential forAuthenticationChallenge: challenge];
return;
}
}
#endif
[challenge.sender cancelAuthenticationChallenge: challenge];
return;
}
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 UnitySendWebRequest(void* connection, unsigned length, unsigned long timeoutSec, bool wantCertificateCallback)
{
UnityWWWConnectionDelegate* delegate = (__bridge UnityWWWConnectionDelegate*)connection;
NSMutableURLRequest* request = delegate.request;
if (length > 0)
{
unsigned dataSize = streamSize;
const void* bytes = UnityWebRequestGetUploadData(delegate.udata, &dataSize);
if (dataSize > 0)
{
CFReadStreamRef readStream;
CFWriteStreamRef writeStream;
CFStreamCreateBoundPair(kCFAllocatorDefault, &readStream, &writeStream, streamSize);
[request setHTTPBodyStream: (__bridge NSInputStream*)readStream];
CFWriteStreamOpen(writeStream);
unsigned transmitted = CFWriteStreamWrite(writeStream, (UInt8*)bytes, dataSize);
UnityWebRequestConsumeUploadData(delegate.udata, transmitted);
if (dataSize < streamSize && 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";
});
if (wantCertificateCallback)
{
delegate.wantCertificateCallback = YES;
}
delegate.connection = [[NSURLConnection alloc] initWithRequest: request delegate: delegate startImmediately: NO];
delegate.manuallyHandleRedirect = YES;
[delegate.connection setDelegateQueue: webOperationQueue];
[delegate startConnection];
}
extern "C" void* UnityCreateWebRequestBackend(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 UnityWebRequestIsDone(void* connection)
{
UnityWWWConnectionDelegate* delegate = (__bridge UnityWWWConnectionDelegate*)connection;
return (delegate.request == nil);
}
extern "C" void UnityDestroyWebRequestBackend(void* connection)
{
UnityWWWConnectionDelegate* delegate = (__bridge_transfer UnityWWWConnectionDelegate*)connection;
[delegate cleanup];
delegate = nil;
}
extern "C" void UnityCancelWebRequest(const void* connection)
{
UnityWWWConnectionDelegate* delegate = (__bridge UnityWWWConnectionDelegate*)connection;
[delegate cancelConnection];
}
#endif
extern "C" void UnityWebRequestClearCookieCache(const char* domain)
{
NSArray<NSHTTPCookie*>* cookies;
NSHTTPCookieStorage* cookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
if (domain == NULL)
cookies = [cookieStorage cookies];
else
{
NSURL* url = [NSURL URLWithString: [NSString stringWithUTF8String: domain]];
if (url.path == nil || [url.path isEqualToString: [NSString string]])
{
NSMutableArray<NSHTTPCookie*>* hostCookies = [[NSMutableArray<NSHTTPCookie *> alloc] init];
cookies = [cookieStorage cookies];
NSUInteger cookieCount = [cookies count];
for (unsigned i = 0; i < cookieCount; ++i)
if ([cookies[i].domain isEqualToString: url.host])
[hostCookies addObject: cookies[i]];
cookies = hostCookies;
}
else
cookies = [cookieStorage cookiesForURL: url];
}
NSUInteger cookieCount = [cookies count];
for (int i = 0; i < cookieCount; ++i)
[cookieStorage deleteCookie: cookies[i]];
}