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