PageRenderTime 57ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/gnustep-base-1.24.0/Source/GSHTTPURLHandle.m

#
Objective C | 1607 lines | 1192 code | 120 blank | 295 comment | 271 complexity | 22d18dc0dd4113f0c9cd77240bd8aced MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1
  1. /** GSHTTPURLHandle.m - Class GSHTTPURLHandle
  2. Copyright (C) 2000 Free Software Foundation, Inc.
  3. Written by: Mark Allison <mark@brainstorm.co.uk>
  4. Integrated by: Richard Frith-Macdonald <rfm@gnu.org>
  5. Date: November 2000
  6. This file is part of the GNUstep Library.
  7. This library is free software; you can redistribute it and/or
  8. modify it under the terms of the GNU Lesser General Public
  9. License as published by the Free Software Foundation; either
  10. version 2 of the License, or (at your option) any later version.
  11. This library is distributed in the hope that it will be useful,
  12. but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. Library General Public License for more details.
  15. You should have received a copy of the GNU Lesser General Public
  16. License along with this library; if not, write to the Free
  17. Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
  18. Boston, MA 02111 USA.
  19. */
  20. #import "common.h"
  21. #import "Foundation/NSArray.h"
  22. #import "Foundation/NSDictionary.h"
  23. #import "Foundation/NSEnumerator.h"
  24. #import "Foundation/NSByteOrder.h"
  25. #import "Foundation/NSData.h"
  26. #import "Foundation/NSException.h"
  27. #import "Foundation/NSFileHandle.h"
  28. #import "Foundation/NSHost.h"
  29. #import "Foundation/NSLock.h"
  30. #import "Foundation/NSMapTable.h"
  31. #import "Foundation/NSNotification.h"
  32. #import "Foundation/NSPathUtilities.h"
  33. #import "Foundation/NSProcessInfo.h"
  34. #import "Foundation/NSRunLoop.h"
  35. #import "Foundation/NSURL.h"
  36. #import "Foundation/NSURLHandle.h"
  37. #import "Foundation/NSValue.h"
  38. #import "GNUstepBase/GSMime.h"
  39. #import "GNUstepBase/GSLock.h"
  40. #import "GNUstepBase/NSString+GNUstepBase.h"
  41. #import "GNUstepBase/NSURL+GNUstepBase.h"
  42. #import "NSCallBacks.h"
  43. #import "GSURLPrivate.h"
  44. #import "GSPrivate.h"
  45. #include <string.h>
  46. #ifdef HAVE_SYS_FILE_H
  47. # include <sys/file.h>
  48. #endif
  49. #if defined(HAVE_SYS_FCNTL_H)
  50. # include <sys/fcntl.h>
  51. #elif defined(HAVE_FCNTL_H)
  52. # include <fcntl.h>
  53. #endif
  54. #ifdef HAVE_SYS_SOCKET_H
  55. # include <sys/socket.h> // For MSG_PEEK, etc
  56. #endif
  57. /*
  58. * Implement map keys for strings with case insensitive comparisons,
  59. * so we can have case insensitive matching of http headers (correct
  60. * behavior), but actually preserve case of headers stored and written
  61. * in case the remote server is buggy and requires particular
  62. * captialisation of headers (some http software is faulty like that).
  63. */
  64. static NSUInteger
  65. _id_hash(void *table, NSString* o)
  66. {
  67. return [[o uppercaseString] hash];
  68. }
  69. static BOOL
  70. _id_is_equal(void *table, NSString *o, NSString *p)
  71. {
  72. return ([o caseInsensitiveCompare: p] == NSOrderedSame) ? YES : NO;
  73. }
  74. typedef NSUInteger (*NSMT_hash_func_t)(NSMapTable *, const void *);
  75. typedef BOOL (*NSMT_is_equal_func_t)(NSMapTable *, const void *, const void *);
  76. typedef void (*NSMT_retain_func_t)(NSMapTable *, const void *);
  77. typedef void (*NSMT_release_func_t)(NSMapTable *, void *);
  78. typedef NSString *(*NSMT_describe_func_t)(NSMapTable *, const void *);
  79. static const NSMapTableKeyCallBacks writeKeyCallBacks =
  80. {
  81. (NSMT_hash_func_t) _id_hash,
  82. (NSMT_is_equal_func_t) _id_is_equal,
  83. (NSMT_retain_func_t) _NS_id_retain,
  84. (NSMT_release_func_t) _NS_id_release,
  85. (NSMT_describe_func_t) _NS_id_describe,
  86. NSNotAPointerMapKey
  87. };
  88. static NSString *httpVersion = @"1.1";
  89. @interface GSHTTPURLHandle : NSURLHandle
  90. {
  91. BOOL tunnel;
  92. BOOL debug;
  93. BOOL keepalive;
  94. BOOL returnAll;
  95. unsigned char challenged;
  96. NSFileHandle *sock;
  97. NSURL *url;
  98. NSURL *u;
  99. NSMutableData *dat;
  100. GSMimeParser *parser;
  101. GSMimeDocument *document;
  102. NSMutableDictionary *pageInfo;
  103. NSMapTable *wProperties;
  104. NSData *wData;
  105. NSMutableDictionary *request;
  106. unsigned int bodyPos;
  107. unsigned int redirects;
  108. enum {
  109. idle,
  110. connecting,
  111. writing,
  112. reading,
  113. } connectionState;
  114. }
  115. - (void) setDebug: (BOOL)flag;
  116. - (void) _tryLoadInBackground: (NSURL*)fromURL;
  117. @end
  118. /**
  119. * <p>
  120. * This is a <em>PRIVATE</em> subclass of NSURLHandle.
  121. * It is documented here in order to give you information about the
  122. * default behavior of an NSURLHandle created to deal with a URL
  123. * that has either the <code>http</code> or <code>https</code> scheme.
  124. * The name and/or other implementation details of this class
  125. * may be changed at any time.
  126. * </p>
  127. * <p>
  128. * A GSHTTPURLHandle instance is used to manage connections to
  129. * <code>http</code> and <code>https</code> URLs.
  130. * Secure connections are handled automatically
  131. * (using openSSL) for URLs with the scheme <code>https</code>.
  132. * Connection via proxy server is supported, as is proxy tunneling
  133. * for secure connections. Basic parsing of <code>http</code>
  134. * headers is performed to extract <code>http</code> status
  135. * information, cookies etc. Cookies are
  136. * retained and automatically sent during subsequent requests where
  137. * the cookie is valid.
  138. * </p>
  139. * <p>
  140. * Header information from the current page may be obtained using
  141. * -propertyForKey and -propertyForKeyIfAvailable. <code>HTTP</code>
  142. * status information can be retrieved as by calling either of these
  143. * methods specifying one of the following keys:
  144. * </p>
  145. * <list>
  146. * <item>
  147. * NSHTTPPropertyStatusCodeKey - numeric status code
  148. * </item>
  149. * <item>
  150. * NSHTTPPropertyStatusReasonKey - text describing status
  151. * </item>
  152. * <item>
  153. * NSHTTPPropertyServerHTTPVersionKey - <code>http</code>
  154. * version supported by remote server
  155. * </item>
  156. * </list>
  157. * <p>
  158. * According to MacOS-X headers, the following should also
  159. * be supported, but currently are not:
  160. * </p>
  161. * <list>
  162. * <item>NSHTTPPropertyRedirectionHeadersKey</item>
  163. * <item>NSHTTPPropertyErrorPageDataKey</item>
  164. * </list>
  165. * <p>
  166. * The omission of these headers is not viewed as important at
  167. * present, since the MacOS-X public beta implementation doesn't
  168. * work either.
  169. * </p>
  170. * <p>
  171. * Other calls to -propertyForKey and -propertyForKeyIfAvailable may
  172. * be made specifying a <code>http</code> header field name.
  173. * For example specifying a key name of &quot;Content-Length&quot;
  174. * would return the value of the &quot;Content-Length&quot; header
  175. * field.
  176. * </p>
  177. * <p>
  178. * [GSHTTPURLHandle-writeProperty:forKey:]
  179. * can be used to specify the parameters
  180. * for the <code>http</code> request. The default request uses the
  181. * &quot;GET&quot; method when fetching a page, and the
  182. * &quot;POST&quot; method when using -writeData:.
  183. * This can be over-ridden by calling -writeProperty:forKey: with
  184. * the key name &quot;GSHTTPPropertyMethodKey&quot; and specifying an
  185. * alternative method (i.e &quot;PUT&quot;).
  186. * </p>
  187. * <p>
  188. * A Proxy may be specified by calling -writeProperty:forKey:
  189. * with the keys &quot;GSHTTPPropertyProxyHostKey&quot; and
  190. * &quot;GSHTTPPropertyProxyPortKey&quot; to set the host and port
  191. * of the proxy server respectively. The GSHTTPPropertyProxyHostKey
  192. * property can be set to either the IP address or the hostname of
  193. * the proxy server. If an attempt is made to load a page via a
  194. * secure connection when a proxy is specified, GSHTTPURLHandle will
  195. * attempt to open an SSL Tunnel through the proxy.
  196. * </p>
  197. * <p>
  198. * Requests to the remote server may be forced to be bound to a
  199. * particular local IP address by using the key
  200. * &quot;GSHTTPPropertyLocalHostKey&quot; which must contain the
  201. * IP address of a network interface on the local host.
  202. * </p>
  203. */
  204. @implementation GSHTTPURLHandle
  205. #define MAX_CACHED 16
  206. static NSMutableDictionary *urlCache = nil;
  207. static NSMutableArray *urlOrder = nil;
  208. static NSLock *urlLock = nil;
  209. static Class sslClass = 0;
  210. static NSLock *debugLock = nil;
  211. static NSString *debugFile;
  212. static void debugRead(GSHTTPURLHandle *handle, NSData *data)
  213. {
  214. NSString *s;
  215. int d;
  216. [debugLock lock];
  217. #if defined(__MINGW__)
  218. d = _wopen((const unichar*)[debugFile fileSystemRepresentation],
  219. O_WRONLY|O_CREAT|O_APPEND, 0644);
  220. #else
  221. d = open([debugFile fileSystemRepresentation],
  222. O_WRONLY|O_CREAT|O_APPEND, 0644);
  223. #endif
  224. if (d >= 0)
  225. {
  226. s = [NSString stringWithFormat: @"\nRead for %p at %@ %u bytes - '",
  227. handle, [NSDate date], [data length]];
  228. write(d, [s cString], [s cStringLength]);
  229. write(d, [data bytes], [data length]);
  230. write(d, "'", 1);
  231. close(d);
  232. }
  233. [debugLock unlock];
  234. }
  235. static void debugWrite(GSHTTPURLHandle *handle, NSData *data)
  236. {
  237. NSString *s;
  238. int d;
  239. [debugLock lock];
  240. #if defined(__MINGW__)
  241. d = _wopen((const unichar*)[debugFile fileSystemRepresentation],
  242. O_WRONLY|O_CREAT|O_APPEND, 0644);
  243. #else
  244. d = open([debugFile fileSystemRepresentation],
  245. O_WRONLY|O_CREAT|O_APPEND, 0644);
  246. #endif
  247. if (d >= 0)
  248. {
  249. s = [NSString stringWithFormat: @"\nWrite for %p at %@ %u bytes - '",
  250. handle, [NSDate date], [data length]];
  251. write(d, [s cString], [s cStringLength]);
  252. write(d, [data bytes], [data length]);
  253. write(d, "'", 1);
  254. close(d);
  255. }
  256. [debugLock unlock];
  257. }
  258. + (NSURLHandle*) cachedHandleForURL: (NSURL*)newUrl
  259. {
  260. NSURLHandle *obj = nil;
  261. if ([[newUrl scheme] caseInsensitiveCompare: @"http"] == NSOrderedSame
  262. || [[newUrl scheme] caseInsensitiveCompare: @"https"] == NSOrderedSame)
  263. {
  264. NSString *page = [newUrl absoluteString];
  265. //NSLog(@"Lookup for handle for '%@'", page);
  266. [urlLock lock];
  267. obj = [urlCache objectForKey: page];
  268. if (obj != nil)
  269. {
  270. [urlOrder removeObjectIdenticalTo: obj];
  271. [urlOrder addObject: obj];
  272. IF_NO_GC([[obj retain] autorelease];)
  273. }
  274. [urlLock unlock];
  275. //NSLog(@"Found handle %@", obj);
  276. }
  277. return obj;
  278. }
  279. + (void) initialize
  280. {
  281. if (self == [GSHTTPURLHandle class])
  282. {
  283. urlCache = [NSMutableDictionary new];
  284. urlOrder = [NSMutableArray new];
  285. urlLock = [GSLazyLock new];
  286. debugLock = [GSLazyLock new];
  287. debugFile = [NSString stringWithFormat: @"%@/GSHTTP.%d",
  288. NSTemporaryDirectory(),
  289. [[NSProcessInfo processInfo] processIdentifier]];
  290. IF_NO_GC([debugFile retain];)
  291. #if !defined(__MINGW__)
  292. sslClass = [NSFileHandle sslClass];
  293. #endif
  294. }
  295. }
  296. - (void) dealloc
  297. {
  298. if (sock != nil)
  299. {
  300. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  301. [nc removeObserver: self name: nil object: sock];
  302. [sock closeFile];
  303. DESTROY(sock);
  304. }
  305. DESTROY(u);
  306. DESTROY(url);
  307. DESTROY(dat);
  308. DESTROY(parser);
  309. DESTROY(document);
  310. DESTROY(pageInfo);
  311. DESTROY(wData);
  312. if (wProperties != 0)
  313. {
  314. NSFreeMapTable(wProperties);
  315. }
  316. DESTROY(request);
  317. [super dealloc];
  318. }
  319. - (id) initWithURL: (NSURL*)newUrl
  320. cached: (BOOL)cached
  321. {
  322. if ((self = [super initWithURL: newUrl cached: cached]) != nil)
  323. {
  324. dat = [NSMutableData new];
  325. pageInfo = [NSMutableDictionary new];
  326. wProperties = NSCreateMapTable(writeKeyCallBacks,
  327. NSObjectMapValueCallBacks, 8);
  328. request = [NSMutableDictionary new];
  329. ASSIGN(url, newUrl);
  330. connectionState = idle;
  331. if (cached == YES)
  332. {
  333. NSString *page = [newUrl absoluteString];
  334. GSHTTPURLHandle *obj;
  335. [urlLock lock];
  336. obj = [urlCache objectForKey: page];
  337. [urlCache setObject: self forKey: page];
  338. if (obj != nil)
  339. {
  340. [urlOrder removeObjectIdenticalTo: obj];
  341. }
  342. [urlOrder addObject: self];
  343. while ([urlOrder count] > MAX_CACHED)
  344. {
  345. obj = [urlOrder objectAtIndex: 0];
  346. [urlCache removeObjectForKey: [obj->url absoluteString]];
  347. [urlOrder removeObjectAtIndex: 0];
  348. }
  349. [urlLock unlock];
  350. //NSLog(@"Cache handle %p for '%@'", self, page);
  351. }
  352. }
  353. return self;
  354. }
  355. + (BOOL) canInitWithURL: (NSURL*)newUrl
  356. {
  357. NSString *scheme = [newUrl scheme];
  358. if ([scheme isEqualToString: @"http"]
  359. || [scheme isEqualToString: @"https"])
  360. {
  361. return YES;
  362. }
  363. return NO;
  364. }
  365. - (void) bgdApply: (NSString*)basic
  366. {
  367. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  368. NSMutableString *s;
  369. NSString *key;
  370. NSString *val;
  371. NSMutableData *buf;
  372. NSString *version;
  373. NSMapEnumerator enumerator;
  374. IF_NO_GC([self retain];)
  375. if (debug)
  376. NSLog(@"%@ %p %s", NSStringFromSelector(_cmd), self, keepalive?"K":"");
  377. s = [basic mutableCopy];
  378. if ([[u query] length] > 0)
  379. {
  380. [s appendFormat: @"?%@", [u query]];
  381. }
  382. version = [request objectForKey: NSHTTPPropertyServerHTTPVersionKey];
  383. if (version == nil)
  384. {
  385. version = httpVersion;
  386. }
  387. [s appendFormat: @" HTTP/%@\r\n", version];
  388. if ((id)NSMapGet(wProperties, (void*)@"Host") == nil)
  389. {
  390. id p = [u port];
  391. id h = [u host];
  392. if (h == nil)
  393. {
  394. h = @""; // Must use an empty host header
  395. }
  396. if (p == nil)
  397. {
  398. NSMapInsert(wProperties, (void*)@"Host", (void*)h);
  399. }
  400. else
  401. {
  402. NSMapInsert(wProperties, (void*)@"Host",
  403. (void*)[NSString stringWithFormat: @"%@:%@", h, p]);
  404. }
  405. }
  406. if ([wData length] > 0)
  407. {
  408. NSMapInsert(wProperties, (void*)@"Content-Length",
  409. (void*)[NSString stringWithFormat: @"%d", [wData length]]);
  410. /*
  411. * Assume content type if not specified.
  412. */
  413. if ((id)NSMapGet(wProperties, (void*)@"Content-Type") == nil)
  414. {
  415. NSMapInsert(wProperties, (void*)@"Content-Type",
  416. (void*)@"application/x-www-form-urlencoded");
  417. }
  418. }
  419. if ((id)NSMapGet(wProperties, (void*)@"Authorization") == nil)
  420. {
  421. NSURLProtectionSpace *space;
  422. /*
  423. * If we have username/password stored in the URL, and there is a
  424. * known protection space for that URL, we generate an authentication
  425. * header.
  426. */
  427. if ([u user] != nil
  428. && (space = [GSHTTPAuthentication protectionSpaceForURL: u]) != nil)
  429. {
  430. NSString *auth;
  431. GSHTTPAuthentication *authentication;
  432. NSURLCredential *cred;
  433. NSString *method;
  434. /* Create credential from user and password stored in the URL.
  435. * Returns nil if we have no username or password.
  436. */
  437. cred = [[NSURLCredential alloc]
  438. initWithUser: [u user]
  439. password: [u password]
  440. persistence: NSURLCredentialPersistenceForSession];
  441. if (cred == nil)
  442. {
  443. authentication = nil;
  444. }
  445. else
  446. {
  447. /* Create authentication from credential ... returns nil if
  448. * we have no credential.
  449. */
  450. authentication = [GSHTTPAuthentication
  451. authenticationWithCredential: cred
  452. inProtectionSpace: space];
  453. RELEASE(cred);
  454. }
  455. method = [request objectForKey: GSHTTPPropertyMethodKey];
  456. if (method == nil)
  457. {
  458. if ([wData length] > 0)
  459. {
  460. method = @"POST";
  461. }
  462. else
  463. {
  464. method = @"GET";
  465. }
  466. }
  467. auth = [authentication authorizationForAuthentication: nil
  468. method: method
  469. path: [u fullPath]];
  470. /* If authentication is nil then auth will also be nil
  471. */
  472. if (auth != nil)
  473. {
  474. [self writeProperty: auth forKey: @"Authorization"];
  475. }
  476. }
  477. }
  478. enumerator = NSEnumerateMapTable(wProperties);
  479. while (NSNextMapEnumeratorPair(&enumerator, (void **)(&key), (void**)&val))
  480. {
  481. [s appendFormat: @"%@: %@\r\n", key, val];
  482. }
  483. NSEndMapTableEnumeration(&enumerator);
  484. [s appendString: @"\r\n"];
  485. buf = [[s dataUsingEncoding: NSASCIIStringEncoding] mutableCopy];
  486. /*
  487. * Append any data to be sent
  488. */
  489. if (wData != nil)
  490. {
  491. [buf appendData: wData];
  492. }
  493. /*
  494. * Watch for write completion.
  495. */
  496. [nc addObserver: self
  497. selector: @selector(bgdWrite:)
  498. name: GSFileHandleWriteCompletionNotification
  499. object: sock];
  500. connectionState = writing;
  501. /*
  502. * Send request to server.
  503. */
  504. if (debug == YES) debugWrite(self, buf);
  505. [sock writeInBackgroundAndNotify: buf];
  506. RELEASE(buf);
  507. RELEASE(s);
  508. DESTROY(self);
  509. }
  510. - (void) bgdRead: (NSNotification*) not
  511. {
  512. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  513. NSDictionary *dict = [not userInfo];
  514. NSData *d;
  515. NSRange r;
  516. unsigned readCount;
  517. IF_NO_GC([self retain];)
  518. if (debug)
  519. NSLog(@"%@ %p %s", NSStringFromSelector(_cmd), self, keepalive?"K":"");
  520. d = [dict objectForKey: NSFileHandleNotificationDataItem];
  521. if (debug == YES) debugRead(self, d);
  522. readCount = [d length];
  523. if (connectionState == idle)
  524. {
  525. /*
  526. * We received an event on a handle which is not in use ...
  527. * it should just be the connection being closed by the other
  528. * end because of a timeout etc.
  529. */
  530. if (debug == YES && [d length] != 0)
  531. {
  532. NSLog(@"%@ %p %s Unexpected data (%*.*s) from remote!",
  533. NSStringFromSelector(_cmd), self, keepalive?"K":"",
  534. (int)[d length], (int)[d length], [d bytes]);
  535. }
  536. [nc removeObserver: self name: nil object: sock];
  537. [sock closeFile];
  538. DESTROY(sock);
  539. }
  540. else if ([parser parse: d] == NO && [parser isComplete] == NO)
  541. {
  542. if (debug == YES)
  543. {
  544. NSLog(@"HTTP parse failure - %@", parser);
  545. }
  546. [self endLoadInBackground];
  547. [self backgroundLoadDidFailWithReason: @"Response parse failed"];
  548. }
  549. else
  550. {
  551. BOOL complete = [parser isComplete];
  552. if (complete == NO && [parser isInHeaders] == NO)
  553. {
  554. GSMimeHeader *info;
  555. NSString *enc;
  556. NSString *len;
  557. int status;
  558. info = [document headerNamed: @"http"];
  559. status = [[info objectForKey: NSHTTPPropertyStatusCodeKey] intValue];
  560. len = [[document headerNamed: @"content-length"] value];
  561. enc = [[document headerNamed: @"content-transfer-encoding"] value];
  562. if (enc == nil)
  563. {
  564. enc = [[document headerNamed: @"transfer-encoding"] value];
  565. }
  566. if (status == 204 || status == 304)
  567. {
  568. complete = YES; // No body expected.
  569. }
  570. else if ([enc isEqualToString: @"chunked"] == YES)
  571. {
  572. complete = NO; // Read chunked body data
  573. }
  574. else if (nil != len && [len intValue] == 0)
  575. {
  576. complete = YES; // content-length explicitly zero
  577. }
  578. if (complete == NO && [d length] == 0)
  579. {
  580. complete = YES; // Had EOF ... terminate
  581. }
  582. }
  583. if (complete == YES)
  584. {
  585. GSMimeHeader *info;
  586. NSString *val;
  587. NSNumber *num;
  588. float ver;
  589. int code;
  590. connectionState = idle;
  591. [nc removeObserver: self name: nil object: sock];
  592. ver = [[[document headerNamed: @"http"] value] floatValue];
  593. if (ver < 1.1)
  594. {
  595. [nc removeObserver: self name: nil object: sock];
  596. [sock closeFile];
  597. DESTROY(sock);
  598. }
  599. else if (nil != (val = [[document headerNamed: @"connection"] value]))
  600. {
  601. val = [val lowercaseString];
  602. if (YES == [val isEqualToString: @"close"])
  603. {
  604. [nc removeObserver: self name: nil object: sock];
  605. [sock closeFile];
  606. DESTROY(sock);
  607. }
  608. else if ([val length] > 5)
  609. {
  610. NSEnumerator *e;
  611. e = [[val componentsSeparatedByString: @","]
  612. objectEnumerator];
  613. while (nil != (val = [e nextObject]))
  614. {
  615. val = [val stringByTrimmingSpaces];
  616. if (YES == [val isEqualToString: @"close"])
  617. {
  618. [nc removeObserver: self name: nil object: sock];
  619. [sock closeFile];
  620. DESTROY(sock);
  621. break;
  622. }
  623. }
  624. }
  625. }
  626. /*
  627. * Retrieve essential keys from document
  628. */
  629. info = [document headerNamed: @"http"];
  630. num = [info objectForKey: NSHTTPPropertyStatusCodeKey];
  631. code = [num intValue];
  632. if (code == 401 && self->challenged < 2)
  633. {
  634. GSMimeHeader *ah;
  635. self->challenged++; // Prevent repeated challenge/auth
  636. if ((ah = [document headerNamed: @"WWW-Authenticate"]) != nil)
  637. {
  638. NSURLProtectionSpace *space;
  639. NSString *ac;
  640. GSHTTPAuthentication *authentication;
  641. NSString *method;
  642. NSString *auth;
  643. ac = [ah value];
  644. space = [GSHTTPAuthentication
  645. protectionSpaceForAuthentication: ac requestURL: url];
  646. if (space == nil)
  647. {
  648. authentication = nil;
  649. }
  650. else
  651. {
  652. NSURLCredential *cred;
  653. /*
  654. * Create credential from user and password
  655. * stored in the URL.
  656. * Returns nil if we have no username or password.
  657. */
  658. cred = [[NSURLCredential alloc]
  659. initWithUser: [url user]
  660. password: [url password]
  661. persistence: NSURLCredentialPersistenceForSession];
  662. if (cred == nil)
  663. {
  664. authentication = nil;
  665. }
  666. else
  667. {
  668. /*
  669. * Get the digest object and ask it for a header
  670. * to use for authorisation.
  671. * Returns nil if we have no credential.
  672. */
  673. authentication = [GSHTTPAuthentication
  674. authenticationWithCredential: cred
  675. inProtectionSpace: space];
  676. RELEASE(cred);
  677. }
  678. }
  679. method = [request objectForKey: GSHTTPPropertyMethodKey];
  680. if (method == nil)
  681. {
  682. if ([wData length] > 0)
  683. {
  684. method = @"POST";
  685. }
  686. else
  687. {
  688. method = @"GET";
  689. }
  690. }
  691. auth = [authentication authorizationForAuthentication: ac
  692. method: method
  693. path: [url fullPath]];
  694. if (auth != nil)
  695. {
  696. [self writeProperty: auth forKey: @"Authorization"];
  697. [self _tryLoadInBackground: u];
  698. return; // Retrying.
  699. }
  700. }
  701. }
  702. if (num != nil)
  703. {
  704. [pageInfo setObject: num forKey: NSHTTPPropertyStatusCodeKey];
  705. }
  706. val = [info objectForKey: NSHTTPPropertyServerHTTPVersionKey];
  707. if (val != nil)
  708. {
  709. [pageInfo setObject: val
  710. forKey: NSHTTPPropertyServerHTTPVersionKey];
  711. }
  712. val = [info objectForKey: NSHTTPPropertyStatusReasonKey];
  713. if (val != nil)
  714. {
  715. [pageInfo setObject: val forKey: NSHTTPPropertyStatusReasonKey];
  716. }
  717. /*
  718. * Tell superclass that we have successfully loaded the data.
  719. */
  720. d = [parser data];
  721. r = NSMakeRange(bodyPos, [d length] - bodyPos);
  722. bodyPos = 0;
  723. DESTROY(wData);
  724. NSResetMapTable(wProperties);
  725. if (returnAll || (code >= 200 && code < 300))
  726. {
  727. [self didLoadBytes: [d subdataWithRange: r]
  728. loadComplete: YES];
  729. }
  730. else
  731. {
  732. [self didLoadBytes: [d subdataWithRange: r]
  733. loadComplete: NO];
  734. [self cancelLoadInBackground];
  735. }
  736. }
  737. else
  738. {
  739. /*
  740. * Report partial data if possible.
  741. */
  742. if ([parser isInBody])
  743. {
  744. d = [parser data];
  745. r = NSMakeRange(bodyPos, [d length] - bodyPos);
  746. bodyPos = [d length];
  747. [self didLoadBytes: [d subdataWithRange: r]
  748. loadComplete: NO];
  749. }
  750. }
  751. if (complete == NO && readCount == 0)
  752. {
  753. /* The read failed ... dropped, but parsing is not complete.
  754. * The request was sent, so we can't know whether it was
  755. * lost in the network or the remote end received it and
  756. * the response was lost.
  757. */
  758. if (debug == YES)
  759. {
  760. NSLog(@"HTTP response not received - %@", parser);
  761. }
  762. [self endLoadInBackground];
  763. [self backgroundLoadDidFailWithReason: @"Response parse failed"];
  764. }
  765. if (sock != nil && connectionState == reading)
  766. {
  767. if ([sock readInProgress] == NO)
  768. {
  769. [sock readInBackgroundAndNotify];
  770. }
  771. }
  772. }
  773. DESTROY(self);
  774. }
  775. - (void) bgdTunnelRead: (NSNotification*) not
  776. {
  777. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  778. NSDictionary *dict = [not userInfo];
  779. NSData *d;
  780. GSMimeParser *p = [GSMimeParser new];
  781. IF_NO_GC([self retain];)
  782. if (debug)
  783. NSLog(@"%@ %p %s", NSStringFromSelector(_cmd), self, keepalive?"K":"");
  784. d = [dict objectForKey: NSFileHandleNotificationDataItem];
  785. if (debug == YES) debugRead(self, d);
  786. if ([d length] > 0)
  787. {
  788. [dat appendData: d];
  789. }
  790. [p parse: dat];
  791. if ([p isInBody] == YES || [d length] == 0)
  792. {
  793. GSMimeHeader *info;
  794. NSString *val;
  795. NSNumber *num;
  796. [p parse: nil];
  797. info = [[p mimeDocument] headerNamed: @"http"];
  798. val = [info objectForKey: NSHTTPPropertyServerHTTPVersionKey];
  799. if (val != nil)
  800. [pageInfo setObject: val forKey: NSHTTPPropertyServerHTTPVersionKey];
  801. num = [info objectForKey: NSHTTPPropertyStatusCodeKey];
  802. if (num != nil)
  803. [pageInfo setObject: num forKey: NSHTTPPropertyStatusCodeKey];
  804. val = [info objectForKey: NSHTTPPropertyStatusReasonKey];
  805. if (val != nil)
  806. [pageInfo setObject: val forKey: NSHTTPPropertyStatusReasonKey];
  807. [nc removeObserver: self
  808. name: NSFileHandleReadCompletionNotification
  809. object: sock];
  810. [dat setLength: 0];
  811. tunnel = NO;
  812. }
  813. else
  814. {
  815. if ([sock readInProgress] == NO)
  816. {
  817. [sock readInBackgroundAndNotify];
  818. }
  819. }
  820. RELEASE(p);
  821. DESTROY(self);
  822. }
  823. - (void) loadInBackground
  824. {
  825. self->challenged = 0;
  826. [self _tryLoadInBackground: nil];
  827. }
  828. - (void) endLoadInBackground
  829. {
  830. DESTROY(wData);
  831. NSResetMapTable(wProperties);
  832. if (connectionState != idle)
  833. {
  834. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  835. [nc removeObserver: self name: nil object: sock];
  836. [sock closeFile];
  837. DESTROY(sock);
  838. connectionState = idle;
  839. }
  840. [super endLoadInBackground];
  841. }
  842. - (void) bgdConnect: (NSNotification*)notification
  843. {
  844. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  845. NSDictionary *userInfo = [notification userInfo];
  846. NSMutableString *s;
  847. NSString *e;
  848. NSString *method;
  849. NSString *path;
  850. IF_NO_GC([self retain];)
  851. if (debug)
  852. NSLog(@"%@ %p %s", NSStringFromSelector(_cmd), self, keepalive?"K":"");
  853. path = [[[u fullPath] stringByTrimmingSpaces]
  854. stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
  855. if ([path length] == 0)
  856. {
  857. path = @"/";
  858. }
  859. /*
  860. * See if the connection attempt caused an error.
  861. */
  862. e = [userInfo objectForKey: GSFileHandleNotificationError];
  863. if (e != nil)
  864. {
  865. NSLog(@"Unable to connect to %@:%@ via socket ... %@",
  866. [sock socketAddress], [sock socketService], e);
  867. /*
  868. * Tell superclass that the load failed - let it do housekeeping.
  869. */
  870. [self endLoadInBackground];
  871. [self backgroundLoadDidFailWithReason:
  872. [NSString stringWithFormat: @"Failed to connect: %@", e]];
  873. DESTROY(self);
  874. return;
  875. }
  876. [nc removeObserver: self
  877. name: GSFileHandleConnectCompletionNotification
  878. object: sock];
  879. /*
  880. * Build HTTP request.
  881. */
  882. /*
  883. * If SSL via proxy, set up tunnel first
  884. */
  885. if ([[u scheme] isEqualToString: @"https"]
  886. && [[request objectForKey: GSHTTPPropertyProxyHostKey] length] > 0)
  887. {
  888. NSRunLoop *loop = [NSRunLoop currentRunLoop];
  889. NSString *cmd;
  890. NSTimeInterval last = 0.0;
  891. NSTimeInterval limit = 0.01;
  892. NSData *buf;
  893. NSDate *when;
  894. int status;
  895. NSString *version;
  896. version = [request objectForKey: NSHTTPPropertyServerHTTPVersionKey];
  897. if (version == nil)
  898. {
  899. version = httpVersion;
  900. }
  901. if ([u port] == nil)
  902. {
  903. cmd = [NSString stringWithFormat: @"CONNECT %@:443 HTTP/%@\r\n\r\n",
  904. [u host], version];
  905. }
  906. else
  907. {
  908. cmd = [NSString stringWithFormat: @"CONNECT %@:%@ HTTP/%@\r\n\r\n",
  909. [u host], [u port], version];
  910. }
  911. /*
  912. * Set up default status for if connection is lost.
  913. */
  914. [pageInfo setObject: @"1.0" forKey: NSHTTPPropertyServerHTTPVersionKey];
  915. [pageInfo setObject: [NSNumber numberWithInt: 503]
  916. forKey: NSHTTPPropertyStatusCodeKey];
  917. [pageInfo setObject: @"Connection dropped by proxy server"
  918. forKey: NSHTTPPropertyStatusReasonKey];
  919. tunnel = YES;
  920. [nc addObserver: self
  921. selector: @selector(bgdWrite:)
  922. name: GSFileHandleWriteCompletionNotification
  923. object: sock];
  924. buf = [cmd dataUsingEncoding: NSASCIIStringEncoding];
  925. if (debug == YES) debugWrite(self, buf);
  926. [sock writeInBackgroundAndNotify: buf];
  927. when = [NSDate alloc];
  928. while (tunnel == YES)
  929. {
  930. if (limit < 1.0)
  931. {
  932. NSTimeInterval tmp = limit;
  933. limit += last;
  934. last = tmp;
  935. }
  936. when = [when initWithTimeIntervalSinceNow: limit];
  937. [loop runUntilDate: when];
  938. }
  939. RELEASE(when);
  940. status = [[pageInfo objectForKey: NSHTTPPropertyStatusCodeKey] intValue];
  941. if (status != 200)
  942. {
  943. [self endLoadInBackground];
  944. [self backgroundLoadDidFailWithReason: @"Failed proxy tunneling"];
  945. DESTROY(self);
  946. return;
  947. }
  948. }
  949. if ([[u scheme] isEqualToString: @"https"])
  950. {
  951. /* If we are an https connection, negotiate secure connection.
  952. * Make sure we are not an observer of the file handle while
  953. * it is connecting...
  954. */
  955. [nc removeObserver: self name: nil object: sock];
  956. if ([sock sslConnect] == NO)
  957. {
  958. if (debug)
  959. NSLog(@"%@ %p %s Failed to make ssl connect",
  960. NSStringFromSelector(_cmd), self, keepalive?"K":"");
  961. [self endLoadInBackground];
  962. [self backgroundLoadDidFailWithReason:
  963. @"Failed to make ssl connect"];
  964. DESTROY(self);
  965. return;
  966. }
  967. }
  968. /*
  969. * Set up request - differs for proxy version unless tunneling via ssl.
  970. */
  971. method = [request objectForKey: GSHTTPPropertyMethodKey];
  972. if (method == nil)
  973. {
  974. if ([wData length] > 0)
  975. {
  976. method = @"POST";
  977. }
  978. else
  979. {
  980. method = @"GET";
  981. }
  982. }
  983. if ([[request objectForKey: GSHTTPPropertyProxyHostKey] length] > 0
  984. && [[u scheme] isEqualToString: @"https"] == NO)
  985. {
  986. if ([u port] == nil)
  987. {
  988. s = [[NSMutableString alloc] initWithFormat: @"%@ http://%@%@",
  989. method, [u host], path];
  990. }
  991. else
  992. {
  993. s = [[NSMutableString alloc] initWithFormat: @"%@ http://%@:%@%@",
  994. method, [u host], [u port], path];
  995. }
  996. }
  997. else // no proxy
  998. {
  999. s = [[NSMutableString alloc] initWithFormat: @"%@ %@",
  1000. method, path];
  1001. }
  1002. [self bgdApply: s];
  1003. RELEASE(s);
  1004. DESTROY(self);
  1005. }
  1006. - (void) bgdWrite: (NSNotification*)notification
  1007. {
  1008. NSNotificationCenter *nc;
  1009. NSDictionary *userInfo = [notification userInfo];
  1010. NSString *e;
  1011. IF_NO_GC([self retain];)
  1012. if (debug)
  1013. NSLog(@"%@ %p %s", NSStringFromSelector(_cmd), self, keepalive?"K":"");
  1014. e = [userInfo objectForKey: GSFileHandleNotificationError];
  1015. if (e != nil)
  1016. {
  1017. tunnel = NO;
  1018. if (keepalive == YES)
  1019. {
  1020. /*
  1021. * The write failed ... connection dropped ... and we
  1022. * are re-using an existing connection (keepalive = YES)
  1023. * then we may try again with a new connection.
  1024. */
  1025. nc = [NSNotificationCenter defaultCenter];
  1026. [nc removeObserver: self name: nil object: sock];
  1027. [sock closeFile];
  1028. DESTROY(sock);
  1029. connectionState = idle;
  1030. if (debug)
  1031. NSLog(@"%@ %p restart on new connection",
  1032. NSStringFromSelector(_cmd), self);
  1033. [self _tryLoadInBackground: u];
  1034. return;
  1035. }
  1036. NSLog(@"Failed to write command to socket - %@ %p %s",
  1037. e, self, keepalive?"K":"");
  1038. /*
  1039. * Tell superclass that the load failed - let it do housekeeping.
  1040. */
  1041. [self endLoadInBackground];
  1042. [self backgroundLoadDidFailWithReason:
  1043. [NSString stringWithFormat: @"Failed to write request: %@", e]];
  1044. DESTROY(self);
  1045. return;
  1046. }
  1047. else
  1048. {
  1049. /*
  1050. * Don't watch for write completions any more.
  1051. */
  1052. nc = [NSNotificationCenter defaultCenter];
  1053. [nc removeObserver: self
  1054. name: GSFileHandleWriteCompletionNotification
  1055. object: sock];
  1056. /*
  1057. * Ok - write completed, let's read the response.
  1058. */
  1059. if (tunnel == YES)
  1060. {
  1061. [nc addObserver: self
  1062. selector: @selector(bgdTunnelRead:)
  1063. name: NSFileHandleReadCompletionNotification
  1064. object: sock];
  1065. }
  1066. else
  1067. {
  1068. bodyPos = 0;
  1069. [nc addObserver: self
  1070. selector: @selector(bgdRead:)
  1071. name: NSFileHandleReadCompletionNotification
  1072. object: sock];
  1073. }
  1074. if ([sock readInProgress] == NO)
  1075. {
  1076. [sock readInBackgroundAndNotify];
  1077. }
  1078. connectionState = reading;
  1079. }
  1080. DESTROY(self);
  1081. }
  1082. /**
  1083. * If necessary, this method calls -loadInForeground to send a
  1084. * request to the webserver, and get a page back. It then returns
  1085. * the property for the specified key -
  1086. * <list>
  1087. * <item>
  1088. * NSHTTPPropertyStatusCodeKey - numeric status code returned
  1089. * by the last request.
  1090. * </item>
  1091. * <item>
  1092. * NSHTTPPropertyStatusReasonKey - text describing status of
  1093. * the last request
  1094. * </item>
  1095. * <item>
  1096. * NSHTTPPropertyServerHTTPVersionKey - <code>http</code>
  1097. * version supported by remote server
  1098. * </item>
  1099. * <item>
  1100. * Other keys are taken to be the names of <code>http</code>
  1101. * headers and the corresponding header value (or nil if there
  1102. * is none) is returned.
  1103. * </item>
  1104. * </list>
  1105. */
  1106. - (id) propertyForKey: (NSString*) propertyKey
  1107. {
  1108. if (document == nil)
  1109. [self loadInForeground];
  1110. return [self propertyForKeyIfAvailable: propertyKey];
  1111. }
  1112. - (id) propertyForKeyIfAvailable: (NSString*) propertyKey
  1113. {
  1114. id result = [pageInfo objectForKey: propertyKey];
  1115. if (result == nil)
  1116. {
  1117. NSString *key = [propertyKey lowercaseString];
  1118. NSArray *array = [document headersNamed: key];
  1119. if ([array count] == 0)
  1120. {
  1121. return nil;
  1122. }
  1123. else if ([array count] == 1)
  1124. {
  1125. GSMimeHeader *hdr = [array objectAtIndex: 0];
  1126. result = [hdr value];
  1127. }
  1128. else
  1129. {
  1130. NSEnumerator *enumerator = [array objectEnumerator];
  1131. GSMimeHeader *val;
  1132. result = [NSMutableArray arrayWithCapacity: [array count]];
  1133. while ((val = [enumerator nextObject]) != nil)
  1134. {
  1135. [result addObject: [val value]];
  1136. }
  1137. }
  1138. }
  1139. return result;
  1140. }
  1141. - (void) setDebug: (BOOL)flag
  1142. {
  1143. debug = flag;
  1144. }
  1145. - (void) setReturnAll: (BOOL)flag
  1146. {
  1147. returnAll = flag;
  1148. }
  1149. - (void) _tryLoadInBackground: (NSURL*)fromURL
  1150. {
  1151. NSNotificationCenter *nc;
  1152. NSString *host = nil;
  1153. NSString *port = nil;
  1154. NSString *s;
  1155. /*
  1156. * Don't start a load if one is in progress.
  1157. */
  1158. if (connectionState != idle)
  1159. {
  1160. NSLog(@"Attempt to load an http handle which is not idle ... ignored");
  1161. return;
  1162. }
  1163. [dat setLength: 0];
  1164. RELEASE(document);
  1165. RELEASE(parser);
  1166. [pageInfo removeAllObjects];
  1167. parser = [GSMimeParser new];
  1168. document = RETAIN([parser mimeDocument]);
  1169. /*
  1170. * First time round, fromURL is nil, so we use the url ivar and
  1171. * we notify that the load is begining. On retries we get a real
  1172. * value in fromURL to use.
  1173. */
  1174. if (fromURL == nil)
  1175. {
  1176. redirects = 0;
  1177. ASSIGN(u, url);
  1178. [self beginLoadInBackground];
  1179. }
  1180. else
  1181. {
  1182. ASSIGN(u, fromURL);
  1183. }
  1184. host = [u host];
  1185. port = (id)[u port];
  1186. if (port != nil)
  1187. {
  1188. port = [NSString stringWithFormat: @"%u", [port intValue]];
  1189. }
  1190. else
  1191. {
  1192. port = [u scheme];
  1193. }
  1194. if ([port isEqualToString: @"https"])
  1195. {
  1196. port = @"443";
  1197. }
  1198. else if ([port isEqualToString: @"http"])
  1199. {
  1200. port = @"80";
  1201. }
  1202. /* An existing socket with keepalive may have been closed by the other
  1203. * end. The portable way to detect it is to run the runloop once to
  1204. * allow us to be sent a notification about end-of-file.
  1205. * On unix systems (google told me it is not reliable on windows) we can
  1206. * simply peek on the file descriptor for a much more efficient check.
  1207. */
  1208. if (sock != nil)
  1209. {
  1210. #if defined(__MINGW__)
  1211. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
  1212. NSRunLoop *loop = [NSRunLoop currentRunLoop];
  1213. NSFileHandle *test = RETAIN(sock);
  1214. if (debug)
  1215. {
  1216. NSLog(@"%@ %p check for reusable socket",
  1217. NSStringFromSelector(_cmd), self);
  1218. }
  1219. [nc addObserver: self
  1220. selector: @selector(bgdRead:)
  1221. name: NSFileHandleReadCompletionNotification
  1222. object: test];
  1223. if ([test readInProgress] == NO)
  1224. {
  1225. [test readInBackgroundAndNotify];
  1226. }
  1227. [loop acceptInputForMode: NSDefaultRunLoopMode
  1228. beforeDate: nil];
  1229. [nc removeObserver: self
  1230. name: nil
  1231. object: test];
  1232. RELEASE(test);
  1233. #else
  1234. int fd = [sock fileDescriptor];
  1235. if (debug)
  1236. {
  1237. NSLog(@"%@ %p check for reusable socket",
  1238. NSStringFromSelector(_cmd), self);
  1239. }
  1240. if (fd >= 0)
  1241. {
  1242. int result;
  1243. unsigned char c;
  1244. #if !defined(MSG_DONTWAIT)
  1245. #define MSG_DONTWAIT 0
  1246. #endif
  1247. result = recv(fd, &c, 1, MSG_PEEK | MSG_DONTWAIT);
  1248. if (result == 0 || (result < 0 && errno != EAGAIN && errno != EINTR))
  1249. {
  1250. DESTROY(sock);
  1251. }
  1252. }
  1253. else
  1254. {
  1255. DESTROY(sock);
  1256. }
  1257. #endif
  1258. if (debug)
  1259. {
  1260. if (sock == nil)
  1261. {
  1262. NSLog(@"%@ %p socket closed by remote",
  1263. NSStringFromSelector(_cmd), self);
  1264. }
  1265. else
  1266. {
  1267. NSLog(@"%@ %p socket is still open",
  1268. NSStringFromSelector(_cmd), self);
  1269. }
  1270. }
  1271. }
  1272. if (sock == nil)
  1273. {
  1274. keepalive = NO; // New connection
  1275. /*
  1276. * If we have a local address specified,
  1277. * tell the file handle to bind to it.
  1278. */
  1279. s = [request objectForKey: GSHTTPPropertyLocalHostKey];
  1280. if ([s length] > 0)
  1281. {
  1282. s = [NSString stringWithFormat: @"bind-%@", s];
  1283. }
  1284. else
  1285. {
  1286. s = @"tcp"; // Bind to any.
  1287. }
  1288. if ([[request objectForKey: GSHTTPPropertyProxyHostKey] length] == 0)
  1289. {
  1290. if ([[u scheme] isEqualToString: @"https"])
  1291. {
  1292. NSString *cert;
  1293. if (sslClass == 0)
  1294. {
  1295. [self backgroundLoadDidFailWithReason:
  1296. @"https not supported ... needs SSL bundle"];
  1297. return;
  1298. }
  1299. sock = [sslClass fileHandleAsClientInBackgroundAtAddress: host
  1300. service: port
  1301. protocol: s];
  1302. cert = [request objectForKey: GSHTTPPropertyCertificateFileKey];
  1303. if ([cert length] > 0)
  1304. {
  1305. NSString *key;
  1306. NSString *pwd;
  1307. key = [request objectForKey: GSHTTPPropertyKeyFileKey];
  1308. pwd = [request objectForKey: GSHTTPPropertyPasswordKey];
  1309. [sock sslSetCertificate: cert privateKey: key PEMpasswd: pwd];
  1310. }
  1311. }
  1312. else
  1313. {
  1314. sock = [NSFileHandle fileHandleAsClientInBackgroundAtAddress: host
  1315. service: port
  1316. protocol: s];
  1317. }
  1318. }
  1319. else
  1320. {
  1321. if ([[request objectForKey: GSHTTPPropertyProxyPortKey] length] == 0)
  1322. {
  1323. [request setObject: @"8080" forKey: GSHTTPPropertyProxyPortKey];
  1324. }
  1325. if ([[u scheme] isEqualToString: @"https"])
  1326. {
  1327. if (sslClass == 0)
  1328. {
  1329. [self backgroundLoadDidFailWithReason:
  1330. @"https not supported ... needs SSL bundle"];
  1331. return;
  1332. }
  1333. host = [request objectForKey: GSHTTPPropertyProxyHostKey];
  1334. port = [request objectForKey: GSHTTPPropertyProxyPortKey];
  1335. sock = [sslClass fileHandleAsClientInBackgroundAtAddress: host
  1336. service: port
  1337. protocol: s];
  1338. }
  1339. else
  1340. {
  1341. host = [request objectForKey: GSHTTPPropertyProxyHostKey];
  1342. port = [request objectForKey: GSHTTPPropertyProxyPortKey];
  1343. sock = [NSFileHandle
  1344. fileHandleAsClientInBackgroundAtAddress: host
  1345. service: port
  1346. protocol: s];
  1347. }
  1348. }
  1349. if (sock == nil)
  1350. {
  1351. /*
  1352. * Tell superclass that the load failed - let it do housekeeping.
  1353. */
  1354. [self backgroundLoadDidFailWithReason:
  1355. [NSString stringWithFormat: @"Unable to connect to %@:%@ ... %@",
  1356. host, port, [NSError _last]]];
  1357. return;
  1358. }
  1359. IF_NO_GC([sock retain];)
  1360. nc = [NSNotificationCenter defaultCenter];
  1361. [nc addObserver: self
  1362. selector: @selector(bgdConnect:)
  1363. name: GSFileHandleConnectCompletionNotification
  1364. object: sock];
  1365. connectionState = connecting;
  1366. if (debug)
  1367. {
  1368. NSLog(@"%@ %p start connect to %@:%@",
  1369. NSStringFromSelector(_cmd), self, host, port);
  1370. }
  1371. }
  1372. else
  1373. {
  1374. NSString *method;
  1375. NSString *path;
  1376. NSString *basic;
  1377. // Stop waiting for connection to be closed down.
  1378. nc = [NSNotificationCenter defaultCenter];
  1379. [nc removeObserver: self
  1380. name: NSFileHandleReadCompletionNotification
  1381. object: sock];
  1382. /* Reusing a connection. Set flag to say that it has been kept
  1383. * alive and we don't know if the other end has dropped it
  1384. * until we write to it and read some response.
  1385. */
  1386. keepalive = YES;
  1387. method = [request objectForKey: GSHTTPPropertyMethodKey];
  1388. if (method == nil)
  1389. {
  1390. if ([wData length] > 0)
  1391. {
  1392. method = @"POST";
  1393. }
  1394. else
  1395. {
  1396. method = @"GET";
  1397. }
  1398. }
  1399. path = [[[u fullPath] stringByTrimmingSpaces]
  1400. stringByAddingPercentEscapesUsingEncoding: NSUTF8StringEncoding];
  1401. if ([path length] == 0)
  1402. {
  1403. path = @"/";
  1404. }
  1405. basic = [NSString stringWithFormat: @"%@ %@", method, path];
  1406. [self bgdApply: basic];
  1407. }
  1408. }
  1409. /**
  1410. * Writes the specified data as the body of an <code>http</code>
  1411. * or <code>https</code> request to the web server.
  1412. * Returns YES on success,
  1413. * NO on failure. By default, this method performs a POST operation.
  1414. * On completion, the resource data for this handle is set to the
  1415. * page returned by the request.
  1416. */
  1417. - (BOOL) writeData: (NSData*)d
  1418. {
  1419. ASSIGN(wData, d);
  1420. return YES;
  1421. }
  1422. /**
  1423. * Sets a property to be used in the next request made by this handle.
  1424. * The property is set as a header in the next request, unless it is
  1425. * one of the following -
  1426. * <list>
  1427. * <item>
  1428. * GSHTTPPropertyBodyKey - set an NSData item to be sent to
  1429. * the server as the body of the request.
  1430. * </item>
  1431. * <item>
  1432. * GSHTTPPropertyMethodKey - override the default method of
  1433. * the request (eg. &quot;PUT&quot;).
  1434. * </item>
  1435. * <item>
  1436. * GSHTTPPropertyProxyHostKey - specify the name or IP address
  1437. * of a host to proxy through.
  1438. * </item>
  1439. * <item>
  1440. * GSHTTPPropertyProxyPortKey - specify the port number to
  1441. * connect to on the proxy host. If not give, this defaults
  1442. * to 8080 for <code>http</code> and 4430 for <code>https</code>.
  1443. * </item>
  1444. * <item>
  1445. * Any NSHTTPProperty... key
  1446. * </item>
  1447. * </list>
  1448. */
  1449. - (BOOL) writeProperty: (id) property forKey: (NSString*) propertyKey
  1450. {
  1451. if (propertyKey == nil
  1452. || [propertyKey isKindOfClass: [NSString class]] == NO)
  1453. {
  1454. [NSException raise: NSInvalidArgumentException
  1455. format: @"%@ %p with invalid key", NSStringFromSelector(_cmd), self];
  1456. }
  1457. if ([propertyKey hasPrefix: @"GSHTTPProperty"]
  1458. || [propertyKey hasPrefix: @"NSHTTPProperty"])
  1459. {
  1460. if (property == nil)
  1461. {
  1462. [request removeObjectForKey: propertyKey];
  1463. }
  1464. else
  1465. {
  1466. [request setObject: property forKey: propertyKey];
  1467. }
  1468. }
  1469. else
  1470. {
  1471. if (property == nil)
  1472. {
  1473. NSMapRemove(wProperties, (void*)propertyKey);
  1474. }
  1475. else
  1476. {
  1477. NSMapInsert(wProperties, (void*)propertyKey, (void*)property);
  1478. }
  1479. }
  1480. return YES;
  1481. }
  1482. @end