Initial QSfera import
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
.idea/
|
||||
.vscode/
|
||||
.DS_Store
|
||||
old.go
|
||||
old_test.go
|
||||
file.txt
|
||||
user_agents.txt
|
||||
ua2.go
|
||||
settings.json
|
||||
benchmarks.txt
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Miloš Mileusnić
|
||||
|
||||
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.
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
# Go/Golang package for parsing user agent strings [](https://godoc.org/github.com/mileusna/useragent)
|
||||
|
||||
Use `useragent.Parse(userAgent string)` function to parse browser's and bot's user agents strings and get:
|
||||
+ User agent name and version (Chrome, Firefox, Googlebot, etc.)
|
||||
+ Operating system name and version (Windows, Android, iOS etc.)
|
||||
+ Device type (mobile, desktop, tablet, bot)
|
||||
+ Device name if available (iPhone, iPad, Huawei VNS-L21)
|
||||
+ URL provided by the bot (http://www.google.com/bot.html etc.)
|
||||
|
||||
## Status
|
||||
|
||||
Stable. I use it on high traffic websites on every single request, as well on my [Lite Analytics](https://liteanalytics.com/) service.
|
||||
|
||||
I constantly improve user agents detection and performance. Fill free to report an issue for any User-Agent string not recognized or misinterpreted.
|
||||
|
||||
## Installation <a id="installation"></a>
|
||||
```
|
||||
go get github.com/mileusna/useragent
|
||||
```
|
||||
|
||||
## Example<a id="example"></a>
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/mileusna/useragent"
|
||||
)
|
||||
|
||||
func main() {
|
||||
userAgents := []string{
|
||||
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/603.3.8 (KHTML, like Gecko) Version/10.1.2 Safari/603.3.8",
|
||||
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_2 like Mac OS X) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.0 Mobile/14F89 Safari/602.1",
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_2 like Mac OS X) AppleWebKit/603.2.4 (KHTML, like Gecko) FxiOS/8.1.1b4948 Mobile/14F89 Safari/603.2.4",
|
||||
"Mozilla/5.0 (iPad; CPU OS 10_3_2 like Mac OS X) AppleWebKit/603.2.4 (KHTML, like Gecko) Version/10.0 Mobile/14F89 Safari/602.1",
|
||||
"Mozilla/5.0 (Linux; Android 4.3; GT-I9300 Build/JSS15J) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.125 Mobile Safari/537.36",
|
||||
"Mozilla/5.0 (Android 4.3; Mobile; rv:54.0) Gecko/54.0 Firefox/54.0",
|
||||
"Mozilla/5.0 (Linux; Android 4.3; GT-I9300 Build/JSS15J) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.91 Mobile Safari/537.36 OPR/42.9.2246.119956",
|
||||
"Opera/9.80 (Android; Opera Mini/28.0.2254/66.318; U; en) Presto/2.12.423 Version/12.16",
|
||||
}
|
||||
|
||||
for _, s := range userAgents {
|
||||
ua := useragent.Parse(s)
|
||||
fmt.Println()
|
||||
fmt.Println(ua.String)
|
||||
fmt.Println(strings.Repeat("=", len(ua.String)))
|
||||
fmt.Println("Name:", ua.Name, "v", ua.Version)
|
||||
fmt.Println("OS:", ua.OS, "v", ua.OSVersion)
|
||||
fmt.Println("Device:", ua.Device)
|
||||
if ua.Mobile {
|
||||
fmt.Println("(Mobile)")
|
||||
}
|
||||
if ua.Tablet {
|
||||
fmt.Println("(Tablet)")
|
||||
}
|
||||
if ua.Desktop {
|
||||
fmt.Println("(Desktop)")
|
||||
}
|
||||
if ua.Bot {
|
||||
fmt.Println("(Bot)")
|
||||
}
|
||||
if ua.URL != "" {
|
||||
fmt.Println(ua.URL)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
## Shorthand functions
|
||||
|
||||
Beside `UserAgent{}` struct and its properties returned by `useragent.Parse()`, there is a bunch of shorthand functions for most popular browsers and operating systems, so this code:
|
||||
|
||||
```go
|
||||
ua := useragent.Parse(userAgentString)
|
||||
if ua.OS == "Android" && ua.Name == "Chrome" {
|
||||
// do something
|
||||
}
|
||||
```
|
||||
can be also written on this way:
|
||||
```go
|
||||
ua := useragent.Parse(userAgentString)
|
||||
if ua.IsAndroid() && ua.IsChrome() {
|
||||
// do something
|
||||
}
|
||||
```
|
||||
|
||||
## Notice
|
||||
|
||||
+ Opera and Opera Mini are two browsers, since they operate on very different ways.
|
||||
+ If Googlebot (or any other bot) is detected and it is using its mobile crawler, both `bot` and `mobile` flags will be set to `true`.
|
||||
|
||||
|
||||
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
package useragent
|
||||
|
||||
// IsWindows shorthand function to check if OS == Windows
|
||||
func (ua UserAgent) IsWindows() bool {
|
||||
return ua.OS == Windows
|
||||
}
|
||||
|
||||
// IsAndroid shorthand function to check if OS == Android
|
||||
func (ua UserAgent) IsAndroid() bool {
|
||||
return ua.OS == Android
|
||||
}
|
||||
|
||||
// IsMacOS shorthand function to check if OS == MacOS
|
||||
func (ua UserAgent) IsMacOS() bool {
|
||||
return ua.OS == MacOS
|
||||
}
|
||||
|
||||
// IsIOS shorthand function to check if OS == IOS
|
||||
func (ua UserAgent) IsIOS() bool {
|
||||
return ua.OS == IOS
|
||||
}
|
||||
|
||||
// IsLinux shorthand function to check if OS == Linux
|
||||
func (ua UserAgent) IsLinux() bool {
|
||||
return ua.OS == Linux
|
||||
}
|
||||
|
||||
// IsChromeOS shorthand function to check if OS == CrOS
|
||||
func (ua UserAgent) IsChromeOS() bool {
|
||||
return ua.OS == ChromeOS || ua.OS == "CrOS"
|
||||
}
|
||||
|
||||
// IsBlackberryOS shorthand function to check if OS == BlackBerry
|
||||
func (ua UserAgent) IsBlackberryOS() bool {
|
||||
return ua.OS == BlackBerry
|
||||
}
|
||||
|
||||
// IsOpera shorthand function to check if Name == Opera
|
||||
func (ua UserAgent) IsOpera() bool {
|
||||
return ua.Name == Opera
|
||||
}
|
||||
|
||||
// IsOperaMini shorthand function to check if Name == Opera Mini
|
||||
func (ua UserAgent) IsOperaMini() bool {
|
||||
return ua.Name == OperaMini
|
||||
}
|
||||
|
||||
// IsChrome shorthand function to check if Name == Chrome
|
||||
func (ua UserAgent) IsChrome() bool {
|
||||
return ua.Name == Chrome
|
||||
}
|
||||
|
||||
// IsFirefox shorthand function to check if Name == Firefox
|
||||
func (ua UserAgent) IsFirefox() bool {
|
||||
return ua.Name == Firefox
|
||||
}
|
||||
|
||||
// IsInternetExplorer shorthand function to check if Name == Internet Explorer
|
||||
func (ua UserAgent) IsInternetExplorer() bool {
|
||||
return ua.Name == InternetExplorer
|
||||
}
|
||||
|
||||
// IsSafari shorthand function to check if Name == Safari
|
||||
func (ua UserAgent) IsSafari() bool {
|
||||
return ua.Name == Safari
|
||||
}
|
||||
|
||||
// IsEdge shorthand function to check if Name == Edge
|
||||
func (ua UserAgent) IsEdge() bool {
|
||||
return ua.Name == Edge
|
||||
}
|
||||
|
||||
// IsBlackBerry shorthand function to check if Name == BlackBerry
|
||||
func (ua UserAgent) IsBlackBerry() bool {
|
||||
return ua.Name == BlackBerry
|
||||
}
|
||||
|
||||
// IsGooglebot shorthand function to check if Name == Googlebot
|
||||
func (ua UserAgent) IsGooglebot() bool {
|
||||
return ua.Name == Googlebot
|
||||
}
|
||||
|
||||
// IsTwitterbot shorthand function to check if Name == Twitterbot
|
||||
func (ua UserAgent) IsTwitterbot() bool {
|
||||
return ua.Name == Twitterbot
|
||||
}
|
||||
|
||||
// IsFacebookbot shorthand function to check if Name == FacebookExternalHit
|
||||
func (ua UserAgent) IsFacebookbot() bool {
|
||||
return ua.Name == FacebookExternalHit
|
||||
}
|
||||
|
||||
// IsYandexbot shorthand function to check if Name == YandexBot
|
||||
func (ua UserAgent) IsYandexbot() bool {
|
||||
return ua.Name == YandexBot
|
||||
}
|
||||
|
||||
// IsUnknown returns true if the package can't determine the user agent reliably.
|
||||
// Fields like Name, OS, etc. might still have values.
|
||||
func (ua UserAgent) IsUnknown() bool {
|
||||
return !ua.Mobile && !ua.Tablet && !ua.Desktop && !ua.Bot
|
||||
}
|
||||
+691
@@ -0,0 +1,691 @@
|
||||
package useragent
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// UserAgent struct containing all data extracted from parsed user-agent string
|
||||
type UserAgent struct {
|
||||
VersionNo VersionNo
|
||||
OSVersionNo VersionNo
|
||||
URL string
|
||||
String string
|
||||
Name string
|
||||
Version string
|
||||
OS string
|
||||
OSVersion string
|
||||
Device string
|
||||
Mobile bool
|
||||
Tablet bool
|
||||
Desktop bool
|
||||
Bot bool
|
||||
}
|
||||
|
||||
// Constants for browsers and operating systems for easier comparison
|
||||
const (
|
||||
Windows = "Windows"
|
||||
WindowsPhone = "Windows Phone"
|
||||
WindowsNT = "Windows NT"
|
||||
WindowsPhoneOS = "Windows Phone OS"
|
||||
Android = "Android"
|
||||
MacOS = "macOS"
|
||||
IOS = "iOS"
|
||||
Linux = "Linux"
|
||||
FreeBSD = "FreeBSD"
|
||||
ChromeOS = "ChromeOS"
|
||||
BlackBerry = "BlackBerry"
|
||||
CrOS = "CrOS"
|
||||
Harmony = "Harmony"
|
||||
|
||||
Opera = "Opera"
|
||||
OperaMini = "Opera Mini"
|
||||
OperaTouch = "Opera Touch"
|
||||
Chrome = "Chrome"
|
||||
HeadlessChrome = "Headless Chrome"
|
||||
Firefox = "Firefox"
|
||||
InternetExplorer = "Internet Explorer"
|
||||
Safari = "Safari"
|
||||
Edge = "Edge"
|
||||
Vivaldi = "Vivaldi"
|
||||
MobileSafari = "Mobile Safari"
|
||||
NetFront = "NetFront"
|
||||
Mozilla = "Mozilla"
|
||||
Msie = "MSIE"
|
||||
SamsungBrowser = "Samsung Browser"
|
||||
|
||||
GoogleAdsBot = "Google Ads Bot"
|
||||
Googlebot = "Googlebot"
|
||||
Twitterbot = "Twitterbot"
|
||||
FacebookExternalHit = "facebookexternalhit"
|
||||
Applebot = "Applebot"
|
||||
Bingbot = "Bingbot"
|
||||
YandexBot = "YandexBot"
|
||||
YandexAdNet = "YandexAdNet"
|
||||
|
||||
FacebookApp = "Facebook App"
|
||||
InstagramApp = "Instagram App"
|
||||
TiktokApp = "TikTok App"
|
||||
|
||||
Version = "Version"
|
||||
Mobile = "Mobile"
|
||||
Tablet = "Tablet"
|
||||
|
||||
tablet = "tablet"
|
||||
)
|
||||
|
||||
// Parse user agent string returning UserAgent struct
|
||||
func Parse(userAgent string) UserAgent {
|
||||
ua := UserAgent{
|
||||
String: userAgent,
|
||||
}
|
||||
|
||||
tokens := parse([]byte(userAgent))
|
||||
ua.URL = tokens.url
|
||||
|
||||
// OS lookup
|
||||
switch {
|
||||
case tokens.exists(Android):
|
||||
ua.OS = Android
|
||||
var osIndex int
|
||||
osIndex, ua.OSVersion = tokens.getIndexValue(Android)
|
||||
ua.Tablet = strings.Contains(strings.ToLower(ua.String), tablet)
|
||||
ua.Device = tokens.findAndroidDevice(osIndex)
|
||||
|
||||
case tokens.exists("iPhone"):
|
||||
ua.OS = IOS
|
||||
ua.OSVersion = tokens.findMacOSVersion()
|
||||
ua.Device = "iPhone"
|
||||
ua.Mobile = true
|
||||
|
||||
case tokens.exists("iPad"):
|
||||
ua.OS = IOS
|
||||
ua.OSVersion = tokens.findMacOSVersion()
|
||||
ua.Device = "iPad"
|
||||
ua.Tablet = true
|
||||
|
||||
case tokens.exists(WindowsNT):
|
||||
ua.OS = Windows
|
||||
ua.OSVersion = tokens.get(WindowsNT)
|
||||
ua.Desktop = true
|
||||
|
||||
case tokens.exists(WindowsPhoneOS):
|
||||
ua.OS = WindowsPhone
|
||||
ua.OSVersion = tokens.get(WindowsPhoneOS)
|
||||
ua.Mobile = true
|
||||
|
||||
case tokens.exists("Macintosh"):
|
||||
ua.OS = MacOS
|
||||
ua.OSVersion = tokens.findMacOSVersion()
|
||||
ua.Desktop = true
|
||||
|
||||
case tokens.exists(Linux):
|
||||
ua.OS = Linux
|
||||
ua.OSVersion = tokens.get(Linux)
|
||||
ua.Desktop = true
|
||||
|
||||
case tokens.exists(FreeBSD):
|
||||
ua.OS = FreeBSD
|
||||
ua.OSVersion = tokens.get(FreeBSD)
|
||||
ua.Desktop = true
|
||||
|
||||
case tokens.exists(CrOS):
|
||||
ua.OS = ChromeOS
|
||||
ua.OSVersion = tokens.get(CrOS)
|
||||
ua.Desktop = true
|
||||
|
||||
case tokens.exists(BlackBerry):
|
||||
ua.OS = BlackBerry
|
||||
ua.OSVersion = tokens.get(BlackBerry)
|
||||
ua.Mobile = true
|
||||
|
||||
case tokens.exists("OpenHarmony"):
|
||||
ua.OS = Harmony
|
||||
ua.OSVersion = tokens.get("OpenHarmony")
|
||||
ua.Mobile = true
|
||||
}
|
||||
|
||||
switch {
|
||||
case tokens.exists(Googlebot):
|
||||
ua.Name = Googlebot
|
||||
ua.Version = tokens.get(Googlebot)
|
||||
ua.Bot = true
|
||||
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
|
||||
|
||||
case tokens.existsAny("GoogleProber", "GoogleProducer"):
|
||||
if name := tokens.findBestMatch(false); name != "" {
|
||||
ua.Name = name
|
||||
}
|
||||
ua.Bot = true
|
||||
|
||||
case tokens.exists("Bytespider"):
|
||||
ua.Name = "Bytespider"
|
||||
ua.Mobile = tokens.exists("Mobile Safari")
|
||||
ua.Bot = true
|
||||
|
||||
case tokens.exists(Applebot):
|
||||
ua.Name = Applebot
|
||||
ua.Version = tokens.get(Applebot)
|
||||
ua.Bot = true
|
||||
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
|
||||
ua.OS = ""
|
||||
|
||||
case tokens.get(OperaMini) != "":
|
||||
ua.Name = OperaMini
|
||||
ua.Version = tokens.get(OperaMini)
|
||||
ua.Mobile = true
|
||||
|
||||
case tokens.get("OPR") != "":
|
||||
ua.Name = Opera
|
||||
ua.Version = tokens.get("OPR")
|
||||
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
|
||||
|
||||
case tokens.get("OPT") != "":
|
||||
ua.Name = OperaTouch
|
||||
ua.Version = tokens.get("OPT")
|
||||
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
|
||||
|
||||
// Opera on iOS
|
||||
case tokens.get("OPiOS") != "":
|
||||
ua.Name = Opera
|
||||
ua.Version = tokens.get("OPiOS")
|
||||
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
|
||||
|
||||
// Chrome on iOS
|
||||
case tokens.get("CriOS") != "":
|
||||
ua.Name = Chrome
|
||||
ua.Version = tokens.get("CriOS")
|
||||
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
|
||||
|
||||
// Firefox on iOS
|
||||
case tokens.get("FxiOS") != "":
|
||||
ua.Name = Firefox
|
||||
ua.Version = tokens.get("FxiOS")
|
||||
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
|
||||
|
||||
case tokens.get(Firefox) != "":
|
||||
ua.Name = Firefox
|
||||
ua.Version = tokens.get(Firefox)
|
||||
ua.Mobile = tokens.exists(Mobile)
|
||||
ua.Tablet = tokens.exists(Tablet)
|
||||
|
||||
case tokens.get(Vivaldi) != "":
|
||||
ua.Name = Vivaldi
|
||||
ua.Version = tokens.get(Vivaldi)
|
||||
|
||||
case tokens.exists(Msie):
|
||||
ua.Name = InternetExplorer
|
||||
ua.Version = tokens.get(Msie)
|
||||
|
||||
case tokens.get("EdgiOS") != "":
|
||||
ua.Name = Edge
|
||||
ua.Version = tokens.get("EdgiOS")
|
||||
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
|
||||
|
||||
case tokens.get(Edge) != "":
|
||||
ua.Name = Edge
|
||||
ua.Version = tokens.get(Edge)
|
||||
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
|
||||
|
||||
case tokens.get("Edg") != "":
|
||||
ua.Name = Edge
|
||||
ua.Version = tokens.get("Edg")
|
||||
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
|
||||
|
||||
case tokens.get("EdgA") != "":
|
||||
ua.Name = Edge
|
||||
ua.Version = tokens.get("EdgA")
|
||||
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
|
||||
|
||||
case tokens.get("bingbot") != "":
|
||||
ua.Name = Bingbot
|
||||
ua.Version = tokens.get("bingbot")
|
||||
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
|
||||
|
||||
case tokens.get(YandexBot) != "":
|
||||
ua.Name = YandexBot
|
||||
ua.Version = tokens.get(YandexBot)
|
||||
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
|
||||
ua.Bot = true
|
||||
|
||||
case tokens.get(YandexAdNet) != "":
|
||||
ua.Name = YandexAdNet
|
||||
ua.Version = tokens.get(YandexAdNet)
|
||||
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
|
||||
ua.Bot = true
|
||||
|
||||
case tokens.get("SamsungBrowser") != "":
|
||||
ua.Name = SamsungBrowser
|
||||
ua.Version = tokens.get("SamsungBrowser")
|
||||
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
|
||||
ua.OS = Android
|
||||
|
||||
case tokens.get("HeadlessChrome") != "":
|
||||
ua.Name = HeadlessChrome
|
||||
ua.Version = tokens.get("HeadlessChrome")
|
||||
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
|
||||
ua.Bot = true
|
||||
|
||||
case tokens.existsAny("AdsBot-Google-Mobile", "Mediapartners-Google", "AdsBot-Google"):
|
||||
ua.Name = GoogleAdsBot
|
||||
ua.Bot = true
|
||||
ua.Mobile = ua.IsAndroid() || ua.IsIOS()
|
||||
|
||||
case tokens.exists("Yahoo Ad monitoring"):
|
||||
ua.Name = "Yahoo Ad monitoring"
|
||||
ua.Bot = true
|
||||
ua.Mobile = ua.IsAndroid() || ua.IsIOS()
|
||||
|
||||
case tokens.exists("XiaoMi"):
|
||||
miui := tokens.get("XiaoMi")
|
||||
if strings.HasPrefix(miui, "MiuiBrowser") {
|
||||
ua.Name = "Miui Browser"
|
||||
ua.Version = strings.TrimPrefix(miui, "MiuiBrowser/")
|
||||
ua.Mobile = true
|
||||
}
|
||||
|
||||
case tokens.exists("FBAN"):
|
||||
ua.Name = FacebookApp
|
||||
ua.Version = tokens.get("FBAN")
|
||||
|
||||
case tokens.exists("FB_IAB"):
|
||||
ua.Name = FacebookApp
|
||||
ua.Version = tokens.get("FBAV")
|
||||
|
||||
case tokens.startsWith("Instagram"):
|
||||
ua.Name = InstagramApp
|
||||
ua.Version = tokens.findInstagramVersion()
|
||||
|
||||
case tokens.exists("BytedanceWebview"):
|
||||
ua.Name = TiktokApp
|
||||
ua.Version = tokens.get("app_version")
|
||||
|
||||
case tokens.get("HuaweiBrowser") != "":
|
||||
ua.Name = "Huawei Browser"
|
||||
ua.Version = tokens.get("HuaweiBrowser")
|
||||
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
|
||||
|
||||
case tokens.exists(BlackBerry):
|
||||
ua.Name = BlackBerry
|
||||
ua.Version = tokens.get(Version)
|
||||
|
||||
case tokens.exists(NetFront):
|
||||
ua.Name = NetFront
|
||||
ua.Version = tokens.get(NetFront)
|
||||
ua.Mobile = true
|
||||
|
||||
// if Chrome and Safari defined, find any other token sent descr
|
||||
case tokens.exists(Chrome) && tokens.exists(Safari):
|
||||
name := tokens.findBestMatch(true)
|
||||
if name != "" {
|
||||
ua.Name = name
|
||||
ua.Version = tokens.get(name)
|
||||
break
|
||||
}
|
||||
fallthrough
|
||||
|
||||
case tokens.exists(Chrome):
|
||||
ua.Name = Chrome
|
||||
ua.Version = tokens.get(Chrome)
|
||||
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
|
||||
|
||||
case tokens.exists("Brave Chrome"):
|
||||
ua.Name = Chrome
|
||||
ua.Version = tokens.get("Brave Chrome")
|
||||
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
|
||||
|
||||
case tokens.exists(Safari):
|
||||
ua.Name = Safari
|
||||
v := tokens.get(Version)
|
||||
if v != "" {
|
||||
ua.Version = v
|
||||
} else {
|
||||
ua.Version = tokens.get(Safari)
|
||||
}
|
||||
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
|
||||
|
||||
default:
|
||||
if ua.IsAndroid() && tokens.get(Version) != "" {
|
||||
ua.Name = "Android browser"
|
||||
ua.Version = tokens.get(Version)
|
||||
ua.Mobile = true
|
||||
} else {
|
||||
if name := tokens.findBestMatch(false); name != "" {
|
||||
ua.Name = name
|
||||
ua.Version = tokens.get(name)
|
||||
} else {
|
||||
ua.Name = ua.String
|
||||
}
|
||||
ua.Bot = strings.Contains(strings.ToLower(ua.Name), "bot")
|
||||
// If mobile flag has already been set, don't override it.
|
||||
if !ua.Mobile {
|
||||
ua.Mobile = tokens.existsAny(Mobile, MobileSafari)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ua.IsAndroid() {
|
||||
ua.Mobile = true
|
||||
}
|
||||
|
||||
// if tablet, switch mobile to off
|
||||
if ua.Tablet {
|
||||
ua.Mobile = false
|
||||
}
|
||||
|
||||
// if not already bot, check some popular bots and whether URL is set
|
||||
if !ua.Bot {
|
||||
switch ua.Name {
|
||||
case Twitterbot, FacebookExternalHit, "facebookcatalog":
|
||||
ua.Bot = true
|
||||
default:
|
||||
ua.Bot = ua.URL != ""
|
||||
}
|
||||
}
|
||||
|
||||
ua.VersionNo = parseVersion(ua.Version)
|
||||
ua.OSVersionNo = parseVersion(ua.OSVersion)
|
||||
|
||||
return ua
|
||||
}
|
||||
|
||||
// var buffPool = sync.Pool{New: func() interface{} {
|
||||
// return bytes.NewBuffer(make([]byte, 0, 30))
|
||||
// }}
|
||||
|
||||
func parse(userAgent []byte) properties {
|
||||
clients := properties{
|
||||
list: make([]property, 0, 8),
|
||||
}
|
||||
slash := false
|
||||
isURL := false
|
||||
// buff := buffPool.Get().(*bytes.Buffer)
|
||||
// val := buffPool.Get().(*bytes.Buffer)
|
||||
// buff.Reset()
|
||||
// val.Reset()
|
||||
|
||||
buff := bytes.NewBuffer(make([]byte, 0, 30))
|
||||
val := bytes.NewBuffer(make([]byte, 0, 30))
|
||||
|
||||
addToken := func() {
|
||||
if buff.Len() != 0 {
|
||||
s := string(bytes.TrimSpace(buff.Bytes()))
|
||||
if !ignore(s) {
|
||||
if isURL {
|
||||
clients.url = strings.TrimPrefix(s, "+")
|
||||
return
|
||||
}
|
||||
if val.Len() == 0 {
|
||||
// if value don't exists, try to get version from the token
|
||||
clients.list = append(clients.list, checkVer(s))
|
||||
} else {
|
||||
clients.list = append(clients.list, property{Key: s, Value: string(bytes.TrimSpace(val.Bytes()))})
|
||||
}
|
||||
}
|
||||
}
|
||||
buff.Reset()
|
||||
val.Reset()
|
||||
slash = false
|
||||
isURL = false
|
||||
}
|
||||
|
||||
parOpen := false
|
||||
braOpen := false
|
||||
|
||||
for i, c := range userAgent {
|
||||
switch {
|
||||
case c == 41: // )
|
||||
addToken()
|
||||
parOpen = false
|
||||
|
||||
case (parOpen || braOpen) && c == 59: // ;
|
||||
addToken()
|
||||
|
||||
case c == 59: // ;
|
||||
addToken()
|
||||
|
||||
case c == 40: // (
|
||||
addToken()
|
||||
parOpen = true
|
||||
|
||||
case c == 91: // [
|
||||
addToken()
|
||||
braOpen = true
|
||||
case c == 93: // ]
|
||||
addToken()
|
||||
braOpen = false
|
||||
|
||||
case c == 58: // :
|
||||
if bytes.HasSuffix(buff.Bytes(), []byte("http")) || bytes.HasSuffix(buff.Bytes(), []byte("https")) {
|
||||
// If we are part of a URL just write the character.
|
||||
buff.WriteByte(c)
|
||||
} else if i != len(userAgent)-1 && userAgent[i+1] != ' ' {
|
||||
// If the following character is not a space, change to a space.
|
||||
buff.WriteByte(' ')
|
||||
}
|
||||
// Otherwise don't write as it's probably a badly formatted key value separator.
|
||||
|
||||
case slash && c == 32:
|
||||
addToken()
|
||||
|
||||
case slash:
|
||||
val.WriteByte(c)
|
||||
|
||||
case c == 47 && !isURL: // /
|
||||
if i != len(userAgent)-1 && userAgent[i+1] == 47 && (bytes.HasSuffix(buff.Bytes(), []byte("http:")) || bytes.HasSuffix(buff.Bytes(), []byte("https:"))) {
|
||||
buff.WriteByte(c)
|
||||
isURL = true
|
||||
} else {
|
||||
if ignore(buff.String()) {
|
||||
buff.Reset()
|
||||
} else {
|
||||
slash = true
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
buff.WriteByte(c)
|
||||
}
|
||||
}
|
||||
addToken()
|
||||
|
||||
// buffPool.Put(buff)
|
||||
// buffPool.Put(val)
|
||||
return clients
|
||||
}
|
||||
|
||||
func checkVer(s string) property {
|
||||
i := strings.LastIndex(s, " ")
|
||||
if i == -1 {
|
||||
return property{Key: s, Value: ""}
|
||||
}
|
||||
|
||||
switch s[:i] {
|
||||
case Linux, WindowsNT, WindowsPhoneOS, Msie, Android, "OpenHarmony":
|
||||
return property{Key: s[:i], Value: s[i+1:]}
|
||||
case "CrOS x86_64", "CrOS aarch64", "CrOS armv7l":
|
||||
j := strings.LastIndex(s[:i], " ")
|
||||
return property{Key: s[:j], Value: s[j+1 : i]}
|
||||
default:
|
||||
return property{Key: s, Value: ""}
|
||||
}
|
||||
}
|
||||
|
||||
// ignore returns true if token should be ignored
|
||||
func ignore(s string) bool {
|
||||
switch s {
|
||||
case "KHTML, like Gecko", "U", "compatible", Mozilla, "WOW64", "en", "en-us", "en-gb", "ru-ru", "Browser":
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type property struct {
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
type properties struct {
|
||||
list []property
|
||||
url string
|
||||
}
|
||||
|
||||
func (p properties) get(key string) string {
|
||||
for _, prop := range p.list {
|
||||
if prop.Key == key {
|
||||
return prop.Value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p properties) getIndexValue(key string) (int, string) {
|
||||
for i, prop := range p.list {
|
||||
if prop.Key == key {
|
||||
return i, prop.Value
|
||||
}
|
||||
}
|
||||
return -1, ""
|
||||
}
|
||||
|
||||
func (p properties) exists(key string) bool {
|
||||
for _, prop := range p.list {
|
||||
if prop.Key == key {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// func (p properties) existsIgnoreCase(key string) bool {
|
||||
// for _, prop := range p.list {
|
||||
// if strings.EqualFold(prop.Key, key) {
|
||||
// return true
|
||||
// }
|
||||
// }
|
||||
// return false
|
||||
// }
|
||||
|
||||
func (p properties) existsAny(keys ...string) bool {
|
||||
for _, k := range keys {
|
||||
for _, prop := range p.list {
|
||||
if prop.Key == k {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p properties) getAny(keys ...string) (key, value string) {
|
||||
for _, k := range keys {
|
||||
for _, prop := range p.list {
|
||||
if prop.Key == k {
|
||||
return prop.Key, prop.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}
|
||||
|
||||
func (p properties) findMacOSVersion() string {
|
||||
for _, token := range p.list {
|
||||
if strings.Contains(token.Key, "OS") {
|
||||
if ver := findVersion(token.Value); ver != "" {
|
||||
return ver
|
||||
} else if ver = findVersion(token.Key); ver != "" {
|
||||
return ver
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (p properties) startsWith(value string) bool {
|
||||
for _, prop := range p.list {
|
||||
if strings.HasPrefix(prop.Key, value) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p properties) findInstagramVersion() string {
|
||||
for _, token := range p.list {
|
||||
if strings.HasPrefix(token.Key, "Instagram") {
|
||||
if ver := findVersion(token.Value); ver != "" {
|
||||
return ver
|
||||
} else if ver = findVersion(token.Key); ver != "" {
|
||||
return ver
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// findBestMatch from the rest of the bunch
|
||||
// in first cycle only return key with version value
|
||||
// if withVerValue is false, do another cycle and return any token
|
||||
func (p properties) findBestMatch(withVerOnly bool) string {
|
||||
n := 2
|
||||
if withVerOnly {
|
||||
n = 1
|
||||
}
|
||||
for i := 0; i < n; i++ {
|
||||
for _, prop := range p.list {
|
||||
switch prop.Key {
|
||||
case Chrome, Firefox, Safari, Version, Mobile, MobileSafari, Mozilla, "AppleWebKit", WindowsNT, WindowsPhoneOS, Android, "Macintosh", Linux, "GSA", CrOS, Tablet, "OpenHarmony":
|
||||
default:
|
||||
// don't pick if starts with number
|
||||
if len(prop.Key) != 0 && prop.Key[0] >= 48 && prop.Key[0] <= 57 {
|
||||
break
|
||||
}
|
||||
if i == 0 {
|
||||
if prop.Value != "" { // in first check, only return keys with value
|
||||
return prop.Key
|
||||
}
|
||||
} else {
|
||||
return prop.Key
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var rxMacOSVer = regexp.MustCompile(`[_\d\.]+`)
|
||||
|
||||
func findVersion(s string) string {
|
||||
if ver := rxMacOSVer.FindString(s); ver != "" {
|
||||
return strings.Replace(ver, "_", ".", -1)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// findAndroidDevice in tokens
|
||||
func (p *properties) findAndroidDevice(startIndex int) string {
|
||||
for i := startIndex; i < startIndex+1; i++ {
|
||||
if len(p.list) > i+1 {
|
||||
dev := p.list[i+1].Key
|
||||
if len(dev) == 2 || (len(dev) == 5 && dev[2] == '-') {
|
||||
// probably language tag (en-us etc..), ignore and continue loop
|
||||
continue
|
||||
}
|
||||
switch dev {
|
||||
case Chrome, Firefox, Safari, OperaMini, "Presto", Version, Mobile, MobileSafari, Mozilla, "AppleWebKit", WindowsNT, WindowsPhoneOS, Android, "Macintosh", Linux, CrOS:
|
||||
// ignore these tokens, not device names
|
||||
default:
|
||||
if strings.Contains(strings.ToLower(dev), tablet) {
|
||||
p.list[i+1].Key = Tablet // leave Tablet tag for later table detection
|
||||
} else {
|
||||
p.list = append(p.list[:i+1], p.list[i+2:]...)
|
||||
}
|
||||
return strings.TrimSpace(strings.TrimSuffix(dev, "Build"))
|
||||
}
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
package useragent
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type VersionNo struct {
|
||||
Major int
|
||||
Minor int
|
||||
Patch int
|
||||
}
|
||||
|
||||
// parseVersion parse version string into Major.Minor.Patch struct
|
||||
func parseVersion(ver string) (verno VersionNo) {
|
||||
var err error
|
||||
parts := strings.Split(ver, ".")
|
||||
if len(parts) > 0 {
|
||||
if verno.Major, err = strconv.Atoi(parts[0]); err != nil {
|
||||
return verno
|
||||
}
|
||||
if len(parts) > 1 {
|
||||
if verno.Minor, err = strconv.Atoi(parts[1]); err != nil {
|
||||
return verno
|
||||
}
|
||||
if len(parts) > 2 {
|
||||
if verno.Patch, err = strconv.Atoi(parts[2]); err != nil {
|
||||
return verno
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return verno
|
||||
}
|
||||
|
||||
// VersionNoShort return version string in format <Major>.<Minor>
|
||||
func (ua UserAgent) VersionNoShort() string {
|
||||
if ua.VersionNo.Major == 0 && ua.VersionNo.Minor == 0 && ua.VersionNo.Patch == 0 {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%d.%d", ua.VersionNo.Major, ua.VersionNo.Minor)
|
||||
}
|
||||
|
||||
// VersionNoFull returns version string in format <Major>.<Minor>.<Patch>
|
||||
func (ua UserAgent) VersionNoFull() string {
|
||||
if ua.VersionNo.Major == 0 && ua.VersionNo.Minor == 0 && ua.VersionNo.Patch == 0 {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%d.%d.%d", ua.VersionNo.Major, ua.VersionNo.Minor, ua.VersionNo.Patch)
|
||||
}
|
||||
|
||||
// OSVersionNoShort returns OS version string in format <Major>.<Minor>
|
||||
func (ua UserAgent) OSVersionNoShort() string {
|
||||
if ua.OSVersionNo.Major == 0 && ua.OSVersionNo.Minor == 0 && ua.OSVersionNo.Patch == 0 {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%d.%d", ua.OSVersionNo.Major, ua.OSVersionNo.Minor)
|
||||
}
|
||||
|
||||
// OSVersionNoFull returns OS version string in format <Major>.<Minor>.<Patch>
|
||||
func (ua UserAgent) OSVersionNoFull() string {
|
||||
if ua.OSVersionNo.Major == 0 && ua.OSVersionNo.Minor == 0 && ua.OSVersionNo.Patch == 0 {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%d.%d.%d", ua.OSVersionNo.Major, ua.OSVersionNo.Minor, ua.OSVersionNo.Patch)
|
||||
}
|
||||
Reference in New Issue
Block a user