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