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