]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WOHTTPConnection.m
bumped dyld versions, updated Xcode build to be in sync with latest commits
[sope] / sope-appserver / NGObjWeb / WOHTTPConnection.m
1 /*
2   Copyright (C) 2000-2005 SKYRIX Software AG
3
4   This file is part of SOPE.
5
6   SOPE is free software; you can redistribute it and/or modify it under
7   the terms of the GNU Lesser General Public License as published by the
8   Free Software Foundation; either version 2, or (at your option) any
9   later version.
10
11   SOPE is distributed in the hope that it will be useful, but WITHOUT ANY
12   WARRANTY; without even the implied warranty of MERCHANTABILITY or
13   FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
14   License for more details.
15
16   You should have received a copy of the GNU Lesser General Public
17   License along with SOPE; see the file COPYING.  If not, write to the
18   Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA
19   02111-1307, USA.
20 */
21
22 #include <NGObjWeb/WOHTTPConnection.h>
23 #include <NGObjWeb/WOCookie.h>
24 #include <NGObjWeb/WORequest.h>
25 #include <NGObjWeb/WOResponse.h>
26 #include <NGObjWeb/WOCookie.h>
27 #include <NGObjWeb/WORunLoop.h>
28 #include <NGStreams/NGStreams.h>
29 #include <NGStreams/NGCTextStream.h>
30 #include <NGStreams/NGBufferedStream.h>
31 #include <NGStreams/NGNet.h>
32 #include <NGHttp/NGHttp.h>
33 #include <NGMime/NGMime.h>
34 #import <Foundation/Foundation.h>
35 #include "WOSimpleHTTPParser.h"
36 #include "WOHttpAdaptor/WORecordRequestStream.h"
37
38 @interface WOHTTPConnection(Privates)
39 - (BOOL)_connect;
40 - (void)_disconnect;
41 - (void)_unregisterNotification;
42 @end
43
44 @interface WOCookie(Privates)
45 + (id)cookieWithString:(NSString *)_string;
46 @end
47
48 NSString *WOHTTPConnectionCanReadResponse = @"WOHTTPConnectionCanReadResponse";
49
50 @interface NSURL(SocketAddress)
51 - (id)socketAddressForURL;
52 - (BOOL)shouldUseWOProxyServer;
53 @end
54
55 @interface WOHTTPConnection(Privates2)
56 + (NSString *)proxyServer;
57 + (NSURL *)proxyServerURL;
58 + (NSArray *)noProxySuffixes;
59 @end
60
61 @implementation WOHTTPConnection
62
63 static Class SSLSocketClass  = Nil;
64 static BOOL  useSimpleParser = YES;
65 static NSString *proxyServer = nil;
66 static NSArray  *noProxy     = nil;
67 static BOOL doDebug   = NO;
68 static BOOL logStream = NO;
69
70 + (int)version {
71   return 3;
72 }
73
74 + (void)initialize {
75   NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
76   static BOOL didInit = NO;
77   if (didInit) return;
78   didInit = YES;
79     
80   useSimpleParser = [ud boolForKey:@"WOHTTPConnectionUseSimpleParser"];
81   proxyServer     = [ud stringForKey:@"WOProxyServer"];
82   noProxy         = [ud arrayForKey:@"WONoProxySuffixes"];
83   doDebug         = [ud boolForKey:@"WODebugHTTPConnection"];
84   logStream       = [ud boolForKey:@"WODebugHTTPConnectionLogStream"];
85 }
86
87 + (NSString *)proxyServer {
88   return proxyServer;
89 }
90 + (NSURL *)proxyServerURL {
91   NSString *ps;
92
93   ps = [self proxyServer];
94   if ([ps length] == 0)
95     return nil;
96   
97   return [NSURL URLWithString:ps];
98 }
99 + (NSArray *)noProxySuffixes {
100   return noProxy;
101 }
102
103 - (id)initWithNSURL:(NSURL *)_url {
104   if ((self = [super init])) {
105     self->url      = [_url retain];
106     self->useSSL   = [[_url scheme] isEqualToString:@"https"];
107     self->useProxy = [_url shouldUseWOProxyServer];
108     
109     if (self->useSSL) {
110       static BOOL didCheck = NO;
111       if (!didCheck) {
112         didCheck = YES;
113         SSLSocketClass = NSClassFromString(@"NGActiveSSLSocket");
114       }
115     }
116   }
117   return self;
118 }
119
120 - (id)initWithURL:(id)_url {
121   NSURL *lurl;
122   
123   /* create an NSURL object if necessary */
124   lurl = [_url isKindOfClass:[NSURL class]]
125     ? _url
126     : [NSURL URLWithString:[_url stringValue]];
127   if (lurl == nil) {
128     if (doDebug)
129       [self logWithFormat:@"could not construct URL from object '%@' !", _url];
130     [self release];
131     return nil;
132   }
133   if (doDebug)
134     [self logWithFormat:@"init with URL: %@", lurl];
135   return [self initWithNSURL:lurl];
136 }
137
138 - (id)initWithHost:(NSString *)_hostName onPort:(unsigned int)_port 
139   secure:(BOOL)_flag
140 {
141   NSString *s;
142   
143   s = [NSString stringWithFormat:@"http%s://%@:%i/",
144                   _flag ? "s" : "", _hostName, 
145                   _port == 0 ? (_flag?443:80) : _port];
146   return [self initWithURL:s];
147 }
148 - (id)initWithHost:(NSString *)_hostName onPort:(unsigned int)_port {
149   return [self initWithHost:_hostName onPort:_port secure:NO];
150 }
151 - (id)init {
152   return [self initWithHost:@"localhost" onPort:80 secure:NO];
153 }
154
155 - (void)dealloc {
156   [self _unregisterNotification];
157   [self->lastException release];
158   [self->log      release];
159   [self->io       release];
160   [self->socket   release];
161   [self->url      release];
162   [super dealloc];
163 }
164
165 /* error handling */
166
167 - (NSException *)lastException {
168   return self->lastException;
169 }
170
171 - (BOOL)isDebuggingEnabled {
172   return doDebug ? YES : NO;
173 }
174 - (NSString *)loggingPrefix {
175   /* improve perf ... */
176   if (self->url) {
177     return [NSString stringWithFormat:@"WOHTTP[0x%08X]<%@>", 
178                        self, [self->url absoluteString]];
179   }
180   else
181     return [NSString stringWithFormat:@"WOHTTP[0x%08X]", self];
182 }
183
184 /* accessors */
185
186 - (NSString *)hostName {
187   return [self->url host];
188 }
189
190 /* IO */
191
192 - (BOOL)_connect {
193   id<NGSocketAddress> address;
194   
195   [self _disconnect];
196   
197 #if DEBUG
198   NSAssert(self->socket == nil, @"socket still available after disconnect");
199   NSAssert(self->io == nil,     @"IO stream still available after disconnect");
200 #endif
201   
202   if (self->useSSL) {
203     if (SSLSocketClass == Nil) {
204       /* no SSL support is available */
205       static BOOL didLog = NO;
206       if (!didLog) {
207         didLog = YES;
208         NSLog(@"NOTE: SSL support is not available !");
209       }
210       return NO;
211     }
212   }
213   
214   if (self->useProxy) {
215     NSURL *purl;
216     
217     purl = [[self class] proxyServerURL];
218     address = [purl socketAddressForURL];
219   }
220   else {
221     address = [self->url socketAddressForURL];
222   }
223   if (address == nil) {
224     [self debugWithFormat:@"got no address for connect .."];
225     return NO;
226   }
227   
228   NS_DURING {
229     self->socket = self->useSSL
230       ? [SSLSocketClass socketConnectedToAddress:address]
231       : [NGActiveSocket socketConnectedToAddress:address];
232   }
233   NS_HANDLER {
234 #if 0
235     fprintf(stderr, "couldn't create socket: %s\n",
236             [[localException description] cString]);
237 #endif
238     ASSIGN(self->lastException, localException);
239     self->socket = nil;
240   }
241   NS_ENDHANDLER;
242   
243   if (self->socket == nil) {
244     [self debugWithFormat:@"socket is not setup: %@", [self lastException]];
245     return NO;
246   }
247   
248   if (![self->socket isConnected]) {
249     self->socket = nil;
250     [self debugWithFormat:@"socket is not connected .."];
251     return NO;
252   }
253   
254   self->socket = [self->socket retain];
255   
256   [(NGActiveSocket *)self->socket setSendTimeout:[self sendTimeout]];
257   [(NGActiveSocket *)self->socket setReceiveTimeout:[self receiveTimeout]];
258   
259   if (self->socket) {
260     id bStr;
261     
262     bStr = [[NGBufferedStream alloc] initWithSource:self->socket];
263     if (logStream)
264       self->log = [[WORecordRequestStream alloc] initWithSource:bStr];
265     else
266       self->log = nil;
267     
268     self->io = 
269       [[NGCTextStream alloc] initWithSource:(id)(self->log?self->log:bStr)];
270     [bStr release]; bStr = nil;
271   }
272   
273   return YES;
274 }
275 - (void)_disconnect {
276   [self->log release]; self->log = nil;
277   [self->io  release]; self->io  = nil;
278   
279   NS_DURING
280     (void)[self->socket shutdown];
281   NS_HANDLER {}
282   NS_ENDHANDLER;
283   
284   [self->socket release]; self->socket = nil;
285 }
286
287 /* runloop based IO */
288
289 - (NSNotificationCenter *)notificationCenter {
290   return [NSNotificationCenter defaultCenter];
291 }
292 - (NSRunLoop *)runLoop {
293   return [NSRunLoop currentRunLoop];
294 }
295 - (NSString *)runLoopMode {
296   return NSDefaultRunLoopMode;
297 }
298
299 - (void)_socketActivated:(NSNotification *)_n {
300   if ([_n object] != self->socket)
301     return;
302
303 #if DEBUG && 0  
304   [self debugWithFormat:@"socket activated ..."];
305 #endif
306   
307   [[self notificationCenter]
308          postNotificationName:WOHTTPConnectionCanReadResponse
309          object:self];
310 }
311
312 - (void)_registerForNotification {
313   NSRunLoop *rl;
314   
315   if (self->didRegisterForNotification)
316     return;
317
318   [[self notificationCenter]
319          addObserver:self selector:@selector(_socketActivated:)
320          name:NSFileObjectBecameActiveNotificationName
321          object:self->socket];
322   
323   rl = [self runLoop];
324   [rl addFileObject:self->socket
325       activities:(NSPosixReadableActivity|NSPosixExceptionalActivity)
326       forMode:[self runLoopMode]];
327 }
328 - (void)_unregisterNotification {
329   if (!self->didRegisterForNotification)
330     return;
331   
332   [[self notificationCenter] removeObserver:self];
333   
334   [[self runLoop] removeFileObject:self->socket
335                   forMode:[self runLoopMode]];
336 }
337
338 /* logging IO */
339
340 - (void)logRequest:(WORequest *)_response data:(NSData *)_data {
341   if (_data == nil) return;
342   
343 #if 1
344   NSLog(@"request is\n");
345   fflush(stderr);
346   fwrite([_data bytes], 1, [_data length], stderr);
347   fflush(stderr);
348   fprintf(stderr,"\n");
349   fflush(stderr);
350 #endif
351 }
352 - (void)logResponse:(WOResponse *)_response data:(NSData *)_data {
353   if (_data == nil) return;
354   
355 #if 1
356   NSLog(@"response is\n");
357   fflush(stderr);
358   fwrite([_data bytes], 1, [_data length], stderr);
359   fflush(stderr);
360   fprintf(stderr,"\n");
361   fflush(stderr);
362 #endif
363 }
364
365 /* sending/receiving HTTP */
366
367 - (BOOL)sendRequest:(WORequest *)_request {
368   NSData *content;
369   BOOL isok = YES;
370   
371   if (doDebug)
372     [self debugWithFormat:@"send request: %@", _request];
373   
374   if (![self->socket isConnected]) {
375     if (![self _connect]) {
376       /* could not connect */
377       if (doDebug)
378         [self debugWithFormat:@"  could not connect socket"];
379       return NO;
380     }
381     /* now connected */
382   }
383   
384   content = [_request content];
385   
386   /* write request line (eg 'GET / HTTP/1.0') */
387   if (doDebug)
388     [self debugWithFormat:@"  method: %@", [_request method]];
389   
390   if (isok) isok = [self->io writeString:[_request method]];
391   if (isok) isok = [self->io writeString:@" "];
392   
393   if (self->useProxy) {
394     if (isok)
395       // TODO: check whether this produces a '//' (may need to strip uri)
396       isok = [self->io writeString:[self->url absoluteString]];
397     [self debugWithFormat:@"  wrote proxy start ..."];
398   }
399   if (isok) isok = [self->io writeString:[_request uri]];
400   
401   if (isok) isok = [self->io writeString:@" "];
402   if (isok) isok = [self->io writeString:[_request httpVersion]];
403   if (isok) isok = [self->io writeString:@"\r\n"];
404
405   /* set content-length header */
406   
407   if ([content length] > 0) {
408     [_request setHeader:[NSString stringWithFormat:@"%d", [content length]]
409               forKey:@"content-length"];
410   }
411
412   if ([[self->url scheme] hasPrefix:@"http"]) {
413     /* host header */
414     
415     if (isok) isok = [self->io writeString:@"Host: "];
416     if (isok) isok = [self->io writeString:[self hostName]];
417     if (isok) isok = [self->io writeString:@"\r\n"];
418     [self debugWithFormat:@"  wrote host header: %@", [self hostName]];
419   }
420   
421   /* write request headers */
422
423   if (isok) {
424     NSEnumerator *fields;
425     NSString *fieldName;
426     int cnt;
427     
428     fields = [[_request headerKeys] objectEnumerator];
429     cnt = 0;
430     while (isok && (fieldName = [fields nextObject])) {
431       NSEnumerator *values;
432       id value;
433
434       if ([fieldName length] == 4) {
435         if ([fieldName isEqualToString:@"host"])
436           /* did already write host ... */
437           continue;
438         if ([fieldName isEqualToString:@"Host"])
439           /* did already write host ... */
440           continue;
441       }
442       
443       values = [[_request headersForKey:fieldName] objectEnumerator];
444         
445       while ((value = [values nextObject]) && isok) {
446         if (isok) isok = [self->io writeString:fieldName];
447         if (isok) isok = [self->io writeString:@": "];
448         if (isok) isok = [self->io writeString:value];
449         if (isok) isok = [self->io writeString:@"\r\n"];
450         cnt++;
451       }
452     }
453     [self debugWithFormat:@"  wrote %i request headers ...", cnt];
454   }
455   
456   /* write some required headers */
457   
458   if ([_request headerForKey:@"accept"] == nil) {
459     if (isok) isok = [self->io writeString:@"Accept: */*\r\n"];
460     [self debugWithFormat:@"  wrote accept header ..."];
461   }
462   if ([_request headerForKey:@"user-agent"] == nil) {
463     if (isok) {
464       static NSString *s = nil;
465       if (s == nil) {
466         s = [[NSString alloc] initWithFormat:@"User-Agent: SOPE/%i.%i.%i\r\n",
467                               SOPE_MAJOR_VERSION, SOPE_MINOR_VERSION, 
468                               SOPE_SUBMINOR_VERSION];
469       }
470       isok = [self->io writeString:s];
471     }
472     [self debugWithFormat:@"  wrote user-agent header ..."];
473   }
474   
475   /* write cookie headers */
476   
477   if ([[_request cookies] count] > 0 && isok) {
478     NSEnumerator *cookies;
479     WOCookie     *cookie;
480     BOOL         isFirst;
481     int cnt;
482     
483     [self->io writeString:@"set-cookie: "];
484     cnt = 0;
485     cookies = [[_request cookies] objectEnumerator];
486     isFirst = YES;
487     while (isok && (cookie = [cookies nextObject])) {
488       if (isFirst) isFirst = NO;
489       else if (isok) isok = [self->io writeString:@"; "];
490       
491       if (isok) isok = [self->io writeString:[cookie stringValue]];
492       cnt ++;
493     }
494     if (isok) isok = [self->io writeString:@"\r\n"];
495     [self debugWithFormat:@"  wrote %i cookies ...", cnt];
496   }
497   
498   /* flush request header on socket */
499   
500   if (isok) isok = [self->io writeString:@"\r\n"];
501   if (isok) isok = [self->io flush];
502   [self debugWithFormat:@"  flushed HTTP header."];
503   
504   /* write content */
505
506   if ([content length] > 0) {
507     [self debugWithFormat:@"  writing HTTP entity (length=%i).", 
508             [content length]];
509     
510     if ([content isKindOfClass:[NSString class]]) {
511       if (isok) isok = [self->io writeString:(NSString *)content];
512     }
513     else if ([content isKindOfClass:[NSData class]]) {
514       if (isok) isok = [[self->io source]
515                          safeWriteBytes:[content bytes]
516                          count:[content length]];
517     }
518     else {
519       if (isok) isok = [self->io writeString:[content description]];
520     }
521     if (isok) isok = [self->io flush];
522   }
523   else if (doDebug) {
524     [self debugWithFormat:@"  no HTTP entity to write ..."];
525   }
526   
527   if (logStream)
528     [self logRequest:_request data:[self->log writeLog]];
529   [self->log resetWriteLog];
530   
531   [self debugWithFormat:@"=> finished:\n  url:  %@\n  sock: %@", 
532           self->url, self->socket];
533   if (!isok) {
534     ASSIGN(self->lastException, [self->socket lastException]);
535     [self->socket shutdown];
536     return NO;
537   }
538   
539   if (![self->socket isConnected])
540     return NO;
541   
542   [self _registerForNotification];
543   
544   return YES;
545 }
546
547 - (NSException *)handleResponseParsingError:(NSException *)_exception {
548     fprintf(stderr, "%s: caught: %s\n",
549             __PRETTY_FUNCTION__,
550             [[_exception description] cString]);
551     return nil;
552 }
553
554 - (WOResponse *)readResponse {
555   /* TODO: split up method */
556   WOResponse *response;
557   
558   *(&response) = nil;
559   [self _unregisterNotification];
560   
561   if (self->socket == nil) {
562     [self debugWithFormat:@"no socket available for reading response ..."];
563     return nil;
564   }
565   
566   [self debugWithFormat:@"parsing response from socket: %@", self->socket];
567   
568   if (useSimpleParser) {
569     WOSimpleHTTPParser *parser;
570     
571     [self debugWithFormat:@"  using simple HTTP parser ..."];
572     
573     parser = [[WOSimpleHTTPParser alloc] initWithStream:[self->io source]];
574     if (parser == nil)
575       return nil;
576     parser = [parser autorelease];
577     
578     if ((response = [parser parseResponse]) == nil) {
579       if (doDebug)
580         [self debugWithFormat:@"parsing failed: %@", [parser lastException]];
581     }
582   }
583   else {
584     NGHttpMessageParser *parser;
585     NGHttpResponse *mresponse;
586     NGMimeType     *ctype;
587     id body;
588     
589     *(&mresponse) = nil;
590     
591     if ((parser = [[[NGHttpMessageParser alloc] init] autorelease]) == nil)
592       return nil;
593     
594     [self debugWithFormat:@"  using MIME HTTP parser (complex parser) ..."];
595     
596     NS_DURING {
597       [parser setDelegate:self];
598       mresponse = [parser parseResponseFromStream:self->socket];
599     }
600     NS_HANDLER
601       [[self handleResponseParsingError:localException] raise];
602     NS_ENDHANDLER;
603     
604     [self debugWithFormat:@"finished parsing response: %@", mresponse];
605     
606     /* transform parsed MIME response to WOResponse */
607     
608     body = [mresponse body];
609     if (body == nil) body = [NSData data];
610     
611     response = [[[WOResponse alloc] init] autorelease];
612     [response setHTTPVersion:[mresponse httpVersion]];
613     [response setStatus:[mresponse statusCode]];
614     [response setUserInfo:[NSDictionary dictionaryWithObjectsAndKeys:
615                                           self,      @"NGHTTPConnection",
616                                           mresponse, @"NGMimeResponse",
617                                           body,      @"NGMimeBody",
618                                           nil]];
619   
620     { /* check content-type */
621       id value;
622       
623       value = [[mresponse valuesOfHeaderFieldWithName:@"content-type"] 
624                         nextObject];
625       if (value) {
626         NSString *charset;
627         
628         ctype = [NGMimeType mimeType:[value stringValue]];
629         charset = [[ctype valueOfParameter:@"charset"] lowercaseString];
630         
631         if ([charset length] == 0) {
632           /* autodetect charset ... */
633           
634           if ([[ctype type] isEqualToString:@"text"]) {
635             if ([[ctype subType] isEqualToString:@"xml"]) {
636               /* default XML encoding is UTF-8 */
637               [response setContentEncoding:NSUTF8StringEncoding];
638             }
639           }
640         }
641         else {
642           NSStringEncoding enc;
643           
644           enc = [NGMimeType stringEncodingForCharset:charset];
645           [response setContentEncoding:enc];
646         }
647         
648         [response setHeader:[ctype stringValue] forKey:@"content-type"];
649         
650       }
651       else {
652         ctype = [NGMimeType mimeType:@"application/octet-stream"];
653       }
654     }
655   
656     /* check content */
657     
658     if ([body isKindOfClass:[NSData class]]) {
659       [response setContent:body];
660     }
661     else if ([body isKindOfClass:[NSString class]]) {
662       NSData *data;
663       
664       data = [body dataUsingEncoding:[response contentEncoding]];
665       if (data)
666         [response setContent:data];
667     }
668     else if (body) {
669       /* generate data from structured body .. */
670       NGMimeBodyGenerator *gen;
671       NSData *data;
672       
673       gen = [[[NGMimeBodyGenerator alloc] init] autorelease];
674       data = [gen generateBodyOfPart:body
675                   additionalHeaders:nil
676                   delegate:self];
677       [response setContent:data];
678     }
679     
680     { /* transfer headers */
681       NSEnumerator *names;
682       NSString     *name;
683       
684       names = [mresponse headerFieldNames];
685       while ((name = [names nextObject])) {
686         NSEnumerator *values;
687         id           value;
688         
689         if ([name isEqualToString:@"content-type"])
690           continue;
691         if ([name isEqualToString:@"set-cookie"])
692           continue;
693         
694         values = [mresponse valuesOfHeaderFieldWithName:name];
695         while ((value = [values nextObject])) {
696           value = [value stringValue];
697           [response appendHeader:value forKey:name];
698         }
699       }
700     }
701     
702     { /* transfer cookies */
703       NSEnumerator *cookies;
704       NGHttpCookie *mcookie;
705   
706       cookies = [mresponse valuesOfHeaderFieldWithName:@"set-cookie"];
707       
708       while ((mcookie = [cookies nextObject])) {
709         WOCookie *woCookie;
710         
711         if (![mcookie isKindOfClass:[NGHttpCookie class]]) {
712           /* parse cookie */
713           woCookie = [WOCookie cookieWithString:[mcookie stringValue]];
714         }
715         else {
716           woCookie = [WOCookie cookieWithName:[mcookie cookieName]
717                                value:[mcookie value]
718                                path:[mcookie path]
719                                domain:[mcookie domainName]
720                                expires:[mcookie expireDate]
721                                isSecure:[mcookie needsSecureChannel]];
722         }
723         if (woCookie == nil) {
724           [self logWithFormat:
725                   @"Couldn't create WOCookie from NGHttp cookie: %@",
726                   mcookie];
727           // could not create cookie
728           continue;
729         }
730         
731         [self debugWithFormat:@"adding cookie: %@", woCookie];
732         
733         [response addCookie:woCookie];
734       }
735     }
736   }
737   
738   if (logStream)
739     [self logResponse:response data:[self->log readLog]];
740   [self->log resetReadLog];
741   
742   if (doDebug)
743     [self debugWithFormat:@"processed response: %@", response];
744   
745   /* check keep-alive */
746   {
747     NSString *conn;
748     
749     conn = [response headerForKey:@"connection"];
750     conn = [conn lowercaseString];
751     
752     if ([conn isEqualToString:@"close"]) {
753       [self setKeepAliveEnabled:NO];
754       [self _disconnect];
755     }
756     else if ([conn isEqualToString:@"keep-alive"]) {
757       [self setKeepAliveEnabled:YES];
758     }
759     else {
760       [self setKeepAliveEnabled:NO];
761       [self _disconnect];
762     }
763   }
764   
765   return response;
766 }
767
768 - (void)setKeepAliveEnabled:(BOOL)_flag {
769   self->keepAlive = _flag;
770 }
771 - (BOOL)keepAliveEnabled {
772   return self->keepAlive;
773 }
774
775 /* timeouts */
776
777 - (void)setConnectTimeout:(int)_seconds {
778   self->connectTimeout = _seconds;
779 }
780 - (int)connectTimeout {
781   return self->connectTimeout;
782 }
783
784 - (void)setReceiveTimeout:(int)_seconds {
785   self->receiveTimeout = _seconds;
786 }
787 - (int)receiveTimeout {
788   return self->receiveTimeout;
789 }
790
791 - (void)setSendTimeout:(int)_seconds {
792   self->sendTimeout = _seconds;
793 }
794 - (int)sendTimeout {
795   return self->sendTimeout;
796 }
797
798 /* description */
799
800 - (NSString *)description {
801   NSMutableString *str;
802   
803   str = [NSMutableString stringWithCapacity:128];
804   [str appendFormat:@"<%@[0x%08X]:", NSStringFromClass([self class]), self];
805   
806   if (self->url)      [str appendFormat:@" url=%@", self->url];
807   if (self->useProxy) [str appendString:@" proxy"];
808   if (self->useSSL)   [str appendString:@" SSL"];
809
810   if (self->socket) [str appendFormat:@" socket=%@", self->socket];
811   
812   [str appendString:@">"];
813   return str;
814 }
815
816 @end /* WOHTTPConnection */
817
818 @implementation NSURL(SocketAddress)
819
820 - (id)socketAddressForURL {
821   NSString *s;
822   
823   s = [self scheme];
824   
825   if ([s isEqualToString:@"http"]) {
826     int p;
827     
828     s = [self host];
829     if ([s length] == 0) s = @"localhost";
830     p = [[self port] intValue];
831     
832     return [NGInternetSocketAddress addressWithPort:p == 0 ? 80 : p onHost:s];
833   }
834   else if ([s isEqualToString:@"https"]) {
835     int p;
836     
837     s = [self host];
838     if ([s length] == 0) s = @"localhost";
839     p = [[self port] intValue];
840     
841     return [NGInternetSocketAddress addressWithPort:p == 0 ? 443 : p onHost:s];
842   }
843   else if ([s isEqualToString:@"unix"] || [s isEqualToString:@"file"]) {
844     return [NGLocalSocketAddress addressWithPath:[self path]];
845   }
846   return nil;
847 }
848
849 - (BOOL)shouldUseWOProxyServer {
850   if ([[self scheme] hasPrefix:@"http"]) {
851     NSString *h;
852     
853     if ((h = [self host]) == nil)
854       return NO;
855     
856     if ([h isEqualToString:@"127.0.0.1"])
857       return NO;
858     if ([h isEqualToString:@"localhost"])
859       return NO;
860     
861     if ([[WOHTTPConnection proxyServer] length] > 0) {
862       NSEnumerator *e;
863       NSString *suffix;
864       BOOL     useProxy;
865       
866       useProxy = YES;
867       e = [[WOHTTPConnection noProxySuffixes] objectEnumerator];
868       while ((suffix = [e nextObject])) {
869         if ([h hasSuffix:suffix]) {
870           useProxy = NO;
871           break;
872         }
873       }
874       return useProxy;
875     }
876   }
877   return NO;
878 }
879
880 @end /* NSURL(SocketAddress) */