]> err.no Git - sope/blob - sope-appserver/NGObjWeb/WEClientCapabilities.m
bugfix, bumped dyld versions
[sope] / sope-appserver / NGObjWeb / WEClientCapabilities.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/WEClientCapabilities.h>
23 #include <NGObjWeb/WOAssociation.h>
24 #include <NGObjWeb/WOContext.h>
25 #include <NGObjWeb/WOComponent.h>
26 #include <NGObjWeb/WOResponse.h>
27 #include <string.h>
28 #include "common.h"
29
30 #define WEUA_UNKNOWN          0
31 #define WEUA_IE               1
32 #define WEUA_Netscape         2
33 #define WEUA_Lynx             3
34 #define WEUA_Opera            4
35 #define WEUA_Amaya            5
36 #define WEUA_Emacs            6
37 #define WEUA_Wget             7
38 #define WEUA_WebFolder        8
39 #define WEUA_Mozilla          9
40 #define WEUA_OmniWeb          10
41 #define WEUA_iCab             11
42 #define WEUA_Konqueror        12
43 #define WEUA_Links            13
44 #define WEUA_DAVFS            14
45 #define WEUA_CADAVER          15
46 #define WEUA_GOLIVE           16
47 #define WEUA_MACOSX_DAVFS     17
48 #define WEUA_Dillo            18
49 #define WEUA_JavaSDK          19
50 #define WEUA_PythonURLLIB     20
51 #define WEUA_AppleDAVAccess   21
52 #define WEUA_MSWebPublisher   22
53 #define WEUA_CURL             23
54 #define WEUA_Evolution        24
55 #define WEUA_MSOutlook        25
56 #define WEUA_MSOutlookExpress 26
57 #define WEUA_GNOMEVFS         27
58 #define WEUA_ZideLook         28
59 #define WEUA_Safari           29
60 #define WEUA_SOUP             30
61 #define WEUA_Entourage        31
62 #define WEUA_NetNewsWire      32
63 #define WEUA_xmlrpclib_py     33
64 #define WEUA_Morgul           34
65 #define WEUA_CFNetwork        35
66 #define WEUA_KungLog          36
67 #define WEUA_SOPE             37
68 #define WEUA_Ecto             38
69 #define WEUA_NewsFire         39
70 #define WEUA_Goliath          40
71 #define WEUA_PerlHTTPDAV      41
72 #define WEUA_Google           42
73
74 #define WEOS_UNKNOWN   0
75 #define WEOS_WINDOWS   1
76 #define WEOS_LINUX     2
77 #define WEOS_MACOS     3
78 #define WEOS_SUNOS     4
79
80 #define WECPU_UNKNOWN  0
81 #define WECPU_IX86     1
82 #define WECPU_SPARC    2
83 #define WECPU_PPC      3
84
85 @interface WEClientCapabilities(Privates)
86 - (id)initWithRequest:(WORequest *)_request;
87 @end
88
89 @implementation WEClientCapabilities
90
91 - (id)initWithRequest:(WORequest *)_request {
92   NSString *ac;
93   const char *ua;
94   const char *tmp;
95   int defaultOS  = WEOS_UNKNOWN;
96   int defaultCPU = WECPU_UNKNOWN;
97   
98   /* check charset */
99   
100   if ((ac = [_request headerForKey:@"accept-charset"])) {
101     /* not really correct ..., eg could have quality "0" ! */
102     ac = [ac lowercaseString];
103     if ([ac rangeOfString:@"utf-8"].length > 0)
104       self->flags.acceptUTF8 = 1;
105   }
106   
107   /* process user-agent */
108   
109   self->userAgent = [[_request headerForKey:@"user-agent"] copy];
110   ua = [self->userAgent cString];
111   if (ua == NULL) {
112     /* no user-agent, eg telnet */
113     ua = "";
114   }
115   
116   /* detect browser */
117   
118   if ((tmp = strstr(ua, "Opera"))) {
119     /* Opera (can fake to be MSIE or Netscape) */
120     self->browser = WEUA_Opera;
121
122     /* go to next space */
123     while (!isspace(*tmp) && (*tmp != '\0')) tmp++;
124     /* skip spaces */
125     while (isspace(*tmp) && (*tmp != '\0')) tmp++;
126     
127     self->browserMajorVersion = atoi(tmp);
128     if ((tmp = index(tmp, '.'))) {
129       tmp++;
130       self->browserMinorVersion = atoi(tmp);
131     }
132   }
133   else if (strstr(ua, "NeonConnection") != NULL || 
134            strstr(ua, "ZIDEStore") != NULL ||
135            strstr(ua, "ZideLook-Codeon") != NULL) {
136     self->browser = WEUA_ZideLook;
137     self->browserMinorVersion = 0;
138     self->browserMajorVersion = 0;
139   }
140   else if ((tmp = strstr(ua, "Safari/"))) {
141     /* Hm, Safari says it is a Mozilla/5.0 ? */
142     int combinedVersion;
143     self->browser = WEUA_Safari;
144     tmp += 7; /* skip "Safari/" */
145     combinedVersion = atoi(tmp);
146     /* well, don't know how this is supposed to work? 100=v1.1 */
147     if (combinedVersion == 100 /* 100 is v1.1 */) {
148       self->browserMajorVersion = 1;
149       self->browserMinorVersion = 1;
150     }
151     else {
152       /* watch for upcoming versions ... */
153       self->browserMajorVersion = combinedVersion / 100;
154     }
155   }
156   else if (strstr(ua, "Outlook-Express/")) {
157     /* Outlook Express 5.5 mailbox access via http */
158     self->browser = WEUA_MSOutlookExpress;
159   }
160   else if (strstr(ua, "Outlook Express/")) {
161     /* Outlook Express 5.0 mailbox access via http */
162     self->browser = WEUA_MSOutlookExpress;
163   }
164   else if (strstr(ua, "Microsoft-Outlook/")) {
165     /* Outlook 2002 mailbox access via http */
166     self->browser = WEUA_MSOutlook;
167   }
168   else if (strstr(ua, "Microsoft HTTP Post")) {
169     /* Outlook 2000 with WebPublishing Assistent */
170     self->browser = WEUA_MSWebPublisher;
171   }
172   else if (strstr(ua, "Entourage/10")) {
173     /* Entourage MacOSX 10.1.4 */
174     self->browser = WEUA_Entourage;
175   }
176   else if ((tmp = strstr(ua, "MSIE"))) {
177     /* Internet Explorer */
178     self->browser = WEUA_IE;
179     
180     /* go to next space */
181     while (!isspace(*tmp) && (*tmp != '\0')) tmp++;
182     /* skip spaces */
183     while (isspace(*tmp) && (*tmp != '\0')) tmp++;
184     
185     self->browserMajorVersion = atoi(tmp);
186     if ((tmp = index(tmp, '.'))) {
187       tmp++;
188       self->browserMinorVersion = atoi(tmp);
189     }
190   }
191   else if ((tmp = strstr(ua, "Konqueror"))) {
192     /* Konqueror (KDE2 FileManager) */
193     self->browser = WEUA_Konqueror;
194     
195     if ((tmp = index(tmp, '/'))) {
196       tmp++;
197       self->browserMajorVersion = atoi(tmp);
198       if ((tmp = index(tmp, '.'))) {
199         tmp++;
200         self->browserMinorVersion = atoi(tmp);
201       }
202     }
203   }
204   else if ((tmp = strstr(ua, "Netscape6"))) {
205     /* Netscape 6 */
206     self->browser = WEUA_Netscape;
207     
208     if ((tmp = index(tmp, '/'))) {
209       tmp++;
210       self->browserMajorVersion = atoi(tmp);
211       if ((tmp = index(tmp, '.'))) {
212         tmp++;
213         self->browserMinorVersion = atoi(tmp);
214       }
215     }
216   }
217   else if (strstr(ua, "Lynx")) {
218     /* Lynx */
219     self->browser = WEUA_Lynx;
220   }
221   else if (strstr(ua, "Links")) {
222     /* Links */
223     self->browser = WEUA_Links;
224   }
225   else if (strstr(ua, "gnome-vfs")) {
226     /* Links */
227     self->browser = WEUA_GNOMEVFS;
228   }
229   else if (strstr(ua, "cadaver")) {
230     /* Cadaver DAV browser */
231     self->browser = WEUA_CADAVER;
232   }
233   else if (strstr(ua, "GoLive")) {
234     /* Adobe GoLive */
235     self->browser = WEUA_GOLIVE;
236   }
237   else if (strstr(ua, "DAV.pm")) {
238     /* Perl HTTP::DAV */
239     self->browser = WEUA_PerlHTTPDAV;
240   }
241   else if (strstr(ua, "Darwin") != NULL && strstr(ua, "fetch/") != NULL) {
242     /* MacOSX 10.0 DAV FileSystem */
243     self->browser = WEUA_MACOSX_DAVFS;
244   }
245   else if (strstr(ua, "Darwin") != NULL && strstr(ua, "WebDAVFS/") != NULL) {
246     /* MacOSX DAV FileSystem */
247     self->browser = WEUA_MACOSX_DAVFS;
248   }
249   else if (strstr(ua, "OmniWeb")) {
250     /* OmniWeb */
251     self->browser = WEUA_OmniWeb;
252   }
253   else if (strstr(ua, "Evolution")) {
254     /* Evolution */
255     self->browser = WEUA_Evolution;
256   }
257   else if (strstr(ua, "Soup/")) {
258     /* SOUP (GNOME WebDAV library) */
259     self->browser = WEUA_SOUP;
260   }
261   else if (strstr(ua, "amaya")) {
262     /* W3C Amaya */
263     self->browser = WEUA_Amaya;
264   }
265   else if (strstr(ua, "NetNewsWire/")) {
266     /* NetNewsWire */
267     self->browser = WEUA_NetNewsWire;
268   }
269   else if (strstr(ua, "Dillo")) {
270     /* Dillo */
271     self->browser = WEUA_Dillo;
272   }
273   else if (strstr(ua, "Java")) {
274     /* Java SDK */
275     self->browser = WEUA_JavaSDK;
276   }
277   else if (strstr(ua, "Python-urllib")) {
278     /* Python URL module */
279     self->browser = WEUA_PythonURLLIB;
280   }
281   else if (strstr(ua, "xmlrpclib.py/")) {
282     /* Python XML-RPC module */
283     self->browser = WEUA_xmlrpclib_py;
284   }
285   else if (strstr(ua, "Emacs")) {
286     /* Emacs */
287     self->browser = WEUA_Emacs;
288   }
289   else if (strstr(ua, "iCab")) {
290     /* iCab ?? */
291     self->browser = WEUA_iCab;
292   }
293   else if (strstr(ua, "Wget")) {
294     /* Wget */
295     self->browser = WEUA_Wget;
296   }
297   else if (strstr(ua, "DAVAccess")) {
298     /* Apple MacOSX 10.2.1 / iCal 1.0 DAV Access Framework */
299     self->browser = WEUA_AppleDAVAccess;
300   }
301   else if (strstr(ua, "DAVKit/")) {
302     /* some iCal 1.x DAV Access Framework, report as Apple DAV access */
303     self->browser = WEUA_AppleDAVAccess;
304   }
305   else if (strstr(ua, "Microsoft Data Access Internet Publishing Provider")) {
306     /* WebFolder */
307     self->browser = WEUA_WebFolder;
308   }
309   else if (strstr(ua, "curl")) {
310     /* curl program */
311     self->browser = WEUA_CURL;
312   }
313   else if (strstr(ua, "Mozilla")) {
314     /* other Netscape browser */
315     if (strstr(ua, "Mozilla/5")) {
316       self->browser = WEUA_Mozilla;
317       self->browserMajorVersion = 5;
318     }
319     else if (strstr(ua, "Mozilla/4")) {
320       self->browser = WEUA_Netscape;
321       self->browserMajorVersion = 4;
322     }
323     else {
324       NSLog(@"%s: Unknown Mozilla Browser: user-agent='%@'",
325             __PRETTY_FUNCTION__, self->userAgent);
326     }
327   }
328   else if (strstr(ua, "Morgul")) {
329     self->browser = WEUA_Morgul;
330   }
331   else if (strstr(ua, "CFNetwork/1.1")) {
332     self->browser = WEUA_CFNetwork;
333   }
334   else if (strstr(ua, "Kung-Log/")) {
335     self->browser = WEUA_KungLog;
336   }
337   else if (strstr(ua, "ecto")) {
338     self->browser = WEUA_Ecto;
339   }
340   else if (strstr(ua, "NewsFire")) {
341     self->browser = WEUA_NewsFire;
342   }
343   else if (strstr(ua, "Goliath")) {
344     self->browser = WEUA_Goliath;
345   }
346   else if (strstr(ua, "SOPE/")) {
347     self->browser = WEUA_SOPE;
348   }
349   else if (strstr(ua, "Mediapartners-Google/")) {
350     self->browser = WEUA_Google;
351   }
352   else {
353     /* unknown browser */
354     self->browser = WEUA_UNKNOWN;
355     
356     if (self->userAgent) {
357       NSLog(@"%s: Unknown WebClient: user-agent='%@'",
358             __PRETTY_FUNCTION__, self->userAgent);
359     }
360   }
361   
362   /* detect OS */
363
364   if (strstr(ua, "Windows") != NULL || strstr(ua, "WinNT") != NULL)
365     self->os = WEOS_WINDOWS;
366   else if (strstr(ua, "Linux"))
367     self->os = WEOS_LINUX;
368   else if (strstr(ua, "Mac"))
369     self->os = WEOS_MACOS;
370   else if (strstr(ua, "SunOS"))
371     self->os = WEOS_SUNOS;
372   else
373     self->os = defaultOS;
374
375   /* detect CPU */
376
377   if (strstr(ua, "sun4u"))
378     self->cpu = WECPU_SPARC;
379   else if (strstr(ua, "i686") || strstr(ua, "i586"))
380     self->cpu = WECPU_IX86;
381   else if (strstr(ua, "PowerPC") || strstr(ua, "ppc") || strstr(ua, "PPC"))
382     self->cpu = WECPU_PPC;
383   else if (self->os == WEOS_WINDOWS)
384     /* assume ix86 if OS is Windows .. */
385     self->cpu = WECPU_IX86;
386   else 
387     self->cpu = defaultCPU;
388   
389   return self;
390 }
391
392 - (void)dealloc {
393   [self->userAgent release];
394   [super dealloc];
395 }
396
397 /* accessors */
398
399 - (NSString *)userAgent {
400   return self->userAgent;
401 }
402
403 - (NSString *)userAgentType {
404   switch (self->browser) {
405     case WEUA_IE:               return @"IE";
406     case WEUA_Netscape:         return @"Netscape";
407     case WEUA_Lynx:             return @"Lynx";
408     case WEUA_Links:            return @"Links";
409     case WEUA_Opera:            return @"Opera";
410     case WEUA_Amaya:            return @"Amaya";
411     case WEUA_Emacs:            return @"Emacs";
412     case WEUA_Wget:             return @"Wget";
413     case WEUA_WebFolder:        return @"WebFolder";
414     case WEUA_DAVFS:            return @"DAVFS";
415     case WEUA_MACOSX_DAVFS:     return @"MacOSXDAVFS";
416     case WEUA_CADAVER:          return @"Cadaver";
417     case WEUA_GOLIVE:           return @"GoLive";
418     case WEUA_Mozilla:          return @"Mozilla";
419     case WEUA_OmniWeb:          return @"OmniWeb";
420     case WEUA_iCab:             return @"iCab";
421     case WEUA_Konqueror:        return @"Konqueror";
422     case WEUA_Dillo:            return @"Dillo";
423     case WEUA_JavaSDK:          return @"Java";
424     case WEUA_PythonURLLIB:     return @"Python-urllib";
425     case WEUA_AppleDAVAccess:   return @"AppleDAVAccess";
426     case WEUA_MSWebPublisher:   return @"MSWebPublisher";
427     case WEUA_CURL:             return @"CURL";
428     case WEUA_Evolution:        return @"Evolution";
429     case WEUA_SOUP:             return @"SOUP";
430     case WEUA_MSOutlook:        return @"MSOutlook";
431     case WEUA_MSOutlookExpress: return @"MSOutlookExpress";
432     case WEUA_GNOMEVFS:         return @"GNOME-VFS";
433     case WEUA_ZideLook:         return @"ZideLook";
434     case WEUA_Safari:           return @"Safari";
435     case WEUA_Entourage:        return @"Entourage";
436     case WEUA_NetNewsWire:      return @"NetNewsWire";
437     case WEUA_xmlrpclib_py:     return @"xmlrpclib.py";
438     case WEUA_Morgul:           return @"Morgul";
439     case WEUA_KungLog:          return @"KungLog";
440     case WEUA_Ecto:             return @"Ecto";
441     case WEUA_NewsFire:         return @"NewsFire";
442     case WEUA_Goliath:          return @"Goliath";
443     case WEUA_PerlHTTPDAV:      return @"PerlHTTPDAV";
444     case WEUA_Google:           return @"Google";
445     default:                    return @"unknown";
446   }
447 }
448 - (NSString *)os {
449   switch (self->os) {
450     case WEOS_WINDOWS: return @"Windows";
451     case WEOS_LINUX:   return @"Linux";
452     case WEOS_MACOS:   return @"MacOS";
453     case WEOS_SUNOS:   return @"SunOS";
454     default:           return @"unknown";
455   }
456 }
457 - (NSString *)cpu {
458   switch (self->cpu) {
459     case WECPU_IX86:  return @"ix86";
460     case WECPU_SPARC: return @"sparc";
461     case WECPU_PPC:   return @"ppc";
462     default:          return @"unknown";
463   }
464 }
465
466 - (unsigned char)majorVersion {
467   return self->browserMajorVersion;
468 }
469 - (unsigned char)minorVersion {
470   return self->browserMinorVersion;
471 }
472
473 /* browser capabilities */
474
475 - (BOOL)isJavaScriptBrowser {
476   switch (self->browser) {
477     case WEUA_Mozilla:
478     case WEUA_IE:
479     case WEUA_Opera:
480     case WEUA_Netscape:
481     case WEUA_OmniWeb:
482     case WEUA_Konqueror:
483       return YES;
484       
485     default:
486       return NO;
487   }
488 }
489 - (BOOL)isVBScriptBrowser {
490   switch (self->browser) {
491     case WEUA_IE:
492       return YES;
493     
494     default:
495       return NO;
496   }
497 }
498
499 - (BOOL)isFastTableBrowser {
500   switch (self->browser) {
501     case WEUA_Mozilla:
502     case WEUA_IE:
503     case WEUA_Opera:
504       return YES;
505
506     case WEUA_Safari:
507     case WEUA_Konqueror:
508       /* to be tried */
509       return YES;
510       
511     case WEUA_Netscape:
512       return (self->browserMajorVersion >= 6)
513         ? YES : NO;
514       
515     default:
516       return NO;
517   }
518 }
519
520 - (BOOL)isCSS2Browser {
521   switch (self->browser) {
522     case WEUA_IE:        return (self->browserMajorVersion >= 5) ? YES : NO;
523     case WEUA_Netscape:  return (self->browserMajorVersion >= 6) ? YES : NO;
524     case WEUA_Opera:     return (self->browserMajorVersion >= 4) ? YES : NO;
525     case WEUA_Mozilla:   return YES;
526     case WEUA_Safari:    return YES;
527     case WEUA_Konqueror: return NO;
528     default:             return NO;
529   }
530 }
531
532 - (BOOL)isCSS1Browser {
533   switch (self->browser) {
534     case WEUA_IE:        return (self->browserMajorVersion >= 4) ? YES : NO;
535     case WEUA_Netscape:  return (self->browserMajorVersion >= 4) ? YES : NO;
536     case WEUA_Opera:     return (self->browserMajorVersion >= 4) ? YES : NO;
537     case WEUA_Safari:    return YES;
538     case WEUA_Konqueror: return NO;
539     default:             return NO;
540   }
541 }
542
543 - (BOOL)ignoresCSSOnFormElements {
544   if (self->browser == WEUA_Safari) /* Safari always displays Aqua buttons */
545     return YES;
546   
547   return [self isCSS1Browser] ? NO : YES;
548 }
549
550 - (BOOL)isXULBrowser {
551   if (self->browser == WEUA_Safari) // TODO: Safari supports some XUL stuff
552     return NO;
553   if ((self->browser == WEUA_Netscape) && (self->browserMajorVersion >= 6))
554     return YES;
555   if (self->browser == WEUA_Mozilla)
556     return YES;
557   return NO;
558 }
559
560 - (BOOL)isTextModeBrowser {
561   if (self->browser == WEUA_Lynx)  return YES;
562   if (self->browser == WEUA_Links) return YES;
563   if (self->browser == WEUA_Emacs) return YES;
564   return NO;
565 }
566
567 - (BOOL)isIFrameBrowser {
568   if ((self->browser == WEUA_IE) && (self->browserMajorVersion >= 5))
569     return YES;
570   
571   /* as suggested in OGo bug #634 */
572   if ((self->browser == WEUA_Mozilla) && (self->browserMajorVersion >= 5))
573     return YES;
574   
575   return NO;
576 }
577
578 - (BOOL)isRobot {
579   if (self->browser == WEUA_Wget)         return YES;
580   if (self->browser == WEUA_JavaSDK)      return YES;
581   if (self->browser == WEUA_PythonURLLIB) return YES;
582   if (self->browser == WEUA_Google)       return YES;
583   return NO;
584 }
585 - (BOOL)isDAVClient {
586   if (self->browser == WEUA_WebFolder)        return YES;
587   if (self->browser == WEUA_DAVFS)            return YES;
588   if (self->browser == WEUA_MACOSX_DAVFS)     return YES;
589   if (self->browser == WEUA_CADAVER)          return YES;
590   if (self->browser == WEUA_GOLIVE)           return YES;
591   if (self->browser == WEUA_AppleDAVAccess)   return YES;
592   if (self->browser == WEUA_Evolution)        return YES;
593   if (self->browser == WEUA_SOUP)             return YES;
594   if (self->browser == WEUA_MSOutlook)        return YES;
595   if (self->browser == WEUA_MSOutlookExpress) return YES;
596   if (self->browser == WEUA_GNOMEVFS)         return YES;
597   if (self->browser == WEUA_ZideLook)         return YES;
598   if (self->browser == WEUA_Entourage)        return YES;
599   if (self->browser == WEUA_Morgul)           return YES;
600   if (self->browser == WEUA_Goliath)          return YES;
601   if (self->browser == WEUA_PerlHTTPDAV)      return YES;
602   return NO;
603 }
604
605 - (BOOL)isXmlRpcClient {
606   if (self->browser == WEUA_xmlrpclib_py) return YES;
607   if (self->browser == WEUA_KungLog)      return YES;
608   if (self->browser == WEUA_Ecto)         return YES;
609   return NO;
610 }
611 - (BOOL)isBLogClient {
612   if (self->browser == WEUA_KungLog) return YES;
613   if (self->browser == WEUA_Ecto)    return YES;
614   return NO;
615 }
616 - (BOOL)isRSSClient {
617   if (self->browser == WEUA_NetNewsWire) return YES;
618   if (self->browser == WEUA_NewsFire)    return YES;
619   return NO;
620 }
621
622 - (BOOL)doesSupportCSSOverflow {
623   if (![self isCSS1Browser])
624     return NO;
625   if ((self->browser == WEUA_IE) && (self->browserMajorVersion >= 5))
626     return YES;
627
628   return NO;
629 }
630
631 - (BOOL)doesSupportDHTMLDragAndDrop {
632   if (![self isJavaScriptBrowser])
633     return NO;
634   if (self->os != WEOS_WINDOWS)
635     return NO;
636   if ((self->browser == WEUA_IE) && (self->browserMajorVersion >= 5))
637     return YES;
638   return NO;
639 }
640
641 - (BOOL)doesSupportXMLDataIslands {
642   if ((self->browser == WEUA_IE) && (self->browserMajorVersion >= 5))
643     return YES;
644   return NO;
645 }
646
647 - (BOOL)doesSupportUTF8Encoding {
648   if (self->flags.acceptUTF8)
649     /* explicit UTF-8 support signaled in HTTP header */
650     return YES;
651   
652   switch (self->browser) {
653   case WEUA_Mozilla:
654   case WEUA_Safari:
655   case WEUA_ZideLook:
656   case WEUA_Evolution:
657   case WEUA_SOUP:
658   case WEUA_Morgul:
659     /* browser so new, that they always supported UTF-8 ... */
660     return YES;
661   case WEUA_IE:
662     if (self->browserMajorVersion >= 5)
663       return YES;
664     return NO; // TODO: find out, whether IE 4 gurantees UTF-8 support
665   default:
666     return NO;
667   }
668 }
669
670 /* user-agent */
671
672 - (BOOL)isInternetExplorer {
673   return self->browser == WEUA_IE ? YES : NO;
674 }
675 - (BOOL)isInternetExplorer5 {
676   return (self->browser == WEUA_IE) && (self->browserMajorVersion == 5)
677     ? YES : NO;
678 }
679
680 - (BOOL)isNetscape {
681   return self->browser == WEUA_Netscape ? YES : NO;
682 }
683 - (BOOL)isNetscape6 {
684   return (self->browser == WEUA_Netscape) && (self->browserMajorVersion == 6)
685     ? YES : NO;
686 }
687
688 - (BOOL)isLynx {
689   return self->browser == WEUA_Lynx ? YES : NO;
690 }
691 - (BOOL)isOpera {
692   return self->browser == WEUA_Opera ? YES : NO;
693 }
694 - (BOOL)isAmaya {
695   return self->browser == WEUA_Amaya ? YES : NO;
696 }
697 - (BOOL)isEmacs {
698   return self->browser == WEUA_Emacs ? YES : NO;
699 }
700 - (BOOL)isWget {
701   return self->browser == WEUA_Wget ? YES : NO;
702 }
703 - (BOOL)isWebFolder {
704   return self->browser == WEUA_WebFolder ? YES : NO;
705 }
706 - (BOOL)isMozilla {
707   return self->browser == WEUA_Mozilla ? YES : NO;
708 }
709 - (BOOL)isOmniWeb {
710   return self->browser == WEUA_OmniWeb ? YES : NO;
711 }
712 - (BOOL)isICab {
713   return self->browser == WEUA_iCab ? YES : NO;
714 }
715 - (BOOL)isKonqueror {
716   return self->browser == WEUA_Konqueror ? YES : NO;
717 }
718
719 /* OS */
720
721 - (BOOL)isWindowsBrowser {
722   return self->os == WEOS_WINDOWS ? YES : NO;
723 }
724 - (BOOL)isLinuxBrowser {
725   return self->os == WEOS_LINUX ? YES : NO;
726 }
727 - (BOOL)isMacBrowser {
728   return self->os == WEOS_MACOS ? YES : NO;
729 }
730 - (BOOL)isSunOSBrowser {
731   return self->os == WEOS_SUNOS ? YES : NO;
732 }
733 - (BOOL)isUnixBrowser {
734   switch (self->os) {
735     case WEOS_SUNOS:
736     case WEOS_LINUX:
737       return YES;
738     default: return NO;
739   }
740 }
741 - (BOOL)isX11Browser {
742   if ([self isTextModeBrowser])
743     return NO;
744   if (![self isUnixBrowser])
745     return NO;
746   return YES;
747 }
748
749 /* NSCopying */
750
751 - (id)copyWithZone:(NSZone *)_zone {
752   return [self retain];
753 }
754
755 /* description */
756
757 - (NSString *)description {
758   NSMutableString *s;
759
760   s = [NSMutableString stringWithFormat:@"<%@[0x%08X]:",
761                          NSStringFromClass([self class]), self];
762   
763   //[s appendFormat:@" ua='%@'", self->userAgent];
764   [s appendFormat:@" type=%@ v%i.%i>",
765        [self userAgentType],
766        self->browserMajorVersion, self->browserMinorVersion];
767   [s appendFormat:@" os=%@",   [self os]];
768   [s appendFormat:@" cpu=%@",  [self cpu]];
769   
770   if ([self isFastTableBrowser])  [s appendString:@" fast-tbl"];
771   if ([self isCSS1Browser])       [s appendString:@" css1"];
772   if ([self isCSS2Browser])       [s appendString:@" css2"];
773   if ([self isXULBrowser])        [s appendString:@" xul"];
774   if ([self isTextModeBrowser])   [s appendString:@" text"];
775   if ([self isRobot])             [s appendString:@" robot"];
776   if ([self isJavaScriptBrowser]) [s appendString:@" js"];
777   if ([self isVBScriptBrowser])   [s appendString:@" vb"];
778   
779   [s appendString:@">"];
780   return s;
781 }
782
783 @end /* WEClientCapabilities */
784
785 static NSString *ClientCapsCacheKey = @"WEClientCapabilities";
786
787 @implementation WORequest(ClientCapabilities)
788
789 - (WEClientCapabilities *)clientCapabilities {
790   NSDictionary         *ua;
791   WEClientCapabilities *ccaps;
792   NSMutableDictionary  *md;
793
794   if ((ua = [self userInfo]) == nil) {
795     ccaps = [WEClientCapabilities alloc];
796     if ((ccaps = [ccaps initWithRequest:self]) == nil)
797       return nil;
798     ccaps = [ccaps autorelease];
799     
800     ua = [[NSDictionary alloc] initWithObjects:&ccaps
801                                forKeys:&ClientCapsCacheKey
802                                count:1];
803     [self setUserInfo:ua];
804     [ua release];
805     return ccaps;
806   }
807   
808   if ((ccaps = [ua objectForKey:ClientCapsCacheKey]))
809     return ccaps;
810   
811   ccaps = [WEClientCapabilities alloc];
812   if ((ccaps = [ccaps initWithRequest:self]) == nil)
813     return nil;
814   ccaps = [ccaps autorelease];
815     
816   md = [ua mutableCopy];
817   [md setObject:ccaps forKey:ClientCapsCacheKey];
818   ua = [md copy];
819   [md release];
820   [self setUserInfo:ua];
821   [ua release];
822   return ccaps;
823 }
824
825 @end /* WORequest(ClientCapabilities) */
826
827 static NSString *WEClientDetectorFormName = @"WEClientDetect";
828
829 @implementation JSClientCapabilityDetector
830
831 - (id)initWithName:(NSString *)_name
832   associations:(NSDictionary *)_config
833   template:(WOElement *)_c
834 {
835   if ((self = [super initWithName:_name associations:_config template:_c])) {
836     self->formName   = OWGetProperty(_config, @"formName");
837     self->clientCaps = OWGetProperty(_config, @"clientCaps");
838   }
839   return self;
840 }
841
842 - (void)dealloc {
843   [self->formName   release];
844   [self->clientCaps release];
845   [super dealloc];
846 }
847
848 - (NSString *)_formNameInContext:(WOContext *)_ctx {
849   if (self->formName)
850     return [self->formName stringValueInComponent:[_ctx component]];
851   return nil;
852 }
853
854 - (void)appendToResponse:(WOResponse *)_response inContext:(WOContext *)_ctx {
855   if (![_ctx isInForm]) {
856     [[_ctx component]
857            warnWithFormat:@"you must use %@ inside a form !",
858              NSStringFromClass([self class])];
859     return;
860   }
861   
862   if (![[[_ctx request] clientCapabilities] isJavaScriptBrowser])
863     /* only works on JavaScript browsers ... */
864     return;
865   
866   [_response appendContentString:@"<input type='hidden' name='"];
867   [_response appendContentString:WEClientDetectorFormName];
868   [_response appendContentString:@"' value='browserConfig' />"];
869   
870   [_response appendContentString:@"<script language='JavaScript'>\n"];
871   [_response appendContentString:@"<!-- hide\n"];
872   
873   [_response appendContentString:@"// -->\n"];
874   [_response appendContentString:@"</script>"];
875 }
876
877 @end /* JSClientCapabilityDetector */
878
879 /*
880   Netscape 4.76, Windows NT 4
881     'Mozilla/4.76 [en] (WinNT; U)'
882   
883   Netscape 6, Windows NT 4
884     'Mozilla/5.0 (Windows; U; WinNT4.0; en-US; m18) Gecko/20001108 Netscape6/6.0'
885   
886   Netscape Navigator 3.01[de], MacOS 8.1
887     'Mozilla/3.01 [de]-C-MACOS8 (Macintosh; I; PPC)'
888
889   Netscape Communicator 4.51, SuSE Linux 6.1
890     'Mozilla/4.51 [en] (X11; I; Linux 2.2.13 i686)'
891   
892   Mozilla M17, Windows NT 4
893     'Mozilla/5.0 (Windows; U; WinNT4.0; en-US; m17) Gecko/20000807'
894   
895   Internet Explorer 5.5, Windows NT 4
896     'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT 4.0)'
897   
898   Internet Explorer 3.0.1, MacOS 8.1
899     'Mozilla/3.0 (compatible; MSIE 3.0.1; Mac_PowerPC; Mac OS8)'
900     
901   Internet Explorer 5.0, MacOS 8.1
902     'Mozilla/4.0 (compatible; MSIE 5.0; Mac_PowerPC)'
903
904   Internet Explorer 5.0, SPARC Solaris 2.6 (trex)
905     'Mozilla/4.0 (compatible; MSIE 5.0; SunOS 5.6 sun4u; X11)'
906   
907   Konqueror/2.0, SuSE Linux 7.0
908     'Mozilla/5.0 (compatible; Konqueror/2.0; X11); Supports MD5-Digest; Supports gzip encoding'
909   
910   Lynx, SuSE Linux 6.1 (marvin)
911     'Lynx/2.8rel.2 libwww-FM/2.14'
912
913   Lynx, SPARC Solaris 2.6 (trex)
914     'Lynx/2.7 libwww-FM/2.14'
915   
916   Opera 4.02, Windows NT 4
917     'Mozilla/4.73 (Windows NT 4.0; U) Opera 4.02  [en]'
918   
919   Opera 5.0, Windows NT 4
920     'Mozilla/4.0 (compatible; MSIE 5.0; Windows NT 4.0) Opera 5.0  [en]'
921   
922   Amaya 4.0, Windows NT 4
923     'amaya/V4.0 libwww/5.3.1'
924   
925   XEmacs, SuSE Linux 6.1
926     'Emacs-W3/4.0pre.39 URL/p4.0pre.39 (i686-pc-linux; X11)'
927
928   XEmacs, SuSE Linux 7.2
929     'Emacs-W3/4.0pre.46 URL/p4.0pre.46 (i386-suse-linux; X11)'
930   
931   wget, SuSE Linux 6.1
932     'Wget/1.5.3'
933   
934   Windows 'WebFolder' NT4
935     'Microsoft Data Access Internet Publishing Provider Cache Manager'
936     'Mozilla/2.0 (compatible; MS FrontPage 4.0)'
937     'MSFrontPage/4.0'
938
939   Windows 98 IE 5 WebFolders
940     'Microsoft Data Access Internet Publishing Provider DAV'
941
942   OmniWeb
943     'OmniWeb/3.0.2 OWF/1999C'
944
945   Links, SuSE Linux 6.1 (marvin)
946     'Links (0.95; Linux 2.2.13 i686)'
947
948   Linux DAVFS
949     'DAV-FS/0.0.1'
950
951   MacOSX 10.0 DAVFS
952     fetch/1.0 Darwin/1.3.7 (Power Macintosh)
953
954   MacOSX 10.1.1 DAV FS
955     WebDAVFS/1.0 Darwin/5.1 (Power Macintosh)
956   
957   MacOSX 10.2.1 DAV FS
958     WebDAVFS/1.2.1 (01218000) Darwin/6.1 (Power Macintosh)
959   
960   Cadaver 0.17.0
961     'cadaver/0.17.0 neon/0.12.0-dev'
962   
963   Adobe GoLive 5
964     'GoLive/5. 0 [] (Windows 98; RATBERT)'
965
966   Dillo 0.6.2
967     - (very) small X11 web browser
968     'Dillo/0.6.1'
969   
970   Java SDK 1.3
971     'Java1.3.0'
972
973   Python 2.0
974     'Python-urllib/1.13'
975   
976   Apple MacOSX 10.2.1 / iCal 1.0 DAV Access Framework
977     'DAVAccess/1.0'
978   
979   Outlook 2000 on W2K with M$ Web Publishing Assistent
980     'Microsoft HTTP Post (RFC1867)'
981   
982   CURL program (libcurl)
983     'curl/7.9.8 (i686-suse-linux) libcurl 7.9.8 (OpenSSL 0.9.6g) (ipv6 enabled)'
984
985   Evolution 1.0.8 with Exchange Connector (WebDAV)
986     'Evolution/1.0.8'
987   
988   Outlook 2002 on W2K (HotMail HTTP access)
989     'Microsoft-Outlook/10.0 (TmstmpExt)'
990   
991   Outlook Express 5.5 on W2K (HotMail HTTP access)
992     'Outlook-Express/5.5 (MSIE 5.5; Windows NT 5.0; Q312461; T312461; TmstmpExt)'
993   
994   Outlook Express 6.0 on W2K (HotMail HTTP access)
995     'Outlook-Express/6.0 (MSIE 6.0; Windows NT 5.0; Q312461; T312461; TmstmpExt)'
996
997   Nautilus (GNOME Virtual Filesystem)
998     'gnome-vfs/1.0.5'
999   
1000   Konqueror 3.0.3 (SuSE 8.1)
1001     - does not send a user-agent in webdav:// mode !
1002
1003   ZideLook 0.0
1004     'neon/0.23.5 NeonConnection 0.0'
1005
1006   Safari v74 (MacOSX 10.2)
1007     'Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/74 (KHTML, like Gecko) Safari/74'
1008     - why does it say Mozilla/5.0 ??
1009
1010   Evolution 1.4.0 with Exchange Connector 1.4.0 (SuSE 8.2)
1011     'Evolution/1.4.0'
1012     
1013   Mozilla Firebird 0.6 (MacOSX 10.2)
1014     'Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.4b) Gecko/20030516 Mozilla Firebird/0.6'
1015
1016   SOUP (Evo OGo Connector by Anders)
1017     'Soup/1.99.24'
1018
1019   Entourage/X (Entourage WebDAV 10.1.4, MacOSX)
1020     'Entourage/10.0 (Mac_PowerPC; DigExt; TmstmpExt)'
1021   
1022   Some unknown iCal.app
1023     'DAVKit/0.1'
1024
1025   NetNewsWire full version: 
1026     'NetNewsWire/1.0.5 (Mac OS X; http://ranchero.com/netnewswire/)'
1027   
1028   NetNewsWire lite version: 
1029     'NetNewsWire/1.0.3 (Mac OS X; Lite; http://ranchero.com/netnewswire/)'
1030
1031   Python xmlrpclib:
1032     'xmlrpclib.py/1.0.0 (by www.pythonware.com)'
1033
1034   Windows 2000 IE 6 WebFolders
1035     'Microsoft Data Access Internet Publishing Provider DAV 1.1'
1036
1037   Morgul, Windows WebDAV client
1038     'Morgul'
1039
1040   Apple iSync v122 / CoreFoundation Network
1041     'CFNetwork/1.1'
1042
1043   Kung-Log (WebServicesCore)
1044     'Kung-Log/1.3 (Macintosh; U; PPC Mac OS X) WebServicesCore'
1045
1046   Ecto (WebServicesCore)
1047     'ecto (Macintosh; U; PPC Mac OS X) WebServicesCore'
1048
1049   NewsFire
1050     'NewsFire/0.23'
1051   
1052   Goliath
1053     'Goliath/1.0.1 (Macintosh-Carbon; PPC)'
1054
1055   PERL HTTP::DAV
1056     'DAV.pm/v0.31'
1057   
1058   Google Ads
1059     'Mediapartners-Google/2.1'
1060 */