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