From ccfc67dea6b8670d77a71f9bb5a2df5dc3ea3ac0 Mon Sep 17 00:00:00 2001 From: Dawid Bialy Date: Wed, 22 Feb 2023 19:12:09 +0100 Subject: [PATCH] user agent up --- docker-compose.local.yml | 12 +- go.mod | 2 +- go.sum | 4 +- middleware.go | 18 +- .../github.com/mileusna/useragent/.gitignore | 4 - .../github.com/mileusna/useragent/LICENSE.md | 21 - .../github.com/mileusna/useragent/README.md | 99 ---- vendor/github.com/mileusna/useragent/is.go | 81 --- vendor/github.com/mileusna/useragent/ua.go | 507 ------------------ .../github.com/mssola/useragent/CHANGELOG.md | 72 +++ .../mssola/useragent/CONTRIBUTING.md | 42 ++ vendor/github.com/mssola/useragent/LICENSE | 20 + vendor/github.com/mssola/useragent/Makefile | 25 + vendor/github.com/mssola/useragent/README.md | 95 ++++ vendor/github.com/mssola/useragent/bot.go | 149 +++++ vendor/github.com/mssola/useragent/browser.go | 193 +++++++ vendor/github.com/mssola/useragent/model.go | 35 ++ .../mssola/useragent/operating_systems.go | 377 +++++++++++++ .../github.com/mssola/useragent/user_agent.go | 190 +++++++ vendor/modules.txt | 6 +- 20 files changed, 1227 insertions(+), 725 deletions(-) delete mode 100644 vendor/github.com/mileusna/useragent/.gitignore delete mode 100644 vendor/github.com/mileusna/useragent/LICENSE.md delete mode 100644 vendor/github.com/mileusna/useragent/README.md delete mode 100644 vendor/github.com/mileusna/useragent/is.go delete mode 100644 vendor/github.com/mileusna/useragent/ua.go create mode 100644 vendor/github.com/mssola/useragent/CHANGELOG.md create mode 100644 vendor/github.com/mssola/useragent/CONTRIBUTING.md create mode 100644 vendor/github.com/mssola/useragent/LICENSE create mode 100644 vendor/github.com/mssola/useragent/Makefile create mode 100644 vendor/github.com/mssola/useragent/README.md create mode 100644 vendor/github.com/mssola/useragent/bot.go create mode 100644 vendor/github.com/mssola/useragent/browser.go create mode 100644 vendor/github.com/mssola/useragent/model.go create mode 100644 vendor/github.com/mssola/useragent/operating_systems.go create mode 100644 vendor/github.com/mssola/useragent/user_agent.go diff --git a/docker-compose.local.yml b/docker-compose.local.yml index 352e01a..1c8c223 100644 --- a/docker-compose.local.yml +++ b/docker-compose.local.yml @@ -7,7 +7,14 @@ services: command: - "--log.level=DEBUG" - "--accesslog" - - "--accesslog.filepath=/var/log/traefik/access.log" + # - "--accesslog.filepath=/var/log/traefik/access.log" + # - --accesslog.format=json + - --accesslog.fields.defaultmode=keep + - --accesslog.fields.names.ClientUsername=drop + - --accesslog.fields.headers.defaultmode=keep + - --accesslog.fields.headers.names.User-Agent=redact + - --accesslog.fields.headers.names.Authorization=drop + - --accesslog.fields.headers.names.Content-Type=keep - "--api.insecure=true" - "--providers.docker=true" - "--providers.docker.exposedbydefault=false" @@ -27,7 +34,8 @@ services: container_name: "simple-service" labels: - "traefik.enable=true" - - "traefik.http.middlewares.cfdevicedetect.plugin.cfdevicedetect.autodetect=true" + - "traefik.http.middlewares.cfdevicedetect.plugin.traefik-cf-device-detector.autodetect=true" + - "traefik.http.routers.whoami.rule=Host(`whoami.localhost`)" - "traefik.http.routers.whoami.middlewares=cfdevicedetect@docker" - "traefik.http.routers.whoami.entrypoints=web" diff --git a/go.mod b/go.mod index 2651866..cf57256 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module github.com/davewhit3/traefik-cf-device-detector go 1.19 -require github.com/mileusna/useragent v1.2.1 +require github.com/mssola/useragent v1.0.0 diff --git a/go.sum b/go.sum index 8e80046..06ed9fb 100644 --- a/go.sum +++ b/go.sum @@ -1,2 +1,2 @@ -github.com/mileusna/useragent v1.2.1 h1:p3RJWhi3LfuI6BHdddojREyK3p6qX67vIfOVMnUIVr0= -github.com/mileusna/useragent v1.2.1/go.mod h1:3d8TOmwL/5I8pJjyVDteHtgDGcefrFUX4ccGOMKNYYc= +github.com/mssola/useragent v1.0.0 h1:WRlDpXyxHDNfvZaPEut5Biveq86Ze4o4EMffyMxmH5o= +github.com/mssola/useragent v1.0.0/go.mod h1:hz9Cqz4RXusgg1EdI4Al0INR62kP7aPSRNHnpU+b85Y= diff --git a/middleware.go b/middleware.go index f10b024..9db6a7a 100644 --- a/middleware.go +++ b/middleware.go @@ -3,10 +3,12 @@ package traefik_cf_device_detector import ( "context" + "log" "net/http" + "os" "strconv" - "github.com/mileusna/useragent" + "github.com/mssola/useragent" ) const ( @@ -36,24 +38,30 @@ func CreateConfig() *Config { // CfDeviceDetector a CfDeviceDetector plugin. type CfDeviceDetector struct { + log *log.Logger next http.Handler name string } // New created a new Demo plugin. func New(ctx context.Context, next http.Handler, config *Config, name string) (http.Handler, error) { + logger := log.New(os.Stdout, "[CfDeviceDetector] ", 0) + + logger.Printf("configured!") + return &CfDeviceDetector{ + log: logger, next: next, name: name, }, nil } func (mw *CfDeviceDetector) ServeHTTP(rw http.ResponseWriter, req *http.Request) { - ua := useragent.Parse(req.Header.Get(UserAgentHeader)) + ua := useragent.New(req.Header.Get(UserAgentHeader)) - req.Header.Set(DeviceIsMobileHeader, strconv.FormatBool(ua.Mobile)) - req.Header.Set(DeviceIsDesktopHeader, strconv.FormatBool(ua.Desktop)) - req.Header.Set(DeviceIsTabletHeader, strconv.FormatBool(ua.Tablet)) + req.Header.Set(DeviceIsMobileHeader, strconv.FormatBool(ua.Mobile())) + req.Header.Set(DeviceIsDesktopHeader, strconv.FormatBool(!ua.Mobile())) + req.Header.Set(DeviceIsTabletHeader, strconv.FormatBool(false)) req.Header.Set(DeviceIsSmartTVHeader, strconv.FormatBool(false)) mw.next.ServeHTTP(rw, req) diff --git a/vendor/github.com/mileusna/useragent/.gitignore b/vendor/github.com/mileusna/useragent/.gitignore deleted file mode 100644 index b9064a7..0000000 --- a/vendor/github.com/mileusna/useragent/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -old.go -old_test.go -file.txt -user_agents.txt diff --git a/vendor/github.com/mileusna/useragent/LICENSE.md b/vendor/github.com/mileusna/useragent/LICENSE.md deleted file mode 100644 index 2da0046..0000000 --- a/vendor/github.com/mileusna/useragent/LICENSE.md +++ /dev/null @@ -1,21 +0,0 @@ -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. \ No newline at end of file diff --git a/vendor/github.com/mileusna/useragent/README.md b/vendor/github.com/mileusna/useragent/README.md deleted file mode 100644 index 0e2179f..0000000 --- a/vendor/github.com/mileusna/useragent/README.md +++ /dev/null @@ -1,99 +0,0 @@ -# Go/Golang package for parsing user agent strings [![GoDoc](https://godoc.org/github.com/mileusna/useragent?status.svg)](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 - -Still need some work on detecting Andorid device names. - -Fill free to report an issue for any User-Agent string not recognized or misinterpreted. - -## Installation -``` -go get github.com/mileusna/useragent -``` - -## Example - -```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`. - - - diff --git a/vendor/github.com/mileusna/useragent/is.go b/vendor/github.com/mileusna/useragent/is.go deleted file mode 100644 index a98bea2..0000000 --- a/vendor/github.com/mileusna/useragent/is.go +++ /dev/null @@ -1,81 +0,0 @@ -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" -} - -// 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 -} diff --git a/vendor/github.com/mileusna/useragent/ua.go b/vendor/github.com/mileusna/useragent/ua.go deleted file mode 100644 index d0fcf3d..0000000 --- a/vendor/github.com/mileusna/useragent/ua.go +++ /dev/null @@ -1,507 +0,0 @@ -package useragent - -import ( - "bytes" - "regexp" - "strings" -) - -// UserAgent struct containing all data extracted 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": {}, - "U": {}, - "compatible": {}, - "Mozilla": {}, - "WOW64": {}, -} - -// Constants for browsers and operating systems for easier comparison -const ( - Windows = "Windows" - WindowsPhone = "Windows Phone" - Android = "Android" - MacOS = "macOS" - IOS = "iOS" - Linux = "Linux" - ChromeOS = "ChromeOS" - - Opera = "Opera" - OperaMini = "Opera Mini" - OperaTouch = "Opera Touch" - Chrome = "Chrome" - HeadlessChrome = "Headless Chrome" - Firefox = "Firefox" - InternetExplorer = "Internet Explorer" - Safari = "Safari" - Edge = "Edge" - Vivaldi = "Vivaldi" - - GoogleAdsBot = "Google Ads Bot" - Googlebot = "Googlebot" - Twitterbot = "Twitterbot" - FacebookExternalHit = "facebookexternalhit" - Applebot = "Applebot" - Bingbot = "Bingbot" -) - -// Parse user agent string returning UserAgent struct -func Parse(userAgent string) UserAgent { - ua := UserAgent{ - String: userAgent, - } - - tokens := parse(userAgent) - - // check is there URL - for i, token := range tokens.list { - if strings.HasPrefix(token.Key, "http://") || strings.HasPrefix(token.Key, "https://") { - ua.URL = token.Key - tokens.list = append(tokens.list[:i], tokens.list[i+1:]...) - break - } - } - - // OS lookup - switch { - case tokens.exists("Android"): - ua.OS = Android - ua.OSVersion = tokens.get(Android) - for _, token := range tokens.list { - s := token.Key - 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.get("Windows NT") - ua.Desktop = true - - case tokens.exists("Windows Phone OS"): - ua.OS = WindowsPhone - ua.OSVersion = tokens.get("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.get(Linux) - ua.Desktop = true - - case tokens.exists("CrOS"): - ua.OS = ChromeOS - ua.OSVersion = tokens.get("CrOS") - ua.Desktop = true - } - - // for s, val := range sys { - // fmt.Println(s, "--", val) - // } - - switch { - - case tokens.exists("Googlebot"): - ua.Name = Googlebot - ua.Version = tokens.get(Googlebot) - ua.Bot = true - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") - - case tokens.exists("Applebot"): - ua.Name = Applebot - ua.Version = tokens.get(Applebot) - ua.Bot = true - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") - ua.OS = "" - - case tokens.get("Opera Mini") != "": - 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", "Mobile Safari") - - case tokens.get("OPT") != "": - ua.Name = OperaTouch - ua.Version = tokens.get("OPT") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") - - // Opera on iOS - case tokens.get("OPiOS") != "": - ua.Name = Opera - ua.Version = tokens.get("OPiOS") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") - - // Chrome on iOS - case tokens.get("CriOS") != "": - ua.Name = Chrome - ua.Version = tokens.get("CriOS") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") - - // Firefox on iOS - case tokens.get("FxiOS") != "": - ua.Name = Firefox - ua.Version = tokens.get("FxiOS") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") - - 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", "Mobile Safari") - - case tokens.get("Edge") != "": - ua.Name = Edge - ua.Version = tokens.get("Edge") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") - - case tokens.get("Edg") != "": - ua.Name = Edge - ua.Version = tokens.get("Edg") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") - - case tokens.get("EdgA") != "": - ua.Name = Edge - ua.Version = tokens.get("EdgA") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") - - case tokens.get("bingbot") != "": - ua.Name = Bingbot - ua.Version = tokens.get("bingbot") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") - - case tokens.get("YandexBot") != "": - ua.Name = "YandexBot" - ua.Version = tokens.get("YandexBot") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") - - case tokens.get("SamsungBrowser") != "": - ua.Name = "Samsung Browser" - ua.Version = tokens.get("SamsungBrowser") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") - - case tokens.get("HeadlessChrome") != "": - ua.Name = HeadlessChrome - ua.Version = tokens.get("HeadlessChrome") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") - ua.Bot = true - - case tokens.exists("AdsBot-Google-Mobile") || tokens.exists("Mediapartners-Google") || tokens.exists("AdsBot-Google"): - ua.Name = GoogleAdsBot - 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.get("HuaweiBrowser") != "": - ua.Name = "Huawei Browser" - ua.Version = tokens.get("HuaweiBrowser") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") - - // 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", "Mobile Safari") - - case tokens.exists("Brave Chrome"): - ua.Name = Chrome - ua.Version = tokens.get("Brave Chrome") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") - - 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", "Mobile Safari") - - default: - if ua.OS == "Android" && 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") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") - } - } - - // if tablet, 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) properties { - clients := properties{ - list: make([]property, 0, 8), - } - 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.add(s, ver) - } else { - clients.add(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:] - case "CrOS x86_64", "CrOS aarch64": - j := strings.LastIndex(s[:i], " ") - return s[:j], s[j+1 : i] - default: - return s, "" - } - - // for _, c := range v { - // if (c >= 48 && c <= 57) || c == 46 { - // } else { - // return s, "" - // } - // } - // return s[:i], s[i+1:] -} - -type property struct { - Key string - Value string -} -type properties struct { - list []property -} - -func (p *properties) add(key, value string) { - p.list = append(p.list, property{Key: key, Value: value}) -} - -func (p properties) get(key string) string { - for _, prop := range p.list { - if prop.Key == key { - return prop.Value - } - } - return "" -} - -func (p properties) exists(key string) bool { - for _, prop := range p.list { - if 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) 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 "" -} - -// 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", "Mobile Safari", "Mozilla", "AppleWebKit", "Windows NT", "Windows Phone OS", Android, "Macintosh", Linux, "GSA", "CrOS": - default: - 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 "" -} diff --git a/vendor/github.com/mssola/useragent/CHANGELOG.md b/vendor/github.com/mssola/useragent/CHANGELOG.md new file mode 100644 index 0000000..a5efa36 --- /dev/null +++ b/vendor/github.com/mssola/useragent/CHANGELOG.md @@ -0,0 +1,72 @@ +# Changelog + +## 1.0.0 + +- Changed package's name to `useragent`. See [b486b63b54cb](https://github.com/mssola/useragent/commit/b486b63b54cbbc69cdf72a38842be4b9e6537e53). +- Renamed default branch to `main`. See [7e944763aee7](https://github.com/mssola/useragent/commit/7e944763aee796efcf4eec461abfbe7ec5fadc18). + +## 0.6.0 + +- Added information on the model of mobile devices. See [746647ad73b5](https://github.com/mssola/useragent/commit/746647ad73b5ad8648175bbd07319c0a8ac559c6). +- Added support for PhantomJS. See [6b5e6f6ebfa8](https://github.com/mssola/useragent/commit/6b5e6f6ebfa87464ccdb42bac5448cbf46ce1ba1). + +## 0.5.4 + +- Add detection of Coc Coc Browser. See [897eb45aec23](https://github.com/mssola/useragent/commit/897eb45aec2330e7566c48c9e54192aae84bd8e9). +- Add detection of Headless Chrome. See [897eb45aec23](https://github.com/mssola/useragent/commit/897eb45aec2330e7566c48c9e54192aae84bd8e9). +- Add detection of iOS WebViews. See [897eb45aec23](https://github.com/mssola/useragent/commit/897eb45aec2330e7566c48c9e54192aae84bd8e9). + +## 0.5.3 + +- Fix detection of Firefox on iPad. See [42e4a8f39125](https://github.com/mssola/useragent/commit/42e4a8f39125a6680fb5367a4602963f1351e069). +- Fix detection of Linux ARM-based Android. See [3b0e113c8047](https://github.com/mssola/useragent/commit/3b0e113c804708c01de00c27aae07d2acfee40d8). +- Add detection of Chromium Edge on Windows. See [ea81f1e9d61c](https://github.com/mssola/useragent/commit/ea81f1e9d61c094df4156690a8f4d5481b0d6c4a). +- Add detection of OkHttp. See [6b33e248e796](https://github.com/mssola/useragent/commit/6b33e248e7969cf3e76128a34d33be88d4eb0dc8). + +## 0.5.2 + +- Detect Electron. See [commit](https://github.com/mssola/useragent/commit/1a36963d74c0efca7de80dc7518a0958c66b3c4f). +- Add support for both http and https site urls. See [commit](https://github.com/mssola/useragent/commit/d78bf2c5886a0ab7e1cf90b68c808fe3e3ab6f8c). +- Add more support for BingBot. See [commit](https://github.com/mssola/useragent/commit/c6402a7b8aefdc4acfbf1e7f3b43eac0b266e49e). +- Add a test case for Firefox focus on iOS. See [commit](https://github.com/mssola/useragent/commit/a1e9c19d5a6887a17cef1d249118ccbd45cf4c0b). +- Detect iMessage-Preview. See [commit](https://github.com/mssola/useragent/commit/e8f5e19ded9711ee1f4b43218b9d57d00ef5c26a). + +## 0.5.1 + +- add Firefox for iOS. See [commit](https://github.com/mssola/useragent/commit/00a868fa17e7). +- Add go.mod. See [commit](https://github.com/mssola/useragent/commit/8c16c37f4e07). +- Use CodeLingo to Address Further Issues. See [commit](https://github.com/mssola/useragent/commit/7e313fc62553). +- Fix function comments based on best practices from Effective Go. See [commit](https://github.com/mssola/useragent/commit/95b0c164394f). +- test: mobile Yandex Browser. See [commit](https://github.com/mssola/useragent/commit/1df9e04ee4f5). +- Add Yandex browser. See [commit](https://github.com/mssola/useragent/commit/6eb76c60b5e8). +- Updating license notice. See [commit](https://github.com/mssola/useragent/commit/8b3999083770). +- Detect Chrome for iOS correctly. See [commit](https://github.com/mssola/useragent/commit/82f141dea4a8). +- Facebook App Handling. See [commit](https://github.com/mssola/useragent/commit/5723c361ed97). +- Add a new google bot user agent format. See [commit](https://github.com/mssola/useragent/commit/57c32981bd5f). + +## 0.5.0 + +### Newly supported and improvements + +- Added support for Microsoft Edge. See [commit](https://github.com/mssola/useragent/commit/f659b9863849). +- Precompile regular expressions. See [commit](https://github.com/mssola/useragent/commit/783ec61292ae). +- Added support for Dalvik user agent parsing. See [commit](https://github.com/mssola/useragent/commit/78413629666f). +- Improved bot support (also e25e612b37a4). See [commit](https://github.com/mssola/useragent/commit/0319fcf00bfd). +- Add Chromium support and Ubuntu specific tests. See [commit](https://github.com/mssola/useragent/commit/6e7843e05771). +- Add OSInfo function to user agent (also 7286ca6abc28). See [commit](https://github.com/mssola/useragent/commit/3335cae017e7). +- Detect updated UA for Googlebot. See [commit](https://github.com/mssola/useragent/commit/6fe362d7cd64). +- Adds the Adsense bot (mobile). See [commit](https://github.com/mssola/useragent/commit/1438bfba89d7). + +### Fixes + +- Fixed bug when extracting windows 10. See [commit](https://github.com/mssola/useragent/commit/8d86c2cf88bf). +- Fixed bug on mobile Firefox browsers running on Android OS versions that report their version number inline.. See [commit](https://github.com/mssola/useragent/commit/9d00ff9e4202). + +### Other + +- Improved testing infrastructure. See [commit](https://github.com/mssola/useragent/commit/63395b193f8812526305bec75ea7117262a124aa). + +## Older releases + +See the description on each release +[here](https://github.com/mssola/useragent/releases). diff --git a/vendor/github.com/mssola/useragent/CONTRIBUTING.md b/vendor/github.com/mssola/useragent/CONTRIBUTING.md new file mode 100644 index 0000000..c29db33 --- /dev/null +++ b/vendor/github.com/mssola/useragent/CONTRIBUTING.md @@ -0,0 +1,42 @@ +# Contributing to useragent + +## Check that your changes do not break anything + +You can safely run tests and the linting utilities with the default `make` target: + +``` +$ make +``` + +Note that this target assumes that you have +[golangci-lint](https://github.com/golangci/golangci-lint) and +[git-valitation](https://github.com/vbatts/git-validation) already installed. If +that is not the case, first install them to get a proper execution of the +default `make` target. + +Otherwise, if you want to be more specific, refer to the `Makefile` to check +which target fits your needs. That being said, the default target is usually +what you want. + +## Issue reporting + +I'm using [Github](https://github.com/mssola/useragent) in order to host the +code. Thus, in order to report issues you can do it on its [issue +tracker](https://github.com/mssola/useragent/issues). A couple of notes on +reports: + +- Check that the issue has not already been reported or fixed in `main`. +- Try to be concise and precise in your description of the problem. +- Provide a step by step guide on how to reproduce this problem. +- Provide the version you are using (the commit SHA, if possible). + +## Pull requests + +- Write a [good commit message](https://chris.beams.io/posts/git-commit/). +- Make sure that tests are passing on your local machine (it will also be +checked by the CI system whenever you submit the pull request). +- Update the [changelog](./CHANGELOG.md). +- Try to use the same coding conventions as used in this project. +- Open a pull request with *only* one subject and a clear title and +description. Refrain from submitting pull requests with tons of different +unrelated commits. diff --git a/vendor/github.com/mssola/useragent/LICENSE b/vendor/github.com/mssola/useragent/LICENSE new file mode 100644 index 0000000..b18c275 --- /dev/null +++ b/vendor/github.com/mssola/useragent/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2012-2023 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/vendor/github.com/mssola/useragent/Makefile b/vendor/github.com/mssola/useragent/Makefile new file mode 100644 index 0000000..d1526f1 --- /dev/null +++ b/vendor/github.com/mssola/useragent/Makefile @@ -0,0 +1,25 @@ +GO ?= go +GO_SRC = $(shell find . -name \*.go) + +.DEFAULT: build +all: test lint + +.PHONY: test +test: + @$(GO) test + +.PHONY: bench +bench: + @$(GO) test -bench=. + +.PHONY: lint +lint: git-validation cilint + +EPOCH_COMMIT ?= 834b6d4d9e84 +.PHONY: git-validation +git-validation: + @git-validation -v -D -range $(EPOCH_COMMIT)..HEAD + +.PHONY: cilint +cilint: + @golangci-lint run diff --git a/vendor/github.com/mssola/useragent/README.md b/vendor/github.com/mssola/useragent/README.md new file mode 100644 index 0000000..0072f10 --- /dev/null +++ b/vendor/github.com/mssola/useragent/README.md @@ -0,0 +1,95 @@ +

+ Build Status for the default branch + go.dev page + MIT +

+ +--- + +UserAgent is a Go library that parses HTTP User Agents. As an example: + +```go +package main + +import ( + "fmt" + + "github.com/mssola/useragent" +) + +func main() { + // The "New" function will create a new UserAgent object and it will parse + // the given string. If you need to parse more strings, you can re-use + // this object and call: ua.Parse("another string") + ua := useragent.New("Mozilla/5.0 (Linux; U; Android 2.3.7; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1") + + fmt.Printf("%v\n", ua.Mobile()) // => true + fmt.Printf("%v\n", ua.Bot()) // => false + fmt.Printf("%v\n", ua.Mozilla()) // => "5.0" + fmt.Printf("%v\n", ua.Model()) // => "Nexus One" + + fmt.Printf("%v\n", ua.Platform()) // => "Linux" + fmt.Printf("%v\n", ua.OS()) // => "Android 2.3.7" + + name, version := ua.Engine() + fmt.Printf("%v\n", name) // => "AppleWebKit" + fmt.Printf("%v\n", version) // => "533.1" + + name, version = ua.Browser() + fmt.Printf("%v\n", name) // => "Android" + fmt.Printf("%v\n", version) // => "4.0" + + // Let's see an example with a bot. + + ua.Parse("Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)") + + fmt.Printf("%v\n", ua.Bot()) // => true + + name, version = ua.Browser() + fmt.Printf("%v\n", name) // => Googlebot + fmt.Printf("%v\n", version) // => 2.1 +} +``` + +If you want to read the full API documentation simply check +[godoc](https://pkg.go.dev/github.com/mssola/useragent). + +## Installation + +``` +go get -u github.com/mssola/useragent +``` + +## Contributing + +Do you want to contribute with code, or to report an issue you are facing? Read +the [CONTRIBUTING.md](./CONTRIBUTING.md) file. + +## [Changelog](https://pbs.twimg.com/media/DJDYCcLXcAA_eIo?format=jpg&name=small) + +Read the [CHANGELOG.md](./CHANGELOG.md) file. + +## License + +``` +Copyright (c) 2012-2023 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/vendor/github.com/mssola/useragent/bot.go b/vendor/github.com/mssola/useragent/bot.go new file mode 100644 index 0000000..d409569 --- /dev/null +++ b/vendor/github.com/mssola/useragent/bot.go @@ -0,0 +1,149 @@ +// Copyright (C) 2014-2023 Miquel Sabaté Solà +// This file is licensed under the MIT license. +// See the LICENSE file. + +package useragent + +import ( + "regexp" + "strings" +) + +var botFromSiteRegexp = regexp.MustCompile(`http[s]?://.+\.\w+`) + +// 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 + } else if len(comment) == 4 { + idx = 3 + } + + // Pick the site. + results := botFromSiteRegexp.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[idx-1]) + } + return "" +} + +// Returns true if the info that we currently have corresponds to the Google +// or Bing mobile bot. This function also modifies some attributes in the receiver +// accordingly. +func (p *UserAgent) googleOrBingBot() bool { + // This is a hackish way to detect + // Google's mobile bot (Googlebot, AdsBot-Google-Mobile, etc.) + // (See https://support.google.com/webmasters/answer/1061943) + // and Bing's mobile bot + // (See https://www.bing.com/webmaster/help/which-crawlers-does-bing-use-8c184ec0) + if strings.Contains(p.ua, "Google") || strings.Contains(p.ua, "bingbot") { + p.platform = "" + p.undecided = true + } + return p.undecided +} + +// Returns true if we think that it is iMessage-Preview. This function also +// modifies some attributes in the receiver accordingly. +func (p *UserAgent) iMessagePreview() bool { + // iMessage-Preview doesn't advertise itself. We have a to rely on a hack + // to detect it: it impersonates both facebook and twitter bots. + // See https://medium.com/@siggi/apples-imessage-impersonates-twitter-facebook-bots-when-scraping-cef85b2cbb7d + if !strings.Contains(p.ua, "facebookexternalhit") { + return false + } + if !strings.Contains(p.ua, "Twitterbot") { + return false + } + p.bot = true + p.browser.Name = "iMessage-Preview" + p.browser.Engine = "" + p.browser.EngineVersion = "" + // We don't set the mobile flag because iMessage can be on iOS (mobile) or macOS (not mobile). + return true +} + +// 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 = "" + } +} + +var botRegex = regexp.MustCompile("(?i)(bot|crawler|sp(i|y)der|search|worm|fetch|nutch)") + +// 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" or "crawler" in his name. + if botRegex.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/vendor/github.com/mssola/useragent/browser.go b/vendor/github.com/mssola/useragent/browser.go new file mode 100644 index 0000000..9c6a41b --- /dev/null +++ b/vendor/github.com/mssola/useragent/browser.go @@ -0,0 +1,193 @@ +// Copyright (C) 2012-2023 Miquel Sabaté Solà +// This file is licensed under the MIT license. +// See the LICENSE file. + +package useragent + +import ( + "regexp" + "strings" +) + +var ie11Regexp = regexp.MustCompile("^rv:(.+)$") + +// Browser is 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.browser.Name = "Opera" + p.browser.Version = sections[0].version + p.browser.Engine = "Presto" + if slen > 1 { + p.browser.EngineVersion = sections[1].version + } + } else if sections[0].name == "Dalvik" { + // When Dalvik VM is in use, there is no browser info attached to ua. + // Although browser is still a Mozilla/5.0 compatible. + p.mozilla = "5.0" + } else if slen > 1 { + engine := sections[1] + p.browser.Engine = engine.name + p.browser.EngineVersion = engine.version + if slen > 2 { + sectionIndex := 2 + // The version after the engine comment is empty on e.g. Ubuntu + // platforms so if this is the case, let's use the next in line. + if sections[2].version == "" && slen > 3 { + sectionIndex = 3 + } + p.browser.Version = sections[sectionIndex].version + if engine.name == "AppleWebKit" { + for _, comment := range engine.comment { + if len(comment) > 5 && + (strings.HasPrefix(comment, "Googlebot") || strings.HasPrefix(comment, "bingbot")) { + p.undecided = true + break + } + } + switch sections[slen-1].name { + case "Edge": + p.browser.Name = "Edge" + p.browser.Version = sections[slen-1].version + p.browser.Engine = "EdgeHTML" + p.browser.EngineVersion = "" + case "Edg": + if !p.undecided { + p.browser.Name = "Edge" + p.browser.Version = sections[slen-1].version + p.browser.Engine = "AppleWebKit" + p.browser.EngineVersion = sections[slen-2].version + } + case "OPR": + p.browser.Name = "Opera" + p.browser.Version = sections[slen-1].version + case "Mobile": + p.browser.Name = "Mobile App" + p.browser.Version = "" + default: + switch sections[slen-3].name { + case "YaBrowser": + p.browser.Name = "YaBrowser" + p.browser.Version = sections[slen-3].version + case "coc_coc_browser": + p.browser.Name = "Coc Coc" + p.browser.Version = sections[slen-3].version + default: + switch sections[slen-2].name { + case "Electron": + p.browser.Name = "Electron" + p.browser.Version = sections[slen-2].version + case "DuckDuckGo": + p.browser.Name = "DuckDuckGo" + p.browser.Version = sections[slen-2].version + case "PhantomJS": + p.browser.Name = "PhantomJS" + p.browser.Version = sections[slen-2].version + default: + switch sections[sectionIndex].name { + case "Chrome", "CriOS": + p.browser.Name = "Chrome" + case "HeadlessChrome": + p.browser.Name = "Headless Chrome" + case "Chromium": + p.browser.Name = "Chromium" + case "GSA": + p.browser.Name = "Google App" + case "FxiOS": + p.browser.Name = "Firefox" + default: + p.browser.Name = "Safari" + } + } + } + // It's possible the google-bot emulates these now + for _, comment := range engine.comment { + if len(comment) > 5 && + (strings.HasPrefix(comment, "Googlebot") || strings.HasPrefix(comment, "bingbot")) { + p.undecided = true + break + } + } + } + } 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" + for _, c := range sections[0].comment { + version := ie11Regexp.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:]) + } + } + } +} + +// Engine 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 +} + +// Browser 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/vendor/github.com/mssola/useragent/model.go b/vendor/github.com/mssola/useragent/model.go new file mode 100644 index 0000000..c564239 --- /dev/null +++ b/vendor/github.com/mssola/useragent/model.go @@ -0,0 +1,35 @@ +package useragent + +import ( + "strings" +) + +// detectModel some properties of the model from the given section. +func (p *UserAgent) detectModel(s section) { + if !p.mobile { + return + } + if p.platform == "iPhone" || p.platform == "iPad" { + p.model = p.platform + return + } + // Android model + if s.name == "Mozilla" && p.platform == "Linux" && len(s.comment) > 2 { + mostAndroidModel := s.comment[2] + if strings.Contains(mostAndroidModel, "Android") || strings.Contains(mostAndroidModel, "Linux") { + mostAndroidModel = s.comment[len(s.comment)-1] + } + tmp := strings.Split(mostAndroidModel, "Build") + if len(tmp) > 0 { + p.model = strings.Trim(tmp[0], " ") + return + } + } + // traverse all item + for _, v := range s.comment { + if strings.Contains(v, "Build") { + tmp := strings.Split(v, "Build") + p.model = strings.Trim(tmp[0], " ") + } + } +} diff --git a/vendor/github.com/mssola/useragent/operating_systems.go b/vendor/github.com/mssola/useragent/operating_systems.go new file mode 100644 index 0000000..78e624f --- /dev/null +++ b/vendor/github.com/mssola/useragent/operating_systems.go @@ -0,0 +1,377 @@ +// Copyright (C) 2012-2023 Miquel Sabaté Solà +// This file is licensed under the MIT license. +// See the LICENSE file. + +package useragent + +import ( + "strings" +) + +// OSInfo represents full information on the operating system extracted from the +// user agent. +type OSInfo struct { + // Full name of the operating system. This is identical to the output of ua.OS() + FullName string + + // Name of the operating system. This is sometimes a shorter version of the + // operating system name, e.g. "Mac OS X" instead of "Intel Mac OS X" + Name string + + // Operating system version, e.g. 7 for Windows 7 or 10.8 for Max OS X Mountain Lion + Version string +} + +// Normalize the name of the operating system. By now, this just +// affects to Windows NT. +// +// 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 || sp[1] != "NT" { + 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 "10.0": + 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" || comment[1] == "arm_64" { + 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) == 3 { + _ = p.googleOrBingBot() + } + } 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.googleOrBingBot() && !p.iMessagePreview() { + 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 + } + } + } + + // Special case for Firefox on iPad, where the platform is advertised as Macintosh instead of iPad + if p.platform == "Macintosh" && p.browser.Engine == "AppleWebKit" && p.browser.Name == "Firefox" { + p.platform = "iPad" + p.mobile = true + } +} + +// 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" || comment[1] == "arm_64" { + if len(comment) > 2 { + p.os = normalizeOS(comment[2]) + } else { + p.os = normalizeOS(comment[1]) + } + } else { + if strings.Contains(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]) + } + } + } + // Only parse 4th comment as localization if it doesn't start with rv:. + // For example Firefox on Ubuntu contains "rv:XX.X" in this field. + if len(comment) > 3 && !strings.HasPrefix(comment[3], "rv:") { + 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] + } + } +} + +// Guess the OS. Android browsers send Dalvik as the user agent in the +// request header. +// +// The first argument p is a reference to the current UserAgent and the second +// argument is a slice of strings containing the comment. +func dalvik(p *UserAgent, comment []string) { + slen := len(comment) + + if strings.HasPrefix(comment[0], "Linux") { + p.platform = comment[0] + if slen > 2 { + p.os = comment[2] + } + p.mobile = true + } +} + +// 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 if s.name == "Dalvik" { + if len(s.comment) > 0 { + dalvik(p, s.comment) + } + } else if s.name == "okhttp" { + p.mobile = true + p.browser.Name = "OkHttp" + p.browser.Version = s.version + } else { + // Check whether this is a bot or just a weird browser. + p.undecided = true + } +} + +// Platform returns a string containing the platform.. +func (p *UserAgent) Platform() string { + return p.platform +} + +// OS returns a string containing the name of the Operating System. +func (p *UserAgent) OS() string { + return p.os +} + +// Localization returns a string containing the localization. +func (p *UserAgent) Localization() string { + return p.localization +} + +// Model returns a string containing the Phone Model like "Nexus 5X" +func (p *UserAgent) Model() string { + return p.model +} + +// Return OS name and version from a slice of strings created from the full name of the OS. +func osName(osSplit []string) (name, version string) { + if len(osSplit) == 1 { + name = osSplit[0] + version = "" + } else { + // Assume version is stored in the last part of the array. + nameSplit := osSplit[:len(osSplit)-1] + version = osSplit[len(osSplit)-1] + + // Nicer looking Mac OS X + if len(nameSplit) >= 2 && nameSplit[0] == "Intel" && nameSplit[1] == "Mac" { + nameSplit = nameSplit[1:] + } + name = strings.Join(nameSplit, " ") + + if strings.Contains(version, "x86") || strings.Contains(version, "i686") { + // x86_64 and i868 are not Linux versions but architectures + version = "" + } else if version == "X" && name == "Mac OS" { + // X is not a version for Mac OS. + name = name + " " + version + version = "" + } + } + return name, version +} + +// OSInfo returns combined information for the operating system. +func (p *UserAgent) OSInfo() OSInfo { + // Special case for iPhone weirdness + os := strings.Replace(p.os, "like Mac OS X", "", 1) + os = strings.Replace(os, "CPU", "", 1) + os = strings.Trim(os, " ") + + osSplit := strings.Split(os, " ") + + // Special case for x64 edition of Windows + if os == "Windows XP x64 Edition" { + osSplit = osSplit[:len(osSplit)-2] + } + + name, version := osName(osSplit) + + // Special case for names that contain a forward slash version separator. + if strings.Contains(name, "/") { + s := strings.Split(name, "/") + name = s[0] + version = s[1] + } + + // Special case for versions that use underscores + version = strings.Replace(version, "_", ".", -1) + + return OSInfo{ + FullName: p.os, + Name: name, + Version: version, + } +} diff --git a/vendor/github.com/mssola/useragent/user_agent.go b/vendor/github.com/mssola/useragent/user_agent.go new file mode 100644 index 0000000..1ccde0c --- /dev/null +++ b/vendor/github.com/mssola/useragent/user_agent.go @@ -0,0 +1,190 @@ +// Copyright (C) 2012-2023 Miquel Sabaté Solà +// This file is licensed under the MIT license. +// See the LICENSE file. + +// Package useragent 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 useragent + +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 + model 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) { + var buffer []byte + + // Check for empty products + if *index < len(ua) && ua[*index] != '(' && ua[*index] != '[' { + 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++ + } + + // Discards any trailing data within square brackets + if *index < len(ua) && ua[*index] == '[' { + *index++ + _ = readUntil(ua, index, ']', true) + *index++ + } + return s +} + +// Initialize the parser. +func (p *UserAgent) initialize() { + p.ua = "" + p.mozilla = "" + p.platform = "" + p.os = "" + p.localization = "" + p.model = "" + p.browser.Engine = "" + p.browser.EngineVersion = "" + p.browser.Name = "" + p.browser.Version = "" + p.bot = false + p.mobile = false + p.undecided = false +} + +// New parses 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 { + if sections[0].name == "Mozilla" { + p.mozilla = sections[0].version + } + + p.detectBrowser(sections) + p.detectOS(sections[0]) + p.detectModel(sections[0]) + + if p.undecided { + p.checkBot(sections) + } + } +} + +// Mozilla 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 +} + +// Bot returns true if it's a bot, false otherwise. +func (p *UserAgent) Bot() bool { + return p.bot +} + +// Mobile returns true if it's a mobile device, false otherwise. +func (p *UserAgent) Mobile() bool { + return p.mobile +} + +// UA returns the original given user agent. +func (p *UserAgent) UA() string { + return p.ua +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 3393da3..48d1e61 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,3 +1,3 @@ -# github.com/mileusna/useragent v1.2.1 -## explicit; go 1.14 -github.com/mileusna/useragent +# github.com/mssola/useragent v1.0.0 +## explicit; go 1.13 +github.com/mssola/useragent