From 56e74239d6b34df8f30ef046f0b0ff4ff0866a71 Mon Sep 17 00:00:00 2001 From: =Corey Hulen Date: Sun, 14 Jun 2015 23:53:32 -0800 Subject: first commit --- .../src/github.com/mssola/user_agent/.travis.yml | 11 + .../src/github.com/mssola/user_agent/LICENSE | 20 ++ .../src/github.com/mssola/user_agent/all_test.go | 257 ++++++++++++++++++++ .../src/github.com/mssola/user_agent/bot.go | 121 ++++++++++ .../src/github.com/mssola/user_agent/browser.go | 120 ++++++++++ .../mssola/user_agent/operating_systems.go | 260 +++++++++++++++++++++ .../src/github.com/mssola/user_agent/user_agent.go | 169 ++++++++++++++ 7 files changed, 958 insertions(+) create mode 100644 Godeps/_workspace/src/github.com/mssola/user_agent/.travis.yml create mode 100644 Godeps/_workspace/src/github.com/mssola/user_agent/LICENSE create mode 100644 Godeps/_workspace/src/github.com/mssola/user_agent/all_test.go create mode 100644 Godeps/_workspace/src/github.com/mssola/user_agent/bot.go create mode 100644 Godeps/_workspace/src/github.com/mssola/user_agent/browser.go create mode 100644 Godeps/_workspace/src/github.com/mssola/user_agent/operating_systems.go create mode 100644 Godeps/_workspace/src/github.com/mssola/user_agent/user_agent.go (limited to 'Godeps/_workspace/src/github.com/mssola/user_agent') diff --git a/Godeps/_workspace/src/github.com/mssola/user_agent/.travis.yml b/Godeps/_workspace/src/github.com/mssola/user_agent/.travis.yml new file mode 100644 index 000000000..922091263 --- /dev/null +++ b/Godeps/_workspace/src/github.com/mssola/user_agent/.travis.yml @@ -0,0 +1,11 @@ +language: go +go: + - 1.0 + - 1.1 + - 1.2 + - 1.3 + - 1.4 + - tip +matrix: + allow_failures: + - go: tip diff --git a/Godeps/_workspace/src/github.com/mssola/user_agent/LICENSE b/Godeps/_workspace/src/github.com/mssola/user_agent/LICENSE new file mode 100644 index 000000000..21d3935c5 --- /dev/null +++ b/Godeps/_workspace/src/github.com/mssola/user_agent/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2012-2015 Miquel Sabaté Solà + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Godeps/_workspace/src/github.com/mssola/user_agent/all_test.go b/Godeps/_workspace/src/github.com/mssola/user_agent/all_test.go new file mode 100644 index 000000000..6dca19d5c --- /dev/null +++ b/Godeps/_workspace/src/github.com/mssola/user_agent/all_test.go @@ -0,0 +1,257 @@ +// Copyright (C) 2012-2015 Miquel Sabaté Solà +// This file is licensed under the MIT license. +// See the LICENSE file. + +package user_agent + +import ( + "fmt" + "testing" +) + +// Slice that contains all the tests. Each test is contained in a struct +// that groups the name of the test and the User-Agent string to be tested. +var uastrings = []struct { + name string + ua string +}{ + // Bots + {"GoogleBot", "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"}, + {"GoogleBotSmartphone", "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)"}, + {"BingBot", "Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)"}, + {"BaiduBot", "Mozilla/5.0 (compatible; Baiduspider/2.0; +http://www.baidu.com/search/spider.html)"}, + {"Twitterbot", "Twitterbot"}, + {"YahooBot", "Mozilla/5.0 (compatible; Yahoo! Slurp; http://help.yahoo.com/help/us/ysearch/slurp)"}, + {"FacebookExternalHit", "facebookexternalhit/1.1 (+http://www.facebook.com/externalhit_uatext.php)"}, + {"FacebookPlatform", "facebookplatform/1.0 (+http://developers.facebook.com)"}, + {"FaceBot", "Facebot"}, + + // Internet Explorer + {"IE10", "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Trident/6.0)"}, + {"Tablet", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.2; ARM; Trident/6.0; Touch; .NET4.0E; .NET4.0C; Tablet PC 2.0)"}, + {"Touch", "Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; ARM; Trident/6.0; Touch)"}, + {"Phone", "Mozilla/4.0 (compatible; MSIE 7.0; Windows Phone OS 7.0; Trident/3.1; IEMobile/7.0; SAMSUNG; SGH-i917)"}, + {"IE6", "Mozilla/4.0 (compatible; MSIE6.0; Windows NT 5.0; .NET CLR 1.1.4322)"}, + {"IE8Compatibility", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; InfoPath.3; MS-RTC LM 8)"}, + {"IE10Compatibility", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/6.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; InfoPath.3; MS-RTC LM 8)"}, + {"IE11Win81", "Mozilla/5.0 (Windows NT 6.3; Trident/7.0; rv:11.0) like Gecko"}, + {"IE11Win7", "Mozilla/5.0 (Windows NT 6.1; Trident/7.0; rv:11.0) like Gecko"}, + {"IE11b32Win7b64", "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko"}, + {"IE11b32Win7b64MDDRJS", "Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; MDDRJS; rv:11.0) like Gecko"}, + {"IE11Compatibility", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.3; Trident/7.0)"}, + + // Gecko + {"FirefoxMac", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.6; rv:2.0b8) Gecko/20100101 Firefox/4.0b8"}, + {"FirefoxMacLoc", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.2.13) Gecko/20101203 Firefox/3.6.13"}, + {"FirefoxLinux", "Mozilla/5.0 (X11; Linux x86_64; rv:17.0) Gecko/20100101 Firefox/17.0"}, + {"FirefoxWin", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.14) Gecko/20080404 Firefox/2.0.0.14"}, + {"Firefox29Win7", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:29.0) Gecko/20100101 Firefox/29.0"}, + {"CaminoMac", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en; rv:1.8.1.14) Gecko/20080409 Camino/1.6 (like Firefox/2.0.0.14)"}, + {"Iceweasel", "Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1) Gecko/20061024 Iceweasel/2.0 (Debian-2.0+dfsg-1)"}, + {"SeaMonkey", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; en-US; rv:1.9.1.4) Gecko/20091017 SeaMonkey/2.0"}, + {"AndroidFirefox", "Mozilla/5.0 (Android; Mobile; rv:17.0) Gecko/17.0 Firefox/17.0"}, + {"AndroidFirefoxTablet", "Mozilla/5.0 (Android; Tablet; rv:26.0) Gecko/26.0 Firefox/26.0"}, + {"FirefoxOS", "Mozilla/5.0 (Mobile; rv:26.0) Gecko/26.0 Firefox/26.0"}, + {"FirefoxOSTablet", "Mozilla/5.0 (Tablet; rv:26.0) Gecko/26.0 Firefox/26.0"}, + {"FirefoxWinXP", "Mozilla/5.0 (Windows NT 5.2; rv:31.0) Gecko/20100101 Firefox/31.0"}, + {"FirefoxMRA", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:24.0) Gecko/20130405 MRA 5.5 (build 02842) Firefox/24.0 (.NET CLR 3.5.30729)"}, + + // Opera + {"OperaMac", "Opera/9.27 (Macintosh; Intel Mac OS X; U; en)"}, + {"OperaWin", "Opera/9.27 (Windows NT 5.1; U; en)"}, + {"OperaWinNoLocale", "Opera/9.80 (Windows NT 5.1) Presto/2.12.388 Version/12.10"}, + {"OperaWin2Comment", "Opera/9.80 (Windows NT 6.0; WOW64) Presto/2.12.388 Version/12.15"}, + {"OperaMinimal", "Opera/9.80"}, + {"OperaFull", "Opera/9.80 (Windows NT 6.0; U; en) Presto/2.2.15 Version/10.10"}, + {"OperaLinux", "Opera/9.80 (X11; Linux x86_64) Presto/2.12.388 Version/12.10"}, + {"OperaAndroid", "Opera/9.80 (Android 4.2.1; Linux; Opera Mobi/ADR-1212030829) Presto/2.11.355 Version/12.10"}, + {"OperaNested", "Opera/9.80 (Windows NT 5.1; MRA 6.0 (build 5831)) Presto/2.12.388 Version/12.10"}, + {"OperaMRA", "Opera/9.80 (Windows NT 6.1; U; MRA 5.8 (build 4139); en) Presto/2.9.168 Version/11.50"}, + + // Other + {"Empty", ""}, + {"Nil", "nil"}, + {"Compatible", "Mozilla/4.0 (compatible)"}, + {"Mozilla", "Mozilla/5.0"}, + {"Amaya", "amaya/9.51 libwww/5.4.0"}, + {"Rails", "Rails Testing"}, + {"Python", "Python-urllib/2.7"}, + {"Curl", "curl/7.28.1"}, + + // WebKit + {"ChromeLinux", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.97 Safari/537.11"}, + {"ChromeWin7", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.168 Safari/535.19"}, + {"ChromeMinimal", "Mozilla/5.0 AppleWebKit/534.10 Chrome/8.0.552.215 Safari/534.10"}, + {"ChromeMac", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_5; en-US) AppleWebKit/534.10 (KHTML, like Gecko) Chrome/8.0.552.231 Safari/534.10"}, + {"SafariMac", "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_3; en-us) AppleWebKit/533.16 (KHTML, like Gecko) Version/5.0 Safari/533.16"}, + {"SafariWin", "Mozilla/5.0 (Windows; U; Windows NT 5.1; en) AppleWebKit/526.9 (KHTML, like Gecko) Version/4.0dp1 Safari/526.8"}, + {"iPhone7", "Mozilla/5.0 (iPhone; CPU iPhone OS 7_0_3 like Mac OS X) AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 Mobile/11B511 Safari/9537.53"}, + {"iPhone", "Mozilla/5.0 (iPhone; U; CPU like Mac OS X; en) AppleWebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/4A102 Safari/419"}, + {"iPod", "Mozilla/5.0 (iPod; U; CPU like Mac OS X; en) AppleWebKit/420.1 (KHTML, like Gecko) Version/3.0 Mobile/4A102 Safari/419"}, + {"iPad", "Mozilla/5.0 (iPad; U; CPU OS 3_2 like Mac OS X; en-us) AppleWebKit/531.21.10 (KHTML, like Gecko) Version/4.0.4 Mobile/7B367 Safari/531.21.10"}, + {"webOS", "Mozilla/5.0 (webOS/1.4.0; U; en-US) AppleWebKit/532.2 (KHTML, like Gecko) Version/1.0 Safari/532.2 Pre/1.1"}, + {"Android", "Mozilla/5.0 (Linux; U; Android 1.5; de-; HTC Magic Build/PLAT-RC33) AppleWebKit/528.5+ (KHTML, like Gecko) Version/3.1.2 Mobile Safari/525.20.1"}, + {"BlackBerry", "Mozilla/5.0 (BlackBerry; U; BlackBerry 9800; en) AppleWebKit/534.1+ (KHTML, Like Gecko) Version/6.0.0.141 Mobile Safari/534.1+"}, + {"BB10", "Mozilla/5.0 (BB10; Touch) AppleWebKit/537.3+ (KHTML, like Gecko) Version/10.0.9.388 Mobile Safari/537.3+"}, + {"Ericsson", "Mozilla/5.0 (SymbianOS/9.4; U; Series60/5.0 Profile/MIDP-2.1 Configuration/CLDC-1.1) AppleWebKit/525 (KHTML, like Gecko) Version/3.0 Safari/525"}, + {"ChromeAndroid", "Mozilla/5.0 (Linux; Android 4.2.1; Galaxy Nexus Build/JOP40D) AppleWebKit/535.19 (KHTML, like Gecko) Chrome/18.0.1025.166 Mobile Safari/535.19"}, + {"WebkitNoPlatform", "Mozilla/5.0 (en-us) AppleWebKit/525.13 (KHTML, like Gecko; Google Web Preview) Version/3.1 Safari/525.13"}, + {"OperaWebkitMobile", "Mozilla/5.0 (Linux; Android 4.2.2; Galaxy Nexus Build/JDQ39) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.58 Mobile Safari/537.31 OPR/14.0.1074.57453"}, + {"OperaWebkitDesktop", "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.31 (KHTML, like Gecko) Chrome/26.0.1410.58 Safari/537.31 OPR/14.0.1074.57453"}, + {"ChromeNothingAfterU", "Mozilla/5.0 (Linux; U) AppleWebKit/537.4 (KHTML, like Gecko) Chrome/22.0.1229.79 Safari/537.4"}, + {"SafariOnSymbian", "Mozilla/5.0 (SymbianOS/9.1; U; [en-us]) AppleWebKit/413 (KHTML, like Gecko) Safari/413"}, +} + +// Slice of the expected results from the previous slice. +var expected = []string{ + // Bots + "Mozilla:5.0 Browser:Googlebot-2.1 Bot:true Mobile:false", + "Mozilla:5.0 Browser:Googlebot-2.1 Bot:true Mobile:true", + "Mozilla:5.0 Browser:bingbot-2.0 Bot:true Mobile:false", + "Mozilla:5.0 Browser:Baiduspider-2.0 Bot:true Mobile:false", + "Browser:Twitterbot Bot:true Mobile:false", + "Mozilla:5.0 Browser:Yahoo! Slurp Bot:true Mobile:false", + "Browser:facebookexternalhit-1.1 Bot:true Mobile:false", + "Browser:facebookplatform-1.0 Bot:true Mobile:false", + "Browser:Facebot Bot:true Mobile:false", + + // Internet Explorer + "Mozilla:5.0 Platform:Windows OS:Windows 8 Browser:Internet Explorer-10.0 Engine:Trident Bot:false Mobile:false", + "Mozilla:4.0 Platform:Windows OS:Windows 8 Browser:Internet Explorer-10.0 Engine:Trident Bot:false Mobile:false", + "Mozilla:5.0 Platform:Windows OS:Windows 8 Browser:Internet Explorer-10.0 Engine:Trident Bot:false Mobile:false", + "Mozilla:4.0 Platform:Windows OS:Windows Phone OS 7.0 Browser:Internet Explorer-7.0 Engine:Trident Bot:false Mobile:true", + "Mozilla:4.0 Platform:Windows OS:Windows 2000 Browser:Internet Explorer-6.0 Engine:Trident Bot:false Mobile:false", + "Mozilla:4.0 Platform:Windows OS:Windows 7 Browser:Internet Explorer-8.0 Engine:Trident Bot:false Mobile:false", + "Mozilla:4.0 Platform:Windows OS:Windows 7 Browser:Internet Explorer-10.0 Engine:Trident Bot:false Mobile:false", + "Mozilla:5.0 Platform:Windows OS:Windows 8.1 Browser:Internet Explorer-11.0 Engine:Trident Bot:false Mobile:false", + "Mozilla:5.0 Platform:Windows OS:Windows 7 Browser:Internet Explorer-11.0 Engine:Trident Bot:false Mobile:false", + "Mozilla:5.0 Platform:Windows OS:Windows 7 Browser:Internet Explorer-11.0 Engine:Trident Bot:false Mobile:false", + "Mozilla:5.0 Platform:Windows OS:Windows 7 Browser:Internet Explorer-11.0 Engine:Trident Bot:false Mobile:false", + "Mozilla:4.0 Platform:Windows OS:Windows 8.1 Browser:Internet Explorer-7.0 Engine:Trident Bot:false Mobile:false", + + // Gecko + "Mozilla:5.0 Platform:Macintosh OS:Intel Mac OS X 10.6 Browser:Firefox-4.0b8 Engine:Gecko-20100101 Bot:false Mobile:false", + "Mozilla:5.0 Platform:Macintosh OS:Intel Mac OS X 10.6 Localization:en-US Browser:Firefox-3.6.13 Engine:Gecko-20101203 Bot:false Mobile:false", + "Mozilla:5.0 Platform:X11 OS:Linux x86_64 Browser:Firefox-17.0 Engine:Gecko-20100101 Bot:false Mobile:false", + "Mozilla:5.0 Platform:Windows OS:Windows XP Localization:en-US Browser:Firefox-2.0.0.14 Engine:Gecko-20080404 Bot:false Mobile:false", + "Mozilla:5.0 Platform:Windows OS:Windows 7 Browser:Firefox-29.0 Engine:Gecko-20100101 Bot:false Mobile:false", + "Mozilla:5.0 Platform:Macintosh OS:Intel Mac OS X Localization:en Browser:Camino-1.6 Engine:Gecko-20080409 Bot:false Mobile:false", + "Mozilla:5.0 Platform:X11 OS:Linux i686 Localization:en-US Browser:Iceweasel-2.0 Engine:Gecko-20061024 Bot:false Mobile:false", + "Mozilla:5.0 Platform:Macintosh OS:Intel Mac OS X 10.6 Localization:en-US Browser:SeaMonkey-2.0 Engine:Gecko-20091017 Bot:false Mobile:false", + "Mozilla:5.0 Platform:Mobile OS:Android Browser:Firefox-17.0 Engine:Gecko-17.0 Bot:false Mobile:true", + "Mozilla:5.0 Platform:Tablet OS:Android Browser:Firefox-26.0 Engine:Gecko-26.0 Bot:false Mobile:true", + "Mozilla:5.0 Platform:Mobile OS:FirefoxOS Browser:Firefox-26.0 Engine:Gecko-26.0 Bot:false Mobile:true", + "Mozilla:5.0 Platform:Tablet OS:FirefoxOS Browser:Firefox-26.0 Engine:Gecko-26.0 Bot:false Mobile:true", + "Mozilla:5.0 Platform:Windows OS:Windows XP x64 Edition Browser:Firefox-31.0 Engine:Gecko-20100101 Bot:false Mobile:false", + "Mozilla:5.0 Platform:Windows OS:Windows XP Localization:en-US Browser:Firefox-24.0 Engine:Gecko-20130405 Bot:false Mobile:false", + + // Opera + "Platform:Macintosh OS:Intel Mac OS X Localization:en Browser:Opera-9.27 Engine:Presto Bot:false Mobile:false", + "Platform:Windows OS:Windows XP Localization:en Browser:Opera-9.27 Engine:Presto Bot:false Mobile:false", + "Platform:Windows OS:Windows XP Browser:Opera-9.80 Engine:Presto-2.12.388 Bot:false Mobile:false", + "Platform:Windows OS:Windows Vista Browser:Opera-9.80 Engine:Presto-2.12.388 Bot:false Mobile:false", + "Browser:Opera-9.80 Engine:Presto Bot:false Mobile:false", + "Platform:Windows OS:Windows Vista Localization:en Browser:Opera-9.80 Engine:Presto-2.2.15 Bot:false Mobile:false", + "Platform:X11 OS:Linux x86_64 Browser:Opera-9.80 Engine:Presto-2.12.388 Bot:false Mobile:false", + "Platform:Android 4.2.1 OS:Linux Browser:Opera-9.80 Engine:Presto-2.11.355 Bot:false Mobile:true", + "Platform:Windows OS:Windows XP Browser:Opera-9.80 Engine:Presto-2.12.388 Bot:false Mobile:false", + "Platform:Windows OS:Windows 7 Localization:en Browser:Opera-9.80 Engine:Presto-2.9.168 Bot:false Mobile:false", + + // Other + "Bot:false Mobile:false", + "Browser:nil Bot:false Mobile:false", + "Browser:Mozilla-4.0 Bot:false Mobile:false", + "Browser:Mozilla-5.0 Bot:false Mobile:false", + "Browser:amaya-9.51 Engine:libwww-5.4.0 Bot:false Mobile:false", + "Browser:Rails Engine:Testing Bot:false Mobile:false", + "Browser:Python-urllib-2.7 Bot:false Mobile:false", + "Browser:curl-7.28.1 Bot:false Mobile:false", + + // WebKit + "Mozilla:5.0 Platform:X11 OS:Linux x86_64 Browser:Chrome-23.0.1271.97 Engine:AppleWebKit-537.11 Bot:false Mobile:false", + "Mozilla:5.0 Platform:Windows OS:Windows 7 Browser:Chrome-18.0.1025.168 Engine:AppleWebKit-535.19 Bot:false Mobile:false", + "Mozilla:5.0 Browser:Chrome-8.0.552.215 Engine:AppleWebKit-534.10 Bot:false Mobile:false", + "Mozilla:5.0 Platform:Macintosh OS:Intel Mac OS X 10_6_5 Localization:en-US Browser:Chrome-8.0.552.231 Engine:AppleWebKit-534.10 Bot:false Mobile:false", + "Mozilla:5.0 Platform:Macintosh OS:Intel Mac OS X 10_6_3 Localization:en-us Browser:Safari-5.0 Engine:AppleWebKit-533.16 Bot:false Mobile:false", + "Mozilla:5.0 Platform:Windows OS:Windows XP Localization:en Browser:Safari-4.0dp1 Engine:AppleWebKit-526.9 Bot:false Mobile:false", + "Mozilla:5.0 Platform:iPhone OS:CPU iPhone OS 7_0_3 like Mac OS X Browser:Safari-7.0 Engine:AppleWebKit-537.51.1 Bot:false Mobile:true", + "Mozilla:5.0 Platform:iPhone OS:CPU like Mac OS X Localization:en Browser:Safari-3.0 Engine:AppleWebKit-420.1 Bot:false Mobile:true", + "Mozilla:5.0 Platform:iPod OS:CPU like Mac OS X Localization:en Browser:Safari-3.0 Engine:AppleWebKit-420.1 Bot:false Mobile:true", + "Mozilla:5.0 Platform:iPad OS:CPU OS 3_2 like Mac OS X Localization:en-us Browser:Safari-4.0.4 Engine:AppleWebKit-531.21.10 Bot:false Mobile:true", + "Mozilla:5.0 Platform:webOS OS:Palm Localization:en-US Browser:webOS-1.0 Engine:AppleWebKit-532.2 Bot:false Mobile:true", + "Mozilla:5.0 Platform:Linux OS:Android 1.5 Localization:de- Browser:Android-3.1.2 Engine:AppleWebKit-528.5+ Bot:false Mobile:true", + "Mozilla:5.0 Platform:BlackBerry OS:BlackBerry 9800 Localization:en Browser:BlackBerry-6.0.0.141 Engine:AppleWebKit-534.1+ Bot:false Mobile:true", + "Mozilla:5.0 Platform:BlackBerry OS:BlackBerry Browser:BlackBerry-10.0.9.388 Engine:AppleWebKit-537.3+ Bot:false Mobile:true", + "Mozilla:5.0 Platform:Symbian OS:SymbianOS/9.4 Browser:Symbian-3.0 Engine:AppleWebKit-525 Bot:false Mobile:true", + "Mozilla:5.0 Platform:Linux OS:Android 4.2.1 Browser:Chrome-18.0.1025.166 Engine:AppleWebKit-535.19 Bot:false Mobile:true", + "Mozilla:5.0 Platform:en-us Localization:en-us Browser:Safari-3.1 Engine:AppleWebKit-525.13 Bot:false Mobile:false", + "Mozilla:5.0 Platform:Linux OS:Android 4.2.2 Browser:Opera-14.0.1074.57453 Engine:AppleWebKit-537.31 Bot:false Mobile:true", + "Mozilla:5.0 Platform:X11 OS:Linux x86_64 Browser:Opera-14.0.1074.57453 Engine:AppleWebKit-537.31 Bot:false Mobile:false", + "Mozilla:5.0 Platform:Linux OS:Linux Browser:Chrome-22.0.1229.79 Engine:AppleWebKit-537.4 Bot:false Mobile:false", + "Mozilla:5.0 Platform:Symbian OS:SymbianOS/9.1 Browser:Symbian-413 Engine:AppleWebKit-413 Bot:false Mobile:true", +} + +// Internal: beautify the UserAgent reference into a string so it can be +// tested later on. +// +// ua - a UserAgent reference. +// +// Returns a string that contains the beautified representation. +func beautify(ua *UserAgent) (s string) { + if len(ua.Mozilla()) > 0 { + s += "Mozilla:" + ua.Mozilla() + " " + } + if len(ua.Platform()) > 0 { + s += "Platform:" + ua.Platform() + " " + } + if len(ua.OS()) > 0 { + s += "OS:" + ua.OS() + " " + } + if len(ua.Localization()) > 0 { + s += "Localization:" + ua.Localization() + " " + } + str1, str2 := ua.Browser() + if len(str1) > 0 { + s += "Browser:" + str1 + if len(str2) > 0 { + s += "-" + str2 + " " + } else { + s += " " + } + } + str1, str2 = ua.Engine() + if len(str1) > 0 { + s += "Engine:" + str1 + if len(str2) > 0 { + s += "-" + str2 + " " + } else { + s += " " + } + } + s += "Bot:" + fmt.Sprintf("%v", ua.Bot()) + " " + s += "Mobile:" + fmt.Sprintf("%v", ua.Mobile()) + return s +} + +// The test suite. +func TestUserAgent(t *testing.T) { + for i, tt := range uastrings { + ua := New(tt.ua) + got := beautify(ua) + if expected[i] != got { + t.Errorf("Test %v => %q, expected %q", tt.name, got, expected[i]) + } + } +} + +// Benchmark: it parses each User-Agent string on the uastrings slice b.N times. +func BenchmarkUserAgent(b *testing.B) { + for i := 0; i < b.N; i++ { + b.StopTimer() + for _, tt := range uastrings { + ua := new(UserAgent) + b.StartTimer() + ua.Parse(tt.ua) + } + } +} diff --git a/Godeps/_workspace/src/github.com/mssola/user_agent/bot.go b/Godeps/_workspace/src/github.com/mssola/user_agent/bot.go new file mode 100644 index 000000000..cc993d8fe --- /dev/null +++ b/Godeps/_workspace/src/github.com/mssola/user_agent/bot.go @@ -0,0 +1,121 @@ +// Copyright (C) 2014-2015 Miquel Sabaté Solà +// This file is licensed under the MIT license. +// See the LICENSE file. + +package user_agent + +import ( + "regexp" + "strings" +) + +// Get the name of the bot from the website that may be in the given comment. If +// there is no website in the comment, then an empty string is returned. +func getFromSite(comment []string) string { + if len(comment) == 0 { + return "" + } + + // Where we should check the website. + idx := 2 + if len(comment) < 3 { + idx = 0 + } + + // Pick the site. + re := regexp.MustCompile("http://.+\\.\\w+") + results := re.FindStringSubmatch(comment[idx]) + if len(results) == 1 { + // If it's a simple comment, just return the name of the site. + if idx == 0 { + return results[0] + } + + // This is a large comment, usually the name will be in the previous + // field of the comment. + return strings.TrimSpace(comment[1]) + } + return "" +} + +// Returns true if the info that we currently have corresponds to the Google +// mobile bot. This function also modifies some attributes in the receiver +// accordingly. +func (p *UserAgent) googleBot() bool { + // This is a hackish way to detect Google's mobile bot. + if strings.Index(p.ua, "Googlebot") != -1 { + p.platform = "" + p.undecided = true + } + return p.undecided +} + +// Set the attributes of the receiver as given by the parameters. All the other +// parameters are set to empty. +func (p *UserAgent) setSimple(name, version string, bot bool) { + p.bot = bot + if !bot { + p.mozilla = "" + } + p.browser.Name = name + p.browser.Version = version + p.browser.Engine = "" + p.browser.EngineVersion = "" + p.os = "" + p.localization = "" +} + +// Fix some values for some weird browsers. +func (p *UserAgent) fixOther(sections []section) { + if len(sections) > 0 { + p.browser.Name = sections[0].name + p.browser.Version = sections[0].version + p.mozilla = "" + } +} + +// Check if we're dealing with a bot or with some weird browser. If that is the +// case, the receiver will be modified accordingly. +func (p *UserAgent) checkBot(sections []section) { + // If there's only one element, and it's doesn't have the Mozilla string, + // check whether this is a bot or not. + if len(sections) == 1 && sections[0].name != "Mozilla" { + p.mozilla = "" + + // Check whether the name has some suspicious "bot" in his name. + reg, _ := regexp.Compile("(?i)bot") + if reg.Match([]byte(sections[0].name)) { + p.setSimple(sections[0].name, "", true) + return + } + + // Tough luck, let's try to see if it has a website in his comment. + if name := getFromSite(sections[0].comment); name != "" { + // First of all, this is a bot. Moreover, since it doesn't have the + // Mozilla string, we can assume that the name and the version are + // the ones from the first section. + p.setSimple(sections[0].name, sections[0].version, true) + return + } + + // At this point we are sure that this is not a bot, but some weirdo. + p.setSimple(sections[0].name, sections[0].version, false) + } else { + // Let's iterate over the available comments and check for a website. + for _, v := range sections { + if name := getFromSite(v.comment); name != "" { + // Ok, we've got a bot name. + results := strings.SplitN(name, "/", 2) + version := "" + if len(results) == 2 { + version = results[1] + } + p.setSimple(results[0], version, true) + return + } + } + + // We will assume that this is some other weird browser. + p.fixOther(sections) + } +} diff --git a/Godeps/_workspace/src/github.com/mssola/user_agent/browser.go b/Godeps/_workspace/src/github.com/mssola/user_agent/browser.go new file mode 100644 index 000000000..9fb27e5f3 --- /dev/null +++ b/Godeps/_workspace/src/github.com/mssola/user_agent/browser.go @@ -0,0 +1,120 @@ +// Copyright (C) 2012-2015 Miquel Sabaté Solà +// This file is licensed under the MIT license. +// See the LICENSE file. + +package user_agent + +import ( + "regexp" + "strings" +) + +// A struct containing all the information that we might be +// interested from the browser. +type Browser struct { + // The name of the browser's engine. + Engine string + + // The version of the browser's engine. + EngineVersion string + + // The name of the browser. + Name string + + // The version of the browser. + Version string +} + +// Extract all the information that we can get from the User-Agent string +// about the browser and update the receiver with this information. +// +// The function receives just one argument "sections", that contains the +// sections from the User-Agent string after being parsed. +func (p *UserAgent) detectBrowser(sections []section) { + slen := len(sections) + + if sections[0].name == "Opera" { + p.mozilla = "" + p.browser.Name = "Opera" + p.browser.Version = sections[0].version + p.browser.Engine = "Presto" + if slen > 1 { + p.browser.EngineVersion = sections[1].version + } + } else if slen > 1 { + engine := sections[1] + p.browser.Engine = engine.name + p.browser.EngineVersion = engine.version + if slen > 2 { + p.browser.Version = sections[2].version + if engine.name == "AppleWebKit" { + if sections[slen-1].name == "OPR" { + p.browser.Name = "Opera" + p.browser.Version = sections[slen-1].version + } else if sections[2].name == "Chrome" { + p.browser.Name = "Chrome" + } else { + p.browser.Name = "Safari" + } + } else if engine.name == "Gecko" { + name := sections[2].name + if name == "MRA" && slen > 4 { + name = sections[4].name + p.browser.Version = sections[4].version + } + p.browser.Name = name + } else if engine.name == "like" && sections[2].name == "Gecko" { + // This is the new user agent from Internet Explorer 11. + p.browser.Engine = "Trident" + p.browser.Name = "Internet Explorer" + reg, _ := regexp.Compile("^rv:(.+)$") + for _, c := range sections[0].comment { + version := reg.FindStringSubmatch(c) + if len(version) > 0 { + p.browser.Version = version[1] + return + } + } + p.browser.Version = "" + } + } + } else if slen == 1 && len(sections[0].comment) > 1 { + comment := sections[0].comment + if comment[0] == "compatible" && strings.HasPrefix(comment[1], "MSIE") { + p.browser.Engine = "Trident" + p.browser.Name = "Internet Explorer" + // The MSIE version may be reported as the compatibility version. + // For IE 8 through 10, the Trident token is more accurate. + // http://msdn.microsoft.com/en-us/library/ie/ms537503(v=vs.85).aspx#VerToken + for _, v := range comment { + if strings.HasPrefix(v, "Trident/") { + switch v[8:] { + case "4.0": + p.browser.Version = "8.0" + case "5.0": + p.browser.Version = "9.0" + case "6.0": + p.browser.Version = "10.0" + } + break + } + } + // If the Trident token is not provided, fall back to MSIE token. + if p.browser.Version == "" { + p.browser.Version = strings.TrimSpace(comment[1][4:]) + } + } + } +} + +// Returns two strings. The first string is the name of the engine and the +// second one is the version of the engine. +func (p *UserAgent) Engine() (string, string) { + return p.browser.Engine, p.browser.EngineVersion +} + +// Returns two strings. The first string is the name of the browser and the +// second one is the version of the browser. +func (p *UserAgent) Browser() (string, string) { + return p.browser.Name, p.browser.Version +} diff --git a/Godeps/_workspace/src/github.com/mssola/user_agent/operating_systems.go b/Godeps/_workspace/src/github.com/mssola/user_agent/operating_systems.go new file mode 100644 index 000000000..2771b57e2 --- /dev/null +++ b/Godeps/_workspace/src/github.com/mssola/user_agent/operating_systems.go @@ -0,0 +1,260 @@ +// Copyright (C) 2012-2015 Miquel Sabaté Solà +// This file is licensed under the MIT license. +// See the LICENSE file. + +package user_agent + +import "strings" + +// Normalize the name of the operating system. By now, this just +// affects to Windows. +// +// Returns a string containing the normalized name for the Operating System. +func normalizeOS(name string) string { + sp := strings.SplitN(name, " ", 3) + if len(sp) != 3 { + return name + } + + switch sp[2] { + case "5.0": + return "Windows 2000" + case "5.01": + return "Windows 2000, Service Pack 1 (SP1)" + case "5.1": + return "Windows XP" + case "5.2": + return "Windows XP x64 Edition" + case "6.0": + return "Windows Vista" + case "6.1": + return "Windows 7" + case "6.2": + return "Windows 8" + case "6.3": + return "Windows 8.1" + case "6.4": + return "Windows 10" + } + return name +} + +// Guess the OS, the localization and if this is a mobile device for a +// Webkit-powered browser. +// +// The first argument p is a reference to the current UserAgent and the second +// argument is a slice of strings containing the comment. +func webkit(p *UserAgent, comment []string) { + if p.platform == "webOS" { + p.browser.Name = p.platform + p.os = "Palm" + if len(comment) > 2 { + p.localization = comment[2] + } + p.mobile = true + } else if p.platform == "Symbian" { + p.mobile = true + p.browser.Name = p.platform + p.os = comment[0] + } else if p.platform == "Linux" { + p.mobile = true + if p.browser.Name == "Safari" { + p.browser.Name = "Android" + } + if len(comment) > 1 { + if comment[1] == "U" { + if len(comment) > 2 { + p.os = comment[2] + } else { + p.mobile = false + p.os = comment[0] + } + } else { + p.os = comment[1] + } + } + if len(comment) > 3 { + p.localization = comment[3] + } + } else if len(comment) > 0 { + if len(comment) > 3 { + p.localization = comment[3] + } + if strings.HasPrefix(comment[0], "Windows NT") { + p.os = normalizeOS(comment[0]) + } else if len(comment) < 2 { + p.localization = comment[0] + } else if len(comment) < 3 { + if !p.googleBot() { + p.os = normalizeOS(comment[1]) + } + } else { + p.os = normalizeOS(comment[2]) + } + if p.platform == "BlackBerry" { + p.browser.Name = p.platform + if p.os == "Touch" { + p.os = p.platform + } + } + } +} + +// Guess the OS, the localization and if this is a mobile device +// for a Gecko-powered browser. +// +// The first argument p is a reference to the current UserAgent and the second +// argument is a slice of strings containing the comment. +func gecko(p *UserAgent, comment []string) { + if len(comment) > 1 { + if comment[1] == "U" { + if len(comment) > 2 { + p.os = normalizeOS(comment[2]) + } else { + p.os = normalizeOS(comment[1]) + } + } else { + if p.platform == "Android" { + p.mobile = true + p.platform, p.os = normalizeOS(comment[1]), p.platform + } else if comment[0] == "Mobile" || comment[0] == "Tablet" { + p.mobile = true + p.os = "FirefoxOS" + } else { + if p.os == "" { + p.os = normalizeOS(comment[1]) + } + } + } + if len(comment) > 3 { + p.localization = comment[3] + } + } +} + +// Guess the OS, the localization and if this is a mobile device +// for Internet Explorer. +// +// The first argument p is a reference to the current UserAgent and the second +// argument is a slice of strings containing the comment. +func trident(p *UserAgent, comment []string) { + // Internet Explorer only runs on Windows. + p.platform = "Windows" + + // The OS can be set before to handle a new case in IE11. + if p.os == "" { + if len(comment) > 2 { + p.os = normalizeOS(comment[2]) + } else { + p.os = "Windows NT 4.0" + } + } + + // Last but not least, let's detect if it comes from a mobile device. + for _, v := range comment { + if strings.HasPrefix(v, "IEMobile") { + p.mobile = true + return + } + } +} + +// Guess the OS, the localization and if this is a mobile device +// for Opera. +// +// The first argument p is a reference to the current UserAgent and the second +// argument is a slice of strings containing the comment. +func opera(p *UserAgent, comment []string) { + slen := len(comment) + + if strings.HasPrefix(comment[0], "Windows") { + p.platform = "Windows" + p.os = normalizeOS(comment[0]) + if slen > 2 { + if slen > 3 && strings.HasPrefix(comment[2], "MRA") { + p.localization = comment[3] + } else { + p.localization = comment[2] + } + } + } else { + if strings.HasPrefix(comment[0], "Android") { + p.mobile = true + } + p.platform = comment[0] + if slen > 1 { + p.os = comment[1] + if slen > 3 { + p.localization = comment[3] + } + } else { + p.os = comment[0] + } + } +} + +// Given the comment of the first section of the UserAgent string, +// get the platform. +func getPlatform(comment []string) string { + if len(comment) > 0 { + if comment[0] != "compatible" { + if strings.HasPrefix(comment[0], "Windows") { + return "Windows" + } else if strings.HasPrefix(comment[0], "Symbian") { + return "Symbian" + } else if strings.HasPrefix(comment[0], "webOS") { + return "webOS" + } else if comment[0] == "BB10" { + return "BlackBerry" + } + return comment[0] + } + } + return "" +} + +// Detect some properties of the OS from the given section. +func (p *UserAgent) detectOS(s section) { + if s.name == "Mozilla" { + // Get the platform here. Be aware that IE11 provides a new format + // that is not backwards-compatible with previous versions of IE. + p.platform = getPlatform(s.comment) + if p.platform == "Windows" && len(s.comment) > 0 { + p.os = normalizeOS(s.comment[0]) + } + + // And finally get the OS depending on the engine. + switch p.browser.Engine { + case "": + p.undecided = true + case "Gecko": + gecko(p, s.comment) + case "AppleWebKit": + webkit(p, s.comment) + case "Trident": + trident(p, s.comment) + } + } else if s.name == "Opera" { + if len(s.comment) > 0 { + opera(p, s.comment) + } + } else { + // Check whether this is a bot or just a weird browser. + p.undecided = true + } +} + +// Returns a string containing the platform.. +func (p *UserAgent) Platform() string { + return p.platform +} + +// Returns a string containing the name of the Operating System. +func (p *UserAgent) OS() string { + return p.os +} + +// Returns a string containing the localization. +func (p *UserAgent) Localization() string { + return p.localization +} diff --git a/Godeps/_workspace/src/github.com/mssola/user_agent/user_agent.go b/Godeps/_workspace/src/github.com/mssola/user_agent/user_agent.go new file mode 100644 index 000000000..df1aa3b78 --- /dev/null +++ b/Godeps/_workspace/src/github.com/mssola/user_agent/user_agent.go @@ -0,0 +1,169 @@ +// Copyright (C) 2012-2015 Miquel Sabaté Solà +// This file is licensed under the MIT license. +// See the LICENSE file. + +// Package user_agent implements an HTTP User Agent string parser. It defines +// the type UserAgent that contains all the information from the parsed string. +// It also implements the Parse function and getters for all the relevant +// information that has been extracted from a parsed User Agent string. +package user_agent + +import ( + "strings" +) + +// A section contains the name of the product, its version and +// an optional comment. +type section struct { + name string + version string + comment []string +} + +// The UserAgent struct contains all the info that can be extracted +// from the User-Agent string. +type UserAgent struct { + ua string + mozilla string + platform string + os string + localization string + browser Browser + bot bool + mobile bool + undecided bool +} + +// Read from the given string until the given delimiter or the +// end of the string have been reached. +// +// The first argument is the user agent string being parsed. The second +// argument is a reference pointing to the current index of the user agent +// string. The delimiter argument specifies which character is the delimiter +// and the cat argument determines whether nested '(' should be ignored or not. +// +// Returns an array of bytes containing what has been read. +func readUntil(ua string, index *int, delimiter byte, cat bool) []byte { + var buffer []byte + + i := *index + catalan := 0 + for ; i < len(ua); i = i + 1 { + if ua[i] == delimiter { + if catalan == 0 { + *index = i + 1 + return buffer + } + catalan-- + } else if cat && ua[i] == '(' { + catalan++ + } + buffer = append(buffer, ua[i]) + } + *index = i + 1 + return buffer +} + +// Parse the given product, that is, just a name or a string +// formatted as Name/Version. +// +// It returns two strings. The first string is the name of the product and the +// second string contains the version of the product. +func parseProduct(product []byte) (string, string) { + prod := strings.SplitN(string(product), "/", 2) + if len(prod) == 2 { + return prod[0], prod[1] + } + return string(product), "" +} + +// Parse a section. A section is typically formatted as follows +// "Name/Version (comment)". Both, the comment and the version are optional. +// +// The first argument is the user agent string being parsed. The second +// argument is a reference pointing to the current index of the user agent +// string. +// +// Returns a section containing the information that we could extract +// from the last parsed section. +func parseSection(ua string, index *int) (s section) { + buffer := readUntil(ua, index, ' ', false) + + s.name, s.version = parseProduct(buffer) + if *index < len(ua) && ua[*index] == '(' { + *index++ + buffer = readUntil(ua, index, ')', true) + s.comment = strings.Split(string(buffer), "; ") + *index++ + } + return s +} + +// Initialize the parser. +func (p *UserAgent) initialize() { + p.ua = "" + p.mozilla = "" + p.platform = "" + p.os = "" + p.localization = "" + p.browser.Engine = "" + p.browser.EngineVersion = "" + p.browser.Name = "" + p.browser.Version = "" + p.bot = false + p.mobile = false + p.undecided = false +} + +// Parse the given User-Agent string and get the resulting UserAgent object. +// +// Returns an UserAgent object that has been initialized after parsing +// the given User-Agent string. +func New(ua string) *UserAgent { + o := &UserAgent{} + o.Parse(ua) + return o +} + +// Parse the given User-Agent string. After calling this function, the +// receiver will be setted up with all the information that we've extracted. +func (p *UserAgent) Parse(ua string) { + var sections []section + + p.initialize() + p.ua = ua + for index, limit := 0, len(ua); index < limit; { + s := parseSection(ua, &index) + if !p.mobile && s.name == "Mobile" { + p.mobile = true + } + sections = append(sections, s) + } + + if len(sections) > 0 { + p.mozilla = sections[0].version + + p.detectBrowser(sections) + p.detectOS(sections[0]) + + if p.undecided { + p.checkBot(sections) + } + } +} + +// Returns the mozilla version (it's how the User Agent string begins: +// "Mozilla/5.0 ...", unless we're dealing with Opera, of course). +func (p *UserAgent) Mozilla() string { + return p.mozilla +} + +// Returns true if it's a bot, false otherwise. +func (p *UserAgent) Bot() bool { + return p.bot +} + +// Returns true if it's a mobile device, false otherwise. +func (p *UserAgent) Mobile() bool { + return p.mobile +} -- cgit v1.2.3-1-g7c22