PageRenderTime 53ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/src/main/java/org/znerd/uasniffer/Sniffer.java

https://github.com/znerd/uasniffer
Java | 745 lines | 510 code | 119 blank | 116 comment | 321 complexity | 0bd4b89d718cd9097c80c628267f8a65 MD5 | raw file
  1. // BSD-licensed, see COPYRIGHT file
  2. // Copyright 2011, Ernst de Haan
  3. package org.znerd.uasniffer;
  4. import org.znerd.util.text.TextUtils;
  5. import java.util.regex.Pattern;
  6. /**
  7. * Class responsible for determining the user agent details.
  8. */
  9. public final class Sniffer {
  10. private static final String[] UA_MOBILE_DEVICE_SNIPPETS = new String[] { "windows ce", "windowsce", "symbian", "nokia", "opera mini", "wget", "fennec", "opera mobi", "windows; ppc", "blackberry", "portable", "vita", "mobile" };
  11. private static final String[] UA_TABLET_DEVICE_SNIPPETS = new String[] { "ipad", "xoom", "tablet" };
  12. private static final String[] UA_MOBILE_DEVICE_WITHOUT_TEL_SUPPORT = new String[] { "opera/8.", "opera/7.", "opera/6.", "opera/5.", "opera/4.", "opera/3.", "ipod", "ipad", "tablet", "playstation" };
  13. private static final String[] UA_BOT_SNIPPETS = new String[] { "spider", "bot", "crawl", "miner", "checker", "java", "pingdom" };
  14. private Sniffer() {
  15. }
  16. /**
  17. * Analyzes the specified user agent string.
  18. *
  19. * @param agentString
  20. * the user agent string, cannot be <code>null</code>.
  21. * @return an {@link UserAgent} instance that describes the user agent, never <code>null</code>.
  22. * @throws IllegalArgumentException
  23. * if <code>agentString == null</code>.
  24. */
  25. public static final UserAgent analyze(String agentString) throws IllegalArgumentException {
  26. UserAgent ua = new UserAgent(agentString);
  27. analyze(ua);
  28. return ua;
  29. }
  30. private static final void analyze(UserAgent ua) {
  31. String agentString = ua.getLowerCaseAgentString();
  32. // Detect specific devices
  33. boolean android = agentString.contains("android");
  34. boolean appleTouch = agentString.contains("ipod") || agentString.contains("iphone") || agentString.contains("ipad");
  35. boolean nook = agentString.contains("nook ") || agentString.contains("bntv250");
  36. boolean psp = agentString.contains("playstation portable") || agentString.contains("playstation vita");
  37. boolean kindleFire = agentString.contains("silk-accelerated"); // TODO
  38. // Mobile devices
  39. boolean matchFound = false;
  40. String uaType = "desktop";
  41. boolean isPhone = false;
  42. boolean isTablet = false;
  43. if (nook) {
  44. matchFound = true;
  45. uaType = "ereader";
  46. isPhone = false;
  47. isTablet = false;
  48. } else if (kindleFire) {
  49. matchFound = true;
  50. uaType = "ereader";
  51. isPhone = false;
  52. isTablet = true;
  53. } else {
  54. for (String mobileDeviceSnippet : UA_MOBILE_DEVICE_SNIPPETS) {
  55. if (agentString.contains(mobileDeviceSnippet)) {
  56. matchFound = true;
  57. uaType = "mobile";
  58. isPhone = true;
  59. for (String mobileWithoutTelSnippet : UA_MOBILE_DEVICE_WITHOUT_TEL_SUPPORT) {
  60. if (agentString.contains(mobileWithoutTelSnippet)) {
  61. isPhone = false;
  62. }
  63. }
  64. }
  65. }
  66. // Tablets
  67. for (String tabletDeviceSnippet : UA_TABLET_DEVICE_SNIPPETS) {
  68. if (agentString.contains(tabletDeviceSnippet)) {
  69. isTablet = true;
  70. }
  71. }
  72. }
  73. if (!matchFound) {
  74. if (!isTablet && agentString.contains("android")) { // Android
  75. matchFound = true;
  76. uaType = "desktop";
  77. isPhone = true;
  78. } else if (agentString.contains("pre/")) { // Palm Pre
  79. matchFound = true;
  80. uaType = "desktop";
  81. isPhone = true;
  82. } else if (agentString.contains("kindle/")) { // Amazon Kindle
  83. matchFound = true;
  84. uaType = "ereader";
  85. isPhone = false;
  86. } else { // Bots
  87. for (String botSnippet : UA_BOT_SNIPPETS) {
  88. if (agentString.contains(botSnippet)) {
  89. matchFound = true;
  90. uaType = "bot";
  91. isPhone = false;
  92. }
  93. }
  94. }
  95. }
  96. // Categorize Device
  97. if (isPhone) {
  98. ua.addName("Device-Phone");
  99. } else {
  100. ua.addName("Device-NoPhone");
  101. }
  102. if ("ereader".equals(uaType)) {
  103. ua.addName("Device-Mobile");
  104. ua.addName("Device-Ereader");
  105. } else if ("mobile".equals(uaType) || appleTouch || android || agentString.contains("webos/")) {
  106. ua.addName("Device-Mobile");
  107. } else if ("bot".equals(uaType)) {
  108. ua.addName("Device-Bot");
  109. } else if (!isTablet) {
  110. ua.addName("Device-Desktop");
  111. }
  112. if (isTablet) {
  113. ua.addName("Device-Tablet");
  114. }
  115. if (psp) {
  116. ua.addName("Device-Gaming");
  117. ua.addName("Device-Mobile");
  118. ua.addName("Device-PSP");
  119. if (agentString.contains("vita")) {
  120. analyze(ua, agentString, "Device-PSP-Vita", "vita ", 2, false);
  121. }
  122. }
  123. if (kindleFire) {
  124. ua.addName("Device-AmazonKindle");
  125. ua.addName("Device-AmazonKindle-Fire");
  126. if (agentString.contains("silk-accelerated=true")) {
  127. ua.addName("CloudAcceleration-Yes");
  128. } else if (agentString.contains("silk-accelerated=false")) {
  129. ua.addName("CloudAcceleration-No");
  130. }
  131. }
  132. if (appleTouch) {
  133. ua.addName("Device-AppleTouch");
  134. if (agentString.contains("ipod")) {
  135. ua.addName("Device-AppleTouch-iPod");
  136. } else if (agentString.contains("ipad")) {
  137. ua.addName("Device-AppleTouch-iPad");
  138. } else {
  139. ua.addName("Device-AppleTouch-iPhone");
  140. }
  141. } else if (agentString.contains("blackberry")) {
  142. analyze(ua, agentString, "Device-Blackberry", "blackberry", 1, false);
  143. analyze(ua, agentString, "Device-Blackberry", "blackberry ", 1, false);
  144. } else if (agentString.contains("kindle/")) {
  145. analyze(ua, agentString, "Device-AmazonKindle", "kindle/", 2, false);
  146. }
  147. // Detect OS, browser engine and browser
  148. if (!"bot".equals(uaType)) {
  149. detectBrowserOS(ua);
  150. detectBrowserEngine(ua);
  151. detectBrowser(ua);
  152. }
  153. if (ua.hasName("BrowserEngine-Trident") && !ua.hasName("Browser-MobileMSIE")) {
  154. analyze(ua, agentString, "BrowserEngine-Trident-MSIE", agentString.contains("msie ") ? "msie " : "(ie ", 2, true);
  155. }
  156. }
  157. private static final void detectBrowserOS(UserAgent ua) {
  158. String agentString = ua.getLowerCaseAgentString();
  159. boolean nook = agentString.contains("nook ") || agentString.contains("nook/") || agentString.contains("bntv250");
  160. // Maemo - check before Linux
  161. if (agentString.contains("maemo")) {
  162. ua.addName("BrowserOS-NIX");
  163. ua.addName("BrowserOS-Linux");
  164. ua.addName("BrowserOS-Linux-Maemo");
  165. }
  166. // Linux
  167. if (agentString.contains("linux") || agentString.contains("android") || nook) {
  168. ua.addName("BrowserOS-NIX");
  169. ua.addName("BrowserOS-Linux");
  170. if (agentString.contains("linux 2.")) {
  171. analyze(ua, agentString, "BrowserOS-Linux", "linux ");
  172. }
  173. // Android
  174. if (agentString.contains("android") || nook) {
  175. analyze(ua, agentString, "BrowserOS-Linux-Android", "android ");
  176. }
  177. // Google Chrome OS
  178. } else if (agentString.contains("cros ")) {
  179. ua.addName("BrowserOS-CrOS");
  180. // webOS, by Palm
  181. } else if (agentString.contains("webos/")) {
  182. analyze(ua, agentString, "BrowserOS-WebOS", "webos/");
  183. // iOS (detect before Mac OS)
  184. } else if (agentString.contains("iphone") || agentString.contains("ipod") || agentString.contains("ipad")) {
  185. analyze(ua, agentString.replace('_', '.').replace("mac os x", ""), "BrowserOS-iOS", "OS ");
  186. // Mac OS
  187. } else if (agentString.contains("mac os") || agentString.contains("mac_") || agentString.contains("macintosh")) {
  188. ua.addName("BrowserOS-MacOS");
  189. // OS X
  190. if (agentString.contains("mac os x")) {
  191. ua.addName("BrowserOS-NIX");
  192. ua.addName("BrowserOS-MacOS-10");
  193. analyze(ua, agentString.replace('_', '.'), "BrowserOS-MacOS", "mac os x ", 0, false);
  194. analyze(ua, agentString.replace('_', '.'), "BrowserOS-MacOS", "mac os x tiger ", 0, false);
  195. analyze(ua, agentString.replace('_', '.'), "BrowserOS-MacOS", "mac os x leopard ", 0, false);
  196. analyze(ua, agentString.replace('_', '.'), "BrowserOS-MacOS", "mac os x snow leopard ", 0, false);
  197. analyze(ua, agentString.replace('_', '.'), "BrowserOS-MacOS", "mac os x lion ", 0, false);
  198. analyze(ua, agentString.replace('_', '.'), "BrowserOS-MacOS", "mac os x mountain lion ", 0, false);
  199. }
  200. // Windows
  201. } else if (agentString.contains("windows") || agentString.contains("win3.") || agentString.contains("win9") || agentString.contains("winnt") || agentString.contains("wince")) {
  202. ua.addName("BrowserOS-Windows");
  203. if (agentString.contains("windows nt")) {
  204. analyze(ua, agentString, "BrowserOS-Windows-NT", "windows nt ", 2, true);
  205. } else if (agentString.contains("windows 5.") || agentString.contains("windows 6.")) {
  206. analyze(ua, agentString, "BrowserOS-Windows-NT", "windows ", 2, false);
  207. } else if (agentString.contains("windows vista")) {
  208. analyze(ua, "nt/6.0", "BrowserOS-Windows-NT", "nt/", 2, false);
  209. } else if (agentString.contains("windows xp")) {
  210. analyze(ua, "nt/5.1", "BrowserOS-Windows-NT", "nt/", 2, false);
  211. } else if (agentString.contains("windows 2000")) {
  212. analyze(ua, "nt/5.0", "BrowserOS-Windows-NT", "nt/", 2, false);
  213. } else if (agentString.contains("winnt")) {
  214. analyze(ua, agentString, "BrowserOS-Windows-NT", "winnt", 2, true);
  215. // Windows ME (needs to be checked before Windows 98)
  216. } else if (agentString.contains("win 9x 4.90") || agentString.contains("windows me")) {
  217. ua.addName("BrowserOS-Windows-ME");
  218. // Windows 98
  219. } else if (agentString.contains("windows 98") || agentString.contains("win98")) {
  220. ua.addName("BrowserOS-Windows-98");
  221. // Windows 95
  222. } else if (agentString.contains("windows 95") || agentString.contains("win95")) {
  223. ua.addName("BrowserOS-Windows-95");
  224. // Windows Phone
  225. } else if (agentString.contains("windows phone os")) {
  226. analyze(ua, agentString, "BrowserOS-Windows-Phone", "windows phone os", 2, false);
  227. } else if (agentString.contains("windows phone")) {
  228. analyze(ua, agentString, "BrowserOS-Windows-Phone", "windows phone", 2, false);
  229. // Windows Mobile
  230. } else if (agentString.contains("windows mobile") || agentString.contains("windows; ppc") || agentString.contains("windows ce") || agentString.contains("wince")) {
  231. analyze(ua, agentString, "BrowserOS-Windows-Mobile", "windows mobile ", 3, true);
  232. // Windows 3.x
  233. } else if (agentString.contains("windows 3.")) {
  234. analyze(ua, agentString, "BrowserOS-Windows", "windows ", 3, true);
  235. } else if (agentString.contains("win3.")) {
  236. int indexWin3 = agentString.indexOf("win3.");
  237. int indexWindows = agentString.indexOf("windows");
  238. String s = indexWindows >= 0 && indexWindows < indexWin3 ? agentString.substring(indexWindows + 1) : agentString;
  239. analyze(ua, s, "BrowserOS-Windows", "win", 3, true);
  240. }
  241. // Add some marketing names for various Windows versions
  242. if (ua.hasName("BrowserOS-Windows-NT-5-0")) {
  243. ua.addName("BrowserOS-Windows-2000");
  244. } else if (ua.hasName("BrowserOS-Windows-NT-5")) {
  245. ua.addName("BrowserOS-Windows-XP");
  246. } else if (ua.hasName("BrowserOS-Windows-NT-6-0")) {
  247. ua.addName("BrowserOS-Windows-Vista");
  248. } else if (ua.hasName("BrowserOS-Windows-NT-6-1")) {
  249. ua.addName("BrowserOS-Windows-7");
  250. } else if (ua.hasName("BrowserOS-Windows-NT-6-2")) {
  251. if (agentString.contains(" arm;")) {
  252. ua.addName("BrowserOS-Windows-RT");
  253. } else {
  254. ua.addName("BrowserOS-Windows-8");
  255. ua.addName("BrowserOS-Windows-8-0");
  256. }
  257. } else if (ua.hasName("BrowserOS-Windows-NT-6-3")) {
  258. if (agentString.contains(" arm;")) {
  259. ua.addName("BrowserOS-Windows-RT");
  260. } else {
  261. ua.addName("BrowserOS-Windows-8");
  262. ua.addName("BrowserOS-Windows-8-1");
  263. }
  264. }
  265. // DragonFlyBSD, extra check
  266. } else if (agentString.contains("dragonfly")) {
  267. ua.addName("BrowserOS-NIX");
  268. ua.addName("BrowserOS-BSD");
  269. ua.addName("BrowserOS-BSD-DragonFlyBSD");
  270. // Other BSD variants
  271. } else if (agentString.contains("bsd")) {
  272. ua.addName("BrowserOS-NIX");
  273. ua.addName("BrowserOS-BSD");
  274. if (agentString.contains("netbsd")) {
  275. ua.addName("BrowserOS-BSD-NetBSD");
  276. } else if (agentString.contains("openbsd")) {
  277. ua.addName("BrowserOS-BSD-OpenBSD");
  278. } else if (agentString.contains("freebsd")) {
  279. ua.addName("BrowserOS-BSD-FreeBSD");
  280. }
  281. // AIX
  282. } else if (agentString.contains("aix")) {
  283. ua.addName("BrowserOS-NIX");
  284. analyze(ua, agentString, "BrowserOS-AIX", "aix ", 1, false);
  285. // IRIX
  286. } else if (agentString.contains("irix")) {
  287. ua.addName("BrowserOS-NIX");
  288. analyze(ua, agentString, "BrowserOS-IRIX", "irix ", 2, false);
  289. analyze(ua, agentString, "BrowserOS-IRIX", "irix64 ", 2, false);
  290. // HP-UX
  291. } else if (agentString.contains("hp-ux")) {
  292. ua.addName("BrowserOS-NIX");
  293. ua.addName("BrowserOS-HPUX");
  294. // Sun Solaris
  295. } else if (agentString.contains("sunos")) {
  296. ua.addName("BrowserOS-NIX");
  297. analyze(ua, agentString, "BrowserOS-Solaris", "sunos ", 1, false);
  298. // Sun Solaris
  299. } else if (agentString.contains("beos")) {
  300. ua.addName("BrowserOS-BeOS");
  301. // OS/2 (a.k.a. Ecomstation)
  302. } else if (agentString.contains("(os/2")) {
  303. analyze(ua, agentString, "BrowserOS-OS2", "warp ", 1, false);
  304. // Symbian
  305. } else if (agentString.contains("symbian")) {
  306. analyze(ua, agentString, "BrowserOS-Symbian", "symbianos/", 3, false);
  307. } else if (agentString.contains("bada/")) {
  308. analyze(ua, agentString, "BrowserOS-Bada", "bada/", 2, false);
  309. }
  310. }
  311. private static final void detectBrowserEngine(UserAgent ua) {
  312. String agentString = ua.getLowerCaseAgentString();
  313. // Apple WebKit
  314. if (agentString.contains("applewebkit/")) {
  315. analyze(ua, agentString, "BrowserEngine-WebKit", "applewebkit/", 4, false);
  316. } else if (agentString.contains("apple webkit/")) {
  317. analyze(ua, agentString, "BrowserEngine-WebKit", "apple webkit/", 4, false);
  318. // Mozilla Gecko
  319. } else if (agentString.contains("gecko/")) {
  320. analyze(ua, agentString, "BrowserEngine-Gecko", "rv:", 4, false);
  321. // Opera Presto
  322. } else if (agentString.contains("presto/")) {
  323. analyze(ua, agentString, "BrowserEngine-Presto", "presto/", 3, false);
  324. } else if (agentString.contains("presto")) {
  325. analyze(ua, agentString, "BrowserEngine-Presto", "presto ", 3, false);
  326. // Microsoft Trident
  327. } else if (agentString.contains("trident/")) {
  328. analyze(ua, agentString, "BrowserEngine-Trident", "trident/", 3, false);
  329. } else if (agentString.contains("trident")) {
  330. analyze(ua, agentString, "BrowserEngine-Trident", "trident ", 3, false);
  331. // KDE KHTML
  332. } else if (agentString.contains("khtml/")) {
  333. analyze(ua, agentString, "BrowserEngine-KHTML", "khtml/", 3, false);
  334. } else {
  335. if (agentString.contains("opera ")) {
  336. ua.addName("BrowserEngine-Presto");
  337. } else if (agentString.contains("msie ") || agentString.contains("msie/")) {
  338. if (agentString.contains("mac") && agentString.contains("msie 5.")) {
  339. ua.addName("BrowserEngine-Tasman");
  340. } else {
  341. ua.addName("BrowserEngine-Trident");
  342. }
  343. }
  344. }
  345. }
  346. private static final void detectBrowser(UserAgent ua) {
  347. String agentString = ua.getLowerCaseAgentString();
  348. // Lunascape, can use different rendering engines
  349. // E.g.: Lunascape5 (Webkit) - Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US)
  350. // AppleWebKit/528+ (KHTML, like Gecko, Safari/528.0) Lunascape/5.0.3.0
  351. if (agentString.contains("lunascape")) {
  352. analyze(ua, agentString, "Browser-Lunascape", "lunascape ", 4, false);
  353. analyze(ua, agentString, "Browser-Lunascape", "lunascape/", 4, false);
  354. // Maxthon
  355. } else if (agentString.contains("maxthon")) {
  356. analyze(ua, agentString, "Browser-Maxthon", "maxthon ", 4, false);
  357. analyze(ua, agentString, "Browser-Maxthon", "maxthon/", 4, false);
  358. // Sleipnir
  359. } else if (agentString.contains("sleipnir/")) {
  360. analyze(ua, agentString, "Browser-Sleipnir", "sleipnir/", 3, false);
  361. // Blackberry
  362. } else if (agentString.contains("blackberry")) {
  363. analyze(ua, agentString, "Browser-Blackberry", "version/");
  364. // Konqueror (needs to be detected before Gecko-based browsers)
  365. // E.g.: Mozilla/5.0 (compatible; Konqueror/4.1; Linux) KHTML/4.1.2 (like Gecko)
  366. } else if (agentString.contains("konqueror")) {
  367. analyze(ua, agentString, "Browser-Konqueror", "konqueror/", 2, false);
  368. ua.addName("BrowserEngine-KHTML");
  369. // Fennec
  370. // E.g.: Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-US; rv:1.9.2a1pre) Gecko/20090317
  371. // Fennec/1.0b1
  372. // Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1b2pre) Gecko/20081015 Fennec/1.0a1
  373. // Mozilla/5.0 (X11; U; Linux armv7l; en-US; rv:1.9.2a1pre) Gecko/20090322
  374. // Fennec/1.0b2pre
  375. } else if (agentString.contains("fennec")) {
  376. analyze(ua, agentString, "Browser-Fennec", "fennec/");
  377. analyze(ua, agentString, "Browser-MobileFirefox", "fennec/");
  378. // Epiphany
  379. // E.g.: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.7.3) Gecko/20041007 Epiphany/1.4.7
  380. } else if (agentString.contains("epiphany")) {
  381. analyze(ua, agentString, "Browser-Epiphany", "epiphany/");
  382. // Flock (needs to be detected before Firefox and Chrome)
  383. // E.g.: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.18) Gecko/20081107
  384. // Firefox/2.0.0.18 Flock/1.2.7
  385. // or: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.7 (KHTML, like
  386. // Gecko) Flock/3.5.2.4599 Chrome/7.0.517.442 Safari/534.7
  387. } else if (agentString.contains("flock")) {
  388. analyze(ua, agentString, "Browser-Flock", "flock/", 4, false);
  389. // Camino (needs to be detected before Firefox)
  390. // E.g.: Mozilla/5.0 (Macintosh; U; Intel Mac OS X; nl; rv:1.8.1.14) Gecko/20080512
  391. // Camino/1.6.1 (MultiLang) (like Firefox/2.0.0.14)
  392. } else if (agentString.contains("camino")) {
  393. analyze(ua, agentString, "Browser-Camino", "camino/");
  394. // SeaMonkey
  395. // E.g.: Mozilla/5.0 (X11; U; Linux x86_64; en-US; rv:1.9.1b3pre) Gecko/20090302
  396. // SeaMonkey/2.0b1pre
  397. } else if (agentString.contains("seamonkey/")) {
  398. analyze(ua, agentString, "Browser-SeaMonkey", "seamonkey/");
  399. // SeaMonkey (again)
  400. // E.g.: Seamonkey-1.1.13-1(X11; U; GNU Fedora fc 10) Gecko/20081112
  401. } else if (agentString.contains("seamonkey-")) {
  402. analyze(ua, agentString, "Browser-SeaMonkey", "seamonkey-");
  403. ua.addName("BrowserEngine-Gecko");
  404. // Netscape Navigator (needs to be detected before Firefox)
  405. // E.g.: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.5pre) Gecko/20070712
  406. // Firefox/2.0.0.4 Navigator/9.0b2
  407. } else if (agentString.contains("navigator/")) {
  408. analyze(ua, agentString, "Browser-Netscape", "navigator/");
  409. ua.addName("BrowserEngine-Gecko");
  410. // Firefox
  411. } else if (agentString.contains("firefox")) {
  412. analyze(ua, agentString, "Browser-Firefox", "firefox/");
  413. if (agentString.contains("mobile") || agentString.contains("tablet")) {
  414. analyze(ua, agentString, "Browser-MobileFirefox", "firefox/");
  415. }
  416. } else if (agentString.contains("minefield/")) {
  417. analyze(ua, agentString, "Browser-Firefox", "minefield/");
  418. } else if (agentString.contains("namoroka/")) {
  419. analyze(ua, agentString, "Browser-Firefox", "namoroka/"); // Firefox 3.6 pre-releases
  420. } else if (agentString.contains("shiretoko/")) {
  421. analyze(ua, agentString, "Browser-Firefox", "shiretoko/"); // Firefox 3.5 pre-releases
  422. } else if (agentString.contains("granparadiso/")) {
  423. analyze(ua, agentString, "Browser-Firefox", "granparadiso/"); // Firefox 3.0/3.1
  424. // pre-releases
  425. } else if (agentString.contains("firebird/")) {
  426. analyze(ua, agentString, "Browser-Firefox", "firebird/"); // Before 1.0
  427. } else if (agentString.contains("phoenix/")) {
  428. analyze(ua, agentString, "Browser-Firefox", "phoenix/"); // Before 1.0 (and before
  429. // Firebird code-name)
  430. // Opera (detect before Chrome)
  431. } else if (agentString.startsWith("opera/") || agentString.contains("opr/")) {
  432. ua.addName("Browser-Opera");
  433. String browserName;
  434. if (agentString.contains("tablet")) { // Opera Tablet
  435. browserName = "Browser-OperaTablet";
  436. } else if (agentString.contains("mini/")) { // Opera Mini
  437. browserName = "Browser-OperaMini";
  438. } else if (agentString.contains("mobi/")) { // Opera Mobile
  439. browserName = "Browser-OperaMobile";
  440. } else { // Opera Desktop
  441. browserName = "Browser-OperaDesktop";
  442. }
  443. if (agentString.contains("opera mini/")) {
  444. analyze(ua, agentString, browserName, "opera mini/", 3, true);
  445. } else if (agentString.contains("opr/")) {
  446. analyze(ua, agentString, browserName, "opr/", 4, true);
  447. } else if (agentString.contains("version/")) {
  448. analyze(ua, agentString, browserName, "version/", 3, true);
  449. } else if (agentString.contains("opera/")) {
  450. ua.addName("BrowserEngine-Presto");
  451. analyze(ua, agentString, browserName, "opera/", 3, true);
  452. } else {
  453. analyze(ua, agentString, "Browser-OperaDesktop", agentString.contains("version/") ? "version/" : "opera/", 3, true);
  454. }
  455. // Opera (older releases)
  456. } else if (agentString.contains("opera")) {
  457. ua.addName("Browser-Opera");
  458. analyze(ua, agentString, "Browser-OperaDesktop", "opera ", 3, true);
  459. ua.addName("BrowserEngine-Presto");
  460. // Palm Pre browser - this one needs to be checked before Safari
  461. } else if (agentString.contains("pre/")) {
  462. analyze(ua, agentString, "Browser-PalmPreBrowser", "version/");
  463. // OmniWeb - this one needs to be checked before Safari
  464. } else if (agentString.contains("omniweb")) {
  465. ua.addName("Browser-OmniWeb");
  466. // RockMelt - this one needs to be checked before Google Chrome
  467. // e.g.: Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/534.13 (KHTML, like
  468. // Gecko) RockMelt/0.9.48.51 Chrome/9.0.597.107 Safari/534.13
  469. } else if (agentString.contains("rockmelt")) {
  470. analyze(ua, agentString, "Browser-RockMelt", "rockmelt/", 4, false);
  471. // Google Chrome - this one needs to be checked before Safari
  472. // e.g.: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US) AppleWebKit/525.13 (KHTML, like
  473. // Gecko) Chrome/0.X.Y.Z Safari/525.13.
  474. } else if (agentString.contains("chrome/") && !agentString.contains("chromeframe")) {
  475. analyze(ua, agentString, "Browser-Chrome", "chrome/", 4, false);
  476. // Nokia browser - needs to be checked before Safari
  477. } else if (agentString.contains("symbianos")) {
  478. if (agentString.contains("version/") || !agentString.contains("browserng/")) {
  479. analyze(ua, agentString, "Browser-Nokia", "version/", 3, false);
  480. } else {
  481. analyze(ua, agentString, "Browser-Nokia", "browserng/", 3, false);
  482. }
  483. // NetFront
  484. } else if (agentString.contains("netfront")) {
  485. analyze(ua, agentString, "Browser-NetFront", "netfront/", 3, true);
  486. // Amazon Kindle browser (detect after NetFront but before Safari)
  487. } else if (agentString.contains("kindle/")) {
  488. analyze(ua, agentString, "Browser-Kindle", "version/", 2, true);
  489. // Dolphin, check before Safari
  490. } else if (agentString.contains("dolfin")) {
  491. analyze(ua, agentString, "Browser-Dolphin", "dolfin/", 2, true);
  492. // Nook, check before Safari
  493. } else if (agentString.contains("nook ") || agentString.contains("bntv250 ")) {
  494. if (agentString.contains("nook browser/")) {
  495. analyze(ua, agentString, "Browser-Nook", "browser/", 2, true);
  496. } else {
  497. analyze(ua, agentString, "Browser-Nook", "version/", 2, true);
  498. }
  499. } else if (agentString.contains("silk/")) {
  500. analyze(ua, agentString, "Browser-Silk", "silk/", 2, true);
  501. // iCab, check before Safari
  502. // E.g.: iCab/4.5 (Macintosh; U; Mac OS X Leopard 10.5.7)
  503. } else if (agentString.contains("icab")) {
  504. analyze(ua, agentString, "Browser-iCab", "icab/");
  505. analyze(ua, agentString, "Browser-iCab", "icab ");
  506. // iCab 4 uses the WebKit rendering engine, although the user agent
  507. // string does not advertise that
  508. if (ua.hasName("Browser-iCab-4")) {
  509. ua.addName("BrowserEngine-WebKit");
  510. }
  511. // Apple Safari
  512. } else if (! agentString.contains("chromeframe") && (agentString.contains("safari") || agentString.contains("applewebkit"))) {
  513. ua.addName("BrowserEngine-WebKit");
  514. ua.addName("Browser-Safari");
  515. if (agentString.contains("mobile/") || agentString.contains("android")) {
  516. if (Pattern.compile("mobile\\/[0-9]+(\\.[0-9]+)+(\\s|\\))").matcher(agentString).find()) {
  517. analyze(ua, agentString, "Browser-MobileSafari", "mobile/");
  518. } else {
  519. analyze(ua, agentString, "Browser-MobileSafari", "version/");
  520. }
  521. } else {
  522. analyze(ua, agentString, "Browser-DesktopSafari", "version/");
  523. }
  524. // Netscape (again)
  525. } else if (agentString.contains("netscape6")) {
  526. analyze(ua, agentString, "Browser-Netscape", "netscape6/");
  527. ua.addName("Browser-Netscape");
  528. ua.addName("Browser-Netscape-6");
  529. ua.addName("BrowserEngine-Gecko");
  530. } else if (agentString.contains("netscape")) {
  531. analyze(ua, agentString, "Browser-Netscape", "netscape/", 3, true);
  532. ua.addName("BrowserEngine-Gecko");
  533. // Internet Explorer
  534. } else if (agentString.contains("msie") || agentString.contains("(ie ") || agentString.contains("chromeframe")) {
  535. ua.addName("Browser-MSIE");
  536. // Mobile IE
  537. if (agentString.contains("iemobile/")) {
  538. analyze(ua, agentString, "Browser-MobileMSIE", "iemobile/", 3, true);
  539. } else if (agentString.contains("iemobile")) {
  540. analyze(ua, agentString, "Browser-MobileMSIE", "iemobile ", 3, true);
  541. } else if (ua.hasName("BrowserOS-Windows-Mobile")) {
  542. ua.addName("Browser-MobileMSIE");
  543. } else {
  544. analyze(ua, agentString, "Browser-DesktopMSIE", agentString.contains("msie ") ? "msie " : "(ie ", 3, true);
  545. // Chrome Frame
  546. if (agentString.contains("chromeframe")) {
  547. analyze(ua, agentString, "BrowserEngine-ChromeFrame", "chromeframe/", 4, false);
  548. }
  549. }
  550. // NCSA Mosaic
  551. } else if (agentString.startsWith("ncsa_mosaic") || agentString.startsWith("ncsa mosaic")) {
  552. analyze(ua, agentString.replace('_', ' '), "Browser-Mosaic", "ncsa mosaic/", 2, true);
  553. // Netscape 1, 2, 3, 4
  554. } else if (!agentString.contains("(compatible") && TextUtils.matches(agentString, "mozilla\\/[1234]")) {
  555. analyze(ua, agentString, "Browser-Netscape", "mozilla/", 3, true);
  556. // Internet Explorer, as of version 11
  557. } else if (agentString.contains("trident/") && agentString.contains("rv")) {
  558. ua.addName("Browser-MSIE");
  559. if (agentString.contains("rv ")) {
  560. analyze(ua, agentString, "Browser-DesktopMSIE", "rv ", 3, true);
  561. analyze(ua, agentString, "BrowserEngine-Trident-MSIE", "rv ", 2, true); // TODO: Move elsewhere
  562. } else if (agentString.contains("rv:")) {
  563. analyze(ua, agentString, "Browser-DesktopMSIE", "rv:", 3, true);
  564. analyze(ua, agentString, "BrowserEngine-Trident-MSIE", "rv:", 2, true); // TODO: Move elsewhere
  565. }
  566. }
  567. }
  568. private static final void analyze(UserAgent ua, String agentString, String basicName, String versionPrefix) {
  569. analyze(ua, agentString, basicName, versionPrefix, 3, false);
  570. }
  571. private static final void analyze(UserAgent ua, String agentString, String basicName, String versionPrefix, int minVersionParts, boolean splitSecondVersionPart) {
  572. versionPrefix = versionPrefix.toLowerCase();
  573. // First add the basic name
  574. ua.addName(basicName);
  575. // Find the location of the version number after the prefix
  576. int index = agentString.indexOf(versionPrefix);
  577. if (index >= 0) {
  578. // Get the version number in a string
  579. String version = cutVersionEnd(agentString.substring(index + versionPrefix.length()).trim());
  580. if (version.length() > 0 && !version.startsWith("00")) {
  581. // Split the version number in pieces
  582. String[] versionParts = version.split("\\.");
  583. // First version part can always be done immediately
  584. String specificName = basicName + '-' + versionParts[0];
  585. ua.addName(specificName);
  586. int versionPartsFound;
  587. if (splitSecondVersionPart && versionParts.length == 2) {
  588. versionPartsFound = 1;
  589. String secondVersionPart = versionParts[1];
  590. for (int i = 0; i < secondVersionPart.length(); i++) {
  591. specificName += "-" + secondVersionPart.charAt(i);
  592. ua.addName(specificName);
  593. versionPartsFound++;
  594. }
  595. } else {
  596. for (int i = 1; i < versionParts.length; i++) {
  597. if (TextUtils.matches(versionParts[i], "^0[0-9]")) {
  598. specificName += '-' + "0";
  599. ua.addName(specificName);
  600. versionParts[i] = versionParts[i].substring(1);
  601. }
  602. specificName += '-' + versionParts[i];
  603. ua.addName(specificName);
  604. }
  605. versionPartsFound = versionParts.length;
  606. }
  607. for (int i = versionPartsFound; i < minVersionParts; i++) {
  608. specificName += "-0";
  609. ua.addName(specificName);
  610. }
  611. }
  612. }
  613. }
  614. private static final String cutVersionEnd(String s) {
  615. String result = "";
  616. for (int i = 0; i < s.length(); i++) {
  617. char c = s.charAt(i);
  618. if (Character.isDigit(c) || c == '.') {
  619. result += c;
  620. } else {
  621. break;
  622. }
  623. }
  624. return result;
  625. }
  626. }