First commit
This commit is contained in:
@@ -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.
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
# Go/Golang package for parsing user agent strings [](https://godoc.org/github.com/mileusna/useragent)
|
||||||
|
|
||||||
|
Parse browser's and bot's user agents strings and determin user agent name, version, operating system name etc.
|
||||||
|
|
||||||
|
## Status
|
||||||
|
|
||||||
|
Still need some work on detecting Andorid device name.
|
||||||
|
|
||||||
|
## 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 := ua.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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notice
|
||||||
|
|
||||||
|
+ Opera and Opera Mini are two browsers, since they operate on very differenta way.
|
||||||
|
+ 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`.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
package ua
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
@@ -0,0 +1,407 @@
|
|||||||
|
package ua
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// UserAgent struct containg all determined datra from parsed user-agent string
|
||||||
|
type UserAgent struct {
|
||||||
|
Name string
|
||||||
|
Version string
|
||||||
|
OS string
|
||||||
|
OSVersion string
|
||||||
|
Device string
|
||||||
|
|
||||||
|
Mobile bool
|
||||||
|
Tablet bool
|
||||||
|
Desktop bool
|
||||||
|
Bot bool
|
||||||
|
URL string
|
||||||
|
|
||||||
|
String string
|
||||||
|
}
|
||||||
|
|
||||||
|
var ignore = map[string]struct{}{
|
||||||
|
"KHTML, like Gecko": struct{}{},
|
||||||
|
"U": struct{}{},
|
||||||
|
"compatible": struct{}{},
|
||||||
|
"Mozilla": struct{}{},
|
||||||
|
"WOW64": struct{}{},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constants for browsers and operating systems for easier comparation
|
||||||
|
const (
|
||||||
|
Windows = "Windows"
|
||||||
|
WindowsPhone = "Windows Phone"
|
||||||
|
Android = "Android"
|
||||||
|
MacOS = "macOS"
|
||||||
|
IOS = "iOS"
|
||||||
|
Linux = "Linux"
|
||||||
|
|
||||||
|
Opera = "Opera"
|
||||||
|
OperaMini = "Opera Mini"
|
||||||
|
Chrome = "Chrome"
|
||||||
|
Firefox = "Firefox"
|
||||||
|
InternetExplorer = "Internet Explorer"
|
||||||
|
Safari = "Safari"
|
||||||
|
Edge = "Edge"
|
||||||
|
Vivaldi = "Vivaldi"
|
||||||
|
|
||||||
|
Googlebot = "Googlebot"
|
||||||
|
Twitterbot = "Twitterbot"
|
||||||
|
FacebookExternalHit = "facebookexternalhit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Parse user agent string returning UserAgent struct
|
||||||
|
func Parse(userAgent string) UserAgent {
|
||||||
|
ua := UserAgent{
|
||||||
|
String: userAgent,
|
||||||
|
}
|
||||||
|
|
||||||
|
tokens := parse(userAgent)
|
||||||
|
|
||||||
|
// check is there URL
|
||||||
|
for k := range tokens {
|
||||||
|
if strings.HasPrefix(k, "http://") || strings.HasPrefix(k, "https://") {
|
||||||
|
ua.URL = k
|
||||||
|
delete(tokens, k)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// OS lookup
|
||||||
|
switch {
|
||||||
|
case tokens.exists("Android"):
|
||||||
|
ua.OS = Android
|
||||||
|
ua.OSVersion = tokens[Android]
|
||||||
|
for s := range tokens {
|
||||||
|
if strings.HasSuffix(s, "Build") {
|
||||||
|
ua.Device = strings.TrimSpace(s[:len(s)-5])
|
||||||
|
ua.Tablet = strings.Contains(strings.ToLower(ua.Device), "tablet")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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("Windows NT"):
|
||||||
|
ua.OS = Windows
|
||||||
|
ua.OSVersion = tokens["Windows NT"]
|
||||||
|
ua.Desktop = true
|
||||||
|
|
||||||
|
case tokens.exists("Windows Phone OS"):
|
||||||
|
ua.OS = WindowsPhone
|
||||||
|
ua.OSVersion = tokens["Windows Phone OS"]
|
||||||
|
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[Linux]
|
||||||
|
ua.Desktop = true
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// for s, val := range sys {
|
||||||
|
// fmt.Println(s, "--", val)
|
||||||
|
// }
|
||||||
|
|
||||||
|
switch {
|
||||||
|
|
||||||
|
case tokens.exists("Googlebot"):
|
||||||
|
ua.Name = Googlebot
|
||||||
|
ua.Version = tokens[Googlebot]
|
||||||
|
ua.Bot = true
|
||||||
|
ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari")
|
||||||
|
|
||||||
|
case tokens["Opera Mini"] != "":
|
||||||
|
ua.Name = OperaMini
|
||||||
|
ua.Version = tokens[OperaMini]
|
||||||
|
ua.Mobile = true
|
||||||
|
|
||||||
|
case tokens["OPR"] != "":
|
||||||
|
ua.Name = Opera
|
||||||
|
ua.Version = tokens["OPR"]
|
||||||
|
ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari")
|
||||||
|
|
||||||
|
// Opera on iOS
|
||||||
|
case tokens["OPiOS"] != "":
|
||||||
|
ua.Name = Opera
|
||||||
|
ua.Version = tokens["OPiOS"]
|
||||||
|
ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari")
|
||||||
|
|
||||||
|
// Chrome on iOS
|
||||||
|
case tokens["CriOS"] != "":
|
||||||
|
ua.Name = Chrome
|
||||||
|
ua.Version = tokens["CriOS"]
|
||||||
|
ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari")
|
||||||
|
|
||||||
|
// Firefox on iOS
|
||||||
|
case tokens["FxiOS"] != "":
|
||||||
|
ua.Name = Firefox
|
||||||
|
ua.Version = tokens["FxiOS"]
|
||||||
|
ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari")
|
||||||
|
|
||||||
|
case tokens["Firefox"] != "":
|
||||||
|
ua.Name = Firefox
|
||||||
|
ua.Version = tokens[Firefox]
|
||||||
|
_, ua.Mobile = tokens["Mobile"]
|
||||||
|
_, ua.Tablet = tokens["Tablet"]
|
||||||
|
|
||||||
|
case tokens["Vivaldi"] != "":
|
||||||
|
ua.Name = Vivaldi
|
||||||
|
ua.Version = tokens[Vivaldi]
|
||||||
|
|
||||||
|
case tokens.exists("MSIE"):
|
||||||
|
ua.Name = InternetExplorer
|
||||||
|
ua.Version = tokens["MSIE"]
|
||||||
|
|
||||||
|
case tokens["Edge"] != "":
|
||||||
|
ua.Name = Edge
|
||||||
|
ua.Version = tokens["Edge"]
|
||||||
|
ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari")
|
||||||
|
|
||||||
|
case tokens["bingbot"] != "":
|
||||||
|
ua.Name = "Bingbot"
|
||||||
|
ua.Version = tokens["bingbot"]
|
||||||
|
ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari")
|
||||||
|
|
||||||
|
case tokens["SamsungBrowser"] != "":
|
||||||
|
ua.Name = "Samsung Browser"
|
||||||
|
ua.Version = tokens["SamsungBrowser"]
|
||||||
|
ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari")
|
||||||
|
|
||||||
|
// if chrome and Safari defined, find any other tokensent descr
|
||||||
|
case tokens.exists(Chrome) && tokens.exists(Safari):
|
||||||
|
name := tokens.findBestMatch(true)
|
||||||
|
if name != "" {
|
||||||
|
ua.Name = name
|
||||||
|
ua.Version = tokens[name]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
fallthrough
|
||||||
|
|
||||||
|
case tokens.exists("Chrome"):
|
||||||
|
ua.Name = Chrome
|
||||||
|
ua.Version = tokens["Chrome"]
|
||||||
|
ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari")
|
||||||
|
|
||||||
|
case tokens.exists("Safari"):
|
||||||
|
ua.Name = Safari
|
||||||
|
if v, ok := tokens["Version"]; ok {
|
||||||
|
ua.Version = v
|
||||||
|
} else {
|
||||||
|
ua.Version = tokens["Safari"]
|
||||||
|
}
|
||||||
|
ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari")
|
||||||
|
|
||||||
|
default:
|
||||||
|
if ua.OS == "Android" && tokens["Version"] != "" {
|
||||||
|
ua.Name = "Android browser"
|
||||||
|
ua.Version = tokens["Version"]
|
||||||
|
ua.Mobile = true
|
||||||
|
} else {
|
||||||
|
if name := tokens.findBestMatch(false); name != "" {
|
||||||
|
ua.Name = name
|
||||||
|
ua.Version = tokens[name]
|
||||||
|
} else {
|
||||||
|
ua.Name = ua.String
|
||||||
|
}
|
||||||
|
ua.Bot = strings.Contains(strings.ToLower(ua.Name), "bot")
|
||||||
|
ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if tabler, switch mobile to off
|
||||||
|
if ua.Tablet {
|
||||||
|
ua.Mobile = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// if not already bot, check some popular bots and weather URL is set
|
||||||
|
if !ua.Bot {
|
||||||
|
ua.Bot = ua.URL != ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ua.Bot {
|
||||||
|
switch ua.Name {
|
||||||
|
case Twitterbot, FacebookExternalHit:
|
||||||
|
ua.Bot = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ua
|
||||||
|
}
|
||||||
|
|
||||||
|
func parse(userAgent string) (clients properties) {
|
||||||
|
clients = make(map[string]string, 0)
|
||||||
|
slash := false
|
||||||
|
isURL := false
|
||||||
|
var buff, val bytes.Buffer
|
||||||
|
addToken := func() {
|
||||||
|
if buff.Len() != 0 {
|
||||||
|
s := strings.TrimSpace(buff.String())
|
||||||
|
if _, ign := ignore[s]; !ign {
|
||||||
|
if isURL {
|
||||||
|
s = strings.TrimPrefix(s, "+")
|
||||||
|
}
|
||||||
|
|
||||||
|
if val.Len() == 0 { // only if value don't exists
|
||||||
|
var ver string
|
||||||
|
s, ver = checkVer(s) // determin version string and split
|
||||||
|
clients[s] = ver
|
||||||
|
} else {
|
||||||
|
clients[s] = strings.TrimSpace(val.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buff.Reset()
|
||||||
|
val.Reset()
|
||||||
|
slash = false
|
||||||
|
isURL = false
|
||||||
|
}
|
||||||
|
|
||||||
|
parOpen := false
|
||||||
|
|
||||||
|
bua := []byte(userAgent)
|
||||||
|
for i, c := range bua {
|
||||||
|
|
||||||
|
//fmt.Println(string(c), c)
|
||||||
|
switch {
|
||||||
|
case c == 41: // )
|
||||||
|
addToken()
|
||||||
|
parOpen = false
|
||||||
|
|
||||||
|
case parOpen && c == 59: // ;
|
||||||
|
addToken()
|
||||||
|
|
||||||
|
case c == 40: // (
|
||||||
|
addToken()
|
||||||
|
parOpen = true
|
||||||
|
|
||||||
|
case slash && c == 32:
|
||||||
|
addToken()
|
||||||
|
|
||||||
|
case slash:
|
||||||
|
val.WriteByte(c)
|
||||||
|
|
||||||
|
case c == 47 && !isURL: // /
|
||||||
|
if i != len(bua)-1 && bua[i+1] == 47 && (bytes.HasSuffix(buff.Bytes(), []byte("http:")) || bytes.HasSuffix(buff.Bytes(), []byte("https:"))) {
|
||||||
|
buff.WriteByte(c)
|
||||||
|
isURL = true
|
||||||
|
} else {
|
||||||
|
slash = true
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
buff.WriteByte(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addToken()
|
||||||
|
|
||||||
|
return clients
|
||||||
|
}
|
||||||
|
|
||||||
|
func checkVer(s string) (name, v string) {
|
||||||
|
i := strings.LastIndex(s, " ")
|
||||||
|
if i == -1 {
|
||||||
|
return s, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
//v = s[i+1:]
|
||||||
|
|
||||||
|
switch s[:i] {
|
||||||
|
case "Linux", "Windows NT", "Windows Phone OS", "MSIE", "Android":
|
||||||
|
return s[:i], s[i+1:]
|
||||||
|
default:
|
||||||
|
return s, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// for _, c := range v {
|
||||||
|
// if (c >= 48 && c <= 57) || c == 46 {
|
||||||
|
// } else {
|
||||||
|
// return s, ""
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// return s[:i], s[i+1:]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type properties map[string]string
|
||||||
|
|
||||||
|
func (p properties) exists(key string) bool {
|
||||||
|
_, ok := p[key]
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p properties) existsAny(keys ...string) bool {
|
||||||
|
for _, k := range keys {
|
||||||
|
if _, ok := p[k]; ok {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p properties) findMacOSVersion() string {
|
||||||
|
for k, v := range p {
|
||||||
|
if strings.Contains(k, "OS") {
|
||||||
|
if ver := findVersion(v); ver != "" {
|
||||||
|
return ver
|
||||||
|
} else if ver = findVersion(k); ver != "" {
|
||||||
|
return ver
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// findBestMatch from the rest of the bunch
|
||||||
|
// in first cycle only return key vith 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 k, v := range p {
|
||||||
|
switch k {
|
||||||
|
case Chrome, Firefox, Safari, "Version", "Mobile", "Mobile Safari", "Mozilla", "AppleWebKit", "Windows NT", "Windows Phone OS", Android, "Macintosh", Linux:
|
||||||
|
default:
|
||||||
|
if i == 0 {
|
||||||
|
if v != "" { // in first check, only return keys with value
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
var rxMacOSVer = regexp.MustCompile("[_\\d\\.]+")
|
||||||
|
|
||||||
|
func findVersion(s string) string {
|
||||||
|
if ver := rxMacOSVer.FindString(s); ver != "" {
|
||||||
|
return strings.Replace(ver, "_", ".", -1)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
+163
@@ -0,0 +1,163 @@
|
|||||||
|
package ua_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mileusna/useragent"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParse(t *testing.T) {
|
||||||
|
var testTable = [][]string{
|
||||||
|
// Mac
|
||||||
|
{"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", ua.Safari, "10.1.2", "desktop", "macOS"},
|
||||||
|
{"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36", ua.Chrome, "60.0.3112.90", "desktop", "macOS"},
|
||||||
|
{"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:54.0) Gecko/20100101 Firefox/54.0", ua.Firefox, "54.0", "desktop", "macOS"},
|
||||||
|
{"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36 OPR/46.0.2597.57", ua.Opera, "46.0.2597.57", "desktop", "macOS"},
|
||||||
|
{"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.91 Safari/537.36 Vivaldi/1.92.917.39", "Vivaldi", "1.92.917.39", "desktop", "macOS"},
|
||||||
|
|
||||||
|
// Windows
|
||||||
|
{"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", ua.Chrome, "59.0.3071.115", "desktop", "Windows"},
|
||||||
|
{"Mozilla/4.0 (compatible; MSIE 8.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.2; GWX:RED)", ua.InternetExplorer, "8.0", "desktop", "Windows"},
|
||||||
|
{"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322) NS8/0.9.6", ua.InternetExplorer, "6.0", "desktop", "Windows"},
|
||||||
|
|
||||||
|
// iPhone
|
||||||
|
{"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", ua.Safari, "10.0", "mobile", "iOS"},
|
||||||
|
{"Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_2 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) CriOS/60.0.3112.89 Mobile/14F89 Safari/602.1", ua.Chrome, "60.0.3112.89", "mobile", "iOS"},
|
||||||
|
{"Mozilla/5.0 (iPhone; CPU iPhone OS 9_3 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) OPiOS/14.0.0.104835 Mobile/13E233 Safari/9537.53", ua.Opera, "14.0.0.104835", "mobile", "iOS"},
|
||||||
|
{"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", ua.Firefox, "8.1.1b4948", "mobile", "iOS"},
|
||||||
|
|
||||||
|
// iPad
|
||||||
|
{"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", ua.Safari, "10.0", "tablet", "iOS"},
|
||||||
|
{"Mozilla/5.0 (iPad; CPU OS 10_3_2 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/58.0.3029.113 Mobile/14F89 Safari/602.1", ua.Chrome, "58.0.3029.113", "tablet", "iOS"},
|
||||||
|
{"Mozilla/5.0 (iPad; CPU 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", ua.Firefox, "8.1.1b4948", "tablet", "iOS"},
|
||||||
|
|
||||||
|
// Andorid
|
||||||
|
{"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", ua.Chrome, "59.0.3071.125", "mobile", "Android"},
|
||||||
|
{"Mozilla/5.0 (Android 4.3; Mobile; rv:54.0) Gecko/54.0 Firefox/54.0", ua.Firefox, "54.0", "mobile", "Android"},
|
||||||
|
{"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", ua.Opera, "42.9.2246.119956", "mobile", ua.Android},
|
||||||
|
{"Opera/9.80 (Android; Opera Mini/28.0.2254/66.318; U; en) Presto/2.12.423 Version/12.16", ua.OperaMini, "28.0.2254/66.318", "mobile", "Android"},
|
||||||
|
{"Mozilla/5.0 (Linux; U; Android 4.3; en-us; GT-I9300 Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", "Android browser", "4.0", "mobile", "Android"},
|
||||||
|
|
||||||
|
{"Mozilla/5.0 (Linux; Android 6.0.1; SAMSUNG SM-A310F/A310FXXU2BQB1 Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/5.4 Chrome/51.0.2704.106 Mobile Safari/537.36", "Samsung Browser", "5.4", "mobile", "Android"},
|
||||||
|
|
||||||
|
// Windows phone
|
||||||
|
{"Mozilla/4.0 (compatible; MSIE 7.0; Windows Phone OS 7.0; Trident/3.1; IEMobile/7.0; NOKIA; Lumia 630)", ua.InternetExplorer, "7.0", "mobile", ua.WindowsPhone},
|
||||||
|
|
||||||
|
// Bots
|
||||||
|
{"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.Googlebot, "2.1", "mobile", "Android"},
|
||||||
|
{"Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)", ua.Googlebot, "2.1", "bot", ""},
|
||||||
|
{"Twitterbot/1.0", ua.Twitterbot, "1.0", "bot", ""},
|
||||||
|
{"facebookexternalhit/1.1", ua.FacebookExternalHit, "1.1", "bot", ""},
|
||||||
|
|
||||||
|
// other
|
||||||
|
{"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36 Google (+https://developers.google.com/+/web/snippet/)", ua.Chrome, "56.0.2924.87", "bot", ua.Linux}, // Google+ fetch
|
||||||
|
|
||||||
|
// tools
|
||||||
|
{"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) QtWebEngine/5.6.0 Chrome/45.0.2454.101 Safari/537.36", "QtWebEngine", "5.6.0", "", "macOS"},
|
||||||
|
{"Go-http-client/1.1", "Go-http-client", "1.1", "", ""},
|
||||||
|
{"Wget/1.12 (linux-gnu)", "Wget", "1.12", "", ""},
|
||||||
|
{"Wget/1.17.1 (darwin15.2.0)", "Wget", "1.17.1", "", ""},
|
||||||
|
|
||||||
|
// unstandard stuff
|
||||||
|
{"BUbiNG (+http://law.di.unimi.it/BUbiNG.html)", "BUbiNG", "", "", ""},
|
||||||
|
|
||||||
|
//GooglePlus "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36 Google (+https://developers.google.com/+/web/snippet/)"
|
||||||
|
//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)
|
||||||
|
//Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) QtWebEngine/5.6.0 Chrome/45.0.2454.101 Safari/537.36
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range testTable {
|
||||||
|
ua := ua.Parse(test[0])
|
||||||
|
if ua.Name != test[1] {
|
||||||
|
t.Error("\n", test[0], "\nName should be", test[1], "not", ua.Name)
|
||||||
|
}
|
||||||
|
if ua.Version != test[2] {
|
||||||
|
t.Error("\nVersion should be", test[2], "not", ua.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(test) > 3 {
|
||||||
|
if test[3] == "desktop" && ua.Mobile {
|
||||||
|
t.Error("\n", ua.String, "should be desktop type not mobile")
|
||||||
|
}
|
||||||
|
|
||||||
|
if test[3] == "mobile" && !ua.Mobile {
|
||||||
|
t.Error("\n", ua.String, "should be mobile")
|
||||||
|
}
|
||||||
|
if test[3] == "tablet" && !ua.Tablet {
|
||||||
|
t.Error("\n", ua.String, "should be tablet")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(test) > 4 && test[4] != ua.OS {
|
||||||
|
t.Error("\n", test[0], "OS should", test[4], "not", ua.OS)
|
||||||
|
}
|
||||||
|
//fmt.Println(ua.OS, ua.OSVersion, ua.Device)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleParse() {
|
||||||
|
userAgents := []string{
|
||||||
|
// Mac
|
||||||
|
"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 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.90 Safari/537.36",
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.12; rv:54.0) Gecko/20100101 Firefox/54.0",
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36 OPR/46.0.2597.57",
|
||||||
|
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3112.91 Safari/537.36 Vivaldi/1.92.917.39",
|
||||||
|
|
||||||
|
// Windows
|
||||||
|
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
|
||||||
|
"Mozilla/4.0 (compatible; MSIE 8.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.2; GWX:RED)",
|
||||||
|
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; .NET CLR 1.1.4322) NS8/0.9.6",
|
||||||
|
|
||||||
|
// iPhone
|
||||||
|
"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.1.30 (KHTML, like Gecko) CriOS/60.0.3112.89 Mobile/14F89 Safari/602.1",
|
||||||
|
"Mozilla/5.0 (iPhone; CPU iPhone OS 9_3 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) OPiOS/14.0.0.104835 Mobile/13E233 Safari/9537.53",
|
||||||
|
"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",
|
||||||
|
|
||||||
|
// iPad
|
||||||
|
"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 (iPad; CPU OS 10_3_2 like Mac OS X) AppleWebKit/602.1.50 (KHTML, like Gecko) CriOS/58.0.3029.113 Mobile/14F89 Safari/602.1",
|
||||||
|
"Mozilla/5.0 (iPad; CPU 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",
|
||||||
|
|
||||||
|
// Andorid
|
||||||
|
"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",
|
||||||
|
"Mozilla/5.0 (Linux; U; Android 4.3; en-us; GT-I9300 Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30",
|
||||||
|
|
||||||
|
"Mozilla/5.0 (Linux; Android 6.0.1; SAMSUNG SM-A310F/A310FXXU2BQB1 Build/MMB29K) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/5.4 Chrome/51.0.2704.106 Mobile Safari/537.36",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range userAgents {
|
||||||
|
ua := ua.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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user