View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.myfaces.tobago.context;
21  
22  import org.apache.myfaces.tobago.internal.util.Deprecation;
23  import org.slf4j.Logger;
24  import org.slf4j.LoggerFactory;
25  
26  import java.io.Serializable;
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.EnumSet;
30  import java.util.List;
31  import java.util.StringTokenizer;
32  
33  public final class UserAgent implements Serializable {
34  
35    private static final long serialVersionUID = 2L;
36  
37    private static final Logger LOG = LoggerFactory.getLogger(UserAgent.class);
38  
39    public static final String DEFAULT_NAME = "standard";
40  
41    public static final UserAgent DEFAULT = new UserAgent(null, null);
42  
43    public static final UserAgent MSIE = new UserAgent("msie", null);
44  
45    /**
46     * @deprecated no longer supported, since Tobago 1.5
47     */
48    @Deprecated
49    public static final UserAgent MSIE_5_0 = new UserAgent("msie", "5_0");
50  
51    /**
52     * @deprecated no longer supported, since Tobago 1.5
53     */
54    @Deprecated
55    public static final UserAgent MSIE_5_5 = new UserAgent("msie", "5_5");
56  
57    public static final UserAgent MSIE_6_0 = new UserAgent(
58        "msie", "6_0", EnumSet.noneOf(Capability.class), CspHeader.NOT_SUPPORTED, CsproHeader.NOT_SUPPORTED);
59  
60    public static final UserAgent MSIE_7_0 = new UserAgent(
61        "msie", "7_0", EnumSet.noneOf(Capability.class), CspHeader.NOT_SUPPORTED, CsproHeader.NOT_SUPPORTED);
62  
63    public static final UserAgent MSIE_7_0_COMPAT = new UserAgent(
64        "msie", "7_0", EnumSet.of(Capability.IE_COMPATIBILITY_MODE), CspHeader.NOT_SUPPORTED, CsproHeader.NOT_SUPPORTED);
65  
66    /**
67     * @deprecated no longer supported, since Tobago 1.5. Misspelled. Use {@link #MSIE_7_0}
68     */
69    @Deprecated
70    public static final UserAgent MSIE_7_O = MSIE_7_0;
71  
72    public static final UserAgent MSIE_8_0 = new UserAgent(
73        "msie", "8_0", EnumSet.of(Capability.CONTENT_TYPE_XHTML), CspHeader.NOT_SUPPORTED, CsproHeader.NOT_SUPPORTED);
74  
75    public static final UserAgent MSIE_9_0 = new UserAgent(
76        "msie", "9_0", EnumSet.of(Capability.CONTENT_TYPE_XHTML), CspHeader.NOT_SUPPORTED, CsproHeader.NOT_SUPPORTED);
77  
78    // CSP is not fully supported, only sandboxing
79    public static final UserAgent MSIE_10_0 = new UserAgent(
80        "msie", "10_0", EnumSet.of(Capability.CONTENT_TYPE_XHTML), CspHeader.X, CsproHeader.X);
81  
82    // CSP is not fully supported, only sandboxing
83    public static final UserAgent MSIE_11_0 = new UserAgent(
84        "msie", "11_0", EnumSet.of(Capability.CONTENT_TYPE_XHTML), CspHeader.X, CsproHeader.X);
85  
86    /**
87     * @deprecated no longer supported, since Tobago 1.5
88     */
89    @Deprecated
90    public static final UserAgent MSIE_5_0_MAC = new UserAgent("msie", "5_0_mac");
91  
92    /**
93     * @deprecated no longer supported, since Tobago 1.5
94     */
95    @Deprecated
96    public static final UserAgent MSIE_6_0_MAC = new UserAgent("msie", "6_0_mac");
97  
98  
99    /**
100    * e. g. Opera 10
101    */
102   public static final UserAgent PRESTO = new UserAgent("presto", null, EnumSet.of(Capability.CONTENT_TYPE_XHTML));
103 
104   /**
105    * @deprecated no longer supported, since Tobago 1.5. Please use {@link #PRESTO}.
106    */
107   public static final UserAgent OPERA = new UserAgent("opera", null);
108 
109   /**
110    * @deprecated no longer supported, since Tobago 1.5. Please use {@link #PRESTO}.
111    */
112   @Deprecated
113   public static final UserAgent OPERA_5_0 = new UserAgent("opera", "5_0");
114 
115   /**
116    * @deprecated no longer supported, since Tobago 1.5. Please use {@link #PRESTO}.
117    */
118   @Deprecated
119   public static final UserAgent OPERA_6_0 = new UserAgent("opera", "6_0");
120 
121   /**
122    * @deprecated no longer supported, since Tobago 1.5. Please use {@link #PRESTO}.
123    */
124   @Deprecated
125   public static final UserAgent OPERA_7_11 = new UserAgent("opera", "7_11");
126 
127   /**
128    * @deprecated no longer supported, since Tobago 1.5
129    */
130   @Deprecated
131   public static final UserAgent MOZILLA = new UserAgent("mozilla", null);
132 
133   /**
134    * @deprecated no longer supported, since Tobago 1.5
135    */
136   @Deprecated
137   public static final UserAgent MOZILLA_4_7 = new UserAgent("mozilla", "4_7");
138 
139   /**
140    * @deprecated no longer supported, since Tobago 1.5
141    */
142   @Deprecated
143   public static final UserAgent MOZILLA_5_0 = new UserAgent("mozilla", "5_0");
144 
145   /**
146    * @deprecated no longer supported, since Tobago 1.5
147    */
148   @Deprecated
149   public static final UserAgent MOZILLA_5_0_R1_6 = new UserAgent("mozilla", "5_0_r1_6");
150 
151   /**
152    * e. g. Firefox
153    */
154   public static final UserAgent GECKO = new UserAgent("gecko", null, EnumSet.of(Capability.CONTENT_TYPE_XHTML));
155 
156   /**
157    * e. g. Firefox 2.0
158    */
159   public static final UserAgent GECKO_1_8 = new UserAgent("gecko", "1_8", EnumSet.of(Capability.CONTENT_TYPE_XHTML),
160       CspHeader.NOT_SUPPORTED, CsproHeader.NOT_SUPPORTED);
161 
162   /**
163    * e. g. Firefox 3.0, 3.5, 3.6
164    */
165   public static final UserAgent GECKO_1_9 = new UserAgent("gecko", "1_9", EnumSet.of(Capability.CONTENT_TYPE_XHTML),
166       CspHeader.NOT_SUPPORTED, CsproHeader.NOT_SUPPORTED);
167 
168   /**
169    * e. g. Firefox 4 to 22
170    */
171   public static final UserAgent GECKO_2_0
172       = new UserAgent("gecko", null,
173       EnumSet.of(Capability.PLACEHOLDER, Capability.CONTENT_TYPE_XHTML), CspHeader.X, CsproHeader.X);
174 
175   /**
176    * e. g. Firefox 23 or higher
177    */
178   public static final UserAgent GECKO_23_0
179       = new UserAgent("gecko", null,
180       EnumSet.of(Capability.PLACEHOLDER, Capability.CONTENT_TYPE_XHTML), CspHeader.STANDARD, CsproHeader.STANDARD);
181 
182   /**
183    * e. g. Safari 4, Safari 5, Chrome
184    */
185   public static final UserAgent WEBKIT
186       = new UserAgent("webkit", null,
187       EnumSet.of(Capability.PLACEHOLDER, Capability.CONTENT_TYPE_XHTML), CspHeader.WEBKIT, CsproHeader.WEBKIT);
188 
189   private final String name;
190 
191   /**
192    * @deprecated Version shouldn't be used in the future. Use capability instead, even better
193    * use same code on the server when possible and use capability via JavaScript.
194    */
195   @Deprecated
196   private final String version;
197 
198   private final EnumSet<Capability> capabilities;
199 
200   private final CspHeader cspHeader;
201 
202   private final CsproHeader csproHeader;
203 
204   private UserAgent(final String name, final String version) {
205     this(name, version, EnumSet.of(Capability.CONTENT_TYPE_XHTML));
206   }
207 
208   private UserAgent(final String name, final String version, final EnumSet<Capability> capabilities) {
209     this(name, version, capabilities, CspHeader.STANDARD, CsproHeader.STANDARD);
210   }
211 
212   private UserAgent(
213       final String name, final String version, final EnumSet<Capability> capabilities, final CspHeader cspHeader,
214       final CsproHeader csproHeader) {
215     this.name = name;
216     this.version = version;
217     this.capabilities = capabilities;
218     this.cspHeader = cspHeader;
219     this.csproHeader = csproHeader;
220   }
221 
222   public boolean hasCapability(final Capability capability) {
223     return capabilities.contains(capability);
224   }
225 
226   public boolean isMsie() {
227     return MSIE.name.equals(name);
228   }
229 
230   public boolean isMsie6() {
231     return MSIE_6_0.name.equals(name) && MSIE_6_0.version.equals(version);
232   }
233 
234   /**
235    * @deprecated no longer supported, since Tobago 1.5
236    */
237   @Deprecated
238   public boolean isMozilla() {
239     return MOZILLA.name.equals(name);
240   }
241 
242   public List<String> getFallbackList() {
243     return getFallbackList(false);
244   }
245 
246   private List<String> getFallbackList(final boolean reverseOrder) {
247     final List<String> list = new ArrayList<String>(3);
248     if (version != null) {
249       list.add(name + '_' + version);
250     }
251     if (name != null) {
252       list.add(name);
253     }
254     list.add(DEFAULT_NAME);
255     if (reverseOrder) {
256       Collections.reverse(list);
257     }
258     return list;
259   }
260 
261   /**
262    * @return The HTTP header names for Content-Security-Policy.
263    */
264   public String[] getCspHeaders() {
265     return cspHeader.getNames();
266   }
267 
268   /**
269    * @return The HTTP header name for Content-Security-Policy-Report-Only.
270    */
271   public String[] getCspReportOnlyHeaders() {
272     return csproHeader.getNames();
273   }
274 
275   public static UserAgent getInstance(final String header) {
276     if (header == null) {
277       return DEFAULT;
278     }
279 
280     if (header.contains("MSIE") || header.contains("Trident")) {
281       if (header.contains("MSIE 6.0")) {
282         return MSIE_6_0;
283       } else if (header.contains("MSIE 7.0")) {
284         if (header.contains("Trident")) {
285           return MSIE_7_0_COMPAT;
286         } else {
287           return MSIE_7_0;
288         }
289       } else if (header.contains("MSIE 8.0")) {
290         return MSIE_8_0;
291       } else if (header.contains("MSIE 9.0")) {
292         return MSIE_9_0;
293       } else if (header.contains("MSIE 10.0")) {
294         return MSIE_10_0;
295       } else if (header.contains("rv:11")) {
296         return MSIE_11_0;
297       } else {
298         return MSIE;
299       }
300     } else if (header.contains("AppleWebKit")) {
301       return WEBKIT;
302     } else if (header.contains("Gecko")) {
303       if (header.contains("rv:1.8")) {
304         return GECKO_1_8;
305       } else if (header.contains("rv:1.9")) {
306         return GECKO_1_9;
307       } else {
308         final int index = header.indexOf("rv:");
309         final StringTokenizer tokenizer = new StringTokenizer(header.substring(index + 3), " .");
310         final String versionString = tokenizer.nextToken();
311         try {
312           final int version = Integer.parseInt(versionString);
313           if (version >= 23) {
314             return GECKO_23_0;
315           } else if (version >= 2) {
316             return GECKO_2_0;
317           }
318         } catch (final NumberFormatException e) {
319           if (LOG.isDebugEnabled()) {
320             LOG.debug(header, e);
321           }
322         }
323         return GECKO;
324       }
325     } else if (header.contains("Presto")) {
326       return PRESTO;
327     }
328 
329     return DEFAULT;
330   }
331 
332   /**
333    * @deprecated no longer supported, since Tobago 1.5
334    */
335   @Deprecated
336   public static UserAgent getInstanceForId(final String id) {
337     Deprecation.LOG.error("Getting the user agent from its id is no longer supported! id='" + id + "'");
338     return DEFAULT;
339   }
340 
341   /**
342    * @deprecated don't use toString() functionality, but for logging!
343    */
344   @Deprecated
345   public String toString() {
346     return version != null
347         ? name + '_' + version
348         : name;
349   }
350 
351   private static enum CspHeader {
352 
353     NOT_SUPPORTED(new String[] {}),
354     X(new String[] {"Content-Security-Policy", "X-Content-Security-Policy"}),
355     WEBKIT(new String[] {"Content-Security-Policy", "X-WebKit-CSP"}),
356     STANDARD(new String[] {"Content-Security-Policy"});
357 
358     private String[] names;
359 
360     private CspHeader(final String[] names) {
361       this.names = names;
362     }
363 
364     public String[] getNames() {
365       return names;
366     }
367   }
368 
369   private static enum CsproHeader {
370 
371     NOT_SUPPORTED(new String[] {}),
372     X(new String[] {"Content-Security-Policy-Report-Only", "X-Content-Security-Policy-Report-Only"}),
373     WEBKIT(new String[] {"Content-Security-Policy-Report-Only", "X-WebKit-CSP-Report-Only"}),
374     STANDARD(new String[] {"Content-Security-Policy-Report-Only"});
375 
376     private String[] names;
377 
378     private CsproHeader(final String[] names) {
379       this.names = names;
380     }
381 
382     public String[] getNames() {
383       return names;
384     }
385   }
386 }