View Javadoc
1   /*
2    * Copyright (c) 2002-2017 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package com.gargoylesoftware.htmlunit;
16  
17  import java.io.File;
18  import java.io.Serializable;
19  import java.lang.reflect.Field;
20  import java.util.Arrays;
21  import java.util.EnumSet;
22  import java.util.HashMap;
23  import java.util.HashSet;
24  import java.util.Locale;
25  import java.util.Map;
26  import java.util.Set;
27  import java.util.TimeZone;
28  
29  import org.apache.commons.io.FilenameUtils;
30  import org.apache.commons.lang3.builder.EqualsBuilder;
31  import org.apache.commons.lang3.builder.HashCodeBuilder;
32  
33  import com.gargoylesoftware.htmlunit.javascript.configuration.AbstractJavaScriptConfiguration;
34  import com.gargoylesoftware.htmlunit.javascript.configuration.BrowserFeature;
35  import com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser;
36  
37  /**
38   * Objects of this class represent one specific version of a given browser. Predefined
39   * constants are provided for common browser versions.
40   *
41   * <p>You can change the constants by something like:
42   * <pre id='htmlUnitCode'>
43   *      String applicationName = "APPNAME";
44   *      String applicationVersion = "APPVERSION";
45   *      String userAgent = "USERAGENT";
46   *      int browserVersionNumeric = NUMERIC;
47   *
48   *      BrowserVersion browser = new BrowserVersion(applicationName, applicationVersion, userAgent, browserVersionNumeric) {
49   *          public boolean hasFeature(BrowserVersionFeatures property) {
50   *
51   *              // change features here
52   *              return BrowserVersion.BROWSER.hasFeature(property);
53   *          }
54   *      };
55   * </pre>
56   * <script>
57         var pre = document.getElementById('htmlUnitCode');
58         pre.innerHTML = pre.innerHTML.replace('APPNAME', navigator.appName);
59         pre.innerHTML = pre.innerHTML.replace('APPVERSION', navigator.appVersion);
60         pre.innerHTML = pre.innerHTML.replace('USERAGENT', navigator.userAgent);
61         var isMicrosoft = navigator.appVersion.indexOf('Trident/') != -1;
62         var isEdge = navigator.appVersion.indexOf('Edge') != -1;
63         var isChrome = navigator.appVersion.indexOf('Chrome') != -1;
64         var numeric = 52;
65         if (isMicrosoft) {
66             numeric = 11;
67         }
68         else if (isEdge) {
69             numeric = 14;
70         }
71         else if (isChrome) {
72             numeric = 58;
73         }
74         pre.innerHTML = pre.innerHTML.replace('NUMERIC', numeric);
75         var browser = "FIREFOX_52";
76         if (isMicrosoft) {
77             browser = "INTERNET_EXPLORER";
78         }
79         else if (isEdge) {
80             browser = "EDGE";
81         }
82         else if (isChrome) {
83             browser = "CHROME";
84         }
85         pre.innerHTML = pre.innerHTML.replace('BROWSER', browser);
86     </script>
87   * However, note that the constants are not enough to fully customize the browser,
88   *   you also need to look into the {@link BrowserVersionFeatures} and the classes inside "javascript" package.
89   *
90   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
91   * @author Daniel Gredler
92   * @author Marc Guillemot
93   * @author Chris Erskine
94   * @author Ahmed Ashour
95   * @author Frank Danek
96   * @author Ronald Brill
97   */
98  public class BrowserVersion implements Serializable, Cloneable {
99  
100     /**
101      * Application name the Netscape navigator series of browsers.
102      */
103     private static final String NETSCAPE = "Netscape";
104 
105     /**
106      * United States English language identifier.
107      */
108     private static final String LANGUAGE_ENGLISH_US = "en-US";
109 
110     /**
111      * United States.
112      */
113     private static final String TIMEZONE_NEW_YORK = "America/New_York";
114 
115     /**
116      * The X86 CPU class.
117      */
118     private static final String CPU_CLASS_X86 = "x86";
119 
120     /**
121      * The WIN32 platform.
122      */
123     private static final String PLATFORM_WIN32 = "Win32";
124 
125     /**
126      * Firefox 45 ESR.
127      * @since 2.21
128      */
129     public static final BrowserVersion FIREFOX_45 = new BrowserVersion(
130         NETSCAPE, "5.0 (Windows)",
131         "Mozilla/5.0 (Windows NT 6.1; rv:45.0) Gecko/20100101 Firefox/45.0",
132         45, "FF45", null);
133 
134     /**
135      * Firefox 52 ESR.
136      * @since 2.26
137      */
138     public static final BrowserVersion FIREFOX_52 = new BrowserVersion(
139         NETSCAPE, "5.0 (Windows)",
140         "Mozilla/5.0 (Windows NT 6.1; rv:52.0) Gecko/20100101 Firefox/52.0",
141         52, "FF52", null);
142 
143     /** Internet Explorer 11. */
144     public static final BrowserVersion INTERNET_EXPLORER = new BrowserVersion(
145         NETSCAPE, "5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko",
146         "Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko", 11, "IE", null);
147 
148     /** Latest Chrome. */
149     public static final BrowserVersion CHROME = new BrowserVersion(
150         NETSCAPE, "5.0 (Windows NT 6.1) AppleWebKit/537.36"
151         + " (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
152         "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36"
153         + " (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36",
154         58, "Chrome", null);
155 
156     /** Microsoft Edge. Work In Progress!!! */
157     public static final BrowserVersion EDGE = new BrowserVersion(
158         NETSCAPE, "5.0 (Windows NT 10.0) AppleWebKit/537.36"
159         + " (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393",
160         "Mozilla/5.0 (Windows NT 10.0) AppleWebKit/537.36"
161         + " (KHTML, like Gecko) Chrome/51.0.2704.79 Safari/537.36 Edge/14.14393",
162         14, "Edge", null);
163 
164     /**
165      * The best supported browser version at the moment.
166      */
167     public static final BrowserVersion BEST_SUPPORTED = CHROME;
168 
169     /** The default browser version. */
170     private static BrowserVersion DefaultBrowserVersion_ = BEST_SUPPORTED;
171 
172     /** Register plugins for the browser versions. */
173     static {
174         // FF45
175         FIREFOX_45.initDefaultFeatures();
176         FIREFOX_45.setVendor("");
177         FIREFOX_45.buildId_ = "20170411115307";
178         FIREFOX_45.setHeaderNamesOrdered(new String[] {
179             "Host", "User-Agent", "Accept", "Accept-Language", "Accept-Encoding", "Referer", "Cookie", "Connection"});
180         FIREFOX_45.setHtmlAcceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
181         FIREFOX_45.setXmlHttpRequestAcceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
182         FIREFOX_45.setImgAcceptHeader("image/png,image/*;q=0.8,*/*;q=0.5");
183         FIREFOX_45.setCssAcceptHeader("text/css,*/*;q=0.1");
184 
185         // FF52
186         FIREFOX_52.initDefaultFeatures();
187         FIREFOX_52.setVendor("");
188         FIREFOX_52.buildId_ = "20170517122419";
189         FIREFOX_52.setHeaderNamesOrdered(new String[] {
190             "Host", "User-Agent", "Accept", "Accept-Language", "Accept-Encoding", "Referer", "Cookie", "Connection"});
191         FIREFOX_52.setHtmlAcceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
192         FIREFOX_52.setCssAcceptHeader("text/css,*/*;q=0.1");
193 
194         // IE
195         INTERNET_EXPLORER.initDefaultFeatures();
196         INTERNET_EXPLORER.setVendor("");
197         INTERNET_EXPLORER.setHeaderNamesOrdered(new String[] {
198             "Accept", "Referer", "Accept-Language", "User-Agent", "Accept-Encoding", "Host", "DNT", "Connection",
199             "Cookie"});
200         INTERNET_EXPLORER.setHtmlAcceptHeader("text/html, application/xhtml+xml, */*");
201         INTERNET_EXPLORER.setImgAcceptHeader("image/png, image/svg+xml, image/*;q=0.8, */*;q=0.5");
202         INTERNET_EXPLORER.setCssAcceptHeader("text/css, */*");
203         INTERNET_EXPLORER.setScriptAcceptHeader("application/javascript, */*;q=0.8");
204 
205         // EDGE
206         EDGE.initDefaultFeatures();
207         EDGE.setVendor("");
208 
209         // CHROME
210         CHROME.initDefaultFeatures();
211         CHROME.setApplicationCodeName("Mozilla");
212         CHROME.setVendor("Google Inc.");
213         CHROME.setPlatform("MacIntel");
214         CHROME.setCpuClass(null);
215         CHROME.setHeaderNamesOrdered(new String[] {
216             "Host", "Connection", "Accept", "User-Agent", "Referer", "Accept-Encoding", "Accept-Language", "Cookie"});
217         CHROME.setHtmlAcceptHeader("text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8");
218         CHROME.setImgAcceptHeader("image/webp,image/*,*/*;q=0.8");
219         CHROME.setCssAcceptHeader("text/css,*/*;q=0.1");
220         CHROME.setScriptAcceptHeader("*/*");
221         // there are other issues with Chrome; a different productSub, etc.
222 
223         // default file upload mime types
224         CHROME.registerUploadMimeType("html", "text/html");
225         CHROME.registerUploadMimeType("htm", "text/html");
226         CHROME.registerUploadMimeType("css", "text/css");
227         CHROME.registerUploadMimeType("xml", "text/xml");
228         CHROME.registerUploadMimeType("gif", "image/gif");
229         CHROME.registerUploadMimeType("jpeg", "image/jpeg");
230         CHROME.registerUploadMimeType("jpg", "image/jpeg");
231         CHROME.registerUploadMimeType("webp", "image/webp");
232         CHROME.registerUploadMimeType("mp4", "video/mp4");
233         CHROME.registerUploadMimeType("m4v", "video/mp4");
234         CHROME.registerUploadMimeType("m4a", "audio/x-m4a");
235         CHROME.registerUploadMimeType("mp3", "audio/mp3");
236         CHROME.registerUploadMimeType("ogv", "video/ogg");
237         CHROME.registerUploadMimeType("ogm", "video/ogg");
238         CHROME.registerUploadMimeType("ogg", "audio/ogg");
239         CHROME.registerUploadMimeType("oga", "audio/ogg");
240         CHROME.registerUploadMimeType("opus", "audio/ogg");
241         CHROME.registerUploadMimeType("webm", "video/webm");
242         CHROME.registerUploadMimeType("wav", "audio/wav");
243         CHROME.registerUploadMimeType("flac", "audio/flac");
244         CHROME.registerUploadMimeType("xhtml", "application/xhtml+xml");
245         CHROME.registerUploadMimeType("xht", "application/xhtml+xml");
246         CHROME.registerUploadMimeType("xhtm", "application/xhtml+xml");
247         CHROME.registerUploadMimeType("txt", "text/plain");
248         CHROME.registerUploadMimeType("text", "text/plain");
249 
250         FIREFOX_45.registerUploadMimeType("html", "text/html");
251         FIREFOX_45.registerUploadMimeType("htm", "text/html");
252         FIREFOX_45.registerUploadMimeType("css", "text/css");
253         FIREFOX_45.registerUploadMimeType("xml", "text/xml");
254         FIREFOX_45.registerUploadMimeType("gif", "image/gif");
255         FIREFOX_45.registerUploadMimeType("jpeg", "image/jpeg");
256         FIREFOX_45.registerUploadMimeType("jpg", "image/jpeg");
257         FIREFOX_45.registerUploadMimeType("mp4", "video/mp4");
258         FIREFOX_45.registerUploadMimeType("m4v", "video/mp4");
259         FIREFOX_45.registerUploadMimeType("m4a", "audio/mp4");
260         FIREFOX_45.registerUploadMimeType("mp3", "audio/mpeg");
261         FIREFOX_45.registerUploadMimeType("ogv", "video/ogg");
262         FIREFOX_45.registerUploadMimeType("ogm", "video/x-ogm");
263         FIREFOX_45.registerUploadMimeType("ogg", "video/ogg");
264         FIREFOX_45.registerUploadMimeType("oga", "audio/ogg");
265         FIREFOX_45.registerUploadMimeType("opus", "audio/ogg");
266         FIREFOX_45.registerUploadMimeType("webm", "video/webm");
267         FIREFOX_45.registerUploadMimeType("wav", "audio/wav");
268         FIREFOX_45.registerUploadMimeType("flac", "audio/x-flac");
269         FIREFOX_45.registerUploadMimeType("xhtml", "application/xhtml+xml");
270         FIREFOX_45.registerUploadMimeType("xht", "application/xhtml+xml");
271         FIREFOX_45.registerUploadMimeType("txt", "text/plain");
272         FIREFOX_45.registerUploadMimeType("text", "text/plain");
273 
274         FIREFOX_52.registerUploadMimeType("html", "text/html");
275         FIREFOX_52.registerUploadMimeType("htm", "text/html");
276         FIREFOX_52.registerUploadMimeType("css", "text/css");
277         FIREFOX_52.registerUploadMimeType("xml", "text/xml");
278         FIREFOX_52.registerUploadMimeType("gif", "image/gif");
279         FIREFOX_52.registerUploadMimeType("jpeg", "image/jpeg");
280         FIREFOX_52.registerUploadMimeType("jpg", "image/jpeg");
281         FIREFOX_52.registerUploadMimeType("mp4", "video/mp4");
282         FIREFOX_52.registerUploadMimeType("m4v", "video/mp4");
283         FIREFOX_52.registerUploadMimeType("m4a", "audio/mp4");
284         FIREFOX_52.registerUploadMimeType("mp3", "audio/mpeg");
285         FIREFOX_52.registerUploadMimeType("ogv", "video/ogg");
286         FIREFOX_52.registerUploadMimeType("ogm", "video/x-ogm");
287         FIREFOX_52.registerUploadMimeType("ogg", "video/ogg");
288         FIREFOX_52.registerUploadMimeType("oga", "audio/ogg");
289         FIREFOX_52.registerUploadMimeType("opus", "audio/ogg");
290         FIREFOX_52.registerUploadMimeType("webm", "video/webm");
291         FIREFOX_52.registerUploadMimeType("wav", "audio/wav");
292         FIREFOX_52.registerUploadMimeType("xhtml", "application/xhtml+xml");
293         FIREFOX_52.registerUploadMimeType("xht", "application/xhtml+xml");
294         FIREFOX_52.registerUploadMimeType("txt", "text/plain");
295         FIREFOX_52.registerUploadMimeType("text", "text/plain");
296 
297         INTERNET_EXPLORER.registerUploadMimeType("html", "text/html");
298         INTERNET_EXPLORER.registerUploadMimeType("htm", "text/html");
299         INTERNET_EXPLORER.registerUploadMimeType("css", "text/css");
300         INTERNET_EXPLORER.registerUploadMimeType("xml", "text/xml");
301         INTERNET_EXPLORER.registerUploadMimeType("gif", "image/gif");
302         INTERNET_EXPLORER.registerUploadMimeType("jpeg", "image/jpeg");
303         INTERNET_EXPLORER.registerUploadMimeType("jpg", "image/jpeg");
304         INTERNET_EXPLORER.registerUploadMimeType("mp4", "video/mp4");
305         INTERNET_EXPLORER.registerUploadMimeType("m4v", "video/mp4");
306         INTERNET_EXPLORER.registerUploadMimeType("m4a", "audio/mp4");
307         INTERNET_EXPLORER.registerUploadMimeType("mp3", "audio/mpeg");
308         INTERNET_EXPLORER.registerUploadMimeType("ogm", "video/x-ogm");
309         INTERNET_EXPLORER.registerUploadMimeType("ogg", "application/ogg");
310         INTERNET_EXPLORER.registerUploadMimeType("wav", "audio/wav");
311         INTERNET_EXPLORER.registerUploadMimeType("xhtml", "application/xhtml+xml");
312         INTERNET_EXPLORER.registerUploadMimeType("xht", "application/xhtml+xml");
313         INTERNET_EXPLORER.registerUploadMimeType("txt", "text/plain");
314 
315         // flush plugin (windows version)
316         PluginConfiguration flash = new PluginConfiguration("Shockwave Flash",
317                 "Shockwave Flash 24.0 r0", "undefined", "internal-not-yet-present");
318         flash.getMimeTypes().add(new PluginConfiguration.MimeType("application/x-shockwave-flash",
319                 "Shockwave Flash", "swf"));
320         CHROME.getPlugins().add(flash);
321 
322         flash = new PluginConfiguration("Shockwave Flash",
323                 "Shockwave Flash 25.0 r0", "25.0.0.171", "NPSWF32_25_0_0_171.dll");
324         flash.getMimeTypes().add(new PluginConfiguration.MimeType("application/x-shockwave-flash",
325                 "Shockwave Flash", "swf"));
326         FIREFOX_45.getPlugins().add(flash);
327 
328         flash = new PluginConfiguration("Shockwave Flash",
329                 "Shockwave Flash 25.0 r0", "25.0.0.171", "NPSWF32_25_0_0_171.dll");
330         flash.getMimeTypes().add(new PluginConfiguration.MimeType("application/x-shockwave-flash",
331                 "Shockwave Flash", "swf"));
332         FIREFOX_52.getPlugins().add(flash);
333 
334         flash = new PluginConfiguration("Shockwave Flash",
335                 "Shockwave Flash 25.0 r0", "25.0.0.171", "Flash32_25_0_0_171.ocx");
336         flash.getMimeTypes().add(new PluginConfiguration.MimeType("application/x-shockwave-flash",
337                 "Shockwave Flash", "swf"));
338         INTERNET_EXPLORER.getPlugins().add(flash);
339 
340         flash = new PluginConfiguration("Shockwave Flash",
341                 "Shockwave Flash 18.0 r0", "18.0.0.232", "Flash.ocx");
342         flash.getMimeTypes().add(new PluginConfiguration.MimeType("application/x-shockwave-flash",
343                 "Shockwave Flash", "swf"));
344         EDGE.getPlugins().add(flash);
345     }
346 
347     private String applicationCodeName_ = "Mozilla";
348     private String applicationMinorVersion_ = "0";
349     private String applicationName_;
350     private String applicationVersion_;
351     private String buildId_;
352     private String vendor_;
353     private String browserLanguage_ = LANGUAGE_ENGLISH_US;
354     private String cpuClass_ = CPU_CLASS_X86;
355     private boolean onLine_ = true;
356     private String platform_ = PLATFORM_WIN32;
357     private String systemLanguage_ = LANGUAGE_ENGLISH_US;
358     private TimeZone systemTimezone_ = TimeZone.getTimeZone(TIMEZONE_NEW_YORK);
359     private String userAgent_;
360     private String userLanguage_ = LANGUAGE_ENGLISH_US;
361     private int browserVersionNumeric_;
362     private final Set<PluginConfiguration> plugins_ = new HashSet<>();
363     private final Set<BrowserVersionFeatures> features_ = EnumSet.noneOf(BrowserVersionFeatures.class);
364     private final String nickname_;
365     private String htmlAcceptHeader_;
366     private String imgAcceptHeader_;
367     private String cssAcceptHeader_;
368     private String scriptAcceptHeader_;
369     private String xmlHttpRequestAcceptHeader_;
370     private String[] headerNamesOrdered_;
371     private Map<String, String> uploadMimeTypes_ = new HashMap<>();
372 
373     /**
374      * Instantiates one.
375      *
376      * @param applicationName the name of the application
377      * @param applicationVersion the version string of the application
378      * @param userAgent the user agent string that will be sent to the server
379      * @param browserVersionNumeric the number version of the browser
380      */
381     public BrowserVersion(final String applicationName, final String applicationVersion,
382         final String userAgent, final int browserVersionNumeric) {
383 
384         this(applicationName, applicationVersion, userAgent,
385                 browserVersionNumeric, applicationName + browserVersionNumeric, null);
386     }
387 
388     /**
389      * Instantiates one.
390      *
391      * @param applicationName the name of the application
392      * @param applicationVersion the version string of the application
393      * @param userAgent the user agent string that will be sent to the server
394      * @param browserVersionNumeric the number version of the browser
395      * @param features the browser features
396      */
397     public BrowserVersion(final String applicationName, final String applicationVersion,
398         final String userAgent, final int browserVersionNumeric,
399         final BrowserVersionFeatures[] features) {
400 
401         this(applicationName, applicationVersion, userAgent,
402                 browserVersionNumeric, applicationName + browserVersionNumeric, features);
403     }
404 
405     /**
406      * Creates a new browser version instance.
407      *
408      * @param applicationName the name of the application
409      * @param applicationVersion the version string of the application
410      * @param userAgent the user agent string that will be sent to the server
411      * @param javaScriptVersion the version of JavaScript
412      * @param browserVersionNumeric the floating number version of the browser
413      * @param nickname the short name of the browser (like "FF3", "IE6", ...)
414      * @param features the browser features
415      */
416     private BrowserVersion(final String applicationName, final String applicationVersion,
417         final String userAgent, final int browserVersionNumeric,
418         final String nickname, final BrowserVersionFeatures[] features) {
419 
420         applicationName_ = applicationName;
421         setApplicationVersion(applicationVersion);
422         userAgent_ = userAgent;
423         browserVersionNumeric_ = browserVersionNumeric;
424         nickname_ = nickname;
425         htmlAcceptHeader_ = "*/*";
426         imgAcceptHeader_ = "*/*";
427         cssAcceptHeader_ = "*/*";
428         scriptAcceptHeader_ = "*/*";
429         xmlHttpRequestAcceptHeader_ = "*/*";
430 
431         if (features != null) {
432             features_.addAll(Arrays.asList(features));
433         }
434     }
435 
436     private void initDefaultFeatures() {
437         final SupportedBrowser expectedBrowser;
438         if (isChrome()) {
439             expectedBrowser = SupportedBrowser.CHROME;
440         }
441         else if (this == FIREFOX_45) {
442             expectedBrowser = SupportedBrowser.FF45;
443         }
444         else if (isFirefox()) {
445             expectedBrowser = SupportedBrowser.FF52;
446         }
447         else if (isIE()) {
448             expectedBrowser = SupportedBrowser.IE;
449         }
450         else {
451             expectedBrowser = SupportedBrowser.EDGE;
452         }
453 
454         for (final BrowserVersionFeatures features : BrowserVersionFeatures.values()) {
455             try {
456                 final Field field = BrowserVersionFeatures.class.getField(features.name());
457                 final BrowserFeature browserFeature = field.getAnnotation(BrowserFeature.class);
458                 if (browserFeature != null) {
459                     for (final SupportedBrowser browser : browserFeature.value()) {
460                         if (AbstractJavaScriptConfiguration.isCompatible(expectedBrowser, browser)) {
461                             features_.add(features);
462                         }
463                     }
464                 }
465             }
466             catch (final NoSuchFieldException e) {
467                 // should never happen
468                 throw new IllegalStateException(e);
469             }
470         }
471     }
472 
473     /**
474      * Returns the default browser version that is used whenever a specific version isn't specified.
475      * Defaults to {@link #BEST_SUPPORTED}.
476      * @return the default browser version
477      */
478     public static BrowserVersion getDefault() {
479         return DefaultBrowserVersion_;
480     }
481 
482     /**
483      * Sets the default browser version that is used whenever a specific version isn't specified.
484      * @param newBrowserVersion the new default browser version
485      */
486     public static void setDefault(final BrowserVersion newBrowserVersion) {
487         WebAssert.notNull("newBrowserVersion", newBrowserVersion);
488         DefaultBrowserVersion_ = newBrowserVersion;
489     }
490 
491     /**
492      * Returns {@code true} if this <tt>BrowserVersion</tt> instance represents some
493      * version of Internet Explorer.
494      * @return whether or not this version is a version of IE
495      */
496     public final boolean isIE() {
497         return getNickname().startsWith("IE");
498     }
499 
500     /**
501      * Returns {@code true} if this <tt>BrowserVersion</tt> instance represents some
502      * version of Google Chrome. Note that Google Chrome does not return 'Chrome'
503      * in the application name, we have to look in the nickname.
504      * @return whether or not this version is a version of a Chrome browser
505      */
506     public final boolean isChrome() {
507         return getNickname().startsWith("Chrome");
508     }
509 
510     /**
511      * Returns {@code true} if this <tt>BrowserVersion</tt> instance represents some
512      * version of Microsoft Edge.
513      * @return whether or not this version is a version of an Edge browser
514      */
515     public final boolean isEdge() {
516         return getNickname().startsWith("Edge");
517     }
518 
519     /**
520      * Returns {@code true} if this <tt>BrowserVersion</tt> instance represents some
521      * version of Firefox.
522      * @return whether or not this version is a version of a Firefox browser
523      */
524     public final boolean isFirefox() {
525         return getNickname().startsWith("FF");
526     }
527 
528     /**
529      * Returns the application code name, for example "Mozilla".
530      * Default value is "Mozilla" if not explicitly configured.
531      * @return the application code name
532      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533077.aspx">MSDN documentation</a>
533      */
534     public String getApplicationCodeName() {
535         return applicationCodeName_;
536     }
537 
538     /**
539      * Returns the application minor version, for example "0".
540      * Default value is "0" if not explicitly configured.
541      * @return the application minor version
542      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533078.aspx">MSDN documentation</a>
543      */
544     public String getApplicationMinorVersion() {
545         return applicationMinorVersion_;
546     }
547 
548     /**
549      * Returns the application name, for example "Microsoft Internet Explorer".
550      * @return the application name
551      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533079.aspx">MSDN documentation</a>
552      */
553     public String getApplicationName() {
554         return applicationName_;
555     }
556 
557     /**
558      * Returns the application version, for example "4.0 (compatible; MSIE 6.0b; Windows 98)".
559      * @return the application version
560      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533080.aspx">MSDN documentation</a>
561      */
562     public String getApplicationVersion() {
563         return applicationVersion_;
564     }
565 
566     /**
567      * @return the vendor
568      */
569     public String getVendor() {
570         return vendor_;
571     }
572 
573     /**
574      * Returns the browser application language, for example "en-us".
575      * Default value is {@link #LANGUAGE_ENGLISH_US} if not explicitly configured.
576      * @return the browser application language
577      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533542.aspx">MSDN documentation</a>
578      */
579     public String getBrowserLanguage() {
580         return browserLanguage_;
581     }
582 
583     /**
584      * Returns the type of CPU in the machine, for example "x86".
585      * Default value is {@link #CPU_CLASS_X86} if not explicitly configured.
586      * @return the type of CPU in the machine
587      * @see <a href="http://msdn.microsoft.com/en-us/library/ms533697.aspx">MSDN documentation</a>
588      */
589     public String getCpuClass() {
590         return cpuClass_;
591     }
592 
593     /**
594      * Returns {@code true} if the browser is currently online.
595      * Default value is {@code true} if not explicitly configured.
596      * @return {@code true} if the browser is currently online
597      * @see <a href="http://msdn.microsoft.com/en-us/library/ms534307.aspx">MSDN documentation</a>
598      */
599     public boolean isOnLine() {
600         return onLine_;
601     }
602 
603     /**
604      * Returns the platform on which the application is running, for example "Win32".
605      * Default value is {@link #PLATFORM_WIN32} if not explicitly configured.
606      * @return the platform on which the application is running
607      * @see <a href="http://msdn.microsoft.com/en-us/library/ms534340.aspx">MSDN documentation</a>
608      */
609     public String getPlatform() {
610         return platform_;
611     }
612 
613     /**
614      * Returns the system language, for example "en-us".
615      * Default value is {@link #LANGUAGE_ENGLISH_US} if not explicitly configured.
616      * @return the system language
617      * @see <a href="http://msdn.microsoft.com/en-us/library/ms534653.aspx">MSDN documentation</a>
618      */
619     public String getSystemLanguage() {
620         return systemLanguage_;
621     }
622 
623     /**
624      * Returns the system {@link TimeZone}.
625      * Default value is {@code America/New_York} if not explicitly configured.
626      * @return the system {@link TimeZone}
627      */
628     public TimeZone getSystemTimezone() {
629         return systemTimezone_;
630     }
631 
632     /**
633      * Returns the user agent string, for example "Mozilla/4.0 (compatible; MSIE 6.0b; Windows 98)".
634      * @return the user agent string
635      */
636     public String getUserAgent() {
637         return userAgent_;
638     }
639 
640     /**
641      * Returns the user language, for example "en-us".
642      * Default value is {@link #LANGUAGE_ENGLISH_US} if not explicitly configured.
643      * @return the user language
644      * @see <a href="http://msdn.microsoft.com/en-us/library/ms534713.aspx">MSDN documentation</a>
645      */
646     public String getUserLanguage() {
647         return userLanguage_;
648     }
649 
650     /**
651      * Returns the value used by the browser for the {@code Accept} header if requesting a page.
652      * @return the accept header string
653      */
654     public String getHtmlAcceptHeader() {
655         return htmlAcceptHeader_;
656     }
657 
658     /**
659      * Returns the value used by the browser for the {@code Accept} header
660      * if requesting an script.
661      * @return the accept header string
662      */
663     public String getScriptAcceptHeader() {
664         return scriptAcceptHeader_;
665     }
666 
667     /**
668      * Returns the value used by the browser for the {@code Accept} header
669      * if performing an XMLHttpRequest.
670      * @return the accept header string
671      */
672     public String getXmlHttpRequestAcceptHeader() {
673         return xmlHttpRequestAcceptHeader_;
674     }
675 
676     /**
677      * Returns the value used by the browser for the {@code Accept} header
678      * if requesting an image.
679      * @return the accept header string
680      */
681     public String getImgAcceptHeader() {
682         return imgAcceptHeader_;
683     }
684 
685     /**
686      * Returns the value used by the browser for the {@code Accept} header
687      * if requesting a CSS declaration.
688      * @return the accept header string
689      */
690     public String getCssAcceptHeader() {
691         return cssAcceptHeader_;
692     }
693 
694     /**
695      * @param applicationCodeName the applicationCodeName to set
696      */
697     public void setApplicationCodeName(final String applicationCodeName) {
698         applicationCodeName_ = applicationCodeName;
699     }
700 
701     /**
702      * @param applicationMinorVersion the applicationMinorVersion to set
703      */
704     public void setApplicationMinorVersion(final String applicationMinorVersion) {
705         applicationMinorVersion_ = applicationMinorVersion;
706     }
707 
708     /**
709      * @param applicationName the applicationName to set
710      */
711     public void setApplicationName(final String applicationName) {
712         applicationName_ = applicationName;
713     }
714 
715     /**
716      * @param applicationVersion the applicationVersion to set
717      */
718     public void setApplicationVersion(final String applicationVersion) {
719         applicationVersion_ = applicationVersion;
720     }
721 
722     /**
723      * @param vendor the vendor to set
724      */
725     public void setVendor(final String vendor) {
726         vendor_ = vendor;
727     }
728 
729     /**
730      * @param browserLanguage the browserLanguage to set
731      */
732     public void setBrowserLanguage(final String browserLanguage) {
733         browserLanguage_ = browserLanguage;
734     }
735 
736     /**
737      * @param cpuClass the cpuClass to set
738      */
739     public void setCpuClass(final String cpuClass) {
740         cpuClass_ = cpuClass;
741     }
742 
743     /**
744      * @param onLine the onLine to set
745      */
746     public void setOnLine(final boolean onLine) {
747         onLine_ = onLine;
748     }
749 
750     /**
751      * @param platform the platform to set
752      */
753     public void setPlatform(final String platform) {
754         platform_ = platform;
755     }
756 
757     /**
758      * @param systemLanguage the systemLanguage to set
759      */
760     public void setSystemLanguage(final String systemLanguage) {
761         systemLanguage_ = systemLanguage;
762     }
763 
764     /**
765      * @param systemTimezone the systemTimezone to set
766      */
767     public void setSystemTimezone(final TimeZone systemTimezone) {
768         systemTimezone_ = systemTimezone;
769     }
770 
771     /**
772      * @param userAgent the userAgent to set
773      */
774     public void setUserAgent(final String userAgent) {
775         userAgent_ = userAgent;
776     }
777 
778     /**
779      * @param userLanguage the userLanguage to set
780      */
781     public void setUserLanguage(final String userLanguage) {
782         userLanguage_ = userLanguage;
783     }
784 
785     /**
786      * @param browserVersion the browserVersion to set
787      */
788     public void setBrowserVersion(final int browserVersion) {
789         browserVersionNumeric_ = browserVersion;
790     }
791 
792     /**
793      * @param htmlAcceptHeader the {@code Accept} header to be used when retrieving pages
794      */
795     public void setHtmlAcceptHeader(final String htmlAcceptHeader) {
796         htmlAcceptHeader_ = htmlAcceptHeader;
797     }
798 
799     /**
800      * @param imgAcceptHeader the {@code Accept} header to be used when retrieving images
801      */
802     public void setImgAcceptHeader(final String imgAcceptHeader) {
803         imgAcceptHeader_ = imgAcceptHeader;
804     }
805 
806     /**
807      * @param cssAcceptHeader the {@code Accept} header to be used when retrieving pages
808      */
809     public void setCssAcceptHeader(final String cssAcceptHeader) {
810         cssAcceptHeader_ = cssAcceptHeader;
811     }
812 
813     /**
814      * @param scriptAcceptHeader the {@code Accept} header to be used when retrieving scripts
815      */
816     public void setScriptAcceptHeader(final String scriptAcceptHeader) {
817         scriptAcceptHeader_ = scriptAcceptHeader;
818     }
819 
820     /**
821      * @param xmlHttpRequestAcceptHeader the {@code Accept} header to be used when
822      * performing XMLHttpRequests
823      */
824     public void setXmlHttpRequestAcceptHeader(final String xmlHttpRequestAcceptHeader) {
825         xmlHttpRequestAcceptHeader_ = xmlHttpRequestAcceptHeader;
826     }
827 
828     /**
829      * @return the browserVersionNumeric
830      */
831     public int getBrowserVersionNumeric() {
832         return browserVersionNumeric_;
833     }
834 
835     /**
836      * {@inheritDoc}
837      */
838     @Override
839     public boolean equals(final Object o) {
840         return EqualsBuilder.reflectionEquals(this, o);
841     }
842 
843     /**
844      * {@inheritDoc}
845      */
846     @Override
847     public int hashCode() {
848         return HashCodeBuilder.reflectionHashCode(this);
849     }
850 
851     /**
852      * Returns the available plugins. This makes only sense for Firefox as only this
853      * browser makes this kind of information available via JavaScript.
854      * @return the available plugins
855      */
856     public Set<PluginConfiguration> getPlugins() {
857         return plugins_;
858     }
859 
860     /**
861      * Indicates if this instance has the given feature. Used for HtmlUnit internal processing.
862      * @param property the property name
863      * @return {@code false} if this browser doesn't have this feature
864      */
865     public boolean hasFeature(final BrowserVersionFeatures property) {
866         return features_.contains(property);
867     }
868 
869     /**
870      * Returns the short name of the browser like {@code FF3}, {@code IE}, etc.
871      *
872      * @return the short name (if any)
873      */
874     public String getNickname() {
875         return nickname_;
876     }
877 
878     /**
879      * Returns the buildId.
880      * @return the buildId
881      */
882     public String getBuildId() {
883         return buildId_;
884     }
885 
886     /**
887      * Gets the headers names, so they are sent in the given order (if included in the request).
888      * @return headerNames the header names in ordered manner
889      */
890     public String[] getHeaderNamesOrdered() {
891         return headerNamesOrdered_;
892     }
893 
894     /**
895      * Sets the headers names, so they are sent in the given order (if included in the request).
896      * @param headerNames the header names in ordered manner
897      */
898     public void setHeaderNamesOrdered(final String[] headerNames) {
899         headerNamesOrdered_ = headerNames;
900     }
901 
902     /**
903      * Registers a new mime type for the provided file extension.
904      * @param fileExtension the file extension used to determine the mime type
905      * @param mimeType the mime type to be used when uploading files with this extension
906      */
907     public void registerUploadMimeType(final String fileExtension, final String mimeType) {
908         if (fileExtension != null) {
909             uploadMimeTypes_.put(fileExtension.toLowerCase(Locale.ROOT), mimeType);
910         }
911     }
912 
913     /**
914      * Determines the content type for the given file.
915      * @param file the file
916      * @return a content type or an empty string if unknown
917      */
918     public String getUploadMimeType(final File file) {
919         if (file == null) {
920             return "";
921         }
922 
923         final String fileExtension = FilenameUtils.getExtension(file.getName());
924 
925         final String mimeType = uploadMimeTypes_.get(fileExtension.toLowerCase(Locale.ROOT));
926         if (mimeType != null) {
927             return mimeType;
928         }
929         return "";
930     }
931 
932     @Override
933     public String toString() {
934         return nickname_;
935     }
936 
937     /**
938      * Creates and return a copy of this object. Current instance and cloned
939      * object can be modified independently.
940      * @return a clone of this instance.
941      */
942     @Override
943     public BrowserVersion clone() {
944         final BrowserVersion clone = new BrowserVersion(getApplicationName(), getApplicationVersion(),
945                 getUserAgent(), getBrowserVersionNumeric(), getNickname(), null);
946 
947         clone.setApplicationCodeName(getApplicationCodeName());
948         clone.setApplicationMinorVersion(getApplicationMinorVersion());
949         clone.setVendor(getVendor());
950         clone.setBrowserLanguage(getBrowserLanguage());
951         clone.setCpuClass(getCpuClass());
952         clone.setOnLine(isOnLine());
953         clone.setPlatform(getPlatform());
954         clone.setSystemLanguage(getSystemLanguage());
955         clone.setUserLanguage(getUserLanguage());
956 
957         clone.buildId_ = getBuildId();
958         clone.htmlAcceptHeader_ = getHtmlAcceptHeader();
959         clone.imgAcceptHeader_ = getImgAcceptHeader();
960         clone.cssAcceptHeader_ = getCssAcceptHeader();
961         clone.scriptAcceptHeader_ = getScriptAcceptHeader();
962         clone.xmlHttpRequestAcceptHeader_ = getXmlHttpRequestAcceptHeader();
963         clone.headerNamesOrdered_ = getHeaderNamesOrdered();
964 
965         for (final PluginConfiguration pluginConf : getPlugins()) {
966             clone.getPlugins().add(pluginConf.clone());
967         }
968 
969         clone.features_.addAll(features_);
970         clone.uploadMimeTypes_.putAll(uploadMimeTypes_);
971 
972         return clone;
973     }
974 }