]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WOHTTPConnection.m
removed bogus line
[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
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) isok = [self->io writeString:@"User-Agent: SOPE/4.2\r\n"];
464     [self debugWithFormat:@"  wrote user-agent header ..."];
465   }
466   
467   /* write cookie headers */
468   
469   if ([[_request cookies] count] > 0 && isok) {
470     NSEnumerator *cookies;
471     WOCookie     *cookie;
472     BOOL         isFirst;
473     int cnt;
474     
475     [self->io writeString:@"set-cookie: "];
476     cnt = 0;
477     cookies = [[_request cookies] objectEnumerator];
478     isFirst = YES;
479     while (isok && (cookie = [cookies nextObject])) {
480       if (isFirst) isFirst = NO;
481       else if (isok) isok = [self->io writeString:@"; "];
482       
483       if (isok) isok = [self->io writeString:[cookie stringValue]];
484       cnt ++;
485     }
486     if (isok) isok = [self->io writeString:@"\r\n"];
487     [self debugWithFormat:@"  wrote %i cookies ...", cnt];
488   }
489   
490   /* flush request header on socket */
491   
492   if (isok) isok = [self->io writeString:@"\r\n"];
493   if (isok) isok = [self->io flush];
494   [self debugWithFormat:@"  flushed HTTP header."];
495   
496   /* write content */
497
498   if ([content length] > 0) {
499     [self debugWithFormat:@"  writing HTTP entity (length=%i).", 
500             [content length]];
501     
502     if ([content isKindOfClass:[NSString class]]) {
503       if (isok) isok = [self->io writeString:(NSString *)content];
504     }
505     else if ([content isKindOfClass:[NSData class]]) {
506       if (isok) isok = [[self->io source]
507                          safeWriteBytes:[content bytes]
508                          count:[content length]];
509     }
510     else {
511       if (isok) isok = [self->io writeString:[content description]];
512     }
513     if (isok) isok = [self->io flush];
514   }
515   else if (doDebug) {
516     [self debugWithFormat:@"  no HTTP entity to write ..."];
517   }
518   
519   if (logStream)
520     [self logRequest:_request data:[self->log writeLog]];
521   [self->log resetWriteLog];
522   
523   [self debugWithFormat:@"=> finished:\n  url:  %@\n  sock: %@", 
524           self->url, self->socket];
525   if (!isok) {
526     ASSIGN(self->lastException, [self->socket lastException]);
527     [self->socket shutdown];
528     return NO;
529   }
530   
531   if (![self->socket isConnected])
532     return NO;
533   
534   [self _registerForNotification];
535   
536   return YES;
537 }
538
539 - (NSException *)handleResponseParsingError:(NSException *)_exception {
540     fprintf(stderr, "%s: caught: %s\n",
541             __PRETTY_FUNCTION__,
542             [[_exception description] cString]);
543     return nil;
544 }
545
546 - (WOResponse *)readResponse {
547   /* TODO: split up method */
548   WOResponse *response;
549   
550   *(&response) = nil;
551   [self _unregisterNotification];
552   
553   if (self->socket == nil) {
554     [self debugWithFormat:@"no socket available for reading response ..."];
555     return nil;
556   }
557   
558   [self debugWithFormat:@"parsing response from socket: %@", self->socket];
559   
560   if (useSimpleParser) {
561     WOSimpleHTTPParser *parser;
562     
563     [self debugWithFormat:@"  using simple HTTP parser ..."];
564     
565     parser = [[WOSimpleHTTPParser alloc] initWithStream:[self->io source]];
566     if (parser == nil)
567       return nil;
568     parser = [parser autorelease];
569     
570     if ((response = [parser parseResponse]) == nil) {
571       if (doDebug)
572         [self debugWithFormat:@"parsing failed: %@", [parser lastException]];
573     }
574   }
575   else {
576     NGHttpMessageParser *parser;
577     NGHttpResponse *mresponse;
578     NGMimeType     *ctype;
579     id body;
580     
581     *(&mresponse) = nil;
582     
583     if ((parser = [[[NGHttpMessageParser alloc] init] autorelease]) == nil)
584       return nil;
585     
586     [self debugWithFormat:@"  using MIME HTTP parser (complex parser) ..."];
587     
588     NS_DURING {
589       [parser setDelegate:self];
590       mresponse = [parser parseResponseFromStream:self->socket];
591     }
592     NS_HANDLER
593       [[self handleResponseParsingError:localException] raise];
594     NS_ENDHANDLER;
595     
596     [self debugWithFormat:@"finished parsing response: %@", mresponse];
597     
598     /* transform parsed MIME response to WOResponse */
599     
600     body = [mresponse body];
601     if (body == nil) body = [NSData data];
602     
603     response = [[[WOResponse alloc] init] autorelease];
604     [response setHTTPVersion:[mresponse httpVersion]];
605     [response setStatus:[mresponse statusCode]];
606     [response setUserInfo:[NSDictionary dictionaryWithObjectsAndKeys:
607                                           self,      @"NGHTTPConnection",
608                                           mresponse, @"NGMimeResponse",
609                                           body,      @"NGMimeBody",
610                                           nil]];
611   
612     { /* check content-type */
613       id value;
614       
615       value = [[mresponse valuesOfHeaderFieldWithName:@"content-type"] 
616                         nextObject];
617       if (value) {
618         NSString *charset;
619         
620         ctype = [NGMimeType mimeType:[value stringValue]];
621         charset = [[ctype valueOfParameter:@"charset"] lowercaseString];
622         
623         if ([charset length] == 0) {
624           /* autodetect charset ... */
625           
626           if ([[ctype type] isEqualToString:@"text"]) {
627             if ([[ctype subType] isEqualToString:@"xml"]) {
628               /* default XML encoding is UTF-8 */
629               [response setContentEncoding:NSUTF8StringEncoding];
630             }
631           }
632         }
633         else {
634           NSStringEncoding enc;
635           
636           enc = [NGMimeType stringEncodingForCharset:charset];
637           [response setContentEncoding:enc];
638         }
639         
640         [response setHeader:[ctype stringValue] forKey:@"content-type"];
641         
642       }
643       else {
644         ctype = [NGMimeType mimeType:@"application/octet-stream"];
645       }
646     }
647   
648     /* check content */
649     
650     if ([body isKindOfClass:[NSData class]]) {
651       [response setContent:body];
652     }
653     else if ([body isKindOfClass:[NSString class]]) {
654       NSData *data;
655       
656       data = [body dataUsingEncoding:[response contentEncoding]];
657       if (data)
658         [response setContent:data];
659     }
660     else if (body) {
661       /* generate data from structured body .. */
662       NGMimeBodyGenerator *gen;
663       NSData *data;
664       
665       gen = [[[NGMimeBodyGenerator alloc] init] autorelease];
666       data = [gen generateBodyOfPart:body
667                   additionalHeaders:nil
668                   delegate:self];
669       [response setContent:data];
670     }
671     
672     { /* transfer headers */
673       NSEnumerator *names;
674       NSString     *name;
675       
676       names = [mresponse headerFieldNames];
677       while ((name = [names nextObject])) {
678         NSEnumerator *values;
679         id           value;
680         
681         if ([name isEqualToString:@"content-type"])
682           continue;
683         if ([name isEqualToString:@"set-cookie"])
684           continue;
685         
686         values = [mresponse valuesOfHeaderFieldWithName:name];
687         while ((value = [values nextObject])) {
688           value = [value stringValue];
689           [response appendHeader:value forKey:name];
690         }
691       }
692     }
693     
694     { /* transfer cookies */
695       NSEnumerator *cookies;
696       NGHttpCookie *mcookie;
697   
698       cookies = [mresponse valuesOfHeaderFieldWithName:@"set-cookie"];
699       
700       while ((mcookie = [cookies nextObject])) {
701         WOCookie *woCookie;
702         
703         if (![mcookie isKindOfClass:[NGHttpCookie class]]) {
704           /* parse cookie */
705           woCookie = [WOCookie cookieWithString:[mcookie stringValue]];
706         }
707         else {
708           woCookie = [WOCookie cookieWithName:[mcookie cookieName]
709                                value:[mcookie value]
710                                path:[mcookie path]
711                                domain:[mcookie domainName]
712                                expires:[mcookie expireDate]
713                                isSecure:[mcookie needsSecureChannel]];
714         }
715         if (woCookie == nil) {
716           [self logWithFormat:
717                   @"Couldn't create WOCookie from NGHttp cookie: %@",
718                   mcookie];
719           // could not create cookie
720           continue;
721         }
722         
723         [self debugWithFormat:@"adding cookie: %@", woCookie];
724         
725         [response addCookie:woCookie];
726       }
727     }
728   }
729   
730   if (logStream)
731     [self logResponse:response data:[self->log readLog]];
732   [self->log resetReadLog];
733   
734   if (doDebug)
735     [self debugWithFormat:@"processed response: %@", response];
736   
737   /* check keep-alive */
738   {
739     NSString *conn;
740     
741     conn = [response headerForKey:@"connection"];
742     conn = [conn lowercaseString];
743     
744     if ([conn isEqualToString:@"close"]) {
745       [self setKeepAliveEnabled:NO];
746       [self _disconnect];
747     }
748     else if ([conn isEqualToString:@"keep-alive"]) {
749       [self setKeepAliveEnabled:YES];
750     }
751     else {
752       [self setKeepAliveEnabled:NO];
753       [self _disconnect];
754     }
755   }
756   
757   return response;
758 }
759
760 - (void)setKeepAliveEnabled:(BOOL)_flag {
761   self->keepAlive = _flag;
762 }
763 - (BOOL)keepAliveEnabled {
764   return self->keepAlive;
765 }
766
767 /* timeouts */
768
769 - (void)setConnectTimeout:(int)_seconds {
770   self->connectTimeout = _seconds;
771 }
772 - (int)connectTimeout {
773   return self->connectTimeout;
774 }
775
776 - (void)setReceiveTimeout:(int)_seconds {
777   self->receiveTimeout = _seconds;
778 }
779 - (int)receiveTimeout {
780   return self->receiveTimeout;
781 }
782
783 - (void)setSendTimeout:(int)_seconds {
784   self->sendTimeout = _seconds;
785 }
786 - (int)sendTimeout {
787   return self->sendTimeout;
788 }
789
790 /* description */
791
792 - (NSString *)description {
793   NSMutableString *str;
794   
795   str = [NSMutableString stringWithCapacity:128];
796   [str appendFormat:@"<%@[0x%08X]:", NSStringFromClass([self class]), self];
797   
798   if (self->url)      [str appendFormat:@" url=%@", self->url];
799   if (self->useProxy) [str appendString:@" proxy"];
800   if (self->useSSL)   [str appendString:@" SSL"];
801
802   if (self->socket) [str appendFormat:@" socket=%@", self->socket];
803   
804   [str appendString:@">"];
805   return str;
806 }
807
808 @end /* WOHTTPConnection */
809
810 @implementation NSURL(SocketAddress)
811
812 - (id)socketAddressForURL {
813   NSString *s;
814   
815   s = [self scheme];
816   
817   if ([s isEqualToString:@"http"]) {
818     int p;
819     
820     s = [self host];
821     if ([s length] == 0) s = @"localhost";
822     p = [[self port] intValue];
823     
824     return [NGInternetSocketAddress addressWithPort:p == 0 ? 80 : p onHost:s];
825   }
826   else if ([s isEqualToString:@"https"]) {
827     int p;
828     
829     s = [self host];
830     if ([s length] == 0) s = @"localhost";
831     p = [[self port] intValue];
832     
833     return [NGInternetSocketAddress addressWithPort:p == 0 ? 443 : p onHost:s];
834   }
835   else if ([s isEqualToString:@"unix"] || [s isEqualToString:@"file"]) {
836     return [NGLocalSocketAddress addressWithPath:[self path]];
837   }
838   return nil;
839 }
840
841 - (BOOL)shouldUseWOProxyServer {
842   if ([[self scheme] hasPrefix:@"http"]) {
843     NSString *h;
844     
845     if ((h = [self host]) == nil)
846       return NO;
847     
848     if ([h isEqualToString:@"127.0.0.1"])
849       return NO;
850     if ([h isEqualToString:@"localhost"])
851       return NO;
852     
853     if ([[WOHTTPConnection proxyServer] length] > 0) {
854       NSEnumerator *e;
855       NSString *suffix;
856       BOOL     useProxy;
857       
858       useProxy = YES;
859       e = [[WOHTTPConnection noProxySuffixes] objectEnumerator];
860       while ((suffix = [e nextObject])) {
861         if ([h hasSuffix:suffix]) {
862           useProxy = NO;
863           break;
864         }
865       }
866       return useProxy;
867     }
868   }
869   return NO;
870 }
871
872 @end /* NSURL(SocketAddress) */