View Javadoc

1   package org.apache.velocity.tools.view;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.util.regex.Matcher;
23  import java.util.regex.Pattern;
24  import java.util.regex.PatternSyntaxException;
25  import java.util.*;
26  import javax.servlet.http.HttpServletRequest;
27  import org.apache.velocity.runtime.log.Log;
28  import org.apache.velocity.tools.Scope;
29  import org.apache.velocity.tools.ConversionUtils;
30  import org.apache.velocity.tools.generic.FormatConfig;
31  import org.apache.velocity.tools.config.DefaultKey;
32  import org.apache.velocity.tools.config.InvalidScope;
33  
34  /**
35   *  <p>browser-sniffing tool (session or request scope requested, session scope advised).</p>
36   *  <p></p>
37   * <p><b>Usage:</b></p>
38   * <p>BrowserTool defines properties that are used to test the client browser, operating system, device, language...
39   * Apart from properties related to browser version and language, all properties are booleans.</p>
40   * <p>The following properties are available:</p>
41   * <ul>
42   * <li><i>Versioning:</i>version majorVersion minorVersion geckoVersion</li>
43   * <li><i>Browser:</i>mosaic netscape nav2 nav3 nav4 nav4up nav45 nav45up nav6 nav6up navgold firefox safari
44   * ie ie3 ie4 ie4up ie5 ie5up ie55 ie55up ie6 ie6up ie7 ie7up ie8 ie8up opera opera3 opera4 opera5 opera6 opera7 opera8 opera9 lynx links w3m
45   * aol aol3 aol4 aol5 aol6 neoplanet neoplanet2 amaya icab avantgo emacs mozilla gecko webtv staroffice java hotjava httpclient lobo
46   * lotusnotes konqueror galeon kmeleon chrome</li>
47   * <li><i>Operating systems:</i>win16 win3x win31 win95 win98 winnt windows win32 winme win2k winxp vista dotnet
48   * mac macosx mac68k macppc os2 unix sun sun4 sun5 suni86 irix irix5 irix6 hpux hpux9 hpux10 aix aix1 aix2 aix3 aix4
49   * linux sco unixware mpras reliant dec sinix freebsd bsd vms x11 amiga</li>
50   * <li><i>Devices:</i>palm audrey iopener wap blackberry</li>
51   * <li><i>Features:</i>javascript css css1 css2 dom0 dom1 dom2</li>
52   * <li><i>Special:</i>robot (true if the page is requested by a robot, i.e. when one of the following properties is true:
53   * wget getright yahoo altavista lycos infoseek lwp webcrawler linkexchange slurp google java)</li>
54   * <li>Language: preferredLanguageTag (a string like 'en', 'da', 'en-US', ...), preferredLocale (a java Locale)</li>
55   * </ul>
56   *
57   * <p>Language properties are filtered by the languagesFilter tool param, if present. If no matching language is found, or if there is no
58   * matching language, the tools defaut locale (or the first value of languagesFilter) is returned.
59   * Their value is guarantied to belong to the set provided in languagesFilter, if any.</p>
60   *
61   * Thanks to Lee Semel (lee@semel.net), the author of the HTTP::BrowserDetect Perl module.
62   * See also:
63   * * http://www.zytrax.com/tech/web/browser_ids.htm
64   * * http://en.wikipedia.org/wiki/User_agent
65   * * http://www.user-agents.org/
66   *
67   * @author <a href="mailto:claude@renegat.net">Claude Brisson</a>
68   * @since VelocityTools 2.0
69   * @version $Revision$ $Date$
70   */
71  @DefaultKey("browser")
72  @InvalidScope(Scope.APPLICATION)
73  public class BrowserTool extends FormatConfig implements java.io.Serializable
74  {
75      private static final long serialVersionUID = 1734529350532353339L;
76  
77      protected Log LOG;
78  
79      /* User-Agent header variables */
80      private String userAgent = null;
81      private String version = null;
82      private int majorVersion = -1;
83      private int minorVersion = -1;
84      private String geckoVersion = null;
85      private int geckoMajorVersion = -1;
86      private int geckoMinorVersion = -1;
87  
88      private static Pattern genericVersion = Pattern.compile(
89              "/"
90              /* Version starts with a slash */
91              +
92              "([A-Za-z]*"
93              /* Eat any letters before the major version */
94              +
95              "( [\\d]* )"
96              /* Major version number is every digit before the first dot */
97              + "\\." /* The first dot */
98              +
99              "( [\\d]* )"
100             /* Minor version number is every digit after the first dot */
101             + "[^\\s]*)" /* Throw away the remaining */
102             , Pattern.COMMENTS);
103     private static Pattern firefoxVersion = Pattern.compile(
104             "/"
105             +
106             "(( [\\d]* )"
107             /* Major version number is every digit before the first dot */
108             + "\\." /* The first dot */
109             +
110             "( [\\d]* )"
111             /* Minor version number is every digit after the first dot */
112             + "[^\\s]*)" /* Throw away the remaining */
113             , Pattern.COMMENTS);
114     private static Pattern ieVersion = Pattern.compile(
115             "compatible;"
116             + "\\s*"
117             + "\\w*" /* Browser name */
118             + "[\\s|/]"
119             +
120             "([A-Za-z]*"
121             /* Eat any letters before the major version */
122             +
123             "( [\\d]* )"
124             /* Major version number is every digit before first dot */
125             + "\\." /* The first dot */
126             +
127             "( [\\d]* )"
128             /* Minor version number is digits after first dot */
129             + "[^\\s]*)" /* Throw away remaining dots and digits */
130             , Pattern.COMMENTS);
131     private static Pattern safariVersion = Pattern.compile(
132             "safari/"
133             +
134             "(( [\\d]* )"
135             /* Major version number is every digit before first dot */
136             + "(?:"
137             + "\\." /* The first dot */
138             +
139             " [\\d]* )?)"
140             /* Minor version number is digits after first dot */
141             , Pattern.COMMENTS);
142     private static Pattern mozillaVersion = Pattern.compile(
143             "netscape/"
144             +
145             "(( [\\d]* )"
146             /* Major version number is every digit before the first dot */
147             + "\\." /* The first dot */
148             +
149             "( [\\d]* )"
150             /* Minor version number is every digit after the first dot */
151             + "[^\\s]*)" /* Throw away the remaining */
152             , Pattern.COMMENTS);
153     private static Pattern fallbackVersion = Pattern.compile(
154             "[\\w]+/"
155             +
156             "( [\\d]+ );"
157             /* Major version number is every digit before the first dot */
158             , Pattern.COMMENTS);
159 
160 
161     /* Accept-Language header variables */
162     private String acceptLanguage = null;
163     private SortedMap<Float,List<String>> languageRangesByQuality = null;
164     private String starLanguageRange = null;
165     // pametrizable filter of retained laguages
166     private List<String> languagesFilter = null;
167     private String preferredLanguage = null;
168 
169     private static Pattern quality = Pattern.compile("^q\\s*=\\s*(\\d(?:0(?:.\\d{0,3})?|1(?:.0{0,3}))?)$");
170 
171     /**
172      * Retrieves the User-Agent header from the request (if any).
173      * @see #setUserAgent
174      */
175     public void setRequest(HttpServletRequest request)
176     {
177         if (request != null)
178         {
179             setUserAgent(request.getHeader("User-Agent"));
180             setAcceptLanguage(request.getHeader("Accept-Language"));
181         }
182         else
183         {
184             setUserAgent(null);
185             setAcceptLanguage(null);
186         }
187     }
188 
189     /**
190      * Set log.
191      */
192     public void setLog(Log log)
193     {
194         if (log == null)
195         {
196             throw new NullPointerException("log should not be set to null");
197         }
198         this.LOG = log;
199     }
200 
201 
202     /**
203      * Sets the User-Agent string to be parsed for info.  If null, the string
204      * will be empty and everything will return false or null.  Otherwise,
205      * it will set the whole string to lower case before storing to simplify
206      * parsing.
207      */
208     public void setUserAgent(String ua)
209     {
210         if (ua == null)
211         {
212             userAgent = "";
213         }
214         else
215         {
216             userAgent = ua.toLowerCase();
217         }
218     }
219 
220     public void setAcceptLanguage(String al)
221     {
222         if(al == null)
223         {
224             acceptLanguage = "";
225         }
226         else
227         {
228             acceptLanguage = al.toLowerCase();
229         }
230     }
231 
232     public void setLanguagesFilter(String filter)
233     {
234         if(filter == null || filter.length() == 0)
235         {
236             languagesFilter = null;
237         }
238         else
239         {
240             languagesFilter = Arrays.asList(filter.split(","));
241         }
242         // clear preferred language cache
243         preferredLanguage = null;
244     }
245 
246     public String getLanguagesFilter()
247     {
248         return languagesFilter.toString();
249     }
250 
251     @Override
252     public String toString()
253     {
254         return this.getClass().getSimpleName()+"[ua="+userAgent+"]";
255     }
256 
257 
258     /* Generic getter for unknown tests
259      */
260     public boolean get(String key)
261     {
262         return test(key);
263     }
264 
265     public String getUserAgent()
266     {
267 	    return userAgent;
268     }
269 
270     public String getAcceptLanguage()
271     {
272         return acceptLanguage;
273     }
274 
275     /* Versioning */
276 
277     public String getVersion()
278     {
279         parseVersion();
280         return version;
281     }
282 
283     public int getMajorVersion()
284     {
285         parseVersion();
286         return majorVersion;
287     }
288 
289     public int getMinorVersion()
290     {
291         parseVersion();
292         return minorVersion;
293     }
294 
295     public String getGeckoVersion()
296     {
297         parseVersion();
298         return geckoVersion;
299     }
300 
301     public int getGeckoMajorVersion()
302     {
303         parseVersion();
304         return geckoMajorVersion;
305     }
306 
307     public int getGeckoMinorVersion()
308     {
309         parseVersion();
310         return geckoMinorVersion;
311     }
312 
313     /* Browsers */
314 
315     public boolean getGecko()
316     {
317         return test("gecko") && !test("like gecko");
318     }
319 
320     public boolean getFirefox()
321     {
322         return test("firefox") || test("firebird") || test("phoenix") || test("iceweasel");
323     }
324 
325     public boolean getIceweasel()
326     {
327         return test("iceweasel");
328     }
329 
330     public boolean getGaleon()
331     {
332         return test("galeon");
333     }
334 
335     public boolean getKmeleon()
336     {
337         return test("k-meleon");
338     }
339 
340     public boolean getEpiphany()
341     {
342         return test("epiphany");
343     }
344 
345     public boolean getSafari()
346     {
347         return (test("safari") || test("applewebkit")) && !test("chrome");
348     }
349 
350     public boolean getChrome() {
351         return test("chrome");
352     }
353 
354     public boolean getDillo()
355     {
356         return test("dillo");
357     }
358 
359     public boolean getNetscape()
360     {
361         return test("netscape") || !getFirefox() && !getSafari() && test("mozilla") &&
362                !test("spoofer") && !test("compatible") && !test("opera") &&
363                !test("webtv") && !test("hotjava");
364     }
365 
366     public boolean getNav2()
367     {
368         return getNetscape() && getMajorVersion() == 2;
369     }
370 
371     public boolean getNav3()
372     {
373         return getNetscape() && getMajorVersion() == 3;
374     }
375 
376     public boolean getNav4()
377     {
378         return getNetscape() && getMajorVersion() == 4;
379     }
380 
381     public boolean getNav4up()
382     {
383         return getNetscape() && getMajorVersion() >= 4;
384     }
385 
386     public boolean getNav45()
387     {
388         return getNetscape() && getMajorVersion() == 4 &&
389                getMinorVersion() == 5;
390     }
391 
392     public boolean getNav45up()
393     {
394         return getNetscape() && getMajorVersion() >= 5 ||
395                getNav4() && getMinorVersion() >= 5;
396     }
397 
398     public boolean getNavgold()
399     {
400         return test("gold");
401     }
402 
403     public boolean getNav6()
404     {
405         return getNetscape() && getMajorVersion() == 5; /* sic */
406     }
407 
408     public boolean getNav6up()
409     {
410         return getNetscape() && getMajorVersion() >= 5;
411     }
412 
413     public boolean getMozilla()
414     {
415         return getNetscape() && getGecko();
416     }
417 
418     public boolean getIe()
419     {
420         return test("msie") && !test("opera") ||
421                test("microsoft internet explorer");
422     }
423 
424     public boolean getIe3()
425     {
426         return getIe() && getMajorVersion() < 4;
427     }
428 
429     public boolean getIe4()
430     {
431         return getIe() && getMajorVersion() == 4;
432     }
433 
434     public boolean getIe4up()
435     {
436         return getIe() && getMajorVersion() >= 4;
437     }
438 
439     public boolean getIe5()
440     {
441         return getIe() && getMajorVersion() == 5;
442     }
443 
444     public boolean getIe5up()
445     {
446         return getIe() && getMajorVersion() >= 5;
447     }
448 
449     public boolean getIe55()
450     {
451         return getIe() && getMajorVersion() == 5 && getMinorVersion() >= 5;
452     }
453 
454     public boolean getIe55up()
455     {
456         return (getIe5() && getMinorVersion() >= 5) ||
457                (getIe() && getMajorVersion() >= 6);
458     }
459 
460     public boolean getIe6()
461     {
462         return getIe() && getMajorVersion() == 6;
463     }
464 
465     public boolean getIe6up()
466     {
467         return getIe() && getMajorVersion() >= 6;
468     }
469 
470     public boolean getIe7()
471     {
472         return getIe() && getMajorVersion() == 7;
473     }
474 
475     public boolean getIe7up()
476     {
477         return getIe() && getMajorVersion() >= 7;
478     }
479 
480     public boolean getIe8()
481     {
482         return getIe() && getMajorVersion() == 8;
483     }
484 
485     public boolean getIe8up()
486     {
487         return getIe() && getMajorVersion() >= 8;
488     }
489 
490     public boolean getNeoplanet()
491     {
492         return test("neoplanet");
493     }
494 
495     public boolean getNeoplanet2()
496     {
497         return getNeoplanet() && test("2.");
498     }
499 
500     public boolean getAol()
501     {
502         return test("aol");
503     }
504 
505     public boolean getAol3()
506     {
507         return test("aol 3.0") || getAol() && getIe3();
508     }
509 
510     public boolean getAol4()
511     {
512         return test("aol 4.0") || getAol() && getIe4();
513     }
514 
515     public boolean getAol5()
516     {
517         return test("aol 5.0");
518     }
519 
520     public boolean getAol6()
521     {
522         return test("aol 6.0");
523     }
524 
525     public boolean getAolTV()
526     {
527         return test("navio") || test("navio_aoltv");
528     }
529 
530     public boolean getOpera()
531     {
532         return test("opera");
533     }
534 
535     public boolean getOpera3()
536     {
537         return test("opera 3") || test("opera/3");
538     }
539 
540     public boolean getOpera4()
541     {
542         return test("opera 4") || test("opera/4");
543     }
544 
545     public boolean getOpera5()
546     {
547         return test("opera 5") || test("opera/5");
548     }
549 
550     public boolean getOpera6()
551     {
552         return test("opera 6") || test("opera/6");
553     }
554 
555     public boolean getOpera7()
556     {
557         return test("opera 7") || test("opera/7");
558     }
559 
560     public boolean getOpera8()
561     {
562         return test("opera 8") || test("opera/8");
563     }
564 
565     public boolean getOpera9()
566     {
567         return test("opera/9");
568     }
569 
570     public boolean getHotjava()
571     {
572         return test("hotjava");
573     }
574 
575     public boolean getHotjava3()
576     {
577         return getHotjava() && getMajorVersion() == 3;
578     }
579 
580     public boolean getHotjava3up()
581     {
582         return getHotjava() && getMajorVersion() >= 3;
583     }
584 
585     public boolean getLobo()
586     {
587         return test("lobo");
588     }
589 
590     public boolean getHttpclient()
591     {
592         return test("httpclient");
593     }
594 
595     public boolean getAmaya()
596     {
597         return test("amaya");
598     }
599 
600     public boolean getCurl()
601     {
602         return test("libcurl");
603     }
604 
605     public boolean getStaroffice()
606     {
607         return test("staroffice");
608     }
609 
610     public boolean getIcab()
611     {
612         return test("icab");
613     }
614 
615     public boolean getLotusnotes()
616     {
617         return test("lotus-notes");
618     }
619 
620     public boolean getKonqueror()
621     {
622         return test("konqueror");
623     }
624 
625     public boolean getLynx()
626     {
627         return test("lynx");
628     }
629 
630     public boolean getLinks()
631     {
632         return test("links");
633     }
634 
635     public boolean getW3m()
636     {
637         return test("w3m");
638     }
639 
640     public boolean getWebTV()
641     {
642         return test("webtv");
643     }
644 
645     public boolean getMosaic()
646     {
647         return test("mosaic");
648     }
649 
650     public boolean getWget()
651     {
652         return test("wget");
653     }
654 
655     public boolean getGetright()
656     {
657         return test("getright");
658     }
659 
660     public boolean getLwp()
661     {
662         return test("libwww-perl") || test("lwp-");
663     }
664 
665     public boolean getYahoo()
666     {
667         return test("yahoo");
668     }
669 
670     public boolean getGoogle()
671     {
672         return test("google");
673     }
674 
675     public boolean getJava()
676     {
677         return test("java") || test("jdk") || test("httpunit") || test("httpclient") || test("lobo");
678     }
679 
680     public boolean getAltavista()
681     {
682         return test("altavista");
683     }
684 
685     public boolean getScooter()
686     {
687         return test("scooter");
688     }
689 
690     public boolean getLycos()
691     {
692         return test("lycos");
693     }
694 
695     public boolean getInfoseek()
696     {
697         return test("infoseek");
698     }
699 
700     public boolean getWebcrawler()
701     {
702         return test("webcrawler");
703     }
704 
705     public boolean getLinkexchange()
706     {
707         return test("lecodechecker");
708     }
709 
710     public boolean getSlurp()
711     {
712         return test("slurp");
713     }
714 
715     public boolean getRobot()
716     {
717         return getWget() || getGetright() || getLwp() || getYahoo() ||
718                getGoogle() || getAltavista() || getScooter() || getLycos() ||
719                getInfoseek() || getWebcrawler() || getLinkexchange() ||
720                test("bot") || test("spider") || test("crawl") ||
721                test("agent") || test("seek") || test("search") ||
722                test("reap") || test("worm") || test("find") || test("index") ||
723                test("copy") || test("fetch") || test("ia_archive") ||
724                test("zyborg");
725     }
726 
727     /* Devices */
728 
729     public boolean getBlackberry()
730     {
731         return test("blackberry");
732     }
733 
734     public boolean getAudrey()
735     {
736         return test("audrey");
737     }
738 
739     public boolean getIopener()
740     {
741         return test("i-opener");
742     }
743 
744     public boolean getAvantgo()
745     {
746         return test("avantgo");
747     }
748 
749     public boolean getPalm()
750     {
751         return getAvantgo() || test("palmos");
752     }
753 
754     public boolean getWap()
755     {
756         return test("up.browser") || test("nokia") || test("alcatel") ||
757                test("ericsson") || userAgent.indexOf("sie-") == 0 ||
758                test("wmlib") || test(" wap") || test("wap ") ||
759                test("wap/") || test("-wap") || test("wap-") ||
760                userAgent.indexOf("wap") == 0 ||
761                test("wapper") || test("zetor");
762     }
763 
764     /* Operating System */
765 
766     public boolean getWin16()
767     {
768         return test("win16") || test("16bit") || test("windows 3") ||
769                test("windows 16-bit");
770     }
771 
772     public boolean getWin3x()
773     {
774         return test("win16") || test("windows 3") || test("windows 16-bit");
775     }
776 
777     public boolean getWin31()
778     {
779         return test("win16") || test("windows 3.1") || test("windows 16-bit");
780     }
781 
782     public boolean getWin95()
783     {
784         return test("win95") || test("windows 95");
785     }
786 
787     public boolean getWin98()
788     {
789         return test("win98") || test("windows 98");
790     }
791 
792     public boolean getWinnt()
793     {
794         return test("winnt") || test("windows nt") || test("nt4") || test("nt3");
795     }
796 
797     public boolean getWin2k()
798     {
799         return test("nt 5.0") || test("nt5");
800     }
801 
802     public boolean getWinxp()
803     {
804         return test("nt 5.1");
805     }
806 
807     public boolean getVista()
808     {
809         return test("nt 6.0");
810     }
811 
812     public boolean getDotnet()
813     {
814         return test(".net clr");
815     }
816 
817     public boolean getWinme()
818     {
819         return test("win 9x 4.90");
820     }
821 
822     public boolean getWin32()
823     {
824         return getWin95() || getWin98() || getWinnt() || getWin2k() ||
825                getWinxp() || getWinme() || test("win32");
826     }
827 
828     public boolean getWindows()
829     {
830         return getWin16() || getWin31() || getWin95() || getWin98() ||
831                getWinnt() || getWin32() || getWin2k() || getWinme() ||
832                test("win");
833     }
834 
835     public boolean getMac()
836     {
837         return test("macintosh") || test("mac_");
838     }
839 
840     public boolean getMacosx()
841     {
842         return test("macintosh") || test("mac os x");
843     }
844 
845     public boolean getMac68k()
846     {
847         return getMac() && (test("68k") || test("68000"));
848     }
849 
850     public boolean getMacppc()
851     {
852         return getMac() && (test("ppc") || test("powerpc"));
853     }
854 
855     public boolean getAmiga()
856     {
857         return test("amiga");
858     }
859 
860     public boolean getEmacs()
861     {
862         return test("emacs");
863     }
864 
865     public boolean getOs2()
866     {
867         return test("os/2");
868     }
869 
870     public boolean getSun()
871     {
872         return test("sun");
873     }
874 
875     public boolean getSun4()
876     {
877         return test("sunos 4");
878     }
879 
880     public boolean getSun5()
881     {
882         return test("sunos 5");
883     }
884 
885     public boolean getSuni86()
886     {
887         return getSun() && test("i86");
888     }
889 
890     public boolean getIrix()
891     {
892         return test("irix");
893     }
894 
895     public boolean getIrix5()
896     {
897         return test("irix5");
898     }
899 
900     public boolean getIrix6()
901     {
902         return test("irix6");
903     }
904 
905     public boolean getHpux()
906     {
907         return test("hp-ux");
908     }
909 
910     public boolean getHpux9()
911     {
912         return getHpux() && test("09.");
913     }
914 
915     public boolean getHpux10()
916     {
917         return getHpux() && test("10.");
918     }
919 
920     public boolean getAix()
921     {
922         return test("aix");
923     }
924 
925     public boolean getAix1()
926     {
927         return test("aix 1");
928     }
929 
930     public boolean getAix2()
931     {
932         return test("aix 2");
933     }
934 
935     public boolean getAix3()
936     {
937         return test("aix 3");
938     }
939 
940     public boolean getAix4()
941     {
942         return test("aix 4");
943     }
944 
945     public boolean getLinux()
946     {
947         return test("linux");
948     }
949 
950     public boolean getSco()
951     {
952         return test("sco") || test("unix_sv");
953     }
954 
955     public boolean getUnixware()
956     {
957         return test("unix_system_v");
958     }
959 
960     public boolean getMpras()
961     {
962         return test("ncr");
963     }
964 
965     public boolean getReliant()
966     {
967         return test("reliantunix");
968     }
969 
970     public boolean getDec()
971     {
972         return test("dec") || test("osf1") || test("delalpha") ||
973                test("alphaserver") || test("ultrix") || test("alphastation");
974     }
975 
976     public boolean getSinix()
977     {
978         return test("sinix");
979     }
980 
981     public boolean getFreebsd()
982     {
983         return test("freebsd");
984     }
985 
986     public boolean getBsd()
987     {
988         return test("bsd");
989     }
990 
991     public boolean getX11()
992     {
993         return test("x11");
994     }
995 
996     public boolean getUnix()
997     {
998         return getX11() || getSun() || getIrix() || getHpux() || getSco() ||
999                getUnixware() || getMpras() || getReliant() || getDec() ||
1000                getLinux() || getBsd() || test("unix");
1001     }
1002 
1003     public boolean getVMS()
1004     {
1005         return test("vax") || test("openvms");
1006     }
1007 
1008     /* Features */
1009 
1010     /* Since support of those features is often partial, the sniffer returns true
1011         when a consequent subset is supported. */
1012 
1013     public boolean getCss()
1014     {
1015         return (getIe() && getMajorVersion() >= 4) ||
1016                (getNetscape() && getMajorVersion() >= 4) ||
1017                getGecko() ||
1018                getKonqueror() ||
1019                (getOpera() && getMajorVersion() >= 3) ||
1020                getSafari() ||
1021                getChrome() ||
1022                getLinks();
1023     }
1024 
1025     public boolean getCss1()
1026     {
1027         return getCss();
1028     }
1029 
1030     public boolean getCss2()
1031     {
1032         return getIe() &&
1033                (getMac() && getMajorVersion() >= 5) ||
1034                (getWin32() && getMajorVersion() >= 6) ||
1035                getGecko() || // && version >= ?
1036                (getOpera() && getMajorVersion() >= 4) ||
1037                (getSafari() && getMajorVersion() >= 2) ||
1038                (getKonqueror() && getMajorVersion() >= 2) ||
1039                getChrome();
1040     }
1041 
1042     public boolean getDom0()
1043     {
1044         return (getIe() && getMajorVersion() >= 3) ||
1045                (getNetscape() && getMajorVersion() >= 2) ||
1046                (getOpera() && getMajorVersion() >= 3) ||
1047                getGecko() ||
1048                getSafari() ||
1049                getChrome() ||
1050                getKonqueror();
1051     }
1052 
1053     public boolean getDom1()
1054     {
1055         return (getIe() && getMajorVersion() >= 5) ||
1056                getGecko() ||
1057                (getSafari() && getMajorVersion() >= 2) ||
1058                (getOpera() && getMajorVersion() >= 4) ||
1059                (getKonqueror() && getMajorVersion() >= 2)
1060                || getChrome();
1061     }
1062 
1063     public boolean getDom2()
1064     {
1065         return (getIe() && getMajorVersion() >= 6) ||
1066                (getMozilla() && getMajorVersion() >= 5.0) ||
1067                (getOpera() && getMajorVersion() >= 7) ||
1068                getFirefox() ||
1069                getChrome();
1070     }
1071 
1072     public boolean getJavascript()
1073     {
1074         return getDom0(); // good approximation
1075     }
1076 
1077     /* Languages */
1078 
1079     public String getPreferredLanguage()
1080     {
1081         if(preferredLanguage != null) return preferredLanguage;
1082 
1083         parseAcceptLanguage();
1084         if(languageRangesByQuality.size() == 0)
1085         {
1086             preferredLanguage = starLanguageRange; // may be null
1087         }
1088         else
1089         {
1090             List<List<String>> lists = new ArrayList<List<String>>(languageRangesByQuality.values());
1091             Collections.reverse(lists);
1092             for(List<String> lst : lists) // sorted by quality (treemap)
1093             {
1094                 for(String l : lst)
1095                 {
1096                     preferredLanguage = filterLanguageTag(l);
1097                     if(preferredLanguage != null) break;
1098                 }
1099                 if(preferredLanguage != null) break;
1100             }
1101         }
1102         // fallback
1103         if(preferredLanguage == null)
1104         {
1105             preferredLanguage = filterLanguageTag(languagesFilter == null ? getLocale().getDisplayName() : languagesFilter.get(0));
1106         }
1107         // preferredLanguage should now never be null
1108         assert(preferredLanguage != null);
1109         return preferredLanguage;
1110     }
1111 
1112     public Locale getPreferredLocale()
1113     {
1114         return ConversionUtils.toLocale(getPreferredLanguage());
1115     }
1116 
1117     /* Helpers */
1118 
1119     private boolean test(String key)
1120     {
1121         return userAgent.indexOf(key) != -1;
1122     }
1123 
1124     private void parseVersion()
1125     {
1126         try
1127         {
1128             if (version != null)
1129             {
1130                 return; /* parsing of version already done */
1131             }
1132 
1133             /* generic versionning */
1134             Matcher v = genericVersion.matcher(userAgent);
1135 
1136             if (v.find())
1137             {
1138                 version = v.group(1);
1139                 if(version.endsWith(";"))
1140                 {
1141                     version = version.substring(0,version.length()-1);
1142                 }
1143                 try
1144                 {
1145                     majorVersion = Integer.valueOf(v.group(2));
1146                     String minor = v.group(3);
1147                     if (minor.startsWith("0"))
1148                     {
1149                         minorVersion = 0;
1150                     }
1151                     else
1152                     {
1153                         minorVersion = Integer.valueOf(minor);
1154                     }
1155                 }
1156                 catch (NumberFormatException nfe)
1157                 {
1158                     LOG.error("BrowserTool: Could not parse browser version for User-Agent: "+userAgent,nfe);
1159                 }
1160             }
1161 
1162             /* Firefox versionning */
1163             if (test("firefox"))
1164             {
1165                 Matcher fx = firefoxVersion.matcher(userAgent);
1166                 if (fx.find())
1167                 {
1168                     version = fx.group(1);
1169                     try
1170                     {
1171                         majorVersion = Integer.valueOf(fx.group(2));
1172                         String minor = fx.group(3);
1173                         if (minor.startsWith("0"))
1174                         {
1175                             minorVersion = 0;
1176                         }
1177                         else
1178                         {
1179                             minorVersion = Integer.valueOf(minor);
1180                         }
1181                     }
1182                     catch (NumberFormatException nfe)
1183                     {
1184                         LOG.error("BrowserTool: Could not parse browser version for User-Agent: "+userAgent,nfe);
1185                     }
1186                 }
1187             }
1188 
1189             /* IE versionning */
1190             else if (test("compatible"))
1191             {
1192                 Matcher ie = ieVersion.matcher(userAgent);
1193 
1194                 if (ie.find())
1195                 {
1196                     version = ie.group(1);
1197                     try
1198                     {
1199                         majorVersion = Integer.valueOf(ie.group(2));
1200                         String minor = ie.group(3);
1201                         if (minor.startsWith("0"))
1202                         {
1203                             minorVersion = 0;
1204                         }
1205                         else
1206                         {
1207                             minorVersion = Integer.valueOf(minor);
1208                         }
1209                     }
1210                     catch (NumberFormatException nfe)
1211                     {
1212                         LOG.error("BrowserTool: Could not parse browser version for User-Agent: "+userAgent,nfe);
1213                     }
1214                 }
1215             }
1216 
1217             /* Safari versionning*/
1218             else if (getSafari())
1219             {
1220                 Matcher safari = safariVersion.matcher(userAgent);
1221                 if (safari.find())
1222                 {
1223                     version = safari.group(1);
1224                     try
1225                     {
1226                         int sv = Integer.valueOf(safari.group(2));
1227                         majorVersion = sv / 100;
1228                         minorVersion = sv % 100;
1229                     }
1230                     catch (NumberFormatException nfe)
1231                     {
1232                         LOG.error("BrowserTool: Could not parse browser version for User-Agent: "+userAgent,nfe);
1233                     }
1234                 }
1235             }
1236 
1237             /* Gecko-powered Netscape (i.e. Mozilla) versions */
1238             else if (getGecko() && getNetscape() && test("netscape"))
1239             {
1240                 Matcher netscape = mozillaVersion.matcher(userAgent);
1241                 if (netscape.find())
1242                 {
1243                     version = netscape.group(1);
1244                     try
1245                     {
1246                         majorVersion = Integer.valueOf(netscape.group(2));
1247                         String minor = netscape.group(3);
1248                         if (minor.startsWith("0"))
1249                         {
1250                             minorVersion = 0;
1251                         }
1252                         else
1253                         {
1254                             minorVersion = Integer.valueOf(minor);
1255                         }
1256                     }
1257                     catch (NumberFormatException nfe)
1258                     {
1259                         LOG.error("BrowserTool: Could not parse browser version for User-Agent: "+userAgent,nfe);
1260                     }
1261                 }
1262             }
1263 
1264             /* last try if version not found */
1265             if (version == null)
1266             {
1267                 Matcher mv = fallbackVersion.matcher(userAgent);
1268                 if (mv.find())
1269                 {
1270                     version = mv.group(1);
1271                     try
1272                     {
1273                         majorVersion = Integer.valueOf(version);
1274                         minorVersion = 0;
1275                     }
1276                     catch (NumberFormatException nfe)
1277                     {
1278                         LOG.error("BrowserTool: Could not parse browser version for User-Agent: "+userAgent,nfe);
1279                     }
1280                 }
1281             }
1282 
1283             /* gecko engine version */
1284             if (getGecko())
1285             {
1286             	Matcher g = Pattern.compile(
1287                         "\\([^)]*rv:(([\\d]*)\\.([\\d]*).*?)\\)"
1288                         ).matcher(userAgent);
1289                 if (g.find())
1290                 {
1291                     geckoVersion = g.group(1);
1292                     try
1293                     {
1294                     	geckoMajorVersion = Integer.valueOf(g.group(2));
1295                     	String minor = g.group(3);
1296                         if (minor.startsWith("0"))
1297                         {
1298                             geckoMinorVersion = 0;
1299                         }
1300                         else
1301                         {
1302                             geckoMinorVersion = Integer.valueOf(minor);
1303                         }
1304                     }
1305                     catch (NumberFormatException nfe)
1306                     {
1307                         LOG.error("BrowserTool: Could not parse browser version for User-Agent: "+userAgent,nfe);
1308                     }
1309                 }
1310             }
1311         }
1312         catch (PatternSyntaxException pse)
1313         {
1314             LOG.error("BrowserTool: Could not parse browser version for User-Agent: "+userAgent,pse);
1315         }
1316     }
1317 
1318     private void parseAcceptLanguage()
1319     {
1320         if(languageRangesByQuality != null)
1321         {
1322             // already done
1323             return;
1324         }
1325 
1326         languageRangesByQuality = new TreeMap<Float,List<String>>();
1327         StringTokenizer languageTokenizer = new StringTokenizer(acceptLanguage, ",");
1328         while (languageTokenizer.hasMoreTokens())
1329         {
1330             String language = languageTokenizer.nextToken().trim();
1331             int qValueIndex = language.indexOf(';');
1332             if(qValueIndex == -1)
1333             {
1334                 language = language.replace('-','_');
1335                 List<String> l = languageRangesByQuality.get(1.0f);
1336                 if(l == null)
1337                 {
1338                     l = new ArrayList<String>();
1339                     languageRangesByQuality.put(1.0f,l);
1340                 }
1341                 l.add(language);
1342             }
1343             else
1344             {
1345                 String code = language.substring(0,qValueIndex).trim().replace('-','_');
1346                 String qval = language.substring(qValueIndex + 1).trim();
1347                 if("*".equals(qval))
1348                 {
1349                     starLanguageRange = code;
1350                 }
1351                 else
1352                 {
1353                     Matcher m = quality.matcher(qval);
1354                     if(m.matches())
1355                     {
1356                         Float q = Float.valueOf(m.group(1));
1357                         List<String> al = languageRangesByQuality.get(q);
1358                         if(al == null)
1359                         {
1360                             al = new ArrayList<String>();
1361                             languageRangesByQuality.put(q,al);
1362                         }
1363                         al.add(code);
1364                     }
1365                     else
1366                     {
1367                         LOG.error("BrowserTool: could not parse language quality value: "+language);
1368                     }
1369                 }
1370             }
1371         }
1372     }
1373 
1374     private String filterLanguageTag(String languageTag)
1375     {
1376         languageTag = languageTag.replace('-','_');
1377         if(languagesFilter == null) return languageTag;
1378         if(languagesFilter.contains(languageTag)) return languageTag;
1379         if(languageTag.contains("_"))
1380         {
1381             String[] parts = languageTag.split("_");
1382             if(languagesFilter.contains(parts[0])) return parts[0];
1383         }
1384         return null;
1385     }
1386 }