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