]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WOHTTPConnection.m
fixed gcc 4.0 warnings
[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 != nil) {
260     NGBufferedStream *bStr;
261     
262     bStr = [NGBufferedStream alloc]; // keep gcc happy
263     bStr = [bStr initWithSource:self->socket];
264     if (logStream) {
265       self->log = [WORecordRequestStream alloc]; // keep gcc happy
266       self->log = [(WORecordRequestStream *)self->log initWithSource:bStr];
267     }
268     else
269       self->log = nil;
270     
271     self->io = [NGCTextStream alloc]; // keep gcc happy
272     self->io = [self->io initWithSource:(id)(self->log ? self->log : bStr)];
273     [bStr release]; bStr = nil;
274   }
275   
276   return YES;
277 }
278 - (void)_disconnect {
279   [self->log release]; self->log = nil;
280   [self->io  release]; self->io  = nil;
281   
282   NS_DURING
283     (void)[self->socket shutdown];
284   NS_HANDLER {}
285   NS_ENDHANDLER;
286   
287   [self->socket release]; self->socket = nil;
288 }
289
290 /* runloop based IO */
291
292 - (NSNotificationCenter *)notificationCenter {
293   return [NSNotificationCenter defaultCenter];
294 }
295 - (NSRunLoop *)runLoop {
296   return [NSRunLoop currentRunLoop];
297 }
298 - (NSString *)runLoopMode {
299   return NSDefaultRunLoopMode;
300 }
301
302 - (void)_socketActivated:(NSNotification *)_n {
303   if ([_n object] != self->socket)
304     return;
305
306 #if DEBUG && 0  
307   [self debugWithFormat:@"socket activated ..."];
308 #endif
309   
310   [[self notificationCenter]
311          postNotificationName:WOHTTPConnectionCanReadResponse
312          object:self];
313 }
314
315 - (void)_registerForNotification {
316   NSRunLoop *rl;
317   
318   if (self->didRegisterForNotification)
319     return;
320
321   [[self notificationCenter]
322          addObserver:self selector:@selector(_socketActivated:)
323          name:NSFileObjectBecameActiveNotificationName
324          object:self->socket];
325   
326   rl = [self runLoop];
327   [rl addFileObject:self->socket
328       activities:(NSPosixReadableActivity|NSPosixExceptionalActivity)
329       forMode:[self runLoopMode]];
330 }
331 - (void)_unregisterNotification {
332   if (!self->didRegisterForNotification)
333     return;
334   
335   [[self notificationCenter] removeObserver:self];
336   
337   [[self runLoop] removeFileObject:self->socket
338                   forMode:[self runLoopMode]];
339 }
340
341 /* logging IO */
342
343 - (void)logRequest:(WORequest *)_response data:(NSData *)_data {
344   if (_data == nil) return;
345   
346 #if 1
347   NSLog(@"request is\n");
348   fflush(stderr);
349   fwrite([_data bytes], 1, [_data length], stderr);
350   fflush(stderr);
351   fprintf(stderr,"\n");
352   fflush(stderr);
353 #endif
354 }
355 - (void)logResponse:(WOResponse *)_response data:(NSData *)_data {
356   if (_data == nil) return;
357   
358 #if 1
359   NSLog(@"response is\n");
360   fflush(stderr);
361   fwrite([_data bytes], 1, [_data length], stderr);
362   fflush(stderr);
363   fprintf(stderr,"\n");
364   fflush(stderr);
365 #endif
366 }
367
368 /* sending/receiving HTTP */
369
370 - (BOOL)sendRequest:(WORequest *)_request {
371   NSData *content;
372   BOOL isok = YES;
373   
374   if (doDebug)
375     [self debugWithFormat:@"send request: %@", _request];
376   
377   if (![self->socket isConnected]) {
378     if (![self _connect]) {
379       /* could not connect */
380       if (doDebug)
381         [self debugWithFormat:@"  could not connect socket"];
382       return NO;
383     }
384     /* now connected */
385   }
386   
387   content = [_request content];
388   
389   /* write request line (eg 'GET / HTTP/1.0') */
390   if (doDebug)
391     [self debugWithFormat:@"  method: %@", [_request method]];
392   
393   if (isok) isok = [self->io writeString:[_request method]];
394   if (isok) isok = [self->io writeString:@" "];
395   
396   if (self->useProxy) {
397     if (isok)
398       // TODO: check whether this produces a '//' (may need to strip uri)
399       isok = [self->io writeString:[self->url absoluteString]];
400     [self debugWithFormat:@"  wrote proxy start ..."];
401   }
402   if (isok) isok = [self->io writeString:[_request uri]];
403   
404   if (isok) isok = [self->io writeString:@" "];
405   if (isok) isok = [self->io writeString:[_request httpVersion]];
406   if (isok) isok = [self->io writeString:@"\r\n"];
407
408   /* set content-length header */
409   
410   if ([content length] > 0) {
411     [_request setHeader:[NSString stringWithFormat:@"%d", [content length]]
412               forKey:@"content-length"];
413   }
414
415   if ([[self->url scheme] hasPrefix:@"http"]) {
416     /* host header */
417     
418     if (isok) isok = [self->io writeString:@"Host: "];
419     if (isok) isok = [self->io writeString:[self hostName]];
420     if (isok) isok = [self->io writeString:@"\r\n"];
421     [self debugWithFormat:@"  wrote host header: %@", [self hostName]];
422   }
423   
424   /* write request headers */
425
426   if (isok) {
427     NSEnumerator *fields;
428     NSString *fieldName;
429     int cnt;
430     
431     fields = [[_request headerKeys] objectEnumerator];
432     cnt = 0;
433     while (isok && (fieldName = [fields nextObject])) {
434       NSEnumerator *values;
435       id value;
436
437       if ([fieldName length] == 4) {
438         if ([fieldName isEqualToString:@"host"])
439           /* did already write host ... */
440           continue;
441         if ([fieldName isEqualToString:@"Host"])
442           /* did already write host ... */
443           continue;
444       }
445       
446       values = [[_request headersForKey:fieldName] objectEnumerator];
447         
448       while ((value = [values nextObject]) && isok) {
449         if (isok) isok = [self->io writeString:fieldName];
450         if (isok) isok = [self->io writeString:@": "];
451         if (isok) isok = [self->io writeString:value];
452         if (isok) isok = [self->io writeString:@"\r\n"];
453         cnt++;
454       }
455     }
456     [self debugWithFormat:@"  wrote %i request headers ...", cnt];
457   }
458   
459   /* write some required headers */
460   
461   if ([_request headerForKey:@"accept"] == nil) {
462     if (isok) isok = [self->io writeString:@"Accept: */*\r\n"];
463     [self debugWithFormat:@"  wrote accept header ..."];
464   }
465   if ([_request headerForKey:@"user-agent"] == nil) {
466     if (isok) {
467       static NSString *s = nil;
468       if (s == nil) {
469         s = [[NSString alloc] initWithFormat:@"User-Agent: SOPE/%i.%i.%i\r\n",
470                               SOPE_MAJOR_VERSION, SOPE_MINOR_VERSION, 
471                               SOPE_SUBMINOR_VERSION];
472       }
473       isok = [self->io writeString:s];
474     }
475     [self debugWithFormat:@"  wrote user-agent header ..."];
476   }
477   
478   /* write cookie headers */
479   
480   if ([[_request cookies] count] > 0 && isok) {
481     NSEnumerator *cookies;
482     WOCookie     *cookie;
483     BOOL         isFirst;
484     int cnt;
485     
486     [self->io writeString:@"set-cookie: "];
487     cnt = 0;
488     cookies = [[_request cookies] objectEnumerator];
489     isFirst = YES;
490     while (isok && (cookie = [cookies nextObject])) {
491       if (isFirst) isFirst = NO;
492       else if (isok) isok = [self->io writeString:@"; "];
493       
494       if (isok) isok = [self->io writeString:[cookie stringValue]];
495       cnt ++;
496     }
497     if (isok) isok = [self->io writeString:@"\r\n"];
498     [self debugWithFormat:@"  wrote %i cookies ...", cnt];
499   }
500   
501   /* flush request header on socket */
502   
503   if (isok) isok = [self->io writeString:@"\r\n"];
504   if (isok) isok = [self->io flush];
505   [self debugWithFormat:@"  flushed HTTP header."];
506   
507   /* write content */
508
509   if ([content length] > 0) {
510     [self debugWithFormat:@"  writing HTTP entity (length=%i).", 
511             [content length]];
512     
513     if ([content isKindOfClass:[NSString class]]) {
514       if (isok) isok = [self->io writeString:(NSString *)content];
515     }
516     else if ([content isKindOfClass:[NSData class]]) {
517       if (isok) isok = [[self->io source]
518                          safeWriteBytes:[content bytes]
519                          count:[content length]];
520     }
521     else {
522       if (isok) isok = [self->io writeString:[content description]];
523     }
524     if (isok) isok = [self->io flush];
525   }
526   else if (doDebug) {
527     [self debugWithFormat:@"  no HTTP entity to write ..."];
528   }
529   
530   if (logStream)
531     [self logRequest:_request data:[self->log writeLog]];
532   [self->log resetWriteLog];
533   
534   [self debugWithFormat:@"=> finished:\n  url:  %@\n  sock: %@", 
535           self->url, self->socket];
536   if (!isok) {
537     ASSIGN(self->lastException, [self->socket lastException]);
538     [self->socket shutdown];
539     return NO;
540   }
541   
542   if (![self->socket isConnected])
543     return NO;
544   
545   [self _registerForNotification];
546   
547   return YES;
548 }
549
550 - (NSException *)handleResponseParsingError:(NSException *)_exception {
551     fprintf(stderr, "%s: caught: %s\n",
552             __PRETTY_FUNCTION__,
553             [[_exception description] cString]);
554     return nil;
555 }
556
557 - (WOResponse *)readResponse {
558   /* TODO: split up method */
559   WOResponse *response;
560   
561   *(&response) = nil;
562   [self _unregisterNotification];
563   
564   if (self->socket == nil) {
565     [self debugWithFormat:@"no socket available for reading response ..."];
566     return nil;
567   }
568   
569   [self debugWithFormat:@"parsing response from socket: %@", self->socket];
570   
571   if (useSimpleParser) {
572     WOSimpleHTTPParser *parser;
573     
574     [self debugWithFormat:@"  using simple HTTP parser ..."];
575     
576     parser = [[WOSimpleHTTPParser alloc] initWithStream:[self->io source]];
577     if (parser == nil)
578       return nil;
579     parser = [parser autorelease];
580     
581     if ((response = [parser parseResponse]) == nil) {
582       if (doDebug)
583         [self debugWithFormat:@"parsing failed: %@", [parser lastException]];
584     }
585   }
586   else {
587     NGHttpMessageParser *parser;
588     NGHttpResponse *mresponse;
589     NGMimeType     *ctype;
590     id body;
591     
592     *(&mresponse) = nil;
593     
594     if ((parser = [[[NGHttpMessageParser alloc] init] autorelease]) == nil)
595       return nil;
596     
597     [self debugWithFormat:@"  using MIME HTTP parser (complex parser) ..."];
598     
599     NS_DURING {
600       [parser setDelegate:self];
601       mresponse = [parser parseResponseFromStream:self->socket];
602     }
603     NS_HANDLER
604       [[self handleResponseParsingError:localException] raise];
605     NS_ENDHANDLER;
606     
607     [self debugWithFormat:@"finished parsing response: %@", mresponse];
608     
609     /* transform parsed MIME response to WOResponse */
610     
611     body = [mresponse body];
612     if (body == nil) body = [NSData data];
613     
614     response = [[[WOResponse alloc] init] autorelease];
615     [response setHTTPVersion:[mresponse httpVersion]];
616     [response setStatus:[mresponse statusCode]];
617     [response setUserInfo:[NSDictionary dictionaryWithObjectsAndKeys:
618                                           self,      @"NGHTTPConnection",
619                                           mresponse, @"NGMimeResponse",
620                                           body,      @"NGMimeBody",
621                                           nil]];
622   
623     { /* check content-type */
624       id value;
625       
626       value = [[mresponse valuesOfHeaderFieldWithName:@"content-type"] 
627                         nextObject];
628       if (value) {
629         NSString *charset;
630         
631         ctype = [NGMimeType mimeType:[value stringValue]];
632         charset = [[ctype valueOfParameter:@"charset"] lowercaseString];
633         
634         if ([charset length] == 0) {
635           /* autodetect charset ... */
636           
637           if ([[ctype type] isEqualToString:@"text"]) {
638             if ([[ctype subType] isEqualToString:@"xml"]) {
639               /* default XML encoding is UTF-8 */
640               [response setContentEncoding:NSUTF8StringEncoding];
641             }
642           }
643         }
644         else {
645           NSStringEncoding enc;
646           
647           enc = [NGMimeType stringEncodingForCharset:charset];
648           [response setContentEncoding:enc];
649         }
650         
651         [response setHeader:[ctype stringValue] forKey:@"content-type"];
652         
653       }
654       else {
655         ctype = [NGMimeType mimeType:@"application/octet-stream"];
656       }
657     }
658   
659     /* check content */
660     
661     if ([body isKindOfClass:[NSData class]]) {
662       [response setContent:body];
663     }
664     else if ([body isKindOfClass:[NSString class]]) {
665       NSData *data;
666       
667       data = [body dataUsingEncoding:[response contentEncoding]];
668       if (data)
669         [response setContent:data];
670     }
671     else if (body) {
672       /* generate data from structured body .. */
673       NGMimeBodyGenerator *gen;
674       NSData *data;
675       
676       gen = [[[NGMimeBodyGenerator alloc] init] autorelease];
677       data = [gen generateBodyOfPart:body
678                   additionalHeaders:nil
679                   delegate:self];
680       [response setContent:data];
681     }
682     
683     { /* transfer headers */
684       NSEnumerator *names;
685       NSString     *name;
686       
687       names = [mresponse headerFieldNames];
688       while ((name = [names nextObject])) {
689         NSEnumerator *values;
690         id           value;
691         
692         if ([name isEqualToString:@"content-type"])
693           continue;
694         if ([name isEqualToString:@"set-cookie"])
695           continue;
696         
697         values = [mresponse valuesOfHeaderFieldWithName:name];
698         while ((value = [values nextObject])) {
699           value = [value stringValue];
700           [response appendHeader:value forKey:name];
701         }
702       }
703     }
704     
705     { /* transfer cookies */
706       NSEnumerator *cookies;
707       NGHttpCookie *mcookie;
708   
709       cookies = [mresponse valuesOfHeaderFieldWithName:@"set-cookie"];
710       
711       while ((mcookie = [cookies nextObject])) {
712         WOCookie *woCookie;
713         
714         if (![mcookie isKindOfClass:[NGHttpCookie class]]) {
715           /* parse cookie */
716           woCookie = [WOCookie cookieWithString:[mcookie stringValue]];
717         }
718         else {
719           woCookie = [WOCookie cookieWithName:[mcookie cookieName]
720                                value:[mcookie value]
721                                path:[mcookie path]
722                                domain:[mcookie domainName]
723                                expires:[mcookie expireDate]
724                                isSecure:[mcookie needsSecureChannel]];
725         }
726         if (woCookie == nil) {
727           [self logWithFormat:
728                   @"Couldn't create WOCookie from NGHttp cookie: %@",
729                   mcookie];
730           // could not create cookie
731           continue;
732         }
733         
734         [self debugWithFormat:@"adding cookie: %@", woCookie];
735         
736         [response addCookie:woCookie];
737       }
738     }
739   }
740   
741   if (logStream)
742     [self logResponse:response data:[self->log readLog]];
743   [self->log resetReadLog];
744   
745   if (doDebug)
746     [self debugWithFormat:@"processed response: %@", response];
747   
748   /* check keep-alive */
749   {
750     NSString *conn;
751     
752     conn = [response headerForKey:@"connection"];
753     conn = [conn lowercaseString];
754     
755     if ([conn isEqualToString:@"close"]) {
756       [self setKeepAliveEnabled:NO];
757       [self _disconnect];
758     }
759     else if ([conn isEqualToString:@"keep-alive"]) {
760       [self setKeepAliveEnabled:YES];
761     }
762     else {
763       [self setKeepAliveEnabled:NO];
764       [self _disconnect];
765     }
766   }
767   
768   return response;
769 }
770
771 - (void)setKeepAliveEnabled:(BOOL)_flag {
772   self->keepAlive = _flag;
773 }
774 - (BOOL)keepAliveEnabled {
775   return self->keepAlive;
776 }
777
778 /* timeouts */
779
780 - (void)setConnectTimeout:(int)_seconds {
781   self->connectTimeout = _seconds;
782 }
783 - (int)connectTimeout {
784   return self->connectTimeout;
785 }
786
787 - (void)setReceiveTimeout:(int)_seconds {
788   self->receiveTimeout = _seconds;
789 }
790 - (int)receiveTimeout {
791   return self->receiveTimeout;
792 }
793
794 - (void)setSendTimeout:(int)_seconds {
795   self->sendTimeout = _seconds;
796 }
797 - (int)sendTimeout {
798   return self->sendTimeout;
799 }
800
801 /* description */
802
803 - (NSString *)description {
804   NSMutableString *str;
805   
806   str = [NSMutableString stringWithCapacity:128];
807   [str appendFormat:@"<%@[0x%08X]:", NSStringFromClass([self class]), self];
808   
809   if (self->url)      [str appendFormat:@" url=%@", self->url];
810   if (self->useProxy) [str appendString:@" proxy"];
811   if (self->useSSL)   [str appendString:@" SSL"];
812
813   if (self->socket) [str appendFormat:@" socket=%@", self->socket];
814   
815   [str appendString:@">"];
816   return str;
817 }
818
819 @end /* WOHTTPConnection */
820
821 @implementation NSURL(SocketAddress)
822
823 - (id)socketAddressForURL {
824   NSString *s;
825   
826   s = [self scheme];
827   
828   if ([s isEqualToString:@"http"]) {
829     int p;
830     
831     s = [self host];
832     if ([s length] == 0) s = @"localhost";
833     p = [[self port] intValue];
834     
835     return [NGInternetSocketAddress addressWithPort:p == 0 ? 80 : p onHost:s];
836   }
837   else if ([s isEqualToString:@"https"]) {
838     int p;
839     
840     s = [self host];
841     if ([s length] == 0) s = @"localhost";
842     p = [[self port] intValue];
843     
844     return [NGInternetSocketAddress addressWithPort:p == 0 ? 443 : p onHost:s];
845   }
846   else if ([s isEqualToString:@"unix"] || [s isEqualToString:@"file"]) {
847     return [NGLocalSocketAddress addressWithPath:[self path]];
848   }
849   return nil;
850 }
851
852 - (BOOL)shouldUseWOProxyServer {
853   if ([[self scheme] hasPrefix:@"http"]) {
854     NSString *h;
855     
856     if ((h = [self host]) == nil)
857       return NO;
858     
859     if ([h isEqualToString:@"127.0.0.1"])
860       return NO;
861     if ([h isEqualToString:@"localhost"])
862       return NO;
863     
864     if ([[WOHTTPConnection proxyServer] length] > 0) {
865       NSEnumerator *e;
866       NSString *suffix;
867       BOOL     useProxy;
868       
869       useProxy = YES;
870       e = [[WOHTTPConnection noProxySuffixes] objectEnumerator];
871       while ((suffix = [e nextObject])) {
872         if ([h hasSuffix:suffix]) {
873           useProxy = NO;
874           break;
875         }
876       }
877       return useProxy;
878     }
879   }
880   return NO;
881 }
882
883 @end /* NSURL(SocketAddress) */