├── .github └── workflows │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── browser.go ├── cmd └── uastats │ └── main.go ├── const_string.go ├── device.go ├── go.mod ├── go.sum ├── system.go ├── uasurfer.go └── uasurfer_test.go /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | 3 | on: 4 | push: 5 | branches: [ "master" ] 6 | pull_request: 7 | branches: [ "master" ] 8 | 9 | jobs: 10 | 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Set up Go 17 | uses: actions/setup-go@v4 18 | with: 19 | go-version: '1.21' 20 | 21 | - name: Test 22 | run: go test -v ./... -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled bin # 2 | ################### 3 | 4 | 5 | # Compiled source # 6 | ################### 7 | *.dll 8 | *.exe 9 | *.o 10 | *.so 11 | 12 | # Packages # 13 | ############ 14 | # it's better to unpack these files and commit the raw source 15 | # git has its own built in compression methods 16 | *.7z 17 | *.dmg 18 | *.gz 19 | *.iso 20 | *.jar 21 | *.rar 22 | *.tar 23 | *.zip 24 | 25 | # Configuration Files # 26 | ####################### 27 | *.cfg 28 | 29 | # Logs and databases # 30 | ###################### 31 | *.log 32 | *.sql 33 | *.sqlite 34 | logs 35 | coverage.html 36 | coverage.out 37 | 38 | # Test Files # 39 | ####################### 40 | *.test 41 | 42 | # OS generated files # 43 | ###################### 44 | .DS_Store 45 | .DS_Store? 46 | .Spotlight-V100 47 | .Trashes 48 | ehthumbs.db 49 | Thumbs.db 50 | 51 | # go.rice generated files 52 | *.rice-box.go 53 | 54 | # Dev Tools # 55 | ###################### 56 | .vagrant -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | Copyright 2015 Avocet Systems Ltd. 7 | http://avocet.io/opensource 8 | 9 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 10 | 11 | 1. Definitions. 12 | 13 | "License" shall mean the terms and conditions for use, reproduction, 14 | and distribution as defined by Sections 1 through 9 of this document. 15 | 16 | "Licensor" shall mean the copyright owner or entity authorized by 17 | the copyright owner that is granting the License. 18 | 19 | "Legal Entity" shall mean the union of the acting entity and all 20 | other entities that control, are controlled by, or are under common 21 | control with that entity. For the purposes of this definition, 22 | "control" means (i) the power, direct or indirect, to cause the 23 | direction or management of such entity, whether by contract or 24 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 25 | outstanding shares, or (iii) beneficial ownership of such entity. 26 | 27 | "You" (or "Your") shall mean an individual or Legal Entity 28 | exercising permissions granted by this License. 29 | 30 | "Source" form shall mean the preferred form for making modifications, 31 | including but not limited to software source code, documentation 32 | source, and configuration files. 33 | 34 | "Object" form shall mean any form resulting from mechanical 35 | transformation or translation of a Source form, including but 36 | not limited to compiled object code, generated documentation, 37 | and conversions to other media types. 38 | 39 | "Work" shall mean the work of authorship, whether in Source or 40 | Object form, made available under the License, as indicated by a 41 | copyright notice that is included in or attached to the work 42 | (an example is provided in the Appendix below). 43 | 44 | "Derivative Works" shall mean any work, whether in Source or Object 45 | form, that is based on (or derived from) the Work and for which the 46 | editorial revisions, annotations, elaborations, or other modifications 47 | represent, as a whole, an original work of authorship. For the purposes 48 | of this License, Derivative Works shall not include works that remain 49 | separable from, or merely link (or bind by name) to the interfaces of, 50 | the Work and Derivative Works thereof. 51 | 52 | "Contribution" shall mean any work of authorship, including 53 | the original version of the Work and any modifications or additions 54 | to that Work or Derivative Works thereof, that is intentionally 55 | submitted to Licensor for inclusion in the Work by the copyright owner 56 | or by an individual or Legal Entity authorized to submit on behalf of 57 | the copyright owner. For the purposes of this definition, "submitted" 58 | means any form of electronic, verbal, or written communication sent 59 | to the Licensor or its representatives, including but not limited to 60 | communication on electronic mailing lists, source code control systems, 61 | and issue tracking systems that are managed by, or on behalf of, the 62 | Licensor for the purpose of discussing and improving the Work, but 63 | excluding communication that is conspicuously marked or otherwise 64 | designated in writing by the copyright owner as "Not a Contribution." 65 | 66 | "Contributor" shall mean Licensor and any individual or Legal Entity 67 | on behalf of whom a Contribution has been received by Licensor and 68 | subsequently incorporated within the Work. 69 | 70 | 2. Grant of Copyright License. Subject to the terms and conditions of 71 | this License, each Contributor hereby grants to You a perpetual, 72 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 73 | copyright license to reproduce, prepare Derivative Works of, 74 | publicly display, publicly perform, sublicense, and distribute the 75 | Work and such Derivative Works in Source or Object form. 76 | 77 | 3. Grant of Patent License. Subject to the terms and conditions of 78 | this License, each Contributor hereby grants to You a perpetual, 79 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 80 | (except as stated in this section) patent license to make, have made, 81 | use, offer to sell, sell, import, and otherwise transfer the Work, 82 | where such license applies only to those patent claims licensable 83 | by such Contributor that are necessarily infringed by their 84 | Contribution(s) alone or by combination of their Contribution(s) 85 | with the Work to which such Contribution(s) was submitted. If You 86 | institute patent litigation against any entity (including a 87 | cross-claim or counterclaim in a lawsuit) alleging that the Work 88 | or a Contribution incorporated within the Work constitutes direct 89 | or contributory patent infringement, then any patent licenses 90 | granted to You under this License for that Work shall terminate 91 | as of the date such litigation is filed. 92 | 93 | 4. Redistribution. You may reproduce and distribute copies of the 94 | Work or Derivative Works thereof in any medium, with or without 95 | modifications, and in Source or Object form, provided that You 96 | meet the following conditions: 97 | 98 | (a) You must give any other recipients of the Work or 99 | Derivative Works a copy of this License; and 100 | 101 | (b) You must cause any modified files to carry prominent notices 102 | stating that You changed the files; and 103 | 104 | (c) You must retain, in the Source form of any Derivative Works 105 | that You distribute, all copyright, patent, trademark, and 106 | attribution notices from the Source form of the Work, 107 | excluding those notices that do not pertain to any part of 108 | the Derivative Works; and 109 | 110 | (d) If the Work includes a "NOTICE" text file as part of its 111 | distribution, then any Derivative Works that You distribute must 112 | include a readable copy of the attribution notices contained 113 | within such NOTICE file, excluding those notices that do not 114 | pertain to any part of the Derivative Works, in at least one 115 | of the following places: within a NOTICE text file distributed 116 | as part of the Derivative Works; within the Source form or 117 | documentation, if provided along with the Derivative Works; or, 118 | within a display generated by the Derivative Works, if and 119 | wherever such third-party notices normally appear. The contents 120 | of the NOTICE file are for informational purposes only and 121 | do not modify the License. You may add Your own attribution 122 | notices within Derivative Works that You distribute, alongside 123 | or as an addendum to the NOTICE text from the Work, provided 124 | that such additional attribution notices cannot be construed 125 | as modifying the License. 126 | 127 | You may add Your own copyright statement to Your modifications and 128 | may provide additional or different license terms and conditions 129 | for use, reproduction, or distribution of Your modifications, or 130 | for any such Derivative Works as a whole, provided Your use, 131 | reproduction, and distribution of the Work otherwise complies with 132 | the conditions stated in this License. 133 | 134 | 5. Submission of Contributions. Unless You explicitly state otherwise, 135 | any Contribution intentionally submitted for inclusion in the Work 136 | by You to the Licensor shall be under the terms and conditions of 137 | this License, without any additional terms or conditions. 138 | Notwithstanding the above, nothing herein shall supersede or modify 139 | the terms of any separate license agreement you may have executed 140 | with Licensor regarding such Contributions. 141 | 142 | 6. Trademarks. This License does not grant permission to use the trade 143 | names, trademarks, service marks, or product names of the Licensor, 144 | except as required for reasonable and customary use in describing the 145 | origin of the Work and reproducing the content of the NOTICE file. 146 | 147 | 7. Disclaimer of Warranty. Unless required by applicable law or 148 | agreed to in writing, Licensor provides the Work (and each 149 | Contributor provides its Contributions) on an "AS IS" BASIS, 150 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 151 | implied, including, without limitation, any warranties or conditions 152 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 153 | PARTICULAR PURPOSE. You are solely responsible for determining the 154 | appropriateness of using or redistributing the Work and assume any 155 | risks associated with Your exercise of permissions under this License. 156 | 157 | 8. Limitation of Liability. In no event and under no legal theory, 158 | whether in tort (including negligence), contract, or otherwise, 159 | unless required by applicable law (such as deliberate and grossly 160 | negligent acts) or agreed to in writing, shall any Contributor be 161 | liable to You for damages, including any direct, indirect, special, 162 | incidental, or consequential damages of any character arising as a 163 | result of this License or out of the use or inability to use the 164 | Work (including but not limited to damages for loss of goodwill, 165 | work stoppage, computer failure or malfunction, or any and all 166 | other commercial damages or losses), even if such Contributor 167 | has been advised of the possibility of such damages. 168 | 169 | 9. Accepting Warranty or Additional Liability. While redistributing 170 | the Work or Derivative Works thereof, You may choose to offer, 171 | and charge a fee for, acceptance of support, warranty, indemnity, 172 | or other liability obligations and/or rights consistent with this 173 | License. However, in accepting such obligations, You may act only 174 | on Your own behalf and on Your sole responsibility, not on behalf 175 | of any other Contributor, and only if You agree to indemnify, 176 | defend, and hold each Contributor harmless for any liability 177 | incurred by, or claims asserted against, such Contributor by reason 178 | of your accepting any such warranty or additional liability. 179 | 180 | Copyright 2015 Avocet Systems Ltd. 181 | 182 | Licensed under the Apache License, Version 2.0 (the "License"); 183 | you may not use this file except in compliance with the License. 184 | You may obtain a copy of the License at 185 | 186 | http://www.apache.org/licenses/LICENSE-2.0 187 | 188 | Unless required by applicable law or agreed to in writing, software 189 | distributed under the License is distributed on an "AS IS" BASIS, 190 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 191 | See the License for the specific language governing permissions and 192 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/avct/uasurfer.svg?branch=master)](https://travis-ci.org/avct/uasurfer) [![GoDoc](https://godoc.org/github.com/avct/uasurfer?status.svg)](https://godoc.org/github.com/avct/uasurfer) [![Go Report Card](https://goreportcard.com/badge/github.com/avct/uasurfer)](https://goreportcard.com/report/github.com/avct/uasurfer) 2 | 3 | # uasurfer 4 | 5 | ![uasurfer-100px](https://cloud.githubusercontent.com/assets/597902/16172506/9debc136-357a-11e6-90fb-c7c46f50dff0.png) 6 | 7 | **User Agent Surfer** (uasurfer) is a lightweight Golang package that parses and abstracts [HTTP User-Agent strings](https://en.wikipedia.org/wiki/User_agent) with particular attention to device type. 8 | 9 | The following information is returned by uasurfer from a raw HTTP User-Agent string: 10 | 11 | | Name | Example | Coverage in 192,792 parses | 12 | |----------------|---------|--------------------------------| 13 | | Browser name | `chrome` | 99.85% | 14 | | Browser version | `53` | 99.17% | 15 | | Platform | `ipad` | 99.97% | 16 | | OS name | `ios` | 99.96% | 17 | | OS version | `10` | 98.81% | 18 | | Device type | `tablet` | 99.98% | 19 | 20 | Layout engine, browser language, and other esoteric attributes are not parsed. 21 | 22 | Coverage is estimated from a random sample of real UA strings collected across thousands of sources in US and EU mid-2016. 23 | 24 | ## Usage 25 | 26 | ### Parse(ua string) Function 27 | 28 | The `Parse()` function accepts a user agent `string` and returns UserAgent struct with named constants and integers for versions (minor, major and patch separately), and the full UA string that was parsed (lowercase). A string can be retrieved by adding `.String()` to a variable, such as `uasurfer.BrowserName.String()`. 29 | 30 | ``` 31 | // Define a user agent string 32 | myUA := "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36" 33 | 34 | // Parse() returns all attributes, including returning the full UA string last 35 | ua, uaString := uasurfer.Parse(myUA) 36 | ``` 37 | 38 | where example UserAgent is: 39 | ``` 40 | { 41 | Browser { 42 | BrowserName: BrowserChrome, 43 | Version: { 44 | Major: 45, 45 | Minor: 0, 46 | Patch: 2454, 47 | }, 48 | }, 49 | OS { 50 | Platform: PlatformMac, 51 | Name: OSMacOSX, 52 | Version: { 53 | Major: 10, 54 | Minor: 10, 55 | Patch: 5, 56 | }, 57 | }, 58 | DeviceType: DeviceComputer, 59 | } 60 | ``` 61 | 62 | **Usage note:** There are some OSes that do not return a version, see docs below. Linux is typically not reported with a specific Linux distro name or version. 63 | 64 | #### Browser Name 65 | * `BrowserChrome` - Google [Chrome](https://en.wikipedia.org/wiki/Google_Chrome), [Chromium](https://en.wikipedia.org/wiki/Chromium_(web_browser)) 66 | * `BrowserSafari` - Apple [Safari](https://en.wikipedia.org/wiki/Safari_(web_browser)), Google Search ([GSA](https://itunes.apple.com/us/app/google/id284815942)) 67 | * `BrowserIE` - Microsoft [Internet Explorer](https://en.wikipedia.org/wiki/Internet_Explorer), [Edge](https://en.wikipedia.org/wiki/Microsoft_Edge) 68 | * `BrowserFirefox` - Mozilla [Firefox](https://en.wikipedia.org/wiki/Firefox), GNU [IceCat](https://en.wikipedia.org/wiki/GNU_IceCat), [Iceweasel](https://en.wikipedia.org/wiki/Mozilla_Corporation_software_rebranded_by_the_Debian_project#Iceweasel), [Seamonkey](https://en.wikipedia.org/wiki/SeaMonkey) 69 | * `BrowserAndroid` - Android [WebView](https://developer.chrome.com/multidevice/webview/overview) (Android OS <4.4 only) 70 | * `BrowserOpera` - [Opera](https://en.wikipedia.org/wiki/Opera_(web_browser)) 71 | * `BrowserUCBrowser` - [UC Browser](https://en.wikipedia.org/wiki/UC_Browser) 72 | * `BrowserSilk` - Amazon [Silk](https://en.wikipedia.org/wiki/Amazon_Silk) 73 | * `BrowserQQ` - Tencent [QQ](https://en.wikipedia.org/wiki/Tencent_QQ) 74 | * `BrowserSpotify` - [Spotify](https://en.wikipedia.org/wiki/Spotify#Clients) desktop client 75 | * `BrowserBlackberry` - RIM [BlackBerry](https://en.wikipedia.org/wiki/BlackBerry) 76 | * `BrowserYandex` - [Yandex](https://en.wikipedia.org/wiki/Yandex_Browser) 77 | * `BrowserNintendo` - [Nintendo DS(i) Browser](https://en.wikipedia.org/wiki/Nintendo_DS_%26_DSi_Browser) 78 | * `BrowserSamsung` - [Samsung Internet](https://en.wikipedia.org/wiki/Samsung_Internet_for_Android) 79 | * `BrowserCocCoc`- [Cốc Cốc](https://en.wikipedia.org/wiki/C%E1%BB%91c_C%E1%BB%91c) 80 | * `BrowserUnknown` - Unknown 81 | 82 | #### Browser Version 83 | 84 | Browser version returns an `unint8` of the major version attribute of the User-Agent String. For example Chrome 45.0.23423 would return `45`. The intention is to support math operators with versions, such as "do XYZ for Chrome version >23". 85 | 86 | Unknown version is returned as `0`. 87 | 88 | #### Platform 89 | * `PlatformWindows` - Microsoft Windows 90 | * `PlatformMac` - Apple Macintosh 91 | * `PlatformLinux` - Linux, including Android and other OSes 92 | * `PlatformiPad` - Apple iPad 93 | * `PlatformiPhone` - Apple iPhone 94 | * `PlatformBlackberry` - RIM Blackberry 95 | * `PlatformWindowsPhone` Microsoft Windows Phone & Mobile 96 | * `PlatformKindle` - Amazon Kindle & Kindle Fire 97 | * `PlatformPlaystation` - Sony Playstation, Vita, PSP 98 | * `PlatformXbox` - Microsoft Xbox 99 | * `PlatformNintendo` - Nintendo DS, Wii, etc. 100 | * `PlatformUnknown` - Unknown 101 | 102 | #### OS Name 103 | * `OSWindows` 104 | * `OSMacOSX` - includes "macOS Sierra" 105 | * `OSiOS` 106 | * `OSAndroid` 107 | * `OSChromeOS` 108 | * `OSWebOS` 109 | * `OSLinux` 110 | * `OSPlaystation` 111 | * `OSXbox` 112 | * `OSNintendo` 113 | * `OSUnknown` 114 | 115 | #### OS Version 116 | 117 | OS X major version is always 10 for releases prior to Big Sur with consecutive minor versions indicating release releases (10 - Yosemite, 11 - El Capitain, 12 Sierra, etc). macOS Big Sur is indicated as `{11, 1, 0}`. Windows version is NT version. `Version{0, 0, 0}` indicated version is unknown or not evaluated. 118 | Versions can be compared using `Less` function: `if ver1.Less(ver2) {}` 119 | 120 | Here are some examples across the platform, os.name, and os.version: 121 | 122 | * For Windows XP (Windows NT 5.1), "`PlatformWindows`" is the platform, "`OSWindows`" is the name, and `{5, 1, 0}` the version. 123 | * For OS X 10.5.1, "`PlatformMac`" is the platform, "`OSMacOSX`" the name, and `{10, 5, 1}` the version. 124 | * For Android 5.1, "`PlatformLinux`" is the platform, "`OSAndroid`" is the name, and `{5, 1, 0}` the version. 125 | * For iOS 5.1, "`PlatformiPhone`" or "`PlatformiPad`" is the platform, "`OSiOS`" is the name, and `{5, 1, 0}` the version. 126 | 127 | ###### Windows Version Guide 128 | 129 | * Windows 10 - `{10, 0, 0}` 130 | * Windows 8.1 - `{6, 3, 0}` 131 | * Windows 8 - `{6, 2, 0}` 132 | * Windows 7 - `{6, 1, 0}` 133 | * Windows Vista - `{6, 0, 0}` 134 | * Windows XP - `{5, 1, 0}` or `{5, 2, 0}` 135 | * Windows 2000 - `{5, 0, 0}` 136 | 137 | Windows 95, 98, and ME represent 0.01% of traffic worldwide and are not available through this package at this time. 138 | 139 | #### DeviceType 140 | DeviceType is typically quite accurate, though determining between phones and tablets on Android is not always possible due to how some vendors design their UA strings. A mobile Android device without tablet indicator defaults to being classified as a phone. DeviceTV supports major brands such as Philips, Sharp, Vizio and steaming boxes such as Apple, Google, Roku, Amazon. 141 | 142 | * `DeviceComputer` 143 | * `DevicePhone` 144 | * `DeviceTablet` 145 | * `DeviceTV` 146 | * `DeviceConsole` 147 | * `DeviceWearable` 148 | * `DeviceUnknown` 149 | 150 | ## Example Combinations of Attributes 151 | * Surface RT -> `OSWindows8`, `DeviceTablet`, OSVersion >= `6` 152 | * Android Tablet -> `OSAndroid`, `DeviceTablet` 153 | * Microsoft Edge -> `BrowserIE`, BrowserVersion >= `12.0.0` 154 | 155 | ## To do 156 | 157 | * Remove compiled regexp in favor of string.Contains wherever possible (lowers mem/alloc) 158 | * Better version support on Firefox derivatives (e.g. SeaMonkey) 159 | * Potential additional browser support: 160 | * "NetFront" (1% share in India) 161 | * "Sogou Explorer" (5% share in China) 162 | * "Maxthon" (1.5% share in China) 163 | * "Nokia" 164 | * Potential additional OS support: 165 | * "Nokia" (5% share in India) 166 | * "Series 40" (5.5% share in India) 167 | * Windows 2003 Server 168 | * iOS safari browser identification based on iOS version 169 | * Add android version to browser identification 170 | * old Macs 171 | * "opera/9.64 (macintosh; ppc mac os x; u; en) presto/2.1.1" 172 | * old Windows 173 | * "mozilla/5.0 (windows nt 4.0; wow64) applewebkit/537.36 (khtml, like gecko) chrome/37.0.2049.0 safari/537.36" 174 | -------------------------------------------------------------------------------- /browser.go: -------------------------------------------------------------------------------- 1 | package uasurfer 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | // Browser struct contains the lowercase name of the browser, along 8 | // with its browser version number. Browser are grouped together without 9 | // consideration for device. For example, Chrome (Chrome/43.0) and Chrome for iOS 10 | // (CriOS/43.0) would both return as "chrome" (name) and 43.0 (version). Similarly 11 | // Internet Explorer 11 and Edge 12 would return as "ie" and "11" or "12", respectively. 12 | // type Browser struct { 13 | // Name BrowserName 14 | // Version struct { 15 | // Major int 16 | // Minor int 17 | // Patch int 18 | // } 19 | // } 20 | 21 | // Retrieve browser name from UA strings 22 | func (u *UserAgent) evalBrowserName(ua string) bool { 23 | // Blackberry goes first because it reads as MSIE & Safari 24 | if strings.Contains(ua, "blackberry") || strings.Contains(ua, "playbook") || strings.Contains(ua, "bb10") || strings.Contains(ua, "rim ") { 25 | u.Browser.Name = BrowserBlackberry 26 | return u.maybeBot() 27 | } 28 | 29 | if strings.Contains(ua, "applewebkit") { 30 | switch { 31 | case strings.Contains(ua, "googlebot"): 32 | u.Browser.Name = BrowserGoogleBot 33 | 34 | case strings.Contains(ua, "qq/") || strings.Contains(ua, "qqbrowser/"): 35 | u.Browser.Name = BrowserQQ 36 | 37 | case strings.Contains(ua, "opr/") || strings.Contains(ua, "opios/"): 38 | u.Browser.Name = BrowserOpera 39 | 40 | case strings.Contains(ua, "silk/"): 41 | u.Browser.Name = BrowserSilk 42 | 43 | case strings.Contains(ua, "edg/") || strings.Contains(ua, "edgios/") || strings.Contains(ua, "edga/")|| strings.Contains(ua, "edge/") || strings.Contains(ua, "iemobile/") || strings.Contains(ua, "msie "): 44 | u.Browser.Name = BrowserIE 45 | 46 | case strings.Contains(ua, "ucbrowser/") || strings.Contains(ua, "ucweb/"): 47 | u.Browser.Name = BrowserUCBrowser 48 | 49 | case strings.Contains(ua, "nintendobrowser/"): 50 | u.Browser.Name = BrowserNintendo 51 | 52 | case strings.Contains(ua, "samsungbrowser/"): 53 | u.Browser.Name = BrowserSamsung 54 | 55 | case strings.Contains(ua, "coc_coc_browser/"): 56 | u.Browser.Name = BrowserCocCoc 57 | 58 | case strings.Contains(ua, "yabrowser/"): 59 | u.Browser.Name = BrowserYandex 60 | 61 | // Edge, Silk and other chrome-identifying browsers must evaluate before chrome, unless we want to add more overhead 62 | case strings.Contains(ua, "chrome/") || strings.Contains(ua, "crios/") || strings.Contains(ua, "chromium/") || strings.Contains(ua, "crmo/"): 63 | u.Browser.Name = BrowserChrome 64 | 65 | case strings.Contains(ua, "android") && !strings.Contains(ua, "chrome/") && strings.Contains(ua, "version/") && !strings.Contains(ua, "like android"): 66 | // Android WebView on Android >= 4.4 is purposefully being identified as Chrome above -- https://developer.chrome.com/multidevice/webview/overview 67 | u.Browser.Name = BrowserAndroid 68 | 69 | case strings.Contains(ua, "fxios"): 70 | u.Browser.Name = BrowserFirefox 71 | 72 | case strings.Contains(ua, " spotify/"): 73 | u.Browser.Name = BrowserSpotify 74 | 75 | // AppleBot uses webkit signature as well 76 | case strings.Contains(ua, "applebot"): 77 | u.Browser.Name = BrowserAppleBot 78 | 79 | // presume it's safari unless an esoteric browser is being specified (webOSBrowser, SamsungBrowser, etc.) 80 | case strings.Contains(ua, "like gecko") && strings.Contains(ua, "mozilla/") && strings.Contains(ua, "safari/") && !strings.Contains(ua, "linux") && !strings.Contains(ua, "android") && !strings.Contains(ua, "browser/") && !strings.Contains(ua, "os/") && !strings.Contains(ua, "yabrowser/"): 81 | u.Browser.Name = BrowserSafari 82 | 83 | // if we got this far and the device is iPhone or iPad, assume safari. Some agents don't actually contain the word "safari" 84 | case strings.Contains(ua, "iphone") || strings.Contains(ua, "ipad"): 85 | u.Browser.Name = BrowserSafari 86 | 87 | // Google's search app on iPhone, leverages native Safari rather than Chrome 88 | case strings.Contains(ua, " gsa/"): 89 | u.Browser.Name = BrowserSafari 90 | 91 | default: 92 | goto notwebkit 93 | 94 | } 95 | return u.maybeBot() 96 | } 97 | 98 | notwebkit: 99 | switch { 100 | case strings.Contains(ua, "qq/") || strings.Contains(ua, "qqbrowser/"): 101 | u.Browser.Name = BrowserQQ 102 | 103 | case strings.Contains(ua, "msie") || strings.Contains(ua, "trident"): 104 | u.Browser.Name = BrowserIE 105 | 106 | case strings.Contains(ua, "gecko") && (strings.Contains(ua, "firefox") || strings.Contains(ua, "iceweasel") || strings.Contains(ua, "seamonkey") || strings.Contains(ua, "icecat")): 107 | u.Browser.Name = BrowserFirefox 108 | 109 | case strings.Contains(ua, "presto") || strings.Contains(ua, "opera"): 110 | u.Browser.Name = BrowserOpera 111 | 112 | case strings.Contains(ua, "ucbrowser"): 113 | u.Browser.Name = BrowserUCBrowser 114 | 115 | case strings.Contains(ua, "applebot"): 116 | u.Browser.Name = BrowserAppleBot 117 | 118 | case strings.Contains(ua, "baiduspider"): 119 | u.Browser.Name = BrowserBaiduBot 120 | 121 | case strings.Contains(ua, "adidxbot") || strings.Contains(ua, "bingbot") || strings.Contains(ua, "bingpreview"): 122 | u.Browser.Name = BrowserBingBot 123 | 124 | case strings.Contains(ua, "duckduckbot"): 125 | u.Browser.Name = BrowserDuckDuckGoBot 126 | 127 | case strings.Contains(ua, "facebot") || strings.Contains(ua, "facebookexternalhit"): 128 | u.Browser.Name = BrowserFacebookBot 129 | 130 | case strings.Contains(ua, "googlebot"): 131 | u.Browser.Name = BrowserGoogleBot 132 | 133 | case strings.Contains(ua, "linkedinbot"): 134 | u.Browser.Name = BrowserLinkedInBot 135 | 136 | case strings.Contains(ua, "msnbot"): 137 | u.Browser.Name = BrowserMsnBot 138 | 139 | case strings.Contains(ua, "pingdom.com_bot"): 140 | u.Browser.Name = BrowserPingdomBot 141 | 142 | case strings.Contains(ua, "twitterbot"): 143 | u.Browser.Name = BrowserTwitterBot 144 | 145 | case strings.Contains(ua, "yandex") || strings.Contains(ua, "yadirectfetcher"): 146 | u.Browser.Name = BrowserYandexBot 147 | 148 | case strings.Contains(ua, "yahoo"): 149 | u.Browser.Name = BrowserYahooBot 150 | 151 | case strings.Contains(ua, "coccocbot"): 152 | u.Browser.Name = BrowserCocCocBot 153 | 154 | case strings.Contains(ua, "phantomjs"): 155 | u.Browser.Name = BrowserBot 156 | 157 | default: 158 | u.Browser.Name = BrowserUnknown 159 | 160 | } 161 | 162 | return u.maybeBot() 163 | } 164 | 165 | // Retrieve browser version 166 | // Methods used in order: 167 | // 1st: look for generic version/# 168 | // 2nd: look for browser-specific instructions (e.g. chrome/34) 169 | // 3rd: infer from OS (iOS only) 170 | func (u *UserAgent) evalBrowserVersion(ua string) { 171 | // if there is a 'version/#' attribute with numeric version, use it -- except for Chrome since Android vendors sometimes hijack version/# 172 | if u.Browser.Name != BrowserChrome && u.Browser.Version.findVersionNumber(ua, "version/") { 173 | return 174 | } 175 | 176 | switch u.Browser.Name { 177 | case BrowserChrome: 178 | // match both chrome and crios 179 | _ = u.Browser.Version.findVersionNumber(ua, "chrome/") || u.Browser.Version.findVersionNumber(ua, "crios/") || u.Browser.Version.findVersionNumber(ua, "crmo/") 180 | case BrowserYandex: 181 | _ = u.Browser.Version.findVersionNumber(ua, "yabrowser/") 182 | case BrowserQQ: 183 | if u.Browser.Version.findVersionNumber(ua, "qq/") { 184 | return 185 | } 186 | _ = u.Browser.Version.findVersionNumber(ua, "qqbrowser/") 187 | case BrowserIE: 188 | if u.Browser.Version.findVersionNumber(ua, "msie ") || u.Browser.Version.findVersionNumber(ua, "edge/") || u.Browser.Version.findVersionNumber(ua, "edgios/") || u.Browser.Version.findVersionNumber(ua, "edga/") || u.Browser.Version.findVersionNumber(ua, "edg/") { 189 | return 190 | } 191 | 192 | // get MSIE version from trident version https://en.wikipedia.org/wiki/Trident_(layout_engine) 193 | if u.Browser.Version.findVersionNumber(ua, "trident/") { 194 | // convert trident versions 3-7 to MSIE version 195 | if (u.Browser.Version.Major >= 3) && (u.Browser.Version.Major <= 7) { 196 | u.Browser.Version.Major += 4 197 | } 198 | } 199 | 200 | case BrowserFirefox: 201 | _ = u.Browser.Version.findVersionNumber(ua, "firefox/") || u.Browser.Version.findVersionNumber(ua, "fxios/") 202 | 203 | case BrowserSafari: // executes typically if we're on iOS and not using a familiar browser 204 | u.Browser.Version = u.OS.Version 205 | // early Safari used a version number +1 to OS version 206 | if (u.Browser.Version.Major <= 3) && (u.Browser.Version.Major >= 1) { 207 | u.Browser.Version.Major++ 208 | } 209 | 210 | case BrowserUCBrowser: 211 | _ = u.Browser.Version.findVersionNumber(ua, "ucbrowser/") 212 | 213 | case BrowserOpera: 214 | _ = u.Browser.Version.findVersionNumber(ua, "opr/") || u.Browser.Version.findVersionNumber(ua, "opios/") || u.Browser.Version.findVersionNumber(ua, "opera/") 215 | 216 | case BrowserSilk: 217 | _ = u.Browser.Version.findVersionNumber(ua, "silk/") 218 | 219 | case BrowserSpotify: 220 | _ = u.Browser.Version.findVersionNumber(ua, "spotify/") 221 | 222 | case BrowserCocCoc: 223 | _ = u.Browser.Version.findVersionNumber(ua, "coc_coc_browser/") 224 | } 225 | } 226 | -------------------------------------------------------------------------------- /cmd/uastats/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "os" 8 | "sort" 9 | "text/tabwriter" 10 | 11 | "github.com/avct/uasurfer" 12 | ) 13 | 14 | func main() { 15 | var count int 16 | ua := &uasurfer.UserAgent{} 17 | stats := stats{ 18 | BrowserNames: make(map[uasurfer.BrowserName]int), 19 | OSNames: make(map[uasurfer.OSName]int), 20 | DeviceTypes: make(map[uasurfer.DeviceType]int), 21 | } 22 | 23 | scanner := bufio.NewScanner(os.Stdin) 24 | for scanner.Scan() { 25 | count++ 26 | ua.Reset() 27 | uasurfer.ParseUserAgent(scanner.Text(), ua) 28 | stats.BrowserNames[ua.Browser.Name]++ 29 | stats.OSNames[ua.OS.Name]++ 30 | stats.DeviceTypes[ua.DeviceType]++ 31 | } 32 | if err := scanner.Err(); err != nil { 33 | fmt.Fprintln(os.Stderr, "reading standard input:", err) 34 | } 35 | fmt.Printf("Read %d useragents\n", count) 36 | fmt.Println() 37 | stats.Summary(count, os.Stdout) 38 | } 39 | 40 | type stats struct { 41 | OSNames map[uasurfer.OSName]int 42 | BrowserNames map[uasurfer.BrowserName]int 43 | DeviceTypes map[uasurfer.DeviceType]int 44 | } 45 | 46 | func (s *stats) Summary(total int, dest io.Writer) { 47 | browserCounts := make([]stringCount, 0, len(s.BrowserNames)) 48 | for k, v := range s.BrowserNames { 49 | browserCounts = append(browserCounts, stringCount{name: k.String(), count: v}) 50 | } 51 | sort.Slice(browserCounts, func(i, j int) bool { return browserCounts[j].count < browserCounts[i].count }) // by count reversed 52 | fmt.Fprintf(dest, "Browsers\n") 53 | err := writeTable(browserCounts, total, dest) 54 | if err != nil { 55 | fmt.Fprintf(os.Stderr, "writing summary: %v", err) 56 | return 57 | } 58 | 59 | fmt.Fprintln(dest) 60 | osCounts := make([]stringCount, 0, len(s.OSNames)) 61 | for k, v := range s.OSNames { 62 | osCounts = append(osCounts, stringCount{name: k.String(), count: v}) 63 | } 64 | sort.Slice(osCounts, func(i, j int) bool { return osCounts[j].count < osCounts[i].count }) // by count reversed 65 | fmt.Fprintf(dest, "Operating Systems\n") 66 | err = writeTable(osCounts, total, dest) 67 | if err != nil { 68 | fmt.Fprintf(os.Stderr, "writing summary: %v", err) 69 | return 70 | } 71 | 72 | fmt.Fprintln(dest) 73 | deviceCounts := make([]stringCount, 0, len(s.DeviceTypes)) 74 | for k, v := range s.DeviceTypes { 75 | deviceCounts = append(deviceCounts, stringCount{name: k.String(), count: v}) 76 | } 77 | sort.Slice(deviceCounts, func(i, j int) bool { return deviceCounts[j].count < deviceCounts[i].count }) // by count reversed 78 | fmt.Fprintf(dest, "Device Types\n") 79 | err = writeTable(deviceCounts, total, dest) 80 | if err != nil { 81 | fmt.Fprintf(os.Stderr, "writing summary: %v", err) 82 | return 83 | } 84 | } 85 | 86 | func writeTable(counts []stringCount, total int, dest io.Writer) error { 87 | tw := tabwriter.NewWriter(dest, 10, 1, 2, ' ', 0) 88 | for i := range counts { 89 | fmt.Fprintf(tw, "%s\t%d (%.2f%%)\n", counts[i].name, counts[i].count, percent(counts[i].count, total)) 90 | } 91 | return tw.Flush() 92 | } 93 | 94 | type stringCount struct { 95 | name string 96 | count int 97 | } 98 | 99 | func percent(num, den int) float64 { 100 | return float64(num) / float64(den) * 100.0 101 | } 102 | -------------------------------------------------------------------------------- /const_string.go: -------------------------------------------------------------------------------- 1 | // Code generated by "stringer -type=DeviceType,BrowserName,OSName,Platform -output=const_string.go"; DO NOT EDIT. 2 | 3 | package uasurfer 4 | 5 | import "strconv" 6 | 7 | const _DeviceType_name = "DeviceUnknownDeviceComputerDeviceTabletDevicePhoneDeviceConsoleDeviceWearableDeviceTV" 8 | 9 | var _DeviceType_index = [...]uint8{0, 13, 27, 39, 50, 63, 77, 85} 10 | 11 | func (i DeviceType) String() string { 12 | if i < 0 || i >= DeviceType(len(_DeviceType_index)-1) { 13 | return "DeviceType(" + strconv.FormatInt(int64(i), 10) + ")" 14 | } 15 | return _DeviceType_name[_DeviceType_index[i]:_DeviceType_index[i+1]] 16 | } 17 | 18 | const _BrowserName_name = "BrowserUnknownBrowserChromeBrowserIEBrowserSafariBrowserFirefoxBrowserAndroidBrowserOperaBrowserBlackberryBrowserUCBrowserBrowserSilkBrowserNokiaBrowserNetFrontBrowserQQBrowserMaxthonBrowserSogouExplorerBrowserSpotifyBrowserNintendoBrowserSamsungBrowserYandexBrowserCocCocBrowserBotBrowserAppleBotBrowserBaiduBotBrowserBingBotBrowserDuckDuckGoBotBrowserFacebookBotBrowserGoogleBotBrowserLinkedInBotBrowserMsnBotBrowserPingdomBotBrowserTwitterBotBrowserYandexBotBrowserCocCocBotBrowserYahooBot" 19 | 20 | var _BrowserName_index = [...]uint16{0, 14, 27, 36, 49, 63, 77, 89, 106, 122, 133, 145, 160, 169, 183, 203, 217, 232, 246, 259, 272, 282, 297, 312, 326, 346, 364, 380, 398, 411, 428, 445, 461, 477, 492} 21 | 22 | func (i BrowserName) String() string { 23 | if i < 0 || i >= BrowserName(len(_BrowserName_index)-1) { 24 | return "BrowserName(" + strconv.FormatInt(int64(i), 10) + ")" 25 | } 26 | return _BrowserName_name[_BrowserName_index[i]:_BrowserName_index[i+1]] 27 | } 28 | 29 | const _OSName_name = "OSUnknownOSWindowsPhoneOSWindowsOSMacOSXOSiOSOSAndroidOSBlackberryOSChromeOSOSKindleOSWebOSOSLinuxOSPlaystationOSXboxOSNintendoOSBot" 30 | 31 | var _OSName_index = [...]uint8{0, 9, 23, 32, 40, 45, 54, 66, 76, 84, 91, 98, 111, 117, 127, 132} 32 | 33 | func (i OSName) String() string { 34 | if i < 0 || i >= OSName(len(_OSName_index)-1) { 35 | return "OSName(" + strconv.FormatInt(int64(i), 10) + ")" 36 | } 37 | return _OSName_name[_OSName_index[i]:_OSName_index[i+1]] 38 | } 39 | 40 | const _Platform_name = "PlatformUnknownPlatformWindowsPlatformMacPlatformLinuxPlatformiPadPlatformiPhonePlatformiPodPlatformBlackberryPlatformWindowsPhonePlatformPlaystationPlatformXboxPlatformNintendoPlatformBot" 41 | 42 | var _Platform_index = [...]uint8{0, 15, 30, 41, 54, 66, 80, 92, 110, 130, 149, 161, 177, 188} 43 | 44 | func (i Platform) String() string { 45 | if i < 0 || i >= Platform(len(_Platform_index)-1) { 46 | return "Platform(" + strconv.FormatInt(int64(i), 10) + ")" 47 | } 48 | return _Platform_name[_Platform_index[i]:_Platform_index[i+1]] 49 | } 50 | -------------------------------------------------------------------------------- /device.go: -------------------------------------------------------------------------------- 1 | package uasurfer 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | func (u *UserAgent) evalDevice(ua string) { 8 | switch { 9 | 10 | case u.OS.Platform == PlatformWindows || u.OS.Platform == PlatformMac || u.OS.Name == OSChromeOS: 11 | if strings.Contains(ua, "mobile") || strings.Contains(ua, "touch") { 12 | u.DeviceType = DeviceTablet // windows rt, linux haxor tablets 13 | return 14 | } 15 | u.DeviceType = DeviceComputer 16 | 17 | // long list of smarttv and tv dongle identifiers - above "phone" and "tablet" check to prevent TVs from being detected as phones/tablets 18 | case strings.Contains(ua, "tv") || strings.Contains(ua, "crkey") || strings.Contains(ua, "googletv") || strings.Contains(ua, "aftb") || strings.Contains(ua, "aftt") || strings.Contains(ua, "aftm") || 19 | strings.Contains(ua, "adt-") || strings.Contains(ua, "roku") || strings.Contains(ua, "viera") || strings.Contains(ua, "aquos") || strings.Contains(ua, "dtv") || 20 | strings.Contains(ua, "appletv") || strings.Contains(ua, "smarttv") || strings.Contains(ua, "tuner") || strings.Contains(ua, "smart-tv") || strings.Contains(ua, "hbbtv") || 21 | strings.Contains(ua, "netcast") || strings.Contains(ua, "vizio") || strings.Contains(ua, "stb") || strings.Contains(ua, "swisscom-ip") || strings.Contains(ua, "youview") || 22 | strings.Contains(ua, "aftkrt") || strings.Contains(ua, "aftsss") || strings.Contains(ua, "aftss") || strings.Contains(ua, "aftka") || strings.Contains(ua, "aftr") || 23 | strings.Contains(ua, "aftgazl") || strings.Contains(ua, "aftanna") || strings.Contains(ua, "aftkauk") || 24 | strings.Contains(ua, "bravia") || strings.Contains(ua, "mibox") || strings.Contains(ua, "chromecast") || strings.Contains(ua, "ott-g1") || strings.Contains(ua, "ottera") || 25 | strings.Contains(ua, "tpm191e") || strings.Contains(ua, "nokia streaming box") || 26 | strings.Contains(ua, "tv box") || (strings.Contains(ua, "mbox") && !strings.Contains(ua, "xbox")): 27 | u.DeviceType = DeviceTV 28 | 29 | case u.OS.Platform == PlatformiPad || u.OS.Platform == PlatformiPod || strings.Contains(ua, "tablet") || strings.Contains(ua, "kindle/") || strings.Contains(ua, "playbook"): 30 | u.DeviceType = DeviceTablet 31 | 32 | case u.OS.Platform == PlatformiPhone || u.OS.Platform == PlatformBlackberry || strings.Contains(ua, "phone"): 33 | u.DeviceType = DevicePhone 34 | 35 | case u.OS.Name == OSAndroid: 36 | // android phones report as "mobile", android tablets should not but often do -- http://android-developers.blogspot.com/2010/12/android-browser-user-agent-issues.html 37 | if strings.Contains(ua, "mobile") { 38 | u.DeviceType = DevicePhone 39 | return 40 | } 41 | 42 | if strings.Contains(ua, "tablet") || strings.Contains(ua, "nexus 7") || strings.Contains(ua, "nexus 9") || strings.Contains(ua, "nexus 10") || strings.Contains(ua, "xoom") || 43 | strings.Contains(ua, "sm-t") || strings.Contains(ua, "; kf") || strings.Contains(ua, "; t1") || strings.Contains(ua, "lenovo tab") { 44 | u.DeviceType = DeviceTablet 45 | return 46 | } 47 | 48 | u.DeviceType = DevicePhone // default to phone 49 | 50 | case u.OS.Platform == PlatformPlaystation || u.OS.Platform == PlatformXbox || u.OS.Platform == PlatformNintendo: 51 | u.DeviceType = DeviceConsole 52 | 53 | case strings.Contains(ua, "glass") || strings.Contains(ua, "watch") || strings.Contains(ua, "sm-v"): 54 | u.DeviceType = DeviceWearable 55 | 56 | // specifically above "mobile" string check as Kindle Fire tablets report as "mobile" 57 | case u.Browser.Name == BrowserSilk || u.OS.Name == OSKindle && !strings.Contains(ua, "sd4930ur"): 58 | u.DeviceType = DeviceTablet 59 | 60 | case strings.Contains(ua, "mobile") || strings.Contains(ua, "touch") || strings.Contains(ua, " mobi") || strings.Contains(ua, "webos"): //anything "mobile"/"touch" that didn't get captured as tablet, console or wearable is presumed a phone 61 | u.DeviceType = DevicePhone 62 | 63 | case u.OS.Name == OSLinux: // linux goes last since it's in so many other device types (tvs, wearables, android-based stuff) 64 | u.DeviceType = DeviceComputer 65 | 66 | default: 67 | u.DeviceType = DeviceUnknown 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/avct/uasurfer 2 | 3 | go 1.19 4 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avct/uasurfer/f2613aa2d406edc3b1135286b913aa277caf86c5/go.sum -------------------------------------------------------------------------------- /system.go: -------------------------------------------------------------------------------- 1 | package uasurfer 2 | 3 | import ( 4 | "regexp" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | var ( 10 | amazonFireFingerprint = regexp.MustCompile("\\s(k[a-z]{3,5}|sd\\d{4}ur)\\s") //tablet or phone 11 | ) 12 | 13 | func (u *UserAgent) evalOS(ua string) bool { 14 | s := strings.IndexRune(ua, '(') 15 | e := strings.IndexRune(ua, ')') 16 | if s > e { 17 | s = 0 18 | e = len(ua) 19 | } 20 | if e == -1 { 21 | e = len(ua) 22 | } 23 | 24 | agentPlatform := ua[s+1 : e] 25 | specsEnd := strings.Index(agentPlatform, ";") 26 | var specs string 27 | if specsEnd != -1 { 28 | specs = agentPlatform[:specsEnd] 29 | } else { 30 | specs = agentPlatform 31 | } 32 | 33 | //strict OS & version identification 34 | switch { 35 | case specs == "android": 36 | u.evalLinux(ua, agentPlatform) 37 | 38 | case specs == "bb10" || specs == "playbook": 39 | u.OS.Platform = PlatformBlackberry 40 | u.OS.Name = OSBlackberry 41 | 42 | case specs == "x11" || specs == "linux": 43 | u.evalLinux(ua, agentPlatform) 44 | 45 | case strings.HasPrefix(specs, "ipad") || strings.HasPrefix(specs, "iphone") || strings.HasPrefix(specs, "ipod touch") || strings.HasPrefix(specs, "ipod"): 46 | u.evaliOS(specs, agentPlatform) 47 | 48 | case specs == "macintosh": 49 | u.evalMacintosh(ua) 50 | 51 | default: 52 | switch { 53 | // Blackberry 54 | case strings.Contains(ua, "blackberry") || strings.Contains(ua, "playbook"): 55 | u.OS.Platform = PlatformBlackberry 56 | u.OS.Name = OSBlackberry 57 | 58 | // Windows Phone 59 | case strings.Contains(agentPlatform, "windows phone "): 60 | u.evalWindowsPhone(agentPlatform) 61 | 62 | // Windows, Xbox 63 | case strings.Contains(ua, "windows ") || strings.Contains(ua, "microsoft-cryptoapi"): 64 | u.evalWindows(ua) 65 | 66 | // Kindle 67 | case strings.Contains(ua, "kindle/") || amazonFireFingerprint.MatchString(agentPlatform): 68 | u.OS.Platform = PlatformLinux 69 | u.OS.Name = OSKindle 70 | 71 | // Linux (broader attempt) 72 | case strings.Contains(ua, "linux"): 73 | u.evalLinux(ua, agentPlatform) 74 | 75 | // WebOS (non-linux flagged) 76 | case strings.Contains(ua, "webos") || strings.Contains(ua, "hpwos"): 77 | u.OS.Platform = PlatformLinux 78 | u.OS.Name = OSWebOS 79 | 80 | // Nintendo 81 | case strings.Contains(ua, "nintendo"): 82 | u.OS.Platform = PlatformNintendo 83 | u.OS.Name = OSNintendo 84 | 85 | // Playstation 86 | case strings.Contains(ua, "playstation") || strings.Contains(ua, "vita") || strings.Contains(ua, "psp"): 87 | u.OS.Platform = PlatformPlaystation 88 | u.OS.Name = OSPlaystation 89 | 90 | // Android 91 | case strings.Contains(ua, "android"): 92 | u.evalLinux(ua, agentPlatform) 93 | 94 | // Apple CFNetwork 95 | case strings.Contains(ua, "cfnetwork") && strings.Contains(ua, "darwin"): 96 | u.evalMacintosh(ua) 97 | 98 | default: 99 | u.OS.Platform = PlatformUnknown 100 | u.OS.Name = OSUnknown 101 | } 102 | } 103 | 104 | return u.maybeBot() 105 | } 106 | 107 | // maybeBot checks if the UserAgent is a bot and sets 108 | // all bot related fields if it is 109 | func (u *UserAgent) maybeBot() bool { 110 | if u.IsBot() { 111 | u.OS.Platform = PlatformBot 112 | u.OS.Name = OSBot 113 | u.DeviceType = DeviceComputer 114 | return true 115 | } 116 | return false 117 | } 118 | 119 | // evalLinux returns the `Platform`, `OSName` and Version of UAs with 120 | // 'linux' listed as their platform. 121 | func (u *UserAgent) evalLinux(ua string, agentPlatform string) { 122 | 123 | switch { 124 | // Kindle Fire 125 | case strings.Contains(ua, "kindle") || amazonFireFingerprint.MatchString(agentPlatform): 126 | // get the version of Android if available, though we don't call this OSAndroid 127 | u.OS.Platform = PlatformLinux 128 | u.OS.Name = OSKindle 129 | u.OS.Version.findVersionNumber(agentPlatform, "android ") 130 | 131 | // Android, Kindle Fire 132 | case strings.Contains(ua, "android") || strings.Contains(ua, "googletv"): 133 | // Android 134 | u.OS.Platform = PlatformLinux 135 | u.OS.Name = OSAndroid 136 | u.OS.Version.findVersionNumber(agentPlatform, "android ") 137 | 138 | // ChromeOS 139 | case strings.Contains(ua, "cros"): 140 | u.OS.Platform = PlatformLinux 141 | u.OS.Name = OSChromeOS 142 | 143 | // WebOS 144 | case strings.Contains(ua, "webos") || strings.Contains(ua, "hpwos"): 145 | u.OS.Platform = PlatformLinux 146 | u.OS.Name = OSWebOS 147 | 148 | // Linux, "Linux-like" 149 | case strings.Contains(ua, "x11") || strings.Contains(ua, "bsd") || strings.Contains(ua, "suse") || strings.Contains(ua, "debian") || strings.Contains(ua, "ubuntu"): 150 | u.OS.Platform = PlatformLinux 151 | u.OS.Name = OSLinux 152 | 153 | default: 154 | u.OS.Platform = PlatformLinux 155 | u.OS.Name = OSLinux 156 | } 157 | } 158 | 159 | // evaliOS returns the `Platform`, `OSName` and Version of UAs with 160 | // 'ipad' or 'iphone' listed as their platform. 161 | func (u *UserAgent) evaliOS(uaPlatform string, agentPlatform string) { 162 | 163 | switch { 164 | // iPhone 165 | case strings.HasPrefix(uaPlatform, "iphone"): 166 | u.OS.Platform = PlatformiPhone 167 | u.OS.Name = OSiOS 168 | u.OS.getiOSVersion(agentPlatform) 169 | 170 | // iPad 171 | case strings.HasPrefix(uaPlatform, "ipad"): 172 | u.OS.Platform = PlatformiPad 173 | u.OS.Name = OSiOS 174 | u.OS.getiOSVersion(agentPlatform) 175 | 176 | // iPod 177 | case strings.HasPrefix(uaPlatform, "ipod touch") || strings.HasPrefix(uaPlatform, "ipod"): 178 | u.OS.Platform = PlatformiPod 179 | u.OS.Name = OSiOS 180 | u.OS.getiOSVersion(agentPlatform) 181 | 182 | default: 183 | u.OS.Platform = PlatformiPad 184 | u.OS.Name = OSUnknown 185 | } 186 | } 187 | 188 | func (u *UserAgent) evalWindowsPhone(agentPlatform string) { 189 | u.OS.Platform = PlatformWindowsPhone 190 | 191 | if u.OS.Version.findVersionNumber(agentPlatform, "windows phone os ") || u.OS.Version.findVersionNumber(agentPlatform, "windows phone ") { 192 | u.OS.Name = OSWindowsPhone 193 | } else { 194 | u.OS.Name = OSUnknown 195 | } 196 | } 197 | 198 | func (u *UserAgent) evalWindows(ua string) { 199 | 200 | switch { 201 | //Xbox -- it reads just like Windows 202 | case strings.Contains(ua, "xbox"): 203 | u.OS.Platform = PlatformXbox 204 | u.OS.Name = OSXbox 205 | if !u.OS.Version.findVersionNumber(ua, "windows nt ") { 206 | u.OS.Version.Major = 6 207 | u.OS.Version.Minor = 0 208 | u.OS.Version.Patch = 0 209 | } 210 | 211 | // No windows version 212 | case !strings.Contains(ua, "windows "): 213 | u.OS.Platform = PlatformWindows 214 | u.OS.Name = OSUnknown 215 | 216 | case strings.Contains(ua, "windows nt ") && u.OS.Version.findVersionNumber(ua, "windows nt "): 217 | u.OS.Platform = PlatformWindows 218 | u.OS.Name = OSWindows 219 | 220 | case strings.Contains(ua, "windows xp"): 221 | u.OS.Platform = PlatformWindows 222 | u.OS.Name = OSWindows 223 | u.OS.Version.Major = 5 224 | u.OS.Version.Minor = 1 225 | u.OS.Version.Patch = 0 226 | 227 | default: 228 | u.OS.Platform = PlatformWindows 229 | u.OS.Name = OSUnknown 230 | 231 | } 232 | } 233 | 234 | func (u *UserAgent) evalMacintosh(uaPlatformGroup string) { 235 | u.OS.Platform = PlatformMac 236 | if i := strings.Index(uaPlatformGroup, "os x "); i != -1 { 237 | u.OS.Name = OSMacOSX 238 | u.OS.Version.parse(uaPlatformGroup[i+5:]) 239 | 240 | return 241 | } 242 | u.OS.Name = OSUnknown 243 | } 244 | 245 | func (v *Version) findVersionNumber(s string, m string) bool { 246 | if ind := strings.Index(s, m); ind != -1 { 247 | return v.parse(s[ind+len(m):]) 248 | } 249 | return false 250 | } 251 | 252 | // getiOSVersion accepts the platform portion of a UA string and returns 253 | // a Version. 254 | func (o *OS) getiOSVersion(uaPlatformGroup string) { 255 | if i := strings.Index(uaPlatformGroup, "cpu iphone os "); i != -1 { 256 | o.Version.parse(uaPlatformGroup[i+14:]) 257 | return 258 | } 259 | 260 | if i := strings.Index(uaPlatformGroup, "cpu os "); i != -1 { 261 | o.Version.parse(uaPlatformGroup[i+7:]) 262 | return 263 | } 264 | 265 | o.Version.parse(uaPlatformGroup) 266 | } 267 | 268 | // strToInt simply accepts a string and returns a `int`, 269 | // with '0' being default. 270 | func strToInt(str string) int { 271 | i, _ := strconv.Atoi(str) 272 | return i 273 | } 274 | 275 | // strToVer accepts a string and returns a Version, 276 | // with {0, 0, 0} being default. 277 | func (v *Version) parse(str string) bool { 278 | if len(str) == 0 || str[0] < '0' || str[0] > '9' { 279 | return false 280 | } 281 | for i := 0; i < 3; i++ { 282 | empty := true 283 | val := 0 284 | l := len(str) - 1 285 | 286 | for k, c := range str { 287 | if c >= '0' && c <= '9' { 288 | if empty { 289 | val = int(c) - 48 290 | empty = false 291 | if k == l { 292 | str = str[:0] 293 | } 294 | continue 295 | } 296 | 297 | if val == 0 { 298 | if c == '0' { 299 | if k == l { 300 | str = str[:0] 301 | } 302 | continue 303 | } 304 | str = str[k:] 305 | break 306 | } 307 | 308 | val = 10*val + int(c) - 48 309 | if k == l { 310 | str = str[:0] 311 | } 312 | continue 313 | } 314 | str = str[k+1:] 315 | break 316 | } 317 | 318 | switch i { 319 | case 0: 320 | v.Major = val 321 | 322 | case 1: 323 | v.Minor = val 324 | 325 | case 2: 326 | v.Patch = val 327 | } 328 | } 329 | return true 330 | } 331 | -------------------------------------------------------------------------------- /uasurfer.go: -------------------------------------------------------------------------------- 1 | // Package uasurfer provides fast and reliable abstraction 2 | // of HTTP User-Agent strings. The philosophy is to identify 3 | // technologies that holds >1% market share, and to avoid 4 | // expending resources and accuracy on guessing at esoteric UA 5 | // strings. 6 | package uasurfer 7 | 8 | import "strings" 9 | 10 | //go:generate stringer -type=DeviceType,BrowserName,OSName,Platform -output=const_string.go 11 | 12 | // DeviceType (int) returns a constant. 13 | type DeviceType int 14 | 15 | // A complete list of supported devices in the 16 | // form of constants. 17 | const ( 18 | DeviceUnknown DeviceType = iota 19 | DeviceComputer 20 | DeviceTablet 21 | DevicePhone 22 | DeviceConsole 23 | DeviceWearable 24 | DeviceTV 25 | ) 26 | 27 | // StringTrimPrefix is like String() but trims the "Device" prefix 28 | func (d DeviceType) StringTrimPrefix() string { 29 | return strings.TrimPrefix(d.String(), "Device") 30 | } 31 | 32 | // BrowserName (int) returns a constant. 33 | type BrowserName int 34 | 35 | // A complete list of supported web browsers in the 36 | // form of constants. 37 | const ( 38 | BrowserUnknown BrowserName = iota 39 | BrowserChrome 40 | BrowserIE 41 | BrowserSafari 42 | BrowserFirefox 43 | BrowserAndroid 44 | BrowserOpera 45 | BrowserBlackberry 46 | BrowserUCBrowser 47 | BrowserSilk 48 | BrowserNokia 49 | BrowserNetFront 50 | BrowserQQ 51 | BrowserMaxthon 52 | BrowserSogouExplorer 53 | BrowserSpotify 54 | BrowserNintendo 55 | BrowserSamsung 56 | BrowserYandex 57 | BrowserCocCoc 58 | BrowserBot // Bot list begins here 59 | BrowserAppleBot 60 | BrowserBaiduBot 61 | BrowserBingBot 62 | BrowserDuckDuckGoBot 63 | BrowserFacebookBot 64 | BrowserGoogleBot 65 | BrowserLinkedInBot 66 | BrowserMsnBot 67 | BrowserPingdomBot 68 | BrowserTwitterBot 69 | BrowserYandexBot 70 | BrowserCocCocBot 71 | BrowserYahooBot // Bot list ends here 72 | ) 73 | 74 | // StringTrimPrefix is like String() but trims the "Browser" prefix 75 | func (b BrowserName) StringTrimPrefix() string { 76 | return strings.TrimPrefix(b.String(), "Browser") 77 | } 78 | 79 | // OSName (int) returns a constant. 80 | type OSName int 81 | 82 | // A complete list of supported OSes in the 83 | // form of constants. For handling particular versions 84 | // of operating systems (e.g. Windows 2000), see 85 | // the README.md file. 86 | const ( 87 | OSUnknown OSName = iota 88 | OSWindowsPhone 89 | OSWindows 90 | OSMacOSX 91 | OSiOS 92 | OSAndroid 93 | OSBlackberry 94 | OSChromeOS 95 | OSKindle 96 | OSWebOS 97 | OSLinux 98 | OSPlaystation 99 | OSXbox 100 | OSNintendo 101 | OSBot 102 | ) 103 | 104 | // StringTrimPrefix is like String() but trims the "OS" prefix 105 | func (o OSName) StringTrimPrefix() string { 106 | return strings.TrimPrefix(o.String(), "OS") 107 | } 108 | 109 | // Platform (int) returns a constant. 110 | type Platform int 111 | 112 | // A complete list of supported platforms in the 113 | // form of constants. Many OSes report their 114 | // true platform, such as Android OS being Linux 115 | // platform. 116 | const ( 117 | PlatformUnknown Platform = iota 118 | PlatformWindows 119 | PlatformMac 120 | PlatformLinux 121 | PlatformiPad 122 | PlatformiPhone 123 | PlatformiPod 124 | PlatformBlackberry 125 | PlatformWindowsPhone 126 | PlatformPlaystation 127 | PlatformXbox 128 | PlatformNintendo 129 | PlatformBot 130 | ) 131 | 132 | // StringTrimPrefix is like String() but trims the "Platform" prefix 133 | func (p Platform) StringTrimPrefix() string { 134 | return strings.TrimPrefix(p.String(), "Platform") 135 | } 136 | 137 | type Version struct { 138 | Major int 139 | Minor int 140 | Patch int 141 | } 142 | 143 | func (v Version) Less(c Version) bool { 144 | if v.Major < c.Major { 145 | return true 146 | } 147 | 148 | if v.Major > c.Major { 149 | return false 150 | } 151 | 152 | if v.Minor < c.Minor { 153 | return true 154 | } 155 | 156 | if v.Minor > c.Minor { 157 | return false 158 | } 159 | 160 | return v.Patch < c.Patch 161 | } 162 | 163 | type UserAgent struct { 164 | Browser Browser 165 | OS OS 166 | DeviceType DeviceType 167 | } 168 | 169 | type Browser struct { 170 | Name BrowserName 171 | Version Version 172 | } 173 | 174 | type OS struct { 175 | Platform Platform 176 | Name OSName 177 | Version Version 178 | } 179 | 180 | // Reset resets the UserAgent to it's zero value 181 | func (ua *UserAgent) Reset() { 182 | ua.Browser = Browser{} 183 | ua.OS = OS{} 184 | ua.DeviceType = DeviceUnknown 185 | } 186 | 187 | // IsBot returns true if the UserAgent represent a bot 188 | func (ua *UserAgent) IsBot() bool { 189 | if ua.Browser.Name >= BrowserBot && ua.Browser.Name <= BrowserYahooBot { 190 | return true 191 | } 192 | if ua.OS.Name == OSBot { 193 | return true 194 | } 195 | if ua.OS.Platform == PlatformBot { 196 | return true 197 | } 198 | return false 199 | } 200 | 201 | // Parse accepts a raw user agent (string) and returns the UserAgent. 202 | func Parse(ua string) *UserAgent { 203 | dest := new(UserAgent) 204 | parse(ua, dest) 205 | return dest 206 | } 207 | 208 | // ParseUserAgent is the same as Parse, but populates the supplied UserAgent. 209 | // It is the caller's responsibility to call Reset() on the UserAgent before 210 | // passing it to this function. 211 | func ParseUserAgent(ua string, dest *UserAgent) { 212 | parse(ua, dest) 213 | } 214 | 215 | func parse(ua string, dest *UserAgent) { 216 | ua = normalise(ua) 217 | switch { 218 | case len(ua) == 0: 219 | dest.OS.Platform = PlatformUnknown 220 | dest.OS.Name = OSUnknown 221 | dest.Browser.Name = BrowserUnknown 222 | dest.DeviceType = DeviceUnknown 223 | 224 | // stop on on first case returning true 225 | case dest.evalOS(ua): 226 | case dest.evalBrowserName(ua): 227 | default: 228 | dest.evalBrowserVersion(ua) 229 | dest.evalDevice(ua) 230 | } 231 | } 232 | 233 | // normalise normalises the user supplied agent string so that 234 | // we can more easily parse it. 235 | func normalise(ua string) string { 236 | if len(ua) <= 1024 { 237 | var buf [1024]byte 238 | ascii := copyLower(buf[:len(ua)], ua) 239 | if !ascii { 240 | // Fall back for non ascii characters 241 | return strings.ToLower(ua) 242 | } 243 | return string(buf[:len(ua)]) 244 | } 245 | // Fallback for unusually long strings 246 | return strings.ToLower(ua) 247 | } 248 | 249 | // copyLower copies a lowercase version of s to b. It assumes s contains only single byte characters 250 | // and will panic if b is nil or is not long enough to contain all the bytes from s. 251 | // It returns early with false if any characters were non ascii. 252 | func copyLower(b []byte, s string) bool { 253 | for j := 0; j < len(s); j++ { 254 | c := s[j] 255 | if c > 127 { 256 | return false 257 | } 258 | 259 | if 'A' <= c && c <= 'Z' { 260 | c += 'a' - 'A' 261 | } 262 | 263 | b[j] = c 264 | } 265 | return true 266 | } 267 | -------------------------------------------------------------------------------- /uasurfer_test.go: -------------------------------------------------------------------------------- 1 | package uasurfer 2 | 3 | import "testing" 4 | 5 | var testUAVars = []struct { 6 | UA string 7 | UserAgent 8 | }{ 9 | // Empty 10 | {"", 11 | UserAgent{}}, 12 | 13 | // Single char 14 | {"a", 15 | UserAgent{}}, 16 | 17 | // Some random string 18 | {"some random string", 19 | UserAgent{}}, 20 | 21 | // Potentially malformed ua 22 | {")(", 23 | UserAgent{}}, 24 | 25 | // iPhone 26 | {"Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) AppleWebKit/546.10 (KHTML, like Gecko) Version/6.0 Mobile/7E18WD Safari/8536.25", 27 | UserAgent{ 28 | Browser{BrowserSafari, Version{6, 0, 0}}, OS{PlatformiPhone, OSiOS, Version{7, 0, 0}}, DevicePhone}}, 29 | 30 | {"Mozilla/5.0 (iPhone; CPU iPhone OS 8_0_2 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12A405 Safari/600.1.4", 31 | UserAgent{ 32 | Browser{BrowserSafari, Version{8, 0, 0}}, OS{PlatformiPhone, OSiOS, Version{8, 0, 2}}, DevicePhone}}, 33 | 34 | {"Mozilla/5.0 (iPhone10,3; CPU iPhone OS 8_0_2 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12A405 Safari/600.1.4", 35 | UserAgent{ 36 | Browser{BrowserSafari, Version{8, 0, 0}}, OS{PlatformiPhone, OSiOS, Version{8, 0, 2}}, DevicePhone}}, 37 | 38 | // iPad 39 | {"Mozilla/5.0(iPad; U; CPU iPhone OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B314 Safari/531.21.10", 40 | UserAgent{ 41 | Browser{BrowserSafari, Version{4, 0, 4}}, OS{PlatformiPad, OSiOS, Version{3, 2, 0}}, DeviceTablet}}, 42 | 43 | {"Mozilla/5.0 (iPad; CPU OS 9_0 like Mac OS X) AppleWebKit/601.1.17 (KHTML, like Gecko) Version/8.0 Mobile/13A175 Safari/600.1.4", 44 | UserAgent{ 45 | Browser{BrowserSafari, Version{8, 0, 0}}, OS{PlatformiPad, OSiOS, Version{9, 0, 0}}, DeviceTablet}}, 46 | 47 | {"Mozilla/5.0 (iPhone; CPU iPhone OS 10_0 like Mac OS X) AppleWebKit/602.1.32 (KHTML, like Gecko) Version/10.0 Mobile/14A5261v Safari/602.1", 48 | UserAgent{ 49 | Browser{BrowserSafari, Version{10, 0, 0}}, OS{PlatformiPhone, OSiOS, Version{10, 0, 0}}, DevicePhone}}, 50 | 51 | // Chrome 52 | {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.130 Safari/537.36", 53 | UserAgent{ 54 | Browser{BrowserChrome, Version{43, 0, 2357}}, OS{PlatformMac, OSMacOSX, Version{10, 10, 4}}, DeviceComputer}}, 55 | 56 | {"Mozilla/5.0 (iPhone; U; CPU iPhone OS 5_1_1 like Mac OS X; en) AppleWebKit/534.46.0 (KHTML, like Gecko) CriOS/19.0.1084.60 Mobile/9B206 Safari/534.48.3", 57 | UserAgent{ 58 | Browser{BrowserChrome, Version{19, 0, 1084}}, OS{PlatformiPhone, OSiOS, Version{5, 1, 1}}, DevicePhone}}, 59 | 60 | {"Mozilla/5.0 (Linux; Android 6.0; Nexus 5X Build/MDB08L) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2490.76 Mobile Safari/537.36", 61 | UserAgent{ 62 | Browser{BrowserChrome, Version{46, 0, 2490}}, OS{PlatformLinux, OSAndroid, Version{6, 0, 0}}, DevicePhone}}, 63 | {"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", 64 | UserAgent{ 65 | Browser{BrowserChrome, Version{124, 0, 0}}, OS{PlatformMac, OSMacOSX, Version{14, 4, 1}}, DeviceComputer}}, 66 | {"Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36", // macOS Big Sur 67 | UserAgent{ 68 | Browser{BrowserChrome, Version{87, 0, 4280}}, OS{PlatformMac, OSMacOSX, Version{11, 1, 0}}, DeviceComputer}}, 69 | 70 | // Chromium (Chrome) 71 | {"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.19 (KHTML, like Gecko) Ubuntu/11.10 Chromium/18.0.1025.142 Chrome/18.0.1025.142 Safari/535.19", 72 | UserAgent{ 73 | Browser{BrowserChrome, Version{18, 0, 1025}}, OS{PlatformLinux, OSLinux, Version{0, 0, 0}}, DeviceComputer}}, 74 | 75 | {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36", 76 | UserAgent{ 77 | Browser{BrowserChrome, Version{45, 0, 2454}}, OS{PlatformMac, OSMacOSX, Version{10, 11, 0}}, DeviceComputer}}, 78 | 79 | //TODO: refactor "getVersion()" to handle this device/chrome version douchebaggery 80 | // {"Mozilla/5.0 (Linux; Android 4.4.2; en-gb; SAMSUNG SM-G800F Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Version/1.6 Chrome/28.0.1500.94 Mobile Safari/537.36", 81 | // UserAgent{ 82 | // Browser{BrowserChrome, Version{28,0,1500}, OS{PlatformLinux, OSAndroid, Version{4,4,2}}, DevicePhone}}, 83 | 84 | // Safari 85 | {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/600.7.12 (KHTML, like Gecko) Version/8.0.7 Safari/600.7.12", 86 | UserAgent{ 87 | Browser{BrowserSafari, Version{8, 0, 7}}, OS{PlatformMac, OSMacOSX, Version{10, 10, 4}}, DeviceComputer}}, 88 | 89 | {"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_5; en-us) AppleWebKit/525.26.2 (KHTML, like Gecko) Version/3.2 Safari/525.26.12", 90 | UserAgent{ 91 | Browser{BrowserSafari, Version{3, 2, 0}}, OS{PlatformMac, OSMacOSX, Version{10, 5, 5}}, DeviceComputer}}, 92 | 93 | {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12) AppleWebKit/602.1.32 (KHTML, like Gecko) Version/10.0 Safari/602.1.32", // macOS Sierra dev beta 94 | UserAgent{ 95 | Browser{BrowserSafari, Version{10, 0, 0}}, OS{PlatformMac, OSMacOSX, Version{10, 12, 0}}, DeviceComputer}}, 96 | {"Mozilla/5.0 (iPhone; CPU iPhone OS 17_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.4.1 Mobile/15E148 Safari/604.1", 97 | UserAgent{ 98 | Browser{BrowserSafari, Version{17, 4, 1}}, OS{PlatformiPhone, OSiOS, Version{17, 4, 1}}, DevicePhone}}, 99 | 100 | // Firefox 101 | {"Mozilla/5.0 (iPhone; CPU iPhone OS 8_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) FxiOS/1.0 Mobile/12F69 Safari/600.1.4", 102 | UserAgent{ 103 | Browser{BrowserFirefox, Version{1, 0, 0}}, OS{PlatformiPhone, OSiOS, Version{8, 3, 0}}, DevicePhone}}, 104 | 105 | {"Mozilla/5.0 (Android 4.4; Tablet; rv:41.0) Gecko/41.0 Firefox/41.0", 106 | UserAgent{ 107 | Browser{BrowserFirefox, Version{41, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{4, 4, 0}}, DeviceTablet}}, 108 | 109 | {"Mozilla/5.0 (Android; Mobile; rv:40.0) Gecko/40.0 Firefox/40.0", 110 | UserAgent{ 111 | Browser{BrowserFirefox, Version{40, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{0, 0, 0}}, DevicePhone}}, 112 | 113 | {"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:38.0) Gecko/20100101 Firefox/38.0", 114 | UserAgent{ 115 | Browser{BrowserFirefox, Version{38, 0, 0}}, OS{PlatformLinux, OSLinux, Version{0, 0, 0}}, DeviceComputer}}, 116 | {"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:125.0) Gecko/20100101 Firefox/125.0", 117 | UserAgent{ 118 | Browser{BrowserFirefox, Version{125, 0, 0}}, OS{PlatformWindows, OSWindows, Version{10, 0, 0}}, DeviceComputer}}, 119 | {"Mozilla/5.0 (iPhone; CPU iPhone OS 17_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) FxiOS/125.0 Mobile/15E148 Safari/605.1.15", 120 | UserAgent{ 121 | Browser{BrowserFirefox, Version{125, 0, 0}}, OS{PlatformiPhone, OSiOS, Version{17, 4, 1}}, DevicePhone}}, 122 | 123 | // Silk 124 | {"Mozilla/5.0 (Linux; U; Android 4.4.3; de-de; KFTHWI Build/KTU84M) AppleWebKit/537.36 (KHTML, like Gecko) Silk/3.47 like Chrome/37.0.2026.117 Safari/537.36", 125 | UserAgent{ 126 | Browser{BrowserSilk, Version{3, 47, 0}}, OS{PlatformLinux, OSKindle, Version{4, 4, 3}}, DeviceTablet}}, 127 | 128 | {"Mozilla/5.0 (Linux; U; en-us; KFJWI Build/IMM76D) AppleWebKit/535.19 (KHTML like Gecko) Silk/2.4 Safari/535.19 Silk-Acceleratedtrue", 129 | UserAgent{ 130 | Browser{BrowserSilk, Version{2, 4, 0}}, OS{PlatformLinux, OSKindle, Version{0, 0, 0}}, DeviceTablet}}, 131 | 132 | // Opera 133 | {"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.63 Safari/537.36 OPR/18.0.1284.68", 134 | UserAgent{ 135 | Browser{BrowserOpera, Version{18, 0, 1284}}, OS{PlatformWindows, OSWindows, Version{6, 1, 0}}, DeviceComputer}}, 136 | 137 | {"Mozilla/5.0 (iPhone; CPU iPhone OS 8_4 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) OPiOS/10.2.0.93022 Mobile/12H143 Safari/9537.53", 138 | UserAgent{ 139 | Browser{BrowserOpera, Version{10, 2, 0}}, OS{PlatformiPhone, OSiOS, Version{8, 4, 0}}, DevicePhone}}, 140 | 141 | // Internet Explorer -- https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx 142 | {"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.123", 143 | UserAgent{ 144 | Browser{BrowserIE, Version{12, 123, 0}}, OS{PlatformWindows, OSWindows, Version{10, 0, 0}}, DeviceComputer}}, 145 | 146 | {"Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0)", 147 | UserAgent{ 148 | Browser{BrowserIE, Version{10, 0, 0}}, OS{PlatformWindows, OSWindows, Version{6, 2, 0}}, DeviceComputer}}, 149 | 150 | {"Mozilla/5.0 (Windows NT 6.3; Trident/7.0; .NET4.0E; .NET4.0C; rv:11.0) like Gecko", 151 | UserAgent{ 152 | Browser{BrowserIE, Version{11, 0, 0}}, OS{PlatformWindows, OSWindows, Version{6, 3, 0}}, DeviceComputer}}, 153 | 154 | {"Mozilla/5.0 (iPhone; CPU iPhone OS 12_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 EdgiOS/44.3.5 Mobile/15E148 Safari/605.1.15", 155 | UserAgent{ 156 | Browser{BrowserIE, Version{12, 0, 0}}, OS{PlatformiPhone, OSiOS, Version{12, 3, 1}}, DevicePhone}}, 157 | 158 | {"Mozilla/5.0 (iPad; CPU OS 12_3_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/12.0 EdgiOS/44.3.2 Mobile/15E148 Safari/605.1.15", 159 | UserAgent{ 160 | Browser{BrowserIE, Version{12, 0, 0}}, OS{PlatformiPad, OSiOS, Version{12, 3, 1}}, DeviceTablet}}, 161 | 162 | {"Mozilla/5.0 (Linux; Android 9; motorola one) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/73.0.3683.90 Mobile Safari/537.36 EdgA/42.0.2.3728", 163 | UserAgent{ 164 | Browser{BrowserIE, Version{42, 0, 2}}, OS{PlatformLinux, OSAndroid, Version{9, 0, 0}}, DevicePhone}}, 165 | 166 | {"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3800.0 Safari/537.36 Edg/76.0.172.0", 167 | UserAgent{ 168 | Browser{BrowserIE, Version{76, 0, 172}}, OS{PlatformWindows, OSWindows, Version{10, 0, 0}}, DeviceComputer}}, 169 | 170 | {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3803.0 Safari/537.36 Edg/76.0.176.0", 171 | UserAgent{ 172 | Browser{BrowserIE, Version{76, 0, 176}}, OS{PlatformMac, OSMacOSX, Version{10, 14, 5}}, DeviceComputer}}, 173 | 174 | {"Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; DEVICE INFO) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Mobile Safari/537.36 Edge/12.123", 175 | UserAgent{ 176 | Browser{BrowserIE, Version{12, 123, 0}}, OS{PlatformWindowsPhone, OSWindowsPhone, Version{10, 0, 0}}, DevicePhone}}, 177 | 178 | {"Mozilla/5.0 (Mobile; Windows Phone 8.1; Android 4.0; ARM; Trident/7.0; Touch; rv:11.0; IEMobile/11.0; NOKIA; Lumia 520) like iPhone OS 7_0_3 Mac OS X AppleWebKit/537 (KHTML, like Gecko) Mobile Safari/537", 179 | UserAgent{ 180 | Browser{BrowserIE, Version{11, 0, 0}}, OS{PlatformWindowsPhone, OSWindowsPhone, Version{8, 1, 0}}, DevicePhone}}, 181 | 182 | {"Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; SV1; .NET CLR 1.1.4322; .NET CLR 1.0.3705; .NET CLR 2.0.50727)", 183 | UserAgent{ 184 | Browser{BrowserIE, Version{5, 0, 1}}, OS{PlatformWindows, OSWindows, Version{5, 0, 0}}, DeviceComputer}}, 185 | 186 | {"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/4.0; GTB6.4; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; OfficeLiveConnector.1.3; OfficeLivePatch.0.0; .NET CLR 1.1.4322)", 187 | UserAgent{ 188 | Browser{BrowserIE, Version{7, 0, 0}}, OS{PlatformWindows, OSWindows, Version{6, 1, 0}}, DeviceComputer}}, 189 | 190 | {"Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; ARM; Trident/6.0; Touch)", //Windows Surface RT tablet 191 | UserAgent{ 192 | Browser{BrowserIE, Version{10, 0, 0}}, OS{PlatformWindows, OSWindows, Version{6, 2, 0}}, DeviceTablet}}, 193 | 194 | // UC Browser 195 | {"Mozilla/5.0 (Linux; U; Android 2.3.4; en-US; MT11i Build/4.0.2.A.0.62) AppleWebKit/534.31 (KHTML, like Gecko) UCBrowser/9.0.1.275 U3/0.8.0 Mobile Safari/534.31", 196 | UserAgent{ 197 | Browser{BrowserUCBrowser, Version{9, 0, 1}}, OS{PlatformLinux, OSAndroid, Version{2, 3, 4}}, DevicePhone}}, 198 | 199 | {"Mozilla/5.0 (Linux; U; Android 4.0.4; en-US; Micromax P255 Build/IMM76D) AppleWebKit/534.31 (KHTML, like Gecko) UCBrowser/9.2.0.308 U3/0.8.0 Mobile Safari/534.31", 200 | UserAgent{ 201 | Browser{BrowserUCBrowser, Version{9, 2, 0}}, OS{PlatformLinux, OSAndroid, Version{4, 0, 4}}, DevicePhone}}, 202 | 203 | {"UCWEB/2.0 (Java; U; MIDP-2.0; en-US; MicromaxQ5) U2/1.0.0 UCBrowser/9.4.0.342 U2/1.0.0 Mobile", 204 | UserAgent{ 205 | Browser{BrowserUCBrowser, Version{9, 4, 0}}, OS{PlatformUnknown, OSUnknown, Version{0, 0, 0}}, DevicePhone}}, 206 | 207 | // Nokia Browser 208 | // {"Mozilla/5.0 (Series40; Nokia501/14.0.4/java_runtime_version=Nokia_Asha_1_2; Profile/MIDP-2.1 Configuration/CLDC-1.1) Gecko/20100401 S40OviBrowser/4.0.0.0.45", 209 | // UserAgent{ 210 | // Browser{BrowserUnknown, Version{4,0,0}}, OS{PlatformUnknown, OSUnknown, Version{0,0,0}}, DevicePhone}}, 211 | 212 | // {"Mozilla/5.0 (Symbian/3; Series60/5.3 NokiaN8-00/111.040.1511; Profile/MIDP-2.1 Configuration/CLDC-1.1 ) AppleWebKit/535.1 (KHTML, like Gecko) NokiaBrowser/8.3.1.4 Mobile Safari/535.1", 213 | // UserAgent{ 214 | // Browser{BrowserUnknown, Version{8,0,0}}, OS{PlatformUnknown, OSUnknown, Version{0,0,0}}, DevicePhone}}, 215 | 216 | // {"NokiaN97/21.1.107 (SymbianOS/9.4; Series60/5.0 Mozilla/5.0; Profile/MIDP-2.1 Configuration/CLDC-1.1) AppleWebkit/525 (KHTML, like Gecko) BrowserNG/7.1.4", 217 | // BrowserUnknown, Version{7,0,0}}, OS{PlatformUnknown, OSUnknown, Version{0,0,0}}, DevicePhone}}, 218 | 219 | // ChromeOS 220 | {"Mozilla/5.0 (X11; U; CrOS i686 9.10.0; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.253.0 Safari/532.5", 221 | UserAgent{ 222 | Browser{BrowserChrome, Version{4, 0, 253}}, OS{PlatformLinux, OSChromeOS, Version{0, 0, 0}}, DeviceComputer}}, 223 | {"Mozilla/5.0 (X11; CrOS x86_64 15633.69.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.6045.212 Safari/537.36", 224 | UserAgent{ 225 | Browser{BrowserChrome, Version{119, 0, 6045}}, OS{PlatformLinux, OSChromeOS, Version{0, 0, 0}}, DeviceComputer}}, 226 | 227 | // iPod, iPod Touch 228 | {"mozilla/5.0 (ipod touch; cpu iphone os 9_3_3 like mac os x) applewebkit/601.1.46 (khtml, like gecko) version/9.0 mobile/13g34 safari/601.1", 229 | UserAgent{ 230 | Browser{BrowserSafari, Version{9, 0, 0}}, OS{PlatformiPod, OSiOS, Version{9, 3, 3}}, DeviceTablet}}, 231 | 232 | {"mozilla/5.0 (ipod; cpu iphone os 6_1_6 like mac os x) applewebkit/536.26 (khtml, like gecko) version/6.0 mobile/10b500 safari/8536.25", 233 | UserAgent{ 234 | Browser{BrowserSafari, Version{6, 0, 0}}, OS{PlatformiPod, OSiOS, Version{6, 1, 6}}, DeviceTablet}}, 235 | 236 | // WebOS 237 | {"Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.0; U; de-DE) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/233.70 Safari/534.6 TouchPad/1.0", 238 | UserAgent{ 239 | Browser{BrowserUnknown, Version{0, 0, 0}}, OS{PlatformLinux, OSWebOS, Version{0, 0, 0}}, DeviceTablet}}, 240 | 241 | {"Mozilla/5.0 (webOS/1.4.1.1; U; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Version/1.0 Safari/532.2 Pre/1.0", 242 | UserAgent{ 243 | Browser{BrowserUnknown, Version{1, 0, 0}}, OS{PlatformLinux, OSWebOS, Version{0, 0, 0}}, DevicePhone}}, 244 | 245 | // Android WebView (Android <= 4.3) 246 | {"Mozilla/5.0 (Linux; U; Android 2.2; en-us; DROID2 GLOBAL Build/S273) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1", 247 | UserAgent{ 248 | Browser{BrowserAndroid, Version{4, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{2, 2, 0}}, DevicePhone}}, 249 | 250 | {"Mozilla/5.0 (Linux; U; Android 4.0.3; de-ch; HTC Sensation Build/IML74K) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari53/4.30", 251 | UserAgent{ 252 | Browser{BrowserAndroid, Version{4, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{4, 0, 3}}, DevicePhone}}, 253 | 254 | // BlackBerry 255 | {"Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML, like Gecko) Version/7.2.1.0 Safari/536.2+", 256 | UserAgent{ 257 | Browser{BrowserBlackberry, Version{7, 2, 1}}, OS{PlatformBlackberry, OSBlackberry, Version{0, 0, 0}}, DeviceTablet}}, 258 | 259 | {"Mozilla/5.0 (BB10; Kbd) AppleWebKit/537.35+ (KHTML, like Gecko) Version/10.2.1.1925 Mobile Safari/537.35+", 260 | UserAgent{ 261 | Browser{BrowserBlackberry, Version{10, 2, 1}}, OS{PlatformBlackberry, OSBlackberry, Version{0, 0, 0}}, DevicePhone}}, 262 | 263 | {"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0) BlackBerry8703e/4.1.0 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/104", 264 | UserAgent{ 265 | Browser{BrowserBlackberry, Version{0, 0, 0}}, OS{PlatformBlackberry, OSBlackberry, Version{0, 0, 0}}, DevicePhone}}, 266 | 267 | // Windows Phone 268 | {"Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 625; ANZ941)", 269 | UserAgent{ 270 | Browser{BrowserIE, Version{10, 0, 0}}, OS{PlatformWindowsPhone, OSWindowsPhone, Version{8, 0, 0}}, DevicePhone}}, 271 | 272 | {"Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; NOKIA; Lumia 900)", 273 | UserAgent{ 274 | Browser{BrowserIE, Version{9, 0, 0}}, OS{PlatformWindowsPhone, OSWindowsPhone, Version{7, 5, 0}}, DevicePhone}}, 275 | 276 | // Kindle eReader 277 | {"Mozilla/5.0 (Linux; U; en-US) AppleWebKit/528.5+ (KHTML, like Gecko, Safari/528.5+) Version/4.0 Kindle/3.0 (screen 600×800; rotate)", 278 | UserAgent{ 279 | Browser{BrowserUnknown, Version{4, 0, 0}}, OS{PlatformLinux, OSKindle, Version{0, 0, 0}}, DeviceTablet}}, 280 | 281 | {"Mozilla/5.0 (X11; U; Linux armv7l like Android; en-us) AppleWebKit/531.2+ (KHTML, like Gecko) Version/5.0 Safari/533.2+ Kindle/3.0+", 282 | UserAgent{ 283 | Browser{BrowserUnknown, Version{5, 0, 0}}, OS{PlatformLinux, OSKindle, Version{0, 0, 0}}, DeviceTablet}}, 284 | 285 | // Amazon Fire 286 | {"Mozilla/5.0 (Linux; U; Android 4.4.3; de-de; KFTHWI Build/KTU84M) AppleWebKit/537.36 (KHTML, like Gecko) Silk/3.67 like Chrome/39.0.2171.93 Safari/537.36", 287 | UserAgent{ 288 | Browser{BrowserSilk, Version{3, 67, 0}}, OS{PlatformLinux, OSKindle, Version{4, 4, 3}}, DeviceTablet}}, // Fire tablet 289 | 290 | {"Mozilla/5.0 (Linux; U; Android 4.2.2; en­us; KFTHWI Build/JDQ39) AppleWebKit/537.36 (KHTML, like Gecko) Silk/3.22 like Chrome/34.0.1847.137 Mobile Safari/537.36", 291 | UserAgent{ 292 | Browser{BrowserSilk, Version{3, 22, 0}}, OS{PlatformLinux, OSKindle, Version{4, 2, 2}}, DeviceTablet}}, // Fire tablet, but with "Mobile" 293 | 294 | {"Mozilla/5.0 (Linux; Android 4.4.4; SD4930UR Build/KTU84P) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/34.0.0.0 Mobile Safari/537.36 [FB_IAB/FB4A;FBAV/35.0.0.48.273;]", 295 | UserAgent{ 296 | Browser{BrowserChrome, Version{34, 0, 0}}, OS{PlatformLinux, OSKindle, Version{4, 4, 4}}, DevicePhone}}, // Facebook app on Fire Phone 297 | 298 | {"mozilla/5.0 (linux; android 4.4.3; kfthwi build/ktu84m) applewebkit/537.36 (khtml, like gecko) version/4.0 chrome/34.0.0.0 safari/537.36 [pinterest/android]", 299 | UserAgent{ 300 | Browser{BrowserChrome, Version{34, 0, 0}}, OS{PlatformLinux, OSKindle, Version{4, 4, 3}}, DeviceTablet}}, // Fire tablet running pinterest 301 | 302 | // extra logic to identify phone when using silk has not been added 303 | // {"Mozilla/5.0 (Linux; Android 4.4.4; SD4930UR Build/KTU84P) AppleWebKit/537.36 (KHTML, like Gecko) Silk/3.67 like Chrome/39.0.2171.93 Mobile Safari/537.36", 304 | // UserAgent{ 305 | // Browser{BrowserSilk, Version{3,0,0}}, OS{PlatformLinux, OSKindle, Version{4,0,0}}, DevicePhone}}, // Silk on Fire Phone 306 | 307 | // Nintendo 308 | {"Opera/9.30 (Nintendo Wii; U; ; 2047-7; fr)", 309 | UserAgent{ 310 | Browser{BrowserOpera, Version{9, 30, 0}}, OS{PlatformNintendo, OSNintendo, Version{0, 0, 0}}, DeviceConsole}}, 311 | 312 | {"Mozilla/5.0 (Nintendo WiiU) AppleWebKit/534.52 (KHTML, like Gecko) NX/2.1.0.8.21 NintendoBrowser/1.0.0.7494.US", 313 | UserAgent{ 314 | Browser{BrowserNintendo, Version{0, 0, 0}}, OS{PlatformNintendo, OSNintendo, Version{0, 0, 0}}, DeviceConsole}}, 315 | 316 | // Xbox 317 | {"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; Xbox)", //Xbox 360 318 | UserAgent{ 319 | Browser{BrowserIE, Version{9, 0, 0}}, OS{PlatformXbox, OSXbox, Version{6, 1, 0}}, DeviceConsole}}, 320 | 321 | // Playstation 322 | {"Mozilla/5.0 (PlayStation 4 4.50) AppleWebKit/601.2 (KHTML, like Gecko)", 323 | UserAgent{ 324 | Browser{BrowserUnknown, Version{0, 0, 0}}, OS{PlatformPlaystation, OSPlaystation, Version{0, 0, 0}}, DeviceConsole}}, 325 | 326 | {"Mozilla/5.0 (Playstation Vita 1.61) AppleWebKit/531.22.8 (KHTML, like Gecko) Silk/3.2", 327 | UserAgent{ 328 | Browser{BrowserSilk, Version{3, 2, 0}}, OS{PlatformPlaystation, OSPlaystation, Version{0, 0, 0}}, DeviceConsole}}, 329 | 330 | // Smart TVs and TV dongles 331 | {"Mozilla/5.0 (CrKey armv7l 1.4.15250) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.0 Safari/537.36", // Chromecast 332 | UserAgent{ 333 | Browser{BrowserChrome, Version{31, 0, 1650}}, OS{PlatformUnknown, OSUnknown, Version{0, 0, 0}}, DeviceTV}}, 334 | 335 | {"Mozilla/5.0 (Linux; GoogleTV 3.2; VAP430 Build/MASTER) AppleWebKit/534.24 (KHTML, like Gecko) Chrome/11.0.696.77 Safari/534.24", // Google TV 336 | UserAgent{ 337 | Browser{BrowserChrome, Version{11, 0, 696}}, OS{PlatformLinux, OSAndroid, Version{0, 0, 0}}, DeviceTV}}, 338 | 339 | {"Mozilla/5.0 (Linux; Android 5.0; ADT-1 Build/LPX13D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.89 Mobile Safari/537.36", // Android TV 340 | UserAgent{ 341 | Browser{BrowserChrome, Version{40, 0, 2214}}, OS{PlatformLinux, OSAndroid, Version{5, 0, 0}}, DeviceTV}}, 342 | 343 | {"Mozilla/5.0 (Linux; Android 4.2.2; AFTB Build/JDQ39) AppleWebKit/537.22 (KHTML, like Gecko) Chrome/25.0.1364.173 Mobile Safari/537.22", // Amazon Fire 344 | UserAgent{ 345 | Browser{BrowserChrome, Version{25, 0, 1364}}, OS{PlatformLinux, OSAndroid, Version{4, 2, 2}}, DeviceTV}}, 346 | 347 | {"Mozilla/5.0 (Unknown; Linux armv7l) AppleWebKit/537.1+ (KHTML, like Gecko) Safari/537.1+ LG Browser/6.00.00(+mouse+3D+SCREEN+TUNER; LGE; GLOBAL-PLAT5; 03.07.01; 0x00000001;); LG NetCast.TV-2013/03.17.01 (LG, GLOBAL-PLAT4, wired)", // LG TV 348 | UserAgent{ 349 | Browser{BrowserUnknown, Version{0, 0, 0}}, OS{PlatformLinux, OSLinux, Version{0, 0, 0}}, DeviceTV}}, 350 | 351 | {"Mozilla/5.0 (X11; FreeBSD; U; Viera; de-DE) AppleWebKit/537.11 (KHTML, like Gecko) Viera/3.10.0 Chrome/23.0.1271.97 Safari/537.11", // Panasonic Viera 352 | UserAgent{ 353 | Browser{BrowserChrome, Version{23, 0, 1271}}, OS{PlatformLinux, OSLinux, Version{0, 0, 0}}, DeviceTV}}, 354 | 355 | // TODO: not catching "browser/" and reporting as safari -- ua string not being fully checked? 356 | // {"Mozilla/5.0 (DTV) AppleWebKit/531.2+ (KHTML, like Gecko) Espial/6.1.5 AQUOSBrowser/2.0 (US01DTV;V;0001;0001)", // Sharp Aquos 357 | // BrowserUnknown, Version{0,0,0}}, OS{PlatformUnknown, OSUnknown, Version{0,0,0}}, DeviceTV}}, 358 | 359 | {"Roku/DVP-5.2 (025.02E03197A)", // Roku 360 | UserAgent{ 361 | Browser{BrowserUnknown, Version{0, 0, 0}}, OS{PlatformUnknown, OSUnknown, Version{0, 0, 0}}, DeviceTV}}, 362 | 363 | {"mozilla/5.0 (smart-tv; linux; tizen 2.3) applewebkit/538.1 (khtml, like gecko) samsungbrowser/1.0 tv safari/538.1", // Samsung SmartTV 364 | UserAgent{ 365 | Browser{BrowserSamsung, Version{0, 0, 0}}, OS{PlatformLinux, OSLinux, Version{0, 0, 0}}, DeviceTV}}, 366 | 367 | {"mozilla/5.0 (linux; u) applewebkit/537.36 (khtml, like gecko) version/4.0 mobile safari/537.36 smarttv/6.0 (netcast)", 368 | UserAgent{ 369 | Browser{BrowserUnknown, Version{4, 0, 0}}, OS{PlatformLinux, OSLinux, Version{0, 0, 0}}, DeviceTV}}, 370 | 371 | // Google search app (GSA) for iOS -- it's Safari in disguise as of v6 372 | {"Mozilla/5.0 (iPad; CPU OS 8_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) GSA/6.0.51363 Mobile/12F69 Safari/600.1.4", 373 | UserAgent{ 374 | Browser{BrowserSafari, Version{8, 3, 0}}, OS{PlatformiPad, OSiOS, Version{8, 3, 0}}, DeviceTablet}}, 375 | 376 | // Spotify (applicable for advertising applications) 377 | {"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Spotify/1.0.9.133 Safari/537.36", 378 | UserAgent{ 379 | Browser{BrowserSpotify, Version{1, 0, 9}}, OS{PlatformWindows, OSWindows, Version{5, 1, 0}}, DeviceComputer}}, 380 | 381 | {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) AppleWebKit/537.36 (KHTML, like Gecko) Spotify/1.0.9.133 Safari/537.36", 382 | UserAgent{ 383 | Browser{BrowserSpotify, Version{1, 0, 9}}, OS{PlatformMac, OSMacOSX, Version{10, 10, 2}}, DeviceComputer}}, 384 | 385 | // OCSP fetchers 386 | {"Microsoft-CryptoAPI/10.0", 387 | UserAgent{ 388 | Browser{BrowserUnknown, Version{0, 0, 0}}, OS{PlatformWindows, OSUnknown, Version{0, 0, 0}}, DeviceComputer}}, 389 | {"trustd (unknown version) CFNetwork/811.7.2 Darwin/16.7.0 (x86_64)", 390 | UserAgent{ 391 | Browser{BrowserUnknown, Version{0, 0, 0}}, OS{PlatformMac, OSUnknown, Version{0, 0, 0}}, DeviceComputer}}, 392 | {"ocspd (unknown version) CFNetwork/520.5.3 Darwin/11.4.2 (x86_64)(MacBookAir5%2C2)", 393 | UserAgent{ 394 | Browser{BrowserUnknown, Version{0, 0, 0}}, OS{PlatformMac, OSUnknown, Version{0, 0, 0}}, DeviceComputer}}, 395 | // Bots 396 | {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/600.2.5 (KHTML, like Gecko) Version/8.0.2 Safari/600.2.5 (Applebot/0.1; +http://www.apple.com/go/applebot)", 397 | UserAgent{ 398 | Browser{BrowserAppleBot, Version{0, 0, 0}}, OS{PlatformBot, OSBot, Version{10, 10, 1}}, DeviceComputer}}, 399 | 400 | {"Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)", 401 | UserAgent{ 402 | Browser{BrowserBaiduBot, Version{0, 0, 0}}, OS{PlatformBot, OSBot, Version{0, 0, 0}}, DeviceComputer}}, 403 | 404 | {"Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)", 405 | UserAgent{ 406 | Browser{BrowserBingBot, Version{0, 0, 0}}, OS{PlatformBot, OSBot, Version{0, 0, 0}}, DeviceComputer}}, 407 | 408 | {"DuckDuckBot/1.0; (+http://duckduckgo.com/duckduckbot.html)", 409 | UserAgent{ 410 | Browser{BrowserDuckDuckGoBot, Version{0, 0, 0}}, OS{PlatformBot, OSBot, Version{0, 0, 0}}, DeviceComputer}}, 411 | 412 | {"facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)", 413 | UserAgent{ 414 | Browser{BrowserFacebookBot, Version{0, 0, 0}}, OS{PlatformBot, OSBot, Version{0, 0, 0}}, DeviceComputer}}, 415 | 416 | {"Facebot/1.0", 417 | UserAgent{ 418 | Browser{BrowserFacebookBot, Version{0, 0, 0}}, OS{PlatformBot, OSBot, Version{0, 0, 0}}, DeviceComputer}}, 419 | 420 | {"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)", 421 | UserAgent{ 422 | Browser{BrowserGoogleBot, Version{0, 0, 0}}, OS{PlatformBot, OSBot, Version{0, 0, 0}}, DeviceComputer}}, 423 | 424 | {"LinkedInBot/1.0 (compatible; Mozilla/5.0; Jakarta Commons-HttpClient/3.1 +http://www.linkedin.com)", 425 | UserAgent{ 426 | Browser{BrowserLinkedInBot, Version{0, 0, 0}}, OS{PlatformBot, OSBot, Version{0, 0, 0}}, DeviceComputer}}, 427 | 428 | {"msnbot/2.0b (+http://search.msn.com/msnbot.htm)", 429 | UserAgent{ 430 | Browser{BrowserMsnBot, Version{0, 0, 0}}, OS{PlatformBot, OSBot, Version{0, 0, 0}}, DeviceComputer}}, 431 | 432 | {"Pingdom.com_bot_version_1.4_(http://www.pingdom.com/)", 433 | UserAgent{ 434 | Browser{BrowserPingdomBot, Version{0, 0, 0}}, OS{PlatformBot, OSBot, Version{0, 0, 0}}, DeviceComputer}}, 435 | 436 | {"Twitterbot/1.0", 437 | UserAgent{ 438 | Browser{BrowserTwitterBot, Version{0, 0, 0}}, OS{PlatformBot, OSBot, Version{0, 0, 0}}, DeviceComputer}}, 439 | 440 | {"Mozilla/5.0 (compatible; YandexBot/3.0; +http://yandex.com/bots)", 441 | UserAgent{ 442 | Browser{BrowserYandexBot, Version{0, 0, 0}}, OS{PlatformBot, OSBot, Version{0, 0, 0}}, DeviceComputer}}, 443 | 444 | {"Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp)", 445 | UserAgent{ 446 | Browser{BrowserYahooBot, Version{0, 0, 0}}, OS{PlatformBot, OSBot, Version{0, 0, 0}}, DeviceComputer}}, 447 | 448 | {"{UA:Mozilla/5.0 (Linux; Android 6.0.1; Nexus 5X Build/MMB29P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.96 Mobile Safari/537.36 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)}, ua: &{Browser:{Name:BrowserGoogleBot Version:{Major:0 Minor:0 Patch:0}} OS:{Platform:PlatformBot Name:OSBot Version:{Major:6 Minor:0 Patch:1}} DeviceType:DeviceComputer}", 449 | UserAgent{ 450 | Browser{BrowserGoogleBot, Version{0, 0, 0}}, OS{PlatformBot, OSBot, Version{6, 0, 1}}, DeviceComputer}}, 451 | 452 | {"Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)", 453 | UserAgent{ 454 | Browser{BrowserGoogleBot, Version{0, 0, 0}}, OS{PlatformBot, OSBot, Version{6, 0, 0}}, DeviceComputer}}, 455 | 456 | {"mozilla/5.0 (unknown; linux x86_64) applewebkit/538.1 (khtml, like gecko) phantomjs/2.1.1 safari/538.1", 457 | UserAgent{ 458 | Browser{BrowserBot, Version{0, 0, 0}}, OS{PlatformBot, OSBot, Version{0, 0, 0}}, DeviceComputer}}, 459 | 460 | // Unknown or partially handled 461 | {"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.4; en-US; rv:1.9.1b3pre) Gecko/20090223 SeaMonkey/2.0a3", //Seamonkey (~FF) 462 | UserAgent{ 463 | Browser{BrowserFirefox, Version{0, 0, 0}}, OS{PlatformMac, OSMacOSX, Version{10, 4, 0}}, DeviceComputer}}, 464 | 465 | {"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en; rv:1.9.0.8pre) Gecko/2009022800 Camino/2.0b3pre", //Camino (~FF) 466 | UserAgent{ 467 | Browser{BrowserUnknown, Version{0, 0, 0}}, OS{PlatformMac, OSMacOSX, Version{10, 5, 0}}, DeviceComputer}}, 468 | 469 | {"Mozilla/5.0 (Mobile; rv:26.0) Gecko/26.0 Firefox/26.0", //firefox OS 470 | UserAgent{ 471 | Browser{BrowserFirefox, Version{26, 0, 0}}, OS{PlatformUnknown, OSUnknown, Version{0, 0, 0}}, DevicePhone}}, 472 | 473 | {"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.45 Safari/535.19", //chrome for android having requested desktop site 474 | UserAgent{ 475 | Browser{BrowserChrome, Version{18, 0, 1025}}, OS{PlatformLinux, OSLinux, Version{0, 0, 0}}, DeviceComputer}}, 476 | 477 | {"Opera/9.80 (S60; SymbOS; Opera Mobi/352; U; de) Presto/2.4.15 Version/10.00", 478 | UserAgent{ 479 | Browser{BrowserOpera, Version{10, 0, 0}}, OS{PlatformUnknown, OSUnknown, Version{0, 0, 0}}, DevicePhone}}, 480 | 481 | // BrowserQQ 482 | {"Mozilla/5.0 (Windows NT 6.2; WOW64; Trident/7.0; Touch; .NET4.0E; .NET4.0C; .NET CLR 3.5.30729; .NET CLR 2.0.50727; .NET CLR 3.0.30729; InfoPath.3; Tablet PC 2.0; QQBrowser/7.6.21433.400; rv:11.0) like Gecko", 483 | UserAgent{ 484 | Browser{BrowserQQ, Version{7, 6, 21433}}, OS{PlatformWindows, OSWindows, Version{6, 2, 0}}, DeviceTablet}}, 485 | 486 | {"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.124 Safari/537.36 QQBrowser/9.0.2191.400", 487 | UserAgent{ 488 | Browser{BrowserQQ, Version{9, 0, 2191}}, OS{PlatformWindows, OSWindows, Version{6, 1, 0}}, DeviceComputer}}, 489 | 490 | {"mozilla/5.0 (iphone; cpu iphone os 8_1_2 like mac os x) applewebkit/600.1.4 (khtml, like gecko) mobile/12b440 qq/5.3.0.319 nettype/wifi mem/205", 491 | UserAgent{ 492 | Browser{BrowserQQ, Version{5, 3, 0}}, OS{PlatformiPhone, OSiOS, Version{8, 1, 2}}, DevicePhone}}, 493 | 494 | // ANDROID TESTS 495 | 496 | {"Mozilla/5.0 (Linux; U; Android 1.0; en-us; dream) AppleWebKit/525.10+ (KHTML,like Gecko) Version/3.0.4 Mobile Safari/523.12.2", 497 | UserAgent{ 498 | Browser{BrowserAndroid, Version{3, 0, 4}}, OS{PlatformLinux, OSAndroid, Version{1, 0, 0}}, DevicePhone}}, 499 | 500 | {"Mozilla/5.0 (Linux; U; Android 1.0; en-us; generic) AppleWebKit/525.10 (KHTML, like Gecko) Version/3.0.4 Mobile Safari/523.12.2", 501 | UserAgent{ 502 | Browser{BrowserAndroid, Version{3, 0, 4}}, OS{PlatformLinux, OSAndroid, Version{1, 0, 0}}, DevicePhone}}, 503 | 504 | {"Mozilla/5.0 (Linux; U; Android 1.0.3; de-de; A80KSC Build/ECLAIR) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17", 505 | UserAgent{ 506 | Browser{BrowserAndroid, Version{4, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{1, 0, 3}}, DevicePhone}}, 507 | 508 | {"Mozilla/5.0 (Linux; U; Android 1.5; en-gb; T-Mobile G1 Build/CRC1) AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1", 509 | UserAgent{ 510 | Browser{BrowserAndroid, Version{3, 1, 2}}, OS{PlatformLinux, OSAndroid, Version{1, 5, 0}}, DevicePhone}}, 511 | 512 | {"Mozilla/5.0 (Linux; U; Android 1.5; es-; FBW1_4 Build/MASTER) AppleWebKit/525.10+ (KHTML, like Gecko) Version/3.0.4 Mobile Safari/523.12.2", 513 | UserAgent{ 514 | Browser{BrowserAndroid, Version{3, 0, 4}}, OS{PlatformLinux, OSAndroid, Version{1, 5, 0}}, DevicePhone}}, 515 | 516 | {"Mozilla/5.0 (Linux U; Android 1.5 en-us hero) AppleWebKit/525.10+ (KHTML, like Gecko) Version/3.0.4 Mobile Safari/523.12.2", 517 | UserAgent{ 518 | Browser{BrowserAndroid, Version{3, 0, 4}}, OS{PlatformLinux, OSAndroid, Version{1, 5, 0}}, DevicePhone}}, 519 | 520 | {"Mozilla/5.0 (Linux; U; Android 1.5; en-us; Opus One Build/RBE.00.00) AppleWebKit/528.18.1 (KHTML, like Gecko) Version/3.1.1 Mobile Safari/525.20.1", 521 | UserAgent{ 522 | Browser{BrowserAndroid, Version{3, 1, 1}}, OS{PlatformLinux, OSAndroid, Version{1, 5, 0}}, DevicePhone}}, 523 | 524 | {"Mozilla/5.0 (Linux; U; Android 1.6; ar-us; SonyEricssonX10i Build/R2BA026) AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1", 525 | UserAgent{ 526 | Browser{BrowserAndroid, Version{3, 1, 2}}, OS{PlatformLinux, OSAndroid, Version{1, 6, 0}}, DevicePhone}}, 527 | 528 | // TODO: support names of Android OS? 529 | //{"Mozilla/5.0 (Linux; U; Android Donut; de-de; HTC Tattoo 1.52.161.1 Build/Donut) AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1", 530 | // UserAgent{ 531 | // Browser{BrowserAndroid, Version{3, 1, 2}}, OS{PlatformLinux, OSAndroid, Version{1, 0, 0}}, DevicePhone}}, 532 | 533 | {"Mozilla/5.0 (Linux; U; Android 1.6; en-gb; HTC Tattoo Build/DRC79) AppleWebKit/525.10+ (KHTML, like Gecko) Version/3.0.4 Mobile Safari/523.12.2", 534 | UserAgent{ 535 | Browser{BrowserAndroid, Version{3, 0, 4}}, OS{PlatformLinux, OSAndroid, Version{1, 6, 0}}, DevicePhone}}, 536 | 537 | {"Mozilla/5.0 (Linux; U; Android 1.6; ja-jp; Docomo HT-03A Build/DRD08) AppleWebKit/525.10 (KHTML, like Gecko) Version/3.0.4 Mobile Safari/523.12.2", 538 | UserAgent{ 539 | Browser{BrowserAndroid, Version{3, 0, 4}}, OS{PlatformLinux, OSAndroid, Version{1, 6, 0}}, DevicePhone}}, 540 | 541 | {"Mozilla/5.0 (Linux; U; Android 2.1; en-us; Nexus One Build/ERD62) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17", 542 | UserAgent{ 543 | Browser{BrowserAndroid, Version{4, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{2, 1, 0}}, DevicePhone}}, 544 | 545 | {"Mozilla/5.0 (Linux; U; Android 2.1-update1; en-au; HTC_Desire_A8183 V1.16.841.1 Build/ERE27) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17", 546 | UserAgent{ 547 | Browser{BrowserAndroid, Version{4, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{2, 1, 0}}, DevicePhone}}, 548 | 549 | {"Mozilla/5.0 (Linux; U; Android 2.1; en-us; generic) AppleWebKit/525.10+ (KHTML, like Gecko) Version/3.0.4 Mobile Safari/523.12.2", 550 | UserAgent{ 551 | Browser{BrowserAndroid, Version{3, 0, 4}}, OS{PlatformLinux, OSAndroid, Version{2, 1, 0}}, DevicePhone}}, 552 | 553 | // TODO support named versions of Android? 554 | {"Mozilla/5.0 (Linux; U; Android Eclair; en-us; sholes) AppleWebKit/525.10+ (KHTML, like Gecko) Version/3.0.4 Mobile Safari/523.12.2", 555 | UserAgent{ 556 | Browser{BrowserAndroid, Version{3, 0, 4}}, OS{PlatformLinux, OSAndroid, Version{0, 0, 0}}, DevicePhone}}, 557 | 558 | {"Mozilla/5.0 (Linux; U; Android 2.2; en-sa; HTC_DesireHD_A9191 Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1", 559 | UserAgent{ 560 | Browser{BrowserAndroid, Version{4, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{2, 2, 0}}, DevicePhone}}, 561 | 562 | {"Mozilla/5.0 (Linux; U; Android 2.2.1; en-gb; HTC_DesireZ_A7272 Build/FRG83D) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1", 563 | UserAgent{ 564 | Browser{BrowserAndroid, Version{4, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{2, 2, 1}}, DevicePhone}}, 565 | 566 | {"Mozilla/5.0 (Linux; U; Android 2.3.3; en-us; Sensation_4G Build/GRI40) AppleWebKit/533.1 (KHTML, like Gecko) Version/5.0 Safari/533.16", 567 | UserAgent{ 568 | Browser{BrowserAndroid, Version{5, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{2, 3, 3}}, DevicePhone}}, 569 | 570 | {"Mozilla/5.0 (Linux; U; Android 2.3.5; ko-kr; SHW-M250S Build/GINGERBREAD) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1", 571 | UserAgent{ 572 | Browser{BrowserAndroid, Version{4, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{2, 3, 5}}, DevicePhone}}, 573 | 574 | {"Mozilla/5.0 (Linux; U; Android 2.3.7; ja-jp; L-02D Build/GWK74) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1", 575 | UserAgent{ 576 | Browser{BrowserAndroid, Version{4, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{2, 3, 7}}, DevicePhone}}, 577 | 578 | // TODO: is tablet, not phone 579 | {"Mozilla/5.0 (Linux; U; Android 3.0; xx-xx; Transformer TF101 Build/HRI66) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13", 580 | UserAgent{ 581 | Browser{BrowserAndroid, Version{4, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{3, 0, 0}}, DevicePhone}}, 582 | 583 | {"Mozilla/5.0 (Linux; U; Android 3.0; en-us; Xoom Build/HRI39) AppleWebKit/534.13 (KHTML, like Gecko) Version/4.0 Safari/534.13", 584 | UserAgent{ 585 | Browser{BrowserAndroid, Version{4, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{3, 0, 0}}, DeviceTablet}}, 586 | 587 | {"Mozilla/5.0 (Linux; U; Android 4.0.1; en-us; sdk Build/ICS_MR0) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", 588 | UserAgent{ 589 | Browser{BrowserAndroid, Version{4, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{4, 0, 1}}, DevicePhone}}, 590 | 591 | // TODO support "android-" version prefix 592 | // However, can't find reference to this naming scheme in real-world UA gathering 593 | // {"Mozilla/5.0 (Linux; U; Android-4.0.3; en-us; Galaxy Nexus Build/IML74K) AppleWebKit/535.7 (KHTML, like Gecko) CrMo/16.0.912.75 Mobile Safari/535.7", 594 | // UserAgent{ 595 | // Browser{BrowserChrome, Version{16,0,0}}, OS{PlatformLinux, OSAndroid, Version{4,0,0}}, DevicePhone}}, 596 | 597 | {"Mozilla/5.0 (Linux; U; Android 4.1.1; en-us; Nexus S Build/JRO03E) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", 598 | UserAgent{ 599 | Browser{BrowserAndroid, Version{4, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{4, 1, 1}}, DevicePhone}}, 600 | 601 | {"Mozilla/5.0 (Linux; U; Android 4.1; en-gb; Build/JRN84D) AppleWebKit/534.30 (KHTML like Gecko) Version/4.0 Mobile Safari/534.30", 602 | UserAgent{ 603 | Browser{BrowserAndroid, Version{4, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{4, 1, 0}}, DevicePhone}}, 604 | 605 | {"Mozilla/5.0 (Linux; U; Android 4.1.1; el-gr; MB525 Build/JRO03H; CyanogenMod-10) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", 606 | UserAgent{ 607 | Browser{BrowserAndroid, Version{4, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{4, 1, 1}}, DevicePhone}}, 608 | 609 | {"Mozilla/5.0 (Linux; U; Android 4.1.1; fr-fr; MB525 Build/JRO03H; CyanogenMod-10) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", 610 | UserAgent{ 611 | Browser{BrowserAndroid, Version{4, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{4, 1, 1}}, DevicePhone}}, 612 | 613 | {"Mozilla/5.0 (Linux; U; Android 4.2; en-us; Nexus 10 Build/JVP15I) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Safari/534.30", 614 | UserAgent{ 615 | Browser{BrowserAndroid, Version{4, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{4, 2, 0}}, DeviceTablet}}, 616 | 617 | {"Mozilla/5.0 (Linux; U; Android 4.2; ro-ro; LT18i Build/4.1.B.0.431) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", 618 | UserAgent{ 619 | Browser{BrowserAndroid, Version{4, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{4, 2, 0}}, DevicePhone}}, 620 | 621 | {"Mozilla/5.0 (Linux; Android 4.3; Nexus 7 Build/JWR66D) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/27.0.1453.111 Safari/537.36", 622 | UserAgent{ 623 | Browser{BrowserChrome, Version{27, 0, 1453}}, OS{PlatformLinux, OSAndroid, Version{4, 3, 0}}, DeviceTablet}}, 624 | 625 | {"Mozilla/5.0 (Linux; Android 4.4; Nexus 7 Build/KOT24) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.105 Safari/537.36", 626 | UserAgent{ 627 | Browser{BrowserChrome, Version{30, 0, 1599}}, OS{PlatformLinux, OSAndroid, Version{4, 4, 0}}, DeviceTablet}}, 628 | 629 | {"Mozilla/5.0 (Linux; Android 4.4; Nexus 4 Build/KRT16E) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.105 Mobile Safari", 630 | UserAgent{ 631 | Browser{BrowserChrome, Version{30, 0, 1599}}, OS{PlatformLinux, OSAndroid, Version{4, 4, 0}}, DevicePhone}}, 632 | 633 | {"Mozilla/5.0 (Linux; Android 6.0.1; SM-G930V Build/MMB29M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36", 634 | UserAgent{ 635 | Browser{BrowserChrome, Version{52, 0, 2743}}, OS{PlatformLinux, OSAndroid, Version{6, 0, 1}}, DevicePhone}}, 636 | 637 | {"Mozilla/5.0 (Linux; Android 7.0; Nexus 5X Build/NRD90M) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.98 Mobile Safari/537.36", 638 | UserAgent{ 639 | Browser{BrowserChrome, Version{52, 0, 2743}}, OS{PlatformLinux, OSAndroid, Version{7, 0, 0}}, DevicePhone}}, 640 | 641 | {"Mozilla/5.0 (Linux; Android 7.0; Nexus 6P Build/NRD90M; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/52.0.2743.98 Mobile Safari/537.36", 642 | UserAgent{ 643 | Browser{BrowserChrome, Version{52, 0, 2743}}, OS{PlatformLinux, OSAndroid, Version{7, 0, 0}}, DevicePhone}}, 644 | 645 | // BLACKBERRY TESTS 646 | 647 | {"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0) BlackBerry8703e/4.1.0 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/104", 648 | UserAgent{ 649 | Browser{BrowserBlackberry, Version{0, 0, 0}}, OS{PlatformBlackberry, OSBlackberry, Version{0, 0, 0}}, DevicePhone}}, 650 | 651 | {"Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.1.0.4633 Mobile Safari/537.10+", 652 | UserAgent{ 653 | Browser{BrowserBlackberry, Version{10, 1, 0}}, OS{PlatformBlackberry, OSBlackberry, Version{0, 0, 0}}, DevicePhone}}, 654 | 655 | {"Mozilla/5.0 (BB10; Kbd) AppleWebKit/537.35+ (KHTML, like Gecko) Version/10.2.1.1925 Mobile Safari/537.35+", 656 | UserAgent{ 657 | Browser{BrowserBlackberry, Version{10, 2, 1}}, OS{PlatformBlackberry, OSBlackberry, Version{0, 0, 0}}, DevicePhone}}, 658 | 659 | {"Mozilla/5.0 (PlayBook; U; RIM Tablet OS 1.0.0; en-US) AppleWebKit/534.11 (KHTML, like Gecko) Version/7.1.0.7 Safari/534.11", 660 | UserAgent{ 661 | Browser{BrowserBlackberry, Version{7, 1, 0}}, OS{PlatformBlackberry, OSBlackberry, Version{0, 0, 0}}, DeviceTablet}}, 662 | 663 | {"Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML, like Gecko) Version/7.2.1.0 Safari/536.2+", 664 | UserAgent{ 665 | Browser{BrowserBlackberry, Version{7, 2, 1}}, OS{PlatformBlackberry, OSBlackberry, Version{0, 0, 0}}, DeviceTablet}}, 666 | 667 | {"Mozilla/5.0 (X11; U; CrOS i686 9.10.0; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.253.0 Safari/532.5", 668 | UserAgent{ 669 | Browser{BrowserChrome, Version{4, 0, 253}}, OS{PlatformLinux, OSChromeOS, Version{0, 0, 0}}, DeviceComputer}}, 670 | 671 | {"Mozilla/5.0 (X11; CrOS armv7l 5500.100.6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/34.0.1847.120 Safari/537.36", 672 | UserAgent{ 673 | Browser{BrowserChrome, Version{34, 0, 1847}}, OS{PlatformLinux, OSChromeOS, Version{0, 0, 0}}, DeviceComputer}}, 674 | 675 | // {"Mozilla/5.0 (Mobile; rv:14.0) Gecko/14.0 Firefox/14.0", 676 | // UserAgent{ 677 | // Browser{BrowserFirefox, 14, OSFirefoxOS, 14}, DevicePhone}}, 678 | 679 | // {"Mozilla/5.0 (Mobile; rv:17.0) Gecko/17.0 Firefox/17.0", 680 | // UserAgent{ 681 | // Browser{BrowserFirefox, , OSFirefoxOS}, DevicePhone}}, 682 | 683 | // {"Mozilla/5.0 (Mobile; rv:18.1) Gecko/18.1 Firefox/18.1", 684 | // UserAgent{ 685 | // Browser{BrowserFirefox, , OSFirefoxOS}, DevicePhone}}, 686 | 687 | // {"Mozilla/5.0 (Tablet; rv:18.1) Gecko/18.1 Firefox/18.1", 688 | // UserAgent{ 689 | // Browser{BrowserFirefox, , OSFirefoxOS}, DevicePhone}}, 690 | 691 | // {"Mozilla/5.0 (Mobile; LG-D300; rv:18.1) Gecko/18.1 Firefox/18.1", 692 | // UserAgent{ 693 | // Browser{BrowserFirefox, , OSFirefoxOS}, DevicePhone}}, 694 | 695 | {"Mozilla/5.0(iPad; U; CPU iPhone OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B314 Safari/531.21.10", 696 | UserAgent{ 697 | Browser{BrowserSafari, Version{4, 0, 4}}, OS{PlatformiPad, OSiOS, Version{3, 2, 0}}, DeviceTablet}}, 698 | 699 | {"Mozilla/5.0 (iPhone; U; CPU iPhone OS 4_0 like Mac OS X; en-us) AppleWebKit/532.9 (KHTML, like Gecko) Version/4.0.5 Mobile/8A293 Safari/6531.22.7", 700 | UserAgent{ 701 | Browser{BrowserSafari, Version{4, 0, 5}}, OS{PlatformiPhone, OSiOS, Version{4, 0, 0}}, DevicePhone}}, 702 | 703 | {"Mozilla/5.0 (iPhone; CPU iPhone OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3", 704 | UserAgent{ 705 | Browser{BrowserSafari, Version{5, 1, 0}}, OS{PlatformiPhone, OSiOS, Version{5, 0, 0}}, DevicePhone}}, 706 | 707 | {"Mozilla/5.0 (iPad; CPU OS 5_0 like Mac OS X) AppleWebKit/534.46 (KHTML, like Gecko) Version/5.1 Mobile/9A334 Safari/7534.48.3", 708 | UserAgent{ 709 | Browser{BrowserSafari, Version{5, 1, 0}}, OS{PlatformiPad, OSiOS, Version{5, 0, 0}}, DeviceTablet}}, 710 | 711 | {"Mozilla/5.0 (iPad; CPU OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5355d Safari/8536.25", 712 | UserAgent{ 713 | Browser{BrowserSafari, Version{6, 0, 0}}, OS{PlatformiPad, OSiOS, Version{6, 0, 0}}, DeviceTablet}}, 714 | 715 | {"Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) AppleWebKit/546.10 (KHTML, like Gecko) Version/6.0 Mobile/7E18WD Safari/8536.25", 716 | UserAgent{ 717 | Browser{BrowserSafari, Version{6, 0, 0}}, OS{PlatformiPhone, OSiOS, Version{7, 0, 0}}, DevicePhone}}, 718 | 719 | {"Mozilla/5.0 (iPad; CPU OS 7_0 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A465 Safari/9537.53", 720 | UserAgent{ 721 | Browser{BrowserSafari, Version{7, 0, 0}}, OS{PlatformiPad, OSiOS, Version{7, 0, 0}}, DeviceTablet}}, 722 | 723 | {"Mozilla/5.0 (iPad; CPU OS 7_0_2 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11A501 Safari/9537.53", 724 | UserAgent{ 725 | Browser{BrowserSafari, Version{7, 0, 0}}, OS{PlatformiPad, OSiOS, Version{7, 0, 2}}, DeviceTablet}}, 726 | 727 | {"Mozilla/5.0 (iPhone; CPU iPhone OS 10_2_1 like Mac OS X) AppleWebKit/602.4.6 (KHTML, like Gecko) Mobile/14D27 [FBAN/FBIOS;FBAV/86.0.0.48.52;FBBV/53842252;FBDV/iPhone9,1;FBMD/iPhone;FBSN/iOS;FBSV/10.2.1;FBSS/2;FBCR/Verizon;FBID/phone;FBLC/en_US;FBOP/5;FBRV/0]", 728 | UserAgent{ 729 | Browser{BrowserSafari, Version{10, 2, 1}}, OS{PlatformiPhone, OSiOS, Version{10, 2, 1}}, DevicePhone}}, 730 | 731 | // TODO handle default browser based on iOS version 732 | // {"Mozilla/5.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/538.34.9 (KHTML, like Gecko) Mobile/12A4265u", 733 | // UserAgent{ 734 | // Browser{BrowserSafari, Version{8,0,0}}, OS{PlatformiPhone, OSiOS, Version{8,0,0}}, DevicePhone}}, 735 | 736 | // TODO extrapolate browser from iOS version 737 | // {"Mozilla/5.0 (iPad; CPU OS 8_0 like Mac OS X) AppleWebKit/538.34.9 (KHTML, like Gecko) Mobile/12A4265u", 738 | // UserAgent{ 739 | // Browser{BrowserSafari, Version{8,0,0}}, OS{PlatformiPad, OSiOS, Version{8,0,0}}, DeviceTablet}}, 740 | 741 | {"Mozilla/5.0 (iPhone; CPU iPhone OS 8_0_2 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12A405 Safari/600.1.4", 742 | UserAgent{ 743 | Browser{BrowserSafari, Version{8, 0, 0}}, OS{PlatformiPhone, OSiOS, Version{8, 0, 2}}, DevicePhone}}, 744 | 745 | {"Mozilla/5.0 (X11; U; Linux x86_64; en; rv:1.9.0.14) Gecko/20080528 Ubuntu/9.10 (karmic) Epiphany/2.22 Firefox/3.0", 746 | UserAgent{ 747 | Browser{BrowserFirefox, Version{3, 0, 0}}, OS{PlatformLinux, OSLinux, Version{0, 0, 0}}, DeviceComputer}}, 748 | 749 | // Can't parse browser due to limitation of user agent library 750 | {"Mozilla/5.0 (X11; U; Linux x86_64; zh-TW; rv:1.9.0.8) Gecko/2009032712 Ubuntu/8.04 (hardy) Firefox/3.0.8 GTB5", 751 | UserAgent{ 752 | Browser{BrowserFirefox, Version{3, 0, 8}}, OS{PlatformLinux, OSLinux, Version{0, 0, 0}}, DeviceComputer}}, 753 | 754 | {"Mozilla/5.0 (compatible; Konqueror/3.5; Linux; x86_64) KHTML/3.5.5 (like Gecko) (Debian)", 755 | UserAgent{ 756 | Browser{BrowserUnknown, Version{0, 0, 0}}, OS{PlatformLinux, OSLinux, Version{0, 0, 0}}, DeviceComputer}}, 757 | 758 | {"Mozilla/5.0 (X11; U; Linux i686; de; rv:1.9.1.5) Gecko/20091112 Iceweasel/3.5.5 (like Firefox/3.5.5; Debian-3.5.5-1)", 759 | UserAgent{ 760 | Browser{BrowserFirefox, Version{3, 5, 5}}, OS{PlatformLinux, OSLinux, Version{0, 0, 0}}, DeviceComputer}}, 761 | 762 | // TODO consider bot? 763 | // {"Miro/2.0.4 (http://www.getmiro.com/; Darwin 10.3.0 i386)", 764 | // UserAgent{ 765 | // Browser{BrowserUnknown, Version{0,0,0}}, OS{PlatformMac, OSMacOSX, Version{3,0,0}}, DeviceComputer}}, 766 | 767 | {"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.4; en-US; rv:1.9.1b3pre) Gecko/20090223 SeaMonkey/2.0a3", 768 | UserAgent{ 769 | Browser{BrowserFirefox, Version{0, 0, 0}}, OS{PlatformMac, OSMacOSX, Version{10, 4, 0}}, DeviceComputer}}, 770 | 771 | {"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_5_5; en-us) AppleWebKit/525.26.2 (KHTML, like Gecko) Version/3.2 Safari/525.26.12", 772 | UserAgent{ 773 | Browser{BrowserSafari, Version{3, 2, 0}}, OS{PlatformMac, OSMacOSX, Version{10, 5, 5}}, DeviceComputer}}, 774 | 775 | {"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en; rv:1.9.0.8pre) Gecko/2009022800 Camino/2.0b3pre", 776 | UserAgent{ 777 | Browser{BrowserUnknown, Version{0, 0, 0}}, OS{PlatformMac, OSMacOSX, Version{10, 5, 0}}, DeviceComputer}}, 778 | 779 | {"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; en-US) AppleWebKit/533.1 (KHTML, like Gecko) Chrome/5.0.329.0 Safari/533.1", 780 | UserAgent{ 781 | Browser{BrowserChrome, Version{5, 0, 329}}, OS{PlatformMac, OSMacOSX, Version{10, 6, 2}}, DeviceComputer}}, 782 | 783 | {"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6 (.NET CLR 3.5.30729)", 784 | UserAgent{ 785 | Browser{BrowserFirefox, Version{3, 5, 6}}, OS{PlatformMac, OSMacOSX, Version{10, 6, 0}}, DeviceComputer}}, 786 | 787 | {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/534.52.7 (KHTML, like Gecko) Version/5.1.2 Safari/534.52.7", 788 | UserAgent{ 789 | Browser{BrowserSafari, Version{5, 1, 2}}, OS{PlatformMac, OSMacOSX, Version{10, 7, 2}}, DeviceComputer}}, 790 | 791 | {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7; rv:9.0) Gecko/20111222 Thunderbird/9.0.1", 792 | UserAgent{ 793 | Browser{BrowserUnknown, Version{0, 0, 0}}, OS{PlatformMac, OSMacOSX, Version{10, 7, 0}}, DeviceComputer}}, 794 | 795 | {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_2) AppleWebKit/535.7 (KHTML, like Gecko) Chrome/16.0.912.75 Safari/535.7", 796 | UserAgent{ 797 | Browser{BrowserChrome, Version{16, 0, 912}}, OS{PlatformMac, OSMacOSX, Version{10, 7, 2}}, DeviceComputer}}, 798 | 799 | {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8) AppleWebKit/535.18.5 (KHTML, like Gecko) Version/5.2 Safari/535.18.5", 800 | UserAgent{ 801 | Browser{BrowserSafari, Version{5, 2, 0}}, OS{PlatformMac, OSMacOSX, Version{10, 8, 0}}, DeviceComputer}}, 802 | 803 | {"Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_8; en-US) AppleWebKit/532.5 (KHTML, like Gecko) Chrome/4.0.249.0 Safari/532.5", 804 | UserAgent{ 805 | Browser{BrowserChrome, Version{4, 0, 249}}, OS{PlatformMac, OSMacOSX, Version{10, 8, 0}}, DeviceComputer}}, 806 | 807 | {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9) AppleWebKit/537.35.1 (KHTML, like Gecko) Version/6.1 Safari/537.35.1", 808 | UserAgent{ 809 | Browser{BrowserSafari, Version{6, 1, 0}}, OS{PlatformMac, OSMacOSX, Version{10, 9, 0}}, DeviceComputer}}, 810 | 811 | {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10) AppleWebKit/538.34.48 (KHTML, like Gecko) Version/8.0 Safari/538.35.8", 812 | UserAgent{ 813 | Browser{BrowserSafari, Version{8, 0, 0}}, OS{PlatformMac, OSMacOSX, Version{10, 10, 0}}, DeviceComputer}}, 814 | 815 | {"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10) AppleWebKit/538.32 (KHTML, like Gecko) Version/7.1 Safari/538.4", 816 | UserAgent{ 817 | Browser{BrowserSafari, Version{7, 1, 0}}, OS{PlatformMac, OSMacOSX, Version{10, 10, 0}}, DeviceComputer}}, 818 | 819 | {"Opera/9.80 (S60; SymbOS; Opera Mobi/352; U; de) Presto/2.4.15 Version/10.00", 820 | UserAgent{ 821 | Browser{BrowserOpera, Version{10, 0, 0}}, OS{PlatformUnknown, OSUnknown, Version{0, 0, 0}}, DevicePhone}}, 822 | 823 | {"Opera/9.80 (S60; SymbOS; Opera Mobi/352; U; de) Presto/2.4.15 Version/10.00", 824 | UserAgent{ 825 | Browser{BrowserOpera, Version{10, 0, 0}}, OS{PlatformUnknown, OSUnknown, Version{0, 0, 0}}, DevicePhone}}, 826 | 827 | // TODO: support OneBrowser? https://play.google.com/store/apps/details?id=com.tencent.ibibo.mtt&hl=en_GB 828 | // {"OneBrowser/3.1 (NokiaN70-1/5.0638.3.0.1)", 829 | // UserAgent{ 830 | // Browser{BrowserUnknown, Version{0,0,0}}, OS{PlatformUnknown, OSUnknown, Version{0,0,0}}, DevicePhone}}, 831 | 832 | // WebOS reports itself as safari :( 833 | {"Mozilla/5.0 (webOS/1.0; U; en-US) AppleWebKit/525.27.1 (KHTML, like Gecko) Version/1.0 Safari/525.27.1 Pre/1.0", 834 | UserAgent{ 835 | Browser{BrowserUnknown, Version{1, 0, 0}}, OS{PlatformLinux, OSWebOS, Version{0, 0, 0}}, DevicePhone}}, 836 | 837 | {"Mozilla/5.0 (webOS/1.4.1.1; U; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Version/1.0 Safari/532.2 Pre/1.0", 838 | UserAgent{ 839 | Browser{BrowserUnknown, Version{1, 0, 0}}, OS{PlatformLinux, OSWebOS, Version{0, 0, 0}}, DevicePhone}}, 840 | 841 | {"Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.0; U; de-DE) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/233.70 Safari/534.6 TouchPad/1.0", 842 | UserAgent{ 843 | Browser{BrowserUnknown, Version{0, 0, 0}}, OS{PlatformLinux, OSWebOS, Version{0, 0, 0}}, DeviceTablet}}, 844 | 845 | {"Mozilla/5.0 (hp-tablet; Linux; hpwOS/3.0.2; U; en-US) AppleWebKit/534.6 (KHTML, like Gecko) wOSBrowser/234.40.1 Safari/534.6 TouchPad/1.0", 846 | UserAgent{ 847 | Browser{BrowserUnknown, Version{0, 0, 0}}, OS{PlatformLinux, OSWebOS, Version{0, 0, 0}}, DeviceTablet}}, 848 | 849 | {"Opera/9.30 (Nintendo Wii; U; ; 2047-7; fr)", 850 | UserAgent{ 851 | Browser{BrowserOpera, Version{9, 30, 0}}, OS{PlatformNintendo, OSNintendo, Version{0, 0, 0}}, DeviceConsole}}, 852 | 853 | {"Mozilla/5.0 (Nintendo WiiU) AppleWebKit/534.52 (KHTML, like Gecko) NX/2.1.0.8.21 NintendoBrowser/1.0.0.7494.US", 854 | UserAgent{ 855 | Browser{BrowserNintendo, Version{0, 0, 0}}, OS{PlatformNintendo, OSNintendo, Version{0, 0, 0}}, DeviceConsole}}, 856 | 857 | {"Mozilla/5.0 (Nintendo WiiU) AppleWebKit/536.28 (KHTML, like Gecko) NX/3.0.3.12.6 NintendoBrowser/2.0.0.9362.US", 858 | UserAgent{ 859 | Browser{BrowserNintendo, Version{0, 0, 0}}, OS{PlatformNintendo, OSNintendo, Version{0, 0, 0}}, DeviceConsole}}, 860 | 861 | // TODO fails to get opera first -- but is this a real UA string or an uncommon spoof? 862 | // {"Mozilla/4.0 (compatible; MSIE 5.0; Windows 2000) Opera 6.0 [en]", 863 | // BrowserIE, Version{5,0,0}}, OS{PlatformWindows, OSWindows, Version{4,0,0}}, DeviceComputer}}, 864 | 865 | {"Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0; SV1; .NET CLR 1.1.4322; .NET CLR 1.0.3705; .NET CLR 2.0.50727)", 866 | UserAgent{ 867 | Browser{BrowserIE, Version{5, 0, 1}}, OS{PlatformWindows, OSWindows, Version{5, 0, 0}}, DeviceComputer}}, 868 | 869 | {"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/4.0; GTB6.4; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; OfficeLiveConnector.1.3; OfficeLivePatch.0.0; .NET CLR 1.1.4322)", 870 | UserAgent{ 871 | Browser{BrowserIE, Version{7, 0, 0}}, OS{PlatformWindows, OSWindows, Version{6, 1, 0}}, DeviceComputer}}, 872 | 873 | {"Mozilla/5.0 (Windows; U; Windows NT 6.1; sk; rv:1.9.1.7) Gecko/20091221 Firefox/3.5.7", 874 | UserAgent{ 875 | Browser{BrowserFirefox, Version{3, 5, 7}}, OS{PlatformWindows, OSWindows, Version{6, 1, 0}}, DeviceComputer}}, 876 | 877 | {"Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0)", 878 | UserAgent{ 879 | Browser{BrowserIE, Version{10, 0, 0}}, OS{PlatformWindows, OSWindows, Version{6, 2, 0}}, DeviceComputer}}, 880 | 881 | {"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/536.5 (KHTML, like Gecko) YaBrowser/1.0.1084.5402 Chrome/19.0.1084.5402 Safari/536.5", 882 | UserAgent{ 883 | Browser{BrowserYandex, Version{1, 0, 1084}}, OS{PlatformWindows, OSWindows, Version{6, 2, 0}}, DeviceComputer}}, 884 | 885 | {"Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.15 (KHTML, like Gecko) Chrome/24.0.1295.0 Safari/537.15", 886 | UserAgent{ 887 | Browser{BrowserChrome, Version{24, 0, 1295}}, OS{PlatformWindows, OSWindows, Version{6, 2, 0}}, DeviceComputer}}, 888 | 889 | {"Mozilla/5.0 (Windows NT 6.3; WOW64; Trident/7.0; Touch; rv:11.0) like Gecko", 890 | UserAgent{ 891 | Browser{BrowserIE, Version{11, 0, 0}}, OS{PlatformWindows, OSWindows, Version{6, 3, 0}}, DeviceTablet}}, 892 | 893 | {"Mozilla/5.0 (IE 11.0; Windows NT 6.3; Trident/7.0; .NET4.0E; .NET4.0C; rv:11.0) like Gecko", 894 | UserAgent{ 895 | Browser{BrowserIE, Version{11, 0, 0}}, OS{PlatformWindows, OSWindows, Version{6, 3, 0}}, DeviceComputer}}, 896 | 897 | // {"Mozilla/4.0 (compatible; MSIE 4.01; Windows 95)", 898 | // UserAgent{ 899 | // Browser{BrowserIE, Version{5,0,0}}, OS{PlatformWindows, OSWindows95, Version{5,0,0}}, DeviceComputer}}, 900 | 901 | // {"Mozilla/4.0 (compatible; MSIE 5.0; Windows 95) Opera 6.02 [en]", 902 | // UserAgent{ 903 | // Browser{BrowserIE, Version{5,0,0}}, OS{PlatformWindows, OSWindows95, Version{5,0,0}}, DeviceComputer}}, 904 | 905 | // {"Mozilla/4.0 (compatible; MSIE 6.0b; Windows 98; YComp 5.0.0.0)", 906 | // UserAgent{ 907 | // Browser{BrowserIE, Version{6,0,0}}, OS{PlatformWindows, OSWindows98, Version{5,0,0}}, DeviceComputer}}, 908 | 909 | // {"Mozilla/4.0 (compatible; MSIE 4.01; Windows 98)", 910 | // UserAgent{ 911 | // Browser{BrowserIE, Version{4,0,0}}, OS{PlatformWindows, OSWindows98, Version{5,0,0}}, DeviceComputer}}, 912 | 913 | // {"Mozilla/5.0 (Windows; U; Windows 98; en-US; rv:1.8.1.8pre) Gecko/20071019 Firefox/2.0.0.8 Navigator/9.0.0.1", 914 | // UserAgent{ 915 | // Browser{BrowserFirefox, Version{2,0,0}}, OS{PlatformWindows, OSWindows98, Version{5,0,0}}, DeviceComputer}}, 916 | 917 | //Can't parse due to limitation of user agent library 918 | // {"Mozilla/5.0 (Windows; U; Windows CE 5.1; rv:1.8.1a3) Gecko/20060610 Minimo/0.016", 919 | // UserAgent{ 920 | // Browser{ BrowserUnknown, Version{0,0,0}}, OS{PlatformWindowsPhone, OSWindowsPhone, Version{0,0,0}}, DevicePhone}}, 921 | 922 | // {"Mozilla/4.0 (compatible; MSIE 4.01; Windows CE; 176x220)", 923 | // UserAgent{ 924 | // Browser{BrowserIE, Version{4,0,0}}, OS{PlatformWindowsPhone, OSWindowsPhone, Version{0,0,0}}, DevicePhone}}, 925 | 926 | // Can't parse browser due to limitation of user agent library 927 | // {"Mozilla/4.0 (compatible; MSIE 5.0; Windows ME) Opera 6.0 [de]", 928 | // UserAgent{ 929 | // Browser{BrowserUnknown, OSWindowsME}, DeviceComputer}}, 930 | 931 | {"Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; SLCC1; .NET CLR 2.0.50727; .NET CLR 1.1.4322; InfoPath.2; .NET CLR 3.5.21022; .NET CLR 3.5.30729; MS-RTC LM 8; OfficeLiveConnector.1.4; OfficeLivePatch.1.3; .NET CLR 3.0.30729)", 932 | UserAgent{ 933 | Browser{BrowserIE, Version{8, 0, 0}}, OS{PlatformWindows, OSWindows, Version{6, 0, 0}}, DeviceComputer}}, 934 | 935 | {"Mozilla/5.0 (Windows; U; Windows NT 5.1; cs; rv:1.9.1.8) Gecko/20100202 Firefox/3.5.8", 936 | UserAgent{ 937 | Browser{BrowserFirefox, Version{3, 5, 8}}, OS{PlatformWindows, OSWindows, Version{5, 1, 0}}, DeviceComputer}}, 938 | 939 | {"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; )", 940 | UserAgent{ 941 | Browser{BrowserIE, Version{7, 0, 0}}, OS{PlatformWindows, OSWindows, Version{5, 1, 0}}, DeviceComputer}}, 942 | 943 | // Can't parse due to limitation of user agent library 944 | {"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; Windows Phone 6.5.3.5)", 945 | UserAgent{ 946 | Browser{BrowserIE, Version{6, 0, 0}}, OS{PlatformWindowsPhone, OSWindowsPhone, Version{6, 5, 3}}, DevicePhone}}, 947 | 948 | // desktop mode for Windows Phone 7 949 | {"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; XBLWP7; ZuneWP7)", 950 | UserAgent{ 951 | Browser{BrowserIE, Version{7, 0, 0}}, OS{PlatformWindows, OSWindows, Version{6, 1, 0}}, DeviceComputer}}, 952 | 953 | // mobile mode for Windows Phone 7 954 | {"Mozilla/4.0 (compatible; MSIE 7.0; Windows Phone OS 7.0; Trident/3.1; IEMobile/7.0; HTC; T8788)", 955 | UserAgent{ 956 | Browser{BrowserIE, Version{7, 0, 0}}, OS{PlatformWindowsPhone, OSWindowsPhone, Version{7, 0, 0}}, DevicePhone}}, 957 | 958 | {"Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0)", 959 | UserAgent{ 960 | Browser{BrowserIE, Version{9, 0, 0}}, OS{PlatformWindowsPhone, OSWindowsPhone, Version{7, 5, 0}}, DevicePhone}}, 961 | 962 | {"Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 920)", 963 | UserAgent{ 964 | Browser{BrowserIE, Version{10, 0, 0}}, OS{PlatformWindowsPhone, OSWindowsPhone, Version{8, 0, 0}}, DevicePhone}}, 965 | 966 | {"Mozilla/5.0 (Windows Phone 8.1; ARM; Trident/7.0; Touch IEMobile/11.0; HTC; Windows Phone 8S by HTC) like Gecko", 967 | UserAgent{ 968 | Browser{BrowserIE, Version{11, 0, 0}}, OS{PlatformWindowsPhone, OSWindowsPhone, Version{8, 1, 0}}, DevicePhone}}, 969 | 970 | {"Mozilla/5.0 (Windows Phone 8.1; ARM; Trident/7.0; Touch IEMobile/11.0; NOKIA; 909) like Gecko", 971 | UserAgent{ 972 | Browser{BrowserIE, Version{11, 0, 0}}, OS{PlatformWindowsPhone, OSWindowsPhone, Version{8, 1, 0}}, DevicePhone}}, 973 | 974 | {"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; Xbox)", 975 | UserAgent{ 976 | Browser{BrowserIE, Version{9, 0, 0}}, OS{PlatformXbox, OSXbox, Version{6, 1, 0}}, DeviceConsole}}, 977 | {"Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) coc_coc_browser/42.0 CoRom/36.0.1985.144 Chrome/36.0.1985.144 Safari/537.36", 978 | UserAgent{ 979 | Browser{BrowserCocCoc, Version{42, 0, 0}}, OS{PlatformWindows, OSWindows, Version{6, 3, 0}}, DeviceComputer}}, 980 | {"Mozilla/5.0 (compatible; coccocbot/1.0; +http://help.coccoc.com/searchengine)", 981 | UserAgent{ 982 | Browser{BrowserCocCocBot, Version{0, 0, 0}}, OS{PlatformBot, OSBot, Version{0, 0, 0}}, DeviceComputer}}, 983 | 984 | {"Mozilla/5.0 (Linux; Android 4.4.4; SM-T560 Build/KTU84P) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.111 Safari/537.36", 985 | UserAgent{ 986 | Browser{BrowserChrome, Version{63, 0, 3239}}, OS{PlatformLinux, OSAndroid, Version{4, 4, 4}}, DeviceTablet}}, 987 | {"Mozilla/5.0 (Linux; Android 5.1.1; KFSUWI) AppleWebKit/537.36 (KHTML, like Gecko) Silk/70.4.2 like Chrome/70.0.3538.80 Safari/537.36", 988 | UserAgent{ 989 | Browser{BrowserSilk, Version{70, 4, 2}}, OS{PlatformLinux, OSAndroid, Version{5, 1, 1}}, DeviceTablet}}, 990 | {"Mozilla/5.0 (Linux; Android 4.4.2; T1-701u Build/HuaweiMediaPad) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.123 Safari/537.36", 991 | UserAgent{ 992 | Browser{BrowserChrome, Version{64, 0, 3282}}, OS{PlatformLinux, OSAndroid, Version{4, 4, 2}}, DeviceTablet}}, 993 | {"Mozilla/5.0 (Linux; Android 4.4.2; Lenovo TAB 2 A7-30F Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.84 Safari/537.36", 994 | UserAgent{ 995 | Browser{BrowserChrome, Version{45, 0, 2454}}, OS{PlatformLinux, OSAndroid, Version{4, 4, 2}}, DeviceTablet}}, 996 | {"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) QtWebEngine/5.9.7 Chrome/56.0.2924.122 Safari/537.36 Sky_STB_BC7445_2018/1.0.0 (Sky, ES140UK, )", 997 | UserAgent{ 998 | Browser{BrowserChrome, Version{56, 0, 2924}}, OS{PlatformLinux, OSLinux, Version{0, 0, 0}}, DeviceTV}}, 999 | {"Mozilla/5.0 (ARRIS_Foxtel_STB_DGX7000NF; Linux mipsel) AppleWebKit/605.1.15 (KHTML, like Gecko) WPE ARRIS_Foxtel_STB_DGX7000NF /1.21.3.9 (Foxtel,DGX7000NF)", 1000 | UserAgent{ 1001 | Browser{BrowserUnknown, Version{0, 0, 0}}, OS{PlatformLinux, OSLinux, Version{0, 0, 0}}, DeviceTV}}, 1002 | {"Mozilla/5.0 (Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0. 3865.120 Safari/537.36 OPR/46.0.2207.0 OMI/4.20.5.80.Catcher3.128 Model/Hisense-MT9602 VIDAA/4.0(Hisense;SmartTV;32A35EUV_0002;MTK9602/V0000.01.00K.M0713;HD)", 1003 | UserAgent{ 1004 | Browser{BrowserOpera, Version{46, 0, 2207}}, OS{PlatformLinux, OSLinux, Version{0, 0, 0}}, DeviceTV}}, 1005 | {"Mozilla/5.0 (Web0S; Linux/SmartTV) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.34 Safari/537.36 WebAppManager", 1006 | UserAgent{ 1007 | Browser{BrowserChrome, Version{53, 0, 2785}}, OS{PlatformLinux, OSLinux, Version{0, 0, 0}}, DeviceTV}}, 1008 | {"Mozilla/5.0 (PlayStation 4 WebMAF) AppleWebKit/601.2 (KHTML, like Gecko) WebMAF/v3.0.2-0-g0f0b69bc SDK: (0x09508001u), Built: Aug 17 2022 20:04:00", 1009 | UserAgent{ 1010 | Browser{BrowserUnknown, Version{0, 0, 0}}, OS{PlatformPlaystation, OSPlaystation, Version{0, 0, 0}}, DeviceConsole}}, 1011 | {"Mozilla/5.0 (PlayStation; PlayStation 5/6.00) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.4 Safari/605.1.15", 1012 | UserAgent{ 1013 | Browser{BrowserSafari, Version{15, 4, 0}}, OS{PlatformPlaystation, OSPlaystation, Version{0, 0, 0}}, DeviceConsole}}, 1014 | {"Mozilla/5.0 (Linux; Tizen 2.3; SmartHub; SMART-TV; SmartTV; U; Maple2012) AppleWebKit/538.1+ (KHTML, like Gecko) TV Safari/538.1+", 1015 | UserAgent{ 1016 | Browser{BrowserUnknown, Version{0, 0, 0}}, OS{PlatformLinux, OSLinux, Version{0, 0, 0}}, DeviceTV}}, 1017 | {"Mozilla/5.0 (Linux; Andr0id 12; IP2300) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.5735.198 Safari/537.36 OPR/46.0.2207.0 OMI/4.24.0.81.CRON5.4 Model/Swisscom-IP2300", 1018 | UserAgent{ 1019 | Browser{BrowserOpera, Version{46, 0, 2207}}, OS{PlatformLinux, OSLinux, Version{0, 0, 0}}, DeviceTV}}, 1020 | {"Mozilla/5.0 (Linux ) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.5359.128 Safari/537.36 OPR/46.0.2207.0 OMI/4.23.2.96.LIMA2.71 Model/Vestel-MB180 VSTVB MB100 FVC/8.0 (OEM; MB180; ) HbbTV/1.6.1 (+DRM; OEM; MB180; 0.20.0.0; ; _TV_G31_2023;) TiVoOS/1.0.0 (Vestel MB180 OEM) SmartTvA/3.0.0", 1021 | UserAgent{ 1022 | Browser{BrowserOpera, Version{46, 0, 2207}}, OS{PlatformLinux, OSLinux, Version{0, 0, 0}}, DeviceTV}}, 1023 | {"Mozilla/5.0 (Linux armv7l) AppleWebKit/602.1.28+ (KHTML, like Gecko) Version/9.1 Safari/601.5.17 WPE/2.22.1, VirginMediaSTB/VIP5002W-mon-web-00.01-148-ae-AL-20220707135023-na001 (Arris_liberty,VIP5002W-PRD,Wireless) HZN/4.43 (MN=VIP5002W-PRD;PC=APLSTB;FV=VIP5002W-mon-web-00.01-148-ae-AL-20220707135023-na001;)", 1024 | UserAgent{ 1025 | Browser{BrowserUnknown, Version{9, 1, 0}}, OS{PlatformLinux, OSLinux, Version{0, 0, 0}}, DeviceTV}}, 1026 | {"Mozilla/5.0 (X11; Linux armv7l) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.121 Safari/537.36 CrKey/1.0.999999 VIZIO SmartCast(Conjure/SX7A-4.6.419.12 FW/11.0.120.1-1 Model/M55-E0)", 1027 | UserAgent{ 1028 | Browser{BrowserChrome, Version{72, 0, 3626}}, OS{PlatformLinux, OSLinux, Version{0, 0, 0}}, DeviceTV}}, 1029 | {"Mozilla/5.0, AppleWebKit/537.36, Chrome/92.0.4515.159, Safari/537.36, OPR/46.0.2207.0, OMI/4.22.1, VODAFONE_STB/7.2.A102.99ba.ngbd BCM7271/7.2.A102.99ba.ngbd/DCIW387/HIGH (Sagemcom_Broadband_SAS, DCIW387_UHD_VF_DE, Wired)", 1030 | UserAgent{ 1031 | Browser{BrowserOpera, Version{46, 0, 2207}}, OS{PlatformUnknown, OSUnknown, Version{0, 0, 0}}, DeviceTV}}, 1032 | {"Mozilla/5.0 (Windows NT 10.0; Win64; x64; Xbox; Xbox One) AppleWebKit/537.36 (KHTML; like Gecko) Chrome/70.0.3538.102 Safari/537.36 Edge/18.19041", 1033 | UserAgent{ 1034 | Browser{BrowserIE, Version{18, 19041, 0}}, OS{PlatformXbox, OSXbox, Version{10, 0, 0}}, DeviceConsole}}, 1035 | {"YouViewHTML/1.0 AppleWebKit/605.1.15 (Sagemcom; RTIW387; RTIW387.002.P; CDS/0.6.216; API/4.0.0; PS/4.14.4) (+DVR+HTML+IPCMC+UHD+DASH+DRM)", 1036 | UserAgent{ 1037 | Browser{BrowserUnknown, Version{0, 0, 0}}, OS{PlatformUnknown, OSUnknown, Version{0, 0, 0}}, DeviceTV}}, 1038 | {"Mozilla/5.0 (Macintosh; Intel Mac OS X 14_4_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.2478.67", 1039 | UserAgent{ 1040 | Browser{BrowserIE, Version{124, 0, 2478}}, OS{PlatformMac, OSMacOSX, Version{14, 4, 1}}, DeviceComputer}}, 1041 | 1042 | // Additional TV user agents 1043 | {"Mozilla/5.0 (Linux; Android 11; AFTKRT Build/RS8133.2817N; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.170 Mobile Safari/537.36", 1044 | UserAgent{ 1045 | Browser{BrowserChrome, Version{130, 0, 6723}}, OS{PlatformLinux, OSAndroid, Version{11, 0, 0}}, DeviceTV}}, 1046 | {"Mozilla/5.0 (Linux; Android 9; AFTSSS Build/PS7690.4719N; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.170 Mobile Safari/537.36", 1047 | UserAgent{ 1048 | Browser{BrowserChrome, Version{130, 0, 6723}}, OS{PlatformLinux, OSAndroid, Version{9, 0, 0}}, DeviceTV}}, 1049 | {"Mozilla/5.0 (Linux; Android 10; BRAVIA 4K UR3 Build/QTG3.200305.006.S73; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/135.0.7049.38 Mobile Safari/537.36", 1050 | UserAgent{ 1051 | Browser{BrowserChrome, Version{135, 0, 7049}}, OS{PlatformLinux, OSAndroid, Version{10, 0, 0}}, DeviceTV}}, 1052 | {"Dalvik/2.1.0 (Linux; U; Android 9; MIBOX4 Build/PI)", 1053 | UserAgent{ 1054 | Browser{BrowserUnknown, Version{0, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{9, 0, 0}}, DeviceTV}}, 1055 | {"Mozilla/5.0 (Linux; Android 12; Chromecast Build/STTL.241013.003; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/135.0.7049.38 Mobile Safari/537.36", 1056 | UserAgent{ 1057 | Browser{BrowserChrome, Version{135, 0, 7049}}, OS{PlatformLinux, OSAndroid, Version{12, 0, 0}}, DeviceTV}}, 1058 | {"Dalvik/2.1.0 (Linux; U; Android 8.0.0; IP100 Build/OPR5.170623.014; Sky) OTTera/14.957 Motorvision", 1059 | UserAgent{ 1060 | Browser{BrowserUnknown, Version{0, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{8, 0, 0}}, DeviceTV}}, 1061 | {"Mozilla/5.0 (Linux; Android 12; OTT-G1 Build/ST; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/130.0.6723.108 Mobile Safari/537.36", 1062 | UserAgent{ 1063 | Browser{BrowserChrome, Version{130, 0, 6723}}, OS{PlatformLinux, OSAndroid, Version{12, 0, 0}}, DeviceTV}}, 1064 | {"Dalvik/2.1.0 (Linux; U; Android 12; Chromecast HD Build/STTL.240812.006)", 1065 | UserAgent{ 1066 | Browser{BrowserUnknown, Version{0, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{12, 0, 0}}, DeviceTV}}, 1067 | {"Mozilla/5.0 (Linux; Android 10; BRAVIA 4K VH21 Build/QTG3.200305.006.S416; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/134.0.6998.135 Mobile Safari/537.36", 1068 | UserAgent{ 1069 | Browser{BrowserChrome, Version{134, 0, 6998}}, OS{PlatformLinux, OSAndroid, Version{10, 0, 0}}, DeviceTV}}, 1070 | {"Mozilla/5.0 (Linux; Android 11; TPM191E Build/RTT2.211108.001; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/135.0.7049.38 Mobile Safari/537.36", 1071 | UserAgent{ 1072 | Browser{BrowserChrome, Version{135, 0, 7049}}, OS{PlatformLinux, OSAndroid, Version{11, 0, 0}}, DeviceTV}}, 1073 | {"Dalvik/2.1.0 (Linux; U; Android 11; BRAVIA TL Build/RTM2.210929.098)", 1074 | UserAgent{ 1075 | Browser{BrowserUnknown, Version{0, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{11, 0, 0}}, DeviceTV}}, 1076 | {"Mozilla/5.0 (Linux; Android 12; Nokia Streaming Box 8000 Build/SC; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/135.0.7049.37 Mobile Safari/537.36", 1077 | UserAgent{ 1078 | Browser{BrowserChrome, Version{135, 0, 7049}}, OS{PlatformLinux, OSAndroid, Version{12, 0, 0}}, DeviceTV}}, 1079 | {"waipu/2025.1.0-49c4c93e14 (Tablet; Google; MBOX; waipu; Android 10)", 1080 | UserAgent{ 1081 | Browser{BrowserUnknown, Version{0, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{10, 0, 0}}, DeviceTV}}, 1082 | {"waipu/2025.5.0-16b788cf99 (Tablet; RockChip; X88Pro13.smartTV.skw.F1010_1.0.0; o2; Android 13)", 1083 | UserAgent{ 1084 | Browser{BrowserUnknown, Version{0, 0, 0}}, OS{PlatformLinux, OSAndroid, Version{13, 0, 0}}, DeviceTV}}, 1085 | } 1086 | 1087 | func TestAgentSurfer(t *testing.T) { 1088 | for _, determined := range testUAVars { 1089 | t.Run("", func(t *testing.T) { 1090 | testFuncs := []func(string) *UserAgent{ 1091 | Parse, 1092 | func(ua string) *UserAgent { 1093 | u := new(UserAgent) 1094 | ParseUserAgent(ua, u) 1095 | return u 1096 | }, 1097 | } 1098 | 1099 | for _, f := range testFuncs { 1100 | ua := f(determined.UA) 1101 | 1102 | if ua.Browser.Name != determined.Browser.Name { 1103 | t.Errorf("browserName: got %v, wanted %v", ua.Browser.Name, determined.Browser.Name) 1104 | t.Logf("agent: %s", determined.UA) 1105 | } 1106 | 1107 | if ua.Browser.Version != determined.Browser.Version { 1108 | t.Errorf("browser version: got %d, wanted %d", ua.Browser.Version, determined.Browser.Version) 1109 | t.Logf("agent: %s", determined.UA) 1110 | } 1111 | 1112 | if ua.OS.Platform != determined.OS.Platform { 1113 | t.Errorf("platform: got %v, wanted %v", ua.OS.Platform, determined.OS.Platform) 1114 | t.Logf("agent: %s", determined.UA) 1115 | } 1116 | 1117 | if ua.OS.Name != determined.OS.Name { 1118 | t.Errorf("os: got %s, wanted %s", ua.OS.Name, determined.OS.Name) 1119 | t.Logf("agent: %s", determined.UA) 1120 | } 1121 | 1122 | if ua.OS.Version != determined.OS.Version { 1123 | t.Errorf("os version: got %d, wanted %d", ua.OS.Version, determined.OS.Version) 1124 | t.Logf("agent: %s", determined.UA) 1125 | } 1126 | 1127 | if ua.DeviceType != determined.DeviceType { 1128 | t.Errorf("device type: got %v, wanted %v", ua.DeviceType, determined.DeviceType) 1129 | t.Logf("agent: %s", determined.UA) 1130 | } 1131 | } 1132 | }) 1133 | } 1134 | } 1135 | 1136 | func BenchmarkAgentSurfer(b *testing.B) { 1137 | num := len(testUAVars) 1138 | b.ResetTimer() 1139 | for i := 0; i < b.N; i++ { 1140 | Parse(testUAVars[i%num].UA) 1141 | } 1142 | } 1143 | 1144 | func BenchmarkAgentSurferReuse(b *testing.B) { 1145 | dest := new(UserAgent) 1146 | num := len(testUAVars) 1147 | b.ResetTimer() 1148 | for i := 0; i < b.N; i++ { 1149 | dest.Reset() 1150 | ParseUserAgent(testUAVars[i%num].UA, dest) 1151 | } 1152 | } 1153 | 1154 | func BenchmarkEvalSystem(b *testing.B) { 1155 | num := len(testUAVars) 1156 | v := UserAgent{} 1157 | b.ResetTimer() 1158 | for i := 0; i < b.N; i++ { 1159 | v.evalOS(testUAVars[i%num].UA) 1160 | } 1161 | } 1162 | 1163 | func BenchmarkEvalBrowserName(b *testing.B) { 1164 | num := len(testUAVars) 1165 | v := UserAgent{} 1166 | b.ResetTimer() 1167 | for i := 0; i < b.N; i++ { 1168 | v.evalBrowserName(testUAVars[i%num].UA) 1169 | } 1170 | } 1171 | 1172 | func BenchmarkEvalBrowserVersion(b *testing.B) { 1173 | num := len(testUAVars) 1174 | v := UserAgent{} 1175 | b.ResetTimer() 1176 | for i := 0; i < b.N; i++ { 1177 | v.Browser.Name = testUAVars[i%num].Browser.Name 1178 | v.evalBrowserVersion(testUAVars[i%num].UA) 1179 | } 1180 | } 1181 | 1182 | func BenchmarkEvalDevice(b *testing.B) { 1183 | num := len(testUAVars) 1184 | v := UserAgent{} 1185 | 1186 | b.ResetTimer() 1187 | for i := 0; i < b.N; i++ { 1188 | v.OS.Name = testUAVars[i%num].OS.Name 1189 | v.OS.Platform = testUAVars[i%num].OS.Platform 1190 | v.Browser.Name = testUAVars[i%num].Browser.Name 1191 | v.evalDevice(testUAVars[i%num].UA) 1192 | } 1193 | } 1194 | 1195 | // Chrome for Mac 1196 | func BenchmarkParseChromeMac(b *testing.B) { 1197 | b.ResetTimer() 1198 | for i := 0; i < b.N; i++ { 1199 | Parse("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.130 Safari/537.36") 1200 | } 1201 | } 1202 | 1203 | // Chrome for Windows 1204 | func BenchmarkParseChromeWin(b *testing.B) { 1205 | b.ResetTimer() 1206 | for i := 0; i < b.N; i++ { 1207 | Parse("Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.134 Safari/537.36") 1208 | } 1209 | } 1210 | 1211 | // Chrome for Android 1212 | func BenchmarkParseChromeAndroid(b *testing.B) { 1213 | b.ResetTimer() 1214 | for i := 0; i < b.N; i++ { 1215 | Parse("Mozilla/5.0 (Linux; Android 4.4.2; GT-P5210 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.93 Safari/537.36") 1216 | } 1217 | } 1218 | 1219 | // Safari for Mac 1220 | func BenchmarkParseSafariMac(b *testing.B) { 1221 | b.ResetTimer() 1222 | for i := 0; i < b.N; i++ { 1223 | Parse("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_4) AppleWebKit/600.7.12 (KHTML, like Gecko) Version/8.0.7 Safari/600.7.12") 1224 | } 1225 | } 1226 | 1227 | // Safari for iPad 1228 | func BenchmarkParseSafariiPad(b *testing.B) { 1229 | b.ResetTimer() 1230 | for i := 0; i < b.N; i++ { 1231 | Parse("Mozilla/5.0 (iPad; CPU OS 8_1_2 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12B440 Safari/600.1.4") 1232 | } 1233 | } 1234 | 1235 | func TestStringTrimPrefix(t *testing.T) { 1236 | testCases := []struct { 1237 | f func() string 1238 | expected string 1239 | }{ 1240 | { 1241 | f: DeviceUnknown.StringTrimPrefix, 1242 | expected: "Unknown", 1243 | }, 1244 | { 1245 | f: BrowserUnknown.StringTrimPrefix, 1246 | expected: "Unknown", 1247 | }, 1248 | { 1249 | f: OSUnknown.StringTrimPrefix, 1250 | expected: "Unknown", 1251 | }, 1252 | { 1253 | f: PlatformUnknown.StringTrimPrefix, 1254 | expected: "Unknown", 1255 | }, 1256 | } 1257 | 1258 | for _, tc := range testCases { 1259 | t.Run("", func(t *testing.T) { 1260 | s := tc.f() 1261 | if tc.expected != s { 1262 | t.Fatalf("Expected %q, got %q", tc.expected, s) 1263 | } 1264 | }) 1265 | } 1266 | } 1267 | --------------------------------------------------------------------------------