SRProxyConnect.m 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. //
  2. // Copyright (c) 2016-present, Facebook, Inc.
  3. // All rights reserved.
  4. //
  5. // This source code is licensed under the BSD-style license found in the
  6. // LICENSE file in the root directory of this source tree. An additional grant
  7. // of patent rights can be found in the PATENTS file in the same directory.
  8. //
  9. #import "SRProxyConnect.h"
  10. #import "NSRunLoop+SRWebSocket.h"
  11. #import "SRConstants.h"
  12. #import "SRError.h"
  13. #import "SRLog.h"
  14. #import "SRURLUtilities.h"
  15. @interface SRProxyConnect() <NSStreamDelegate>
  16. @property (nonatomic, strong) NSURL *url;
  17. @property (nonatomic, strong) NSInputStream *inputStream;
  18. @property (nonatomic, strong) NSOutputStream *outputStream;
  19. @end
  20. @implementation SRProxyConnect
  21. {
  22. SRProxyConnectCompletion _completion;
  23. NSString *_httpProxyHost;
  24. uint32_t _httpProxyPort;
  25. CFHTTPMessageRef _receivedHTTPHeaders;
  26. NSString *_socksProxyHost;
  27. uint32_t _socksProxyPort;
  28. NSString *_socksProxyUsername;
  29. NSString *_socksProxyPassword;
  30. BOOL _connectionRequiresSSL;
  31. NSMutableArray<NSData *> *_inputQueue;
  32. dispatch_queue_t _writeQueue;
  33. }
  34. ///--------------------------------------
  35. #pragma mark - Init
  36. ///--------------------------------------
  37. -(instancetype)initWithURL:(NSURL *)url
  38. {
  39. self = [super init];
  40. if (!self) return self;
  41. _url = url;
  42. _connectionRequiresSSL = SRURLRequiresSSL(url);
  43. _writeQueue = dispatch_queue_create("com.facebook.socketrocket.proxyconnect.write", DISPATCH_QUEUE_SERIAL);
  44. _inputQueue = [NSMutableArray arrayWithCapacity:2];
  45. return self;
  46. }
  47. - (void)dealloc
  48. {
  49. // If we get deallocated before the socket open finishes - we need to cleanup everything.
  50. [self.inputStream removeFromRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultRunLoopMode];
  51. self.inputStream.delegate = nil;
  52. [self.inputStream close];
  53. self.inputStream = nil;
  54. self.outputStream.delegate = nil;
  55. [self.outputStream close];
  56. self.outputStream = nil;
  57. }
  58. ///--------------------------------------
  59. #pragma mark - Open
  60. ///--------------------------------------
  61. - (void)openNetworkStreamWithCompletion:(SRProxyConnectCompletion)completion
  62. {
  63. _completion = completion;
  64. [self _configureProxy];
  65. }
  66. ///--------------------------------------
  67. #pragma mark - Flow
  68. ///--------------------------------------
  69. - (void)_didConnect
  70. {
  71. SRDebugLog(@"_didConnect, return streams");
  72. if (_connectionRequiresSSL) {
  73. if (_httpProxyHost) {
  74. // Must set the real peer name before turning on SSL
  75. SRDebugLog(@"proxy set peer name to real host %@", self.url.host);
  76. [self.outputStream setProperty:self.url.host forKey:@"_kCFStreamPropertySocketPeerName"];
  77. }
  78. }
  79. if (_receivedHTTPHeaders) {
  80. CFRelease(_receivedHTTPHeaders);
  81. _receivedHTTPHeaders = NULL;
  82. }
  83. NSInputStream *inputStream = self.inputStream;
  84. NSOutputStream *outputStream = self.outputStream;
  85. self.inputStream = nil;
  86. self.outputStream = nil;
  87. [inputStream removeFromRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultRunLoopMode];
  88. inputStream.delegate = nil;
  89. outputStream.delegate = nil;
  90. _completion(nil, inputStream, outputStream);
  91. }
  92. - (void)_failWithError:(NSError *)error
  93. {
  94. SRDebugLog(@"_failWithError, return error");
  95. if (!error) {
  96. error = SRHTTPErrorWithCodeDescription(500, 2132,@"Proxy Error");
  97. }
  98. if (_receivedHTTPHeaders) {
  99. CFRelease(_receivedHTTPHeaders);
  100. _receivedHTTPHeaders = NULL;
  101. }
  102. self.inputStream.delegate = nil;
  103. self.outputStream.delegate = nil;
  104. [self.inputStream removeFromRunLoop:[NSRunLoop SR_networkRunLoop]
  105. forMode:NSDefaultRunLoopMode];
  106. [self.inputStream close];
  107. [self.outputStream close];
  108. self.inputStream = nil;
  109. self.outputStream = nil;
  110. _completion(error, nil, nil);
  111. }
  112. // get proxy setting from device setting
  113. - (void)_configureProxy
  114. {
  115. SRDebugLog(@"configureProxy");
  116. NSDictionary *proxySettings = CFBridgingRelease(CFNetworkCopySystemProxySettings());
  117. // CFNetworkCopyProxiesForURL doesn't understand ws:// or wss://
  118. NSURL *httpURL;
  119. if (_connectionRequiresSSL) {
  120. httpURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@", _url.host]];
  121. } else {
  122. httpURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@", _url.host]];
  123. }
  124. NSArray *proxies = CFBridgingRelease(CFNetworkCopyProxiesForURL((__bridge CFURLRef)httpURL, (__bridge CFDictionaryRef)proxySettings));
  125. if (proxies.count == 0) {
  126. SRDebugLog(@"configureProxy no proxies");
  127. [self _openConnection];
  128. return; // no proxy
  129. }
  130. NSDictionary *settings = [proxies objectAtIndex:0];
  131. NSString *proxyType = settings[(NSString *)kCFProxyTypeKey];
  132. if ([proxyType isEqualToString:(NSString *)kCFProxyTypeAutoConfigurationURL]) {
  133. NSURL *pacURL = settings[(NSString *)kCFProxyAutoConfigurationURLKey];
  134. if (pacURL) {
  135. [self _fetchPAC:pacURL withProxySettings:proxySettings];
  136. return;
  137. }
  138. }
  139. if ([proxyType isEqualToString:(__bridge NSString *)kCFProxyTypeAutoConfigurationJavaScript]) {
  140. NSString *script = settings[(__bridge NSString *)kCFProxyAutoConfigurationJavaScriptKey];
  141. if (script) {
  142. [self _runPACScript:script withProxySettings:proxySettings];
  143. return;
  144. }
  145. }
  146. [self _readProxySettingWithType:proxyType settings:settings];
  147. [self _openConnection];
  148. }
  149. - (void)_readProxySettingWithType:(NSString *)proxyType settings:(NSDictionary *)settings
  150. {
  151. if ([proxyType isEqualToString:(NSString *)kCFProxyTypeHTTP] ||
  152. [proxyType isEqualToString:(NSString *)kCFProxyTypeHTTPS]) {
  153. _httpProxyHost = settings[(NSString *)kCFProxyHostNameKey];
  154. NSNumber *portValue = settings[(NSString *)kCFProxyPortNumberKey];
  155. if (portValue) {
  156. _httpProxyPort = [portValue intValue];
  157. }
  158. }
  159. if ([proxyType isEqualToString:(NSString *)kCFProxyTypeSOCKS]) {
  160. _socksProxyHost = settings[(NSString *)kCFProxyHostNameKey];
  161. NSNumber *portValue = settings[(NSString *)kCFProxyPortNumberKey];
  162. if (portValue)
  163. _socksProxyPort = [portValue intValue];
  164. _socksProxyUsername = settings[(NSString *)kCFProxyUsernameKey];
  165. _socksProxyPassword = settings[(NSString *)kCFProxyPasswordKey];
  166. }
  167. if (_httpProxyHost) {
  168. SRDebugLog(@"Using http proxy %@:%u", _httpProxyHost, _httpProxyPort);
  169. } else if (_socksProxyHost) {
  170. SRDebugLog(@"Using socks proxy %@:%u", _socksProxyHost, _socksProxyPort);
  171. } else {
  172. SRDebugLog(@"configureProxy no proxies");
  173. }
  174. }
  175. - (void)_fetchPAC:(NSURL *)PACurl withProxySettings:(NSDictionary *)proxySettings
  176. {
  177. SRDebugLog(@"SRWebSocket fetchPAC:%@", PACurl);
  178. if ([PACurl isFileURL]) {
  179. NSError *error = nil;
  180. NSString *script = [NSString stringWithContentsOfURL:PACurl
  181. usedEncoding:NULL
  182. error:&error];
  183. if (error) {
  184. [self _openConnection];
  185. } else {
  186. [self _runPACScript:script withProxySettings:proxySettings];
  187. }
  188. return;
  189. }
  190. NSString *scheme = [PACurl.scheme lowercaseString];
  191. if (![scheme isEqualToString:@"http"] && ![scheme isEqualToString:@"https"]) {
  192. // Don't know how to read data from this URL, we'll have to give up
  193. // We'll simply assume no proxies, and start the request as normal
  194. [self _openConnection];
  195. return;
  196. }
  197. __weak typeof(self) wself = self;
  198. NSURLRequest *request = [NSURLRequest requestWithURL:PACurl];
  199. NSURLSession *session = [NSURLSession sharedSession];
  200. [[session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
  201. __strong typeof(wself) sself = wself;
  202. if (!error) {
  203. NSString *script = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
  204. [sself _runPACScript:script withProxySettings:proxySettings];
  205. } else {
  206. [sself _openConnection];
  207. }
  208. }] resume];
  209. }
  210. - (void)_runPACScript:(NSString *)script withProxySettings:(NSDictionary *)proxySettings
  211. {
  212. if (!script) {
  213. [self _openConnection];
  214. return;
  215. }
  216. SRDebugLog(@"runPACScript");
  217. // From: http://developer.apple.com/samplecode/CFProxySupportTool/listing1.html
  218. // Work around <rdar://problem/5530166>. This dummy call to
  219. // CFNetworkCopyProxiesForURL initialise some state within CFNetwork
  220. // that is required by CFNetworkCopyProxiesForAutoConfigurationScript.
  221. CFBridgingRelease(CFNetworkCopyProxiesForURL((__bridge CFURLRef)_url, (__bridge CFDictionaryRef)proxySettings));
  222. // Obtain the list of proxies by running the autoconfiguration script
  223. CFErrorRef err = NULL;
  224. // CFNetworkCopyProxiesForAutoConfigurationScript doesn't understand ws:// or wss://
  225. NSURL *httpURL;
  226. if (_connectionRequiresSSL)
  227. httpURL = [NSURL URLWithString:[NSString stringWithFormat:@"https://%@", _url.host]];
  228. else
  229. httpURL = [NSURL URLWithString:[NSString stringWithFormat:@"http://%@", _url.host]];
  230. NSArray *proxies = CFBridgingRelease(CFNetworkCopyProxiesForAutoConfigurationScript((__bridge CFStringRef)script,(__bridge CFURLRef)httpURL, &err));
  231. if (!err && [proxies count] > 0) {
  232. NSDictionary *settings = [proxies objectAtIndex:0];
  233. NSString *proxyType = settings[(NSString *)kCFProxyTypeKey];
  234. [self _readProxySettingWithType:proxyType settings:settings];
  235. }
  236. [self _openConnection];
  237. }
  238. - (void)_openConnection
  239. {
  240. [self _initializeStreams];
  241. [self.inputStream scheduleInRunLoop:[NSRunLoop SR_networkRunLoop]
  242. forMode:NSDefaultRunLoopMode];
  243. //[self.outputStream scheduleInRunLoop:[NSRunLoop SR_networkRunLoop]
  244. // forMode:NSDefaultRunLoopMode];
  245. [self.outputStream open];
  246. [self.inputStream open];
  247. }
  248. - (void)_initializeStreams
  249. {
  250. assert(_url.port.unsignedIntValue <= UINT32_MAX);
  251. uint32_t port = _url.port.unsignedIntValue;
  252. if (port == 0) {
  253. port = (_connectionRequiresSSL ? 443 : 80);
  254. }
  255. NSString *host = _url.host;
  256. if (_httpProxyHost) {
  257. host = _httpProxyHost;
  258. port = (_httpProxyPort ?: 80);
  259. }
  260. CFReadStreamRef readStream = NULL;
  261. CFWriteStreamRef writeStream = NULL;
  262. SRDebugLog(@"ProxyConnect connect stream to %@:%u", host, port);
  263. CFStreamCreatePairWithSocketToHost(NULL, (__bridge CFStringRef)host, port, &readStream, &writeStream);
  264. self.outputStream = CFBridgingRelease(writeStream);
  265. self.inputStream = CFBridgingRelease(readStream);
  266. if (_socksProxyHost) {
  267. SRDebugLog(@"ProxyConnect set sock property stream to %@:%u user %@ password %@", _socksProxyHost, _socksProxyPort, _socksProxyUsername, _socksProxyPassword);
  268. NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithCapacity:4];
  269. settings[NSStreamSOCKSProxyHostKey] = _socksProxyHost;
  270. if (_socksProxyPort) {
  271. settings[NSStreamSOCKSProxyPortKey] = @(_socksProxyPort);
  272. }
  273. if (_socksProxyUsername) {
  274. settings[NSStreamSOCKSProxyUserKey] = _socksProxyUsername;
  275. }
  276. if (_socksProxyPassword) {
  277. settings[NSStreamSOCKSProxyPasswordKey] = _socksProxyPassword;
  278. }
  279. [self.inputStream setProperty:settings forKey:NSStreamSOCKSProxyConfigurationKey];
  280. [self.outputStream setProperty:settings forKey:NSStreamSOCKSProxyConfigurationKey];
  281. }
  282. self.inputStream.delegate = self;
  283. self.outputStream.delegate = self;
  284. }
  285. - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode;
  286. {
  287. SRDebugLog(@"stream handleEvent %u", eventCode);
  288. switch (eventCode) {
  289. case NSStreamEventOpenCompleted: {
  290. if (aStream == self.inputStream) {
  291. if (_httpProxyHost) {
  292. [self _proxyDidConnect];
  293. } else {
  294. [self _didConnect];
  295. }
  296. }
  297. } break;
  298. case NSStreamEventErrorOccurred: {
  299. [self _failWithError:aStream.streamError];
  300. } break;
  301. case NSStreamEventEndEncountered: {
  302. [self _failWithError:aStream.streamError];
  303. } break;
  304. case NSStreamEventHasBytesAvailable: {
  305. if (aStream == _inputStream) {
  306. [self _processInputStream];
  307. }
  308. } break;
  309. case NSStreamEventHasSpaceAvailable:
  310. case NSStreamEventNone:
  311. SRDebugLog(@"(default) %@", aStream);
  312. break;
  313. }
  314. }
  315. - (void)_proxyDidConnect
  316. {
  317. SRDebugLog(@"Proxy Connected");
  318. uint32_t port = _url.port.unsignedIntValue;
  319. if (port == 0) {
  320. port = (_connectionRequiresSSL ? 443 : 80);
  321. }
  322. // Send HTTP CONNECT Request
  323. NSString *connectRequestStr = [NSString stringWithFormat:@"CONNECT %@:%u HTTP/1.1\r\nHost: %@\r\nConnection: keep-alive\r\nProxy-Connection: keep-alive\r\n\r\n", _url.host, port, _url.host];
  324. NSData *message = [connectRequestStr dataUsingEncoding:NSUTF8StringEncoding];
  325. SRDebugLog(@"Proxy sending %@", connectRequestStr);
  326. [self _writeData:message];
  327. }
  328. ///handles the incoming bytes and sending them to the proper processing method
  329. - (void)_processInputStream
  330. {
  331. NSMutableData *buf = [NSMutableData dataWithCapacity:SRDefaultBufferSize()];
  332. uint8_t *buffer = buf.mutableBytes;
  333. NSInteger length = [_inputStream read:buffer maxLength:SRDefaultBufferSize()];
  334. if (length <= 0) {
  335. return;
  336. }
  337. BOOL process = (_inputQueue.count == 0);
  338. [_inputQueue addObject:[NSData dataWithBytes:buffer length:length]];
  339. if (process) {
  340. [self _dequeueInput];
  341. }
  342. }
  343. // dequeue the incoming input so it is processed in order
  344. - (void)_dequeueInput
  345. {
  346. while (_inputQueue.count > 0) {
  347. NSData *data = _inputQueue.firstObject;
  348. [_inputQueue removeObjectAtIndex:0];
  349. // No need to process any data further, we got the full header data.
  350. if ([self _proxyProcessHTTPResponseWithData:data]) {
  351. break;
  352. }
  353. }
  354. }
  355. //handle checking the proxy connection status
  356. - (BOOL)_proxyProcessHTTPResponseWithData:(NSData *)data
  357. {
  358. if (_receivedHTTPHeaders == NULL) {
  359. _receivedHTTPHeaders = CFHTTPMessageCreateEmpty(NULL, NO);
  360. }
  361. CFHTTPMessageAppendBytes(_receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length);
  362. if (CFHTTPMessageIsHeaderComplete(_receivedHTTPHeaders)) {
  363. SRDebugLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(_receivedHTTPHeaders)));
  364. [self _proxyHTTPHeadersDidFinish];
  365. return YES;
  366. }
  367. return NO;
  368. }
  369. - (void)_proxyHTTPHeadersDidFinish
  370. {
  371. NSInteger responseCode = CFHTTPMessageGetResponseStatusCode(_receivedHTTPHeaders);
  372. if (responseCode >= 299) {
  373. SRDebugLog(@"Connect to Proxy Request failed with response code %d", responseCode);
  374. NSError *error = SRHTTPErrorWithCodeDescription(responseCode, 2132,
  375. [NSString stringWithFormat:@"Received bad response code from proxy server: %d.",
  376. (int)responseCode]);
  377. [self _failWithError:error];
  378. return;
  379. }
  380. SRDebugLog(@"proxy connect return %d, call socket connect", responseCode);
  381. [self _didConnect];
  382. }
  383. static NSTimeInterval const SRProxyConnectWriteTimeout = 5.0;
  384. - (void)_writeData:(NSData *)data
  385. {
  386. const uint8_t * bytes = data.bytes;
  387. __block NSInteger timeout = (NSInteger)(SRProxyConnectWriteTimeout * 1000000); // wait timeout before giving up
  388. __weak typeof(self) wself = self;
  389. dispatch_async(_writeQueue, ^{
  390. __strong typeof(wself) sself = self;
  391. if (!sself) {
  392. return;
  393. }
  394. NSOutputStream *outStream = sself.outputStream;
  395. if (!outStream) {
  396. return;
  397. }
  398. while (![outStream hasSpaceAvailable]) {
  399. usleep(100); //wait until the socket is ready
  400. timeout -= 100;
  401. if (timeout < 0) {
  402. NSError *error = SRHTTPErrorWithCodeDescription(408, 2132, @"Proxy timeout");
  403. [sself _failWithError:error];
  404. } else if (outStream.streamError != nil) {
  405. [sself _failWithError:outStream.streamError];
  406. }
  407. }
  408. [outStream write:bytes maxLength:data.length];
  409. });
  410. }
  411. @end