diff --git a/.gitignore b/.gitignore index 8078d86..9e68d87 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,10 @@ +.idea/ +.vscode/ +.DS_Store old.go old_test.go file.txt user_agents.txt ua2.go settings.json -benchmarks.txt +benchmarks.txt \ No newline at end of file diff --git a/is.go b/is.go index a3bf5b3..3fff5a4 100644 --- a/is.go +++ b/is.go @@ -30,6 +30,11 @@ func (ua UserAgent) IsChromeOS() bool { return ua.OS == ChromeOS || ua.OS == "CrOS" } +// IsBlackberryOS shorthand function to check if OS == BlackBerry +func (ua UserAgent) IsBlackberryOS() bool { + return ua.OS == BlackBerry +} + // IsOpera shorthand function to check if Name == Opera func (ua UserAgent) IsOpera() bool { return ua.Name == Opera @@ -65,6 +70,11 @@ func (ua UserAgent) IsEdge() bool { return ua.Name == Edge } +// IsBlackBerry shorthand function to check if Name == BlackBerry +func (ua UserAgent) IsBlackBerry() bool { + return ua.Name == BlackBerry +} + // IsGooglebot shorthand function to check if Name == Googlebot func (ua UserAgent) IsGooglebot() bool { return ua.Name == Googlebot @@ -80,6 +90,11 @@ func (ua UserAgent) IsFacebookbot() bool { return ua.Name == FacebookExternalHit } +// IsYandexbot shorthand function to check if Name == YandexBot +func (ua UserAgent) IsYandexbot() bool { + return ua.Name == YandexBot +} + // IsUnknown returns true if the package can't determine the user agent reliably. // Fields like Name, OS, etc. might still have values. func (ua UserAgent) IsUnknown() bool { diff --git a/ua.go b/ua.go index a292f9c..4769b81 100644 --- a/ua.go +++ b/ua.go @@ -25,15 +25,19 @@ type UserAgent struct { // Constants for browsers and operating systems for easier comparison const ( - Windows = "Windows" - WindowsPhone = "Windows Phone" - Android = "Android" - MacOS = "macOS" - IOS = "iOS" - Linux = "Linux" - FreeBSD = "FreeBSD" - ChromeOS = "ChromeOS" - BlackBerry = "BlackBerry" + Windows = "Windows" + WindowsPhone = "Windows Phone" + WindowsNT = "Windows NT" + WindowsPhoneOS = "Windows Phone OS" + Android = "Android" + MacOS = "macOS" + IOS = "iOS" + Linux = "Linux" + FreeBSD = "FreeBSD" + ChromeOS = "ChromeOS" + BlackBerry = "BlackBerry" + CrOS = "CrOS" + Harmony = "Harmony" Opera = "Opera" OperaMini = "Opera Mini" @@ -45,6 +49,11 @@ const ( Safari = "Safari" Edge = "Edge" Vivaldi = "Vivaldi" + MobileSafari = "Mobile Safari" + NetFront = "NetFront" + Mozilla = "Mozilla" + Msie = "MSIE" + SamsungBrowser = "Samsung Browser" GoogleAdsBot = "Google Ads Bot" Googlebot = "Googlebot" @@ -52,10 +61,18 @@ const ( FacebookExternalHit = "facebookexternalhit" Applebot = "Applebot" Bingbot = "Bingbot" + YandexBot = "YandexBot" + YandexAdNet = "YandexAdNet" FacebookApp = "Facebook App" InstagramApp = "Instagram App" TiktokApp = "TikTok App" + + Version = "Version" + Mobile = "Mobile" + Tablet = "Tablet" + + tablet = "tablet" ) // Parse user agent string returning UserAgent struct @@ -64,26 +81,16 @@ func Parse(userAgent string) 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 - } - } - - //fmt.Printf("%+v\n", tokens) + tokens := parse([]byte(userAgent)) + ua.URL = tokens.url // OS lookup switch { - case tokens.exists("Android"): + case tokens.exists(Android): ua.OS = Android var osIndex int osIndex, ua.OSVersion = tokens.getIndexValue(Android) - ua.Tablet = strings.Contains(strings.ToLower(ua.String), "tablet") + ua.Tablet = strings.Contains(strings.ToLower(ua.String), tablet) ua.Device = tokens.findAndroidDevice(osIndex) case tokens.exists("iPhone"): @@ -98,14 +105,14 @@ func Parse(userAgent string) UserAgent { ua.Device = "iPad" ua.Tablet = true - case tokens.exists("Windows NT"): + case tokens.exists(WindowsNT): ua.OS = Windows - ua.OSVersion = tokens.get("Windows NT") + ua.OSVersion = tokens.get(WindowsNT) ua.Desktop = true - case tokens.exists("Windows Phone OS"): + case tokens.exists(WindowsPhoneOS): ua.OS = WindowsPhone - ua.OSVersion = tokens.get("Windows Phone OS") + ua.OSVersion = tokens.get(WindowsPhoneOS) ua.Mobile = true case tokens.exists("Macintosh"): @@ -113,33 +120,38 @@ func Parse(userAgent string) UserAgent { ua.OSVersion = tokens.findMacOSVersion() ua.Desktop = true - case tokens.exists("Linux"): + case tokens.exists(Linux): ua.OS = Linux ua.OSVersion = tokens.get(Linux) ua.Desktop = true - case tokens.exists("FreeBSD"): + case tokens.exists(FreeBSD): ua.OS = FreeBSD ua.OSVersion = tokens.get(FreeBSD) ua.Desktop = true - case tokens.exists("CrOS"): + case tokens.exists(CrOS): ua.OS = ChromeOS - ua.OSVersion = tokens.get("CrOS") + ua.OSVersion = tokens.get(CrOS) ua.Desktop = true - case tokens.exists("BlackBerry"): + case tokens.exists(BlackBerry): ua.OS = BlackBerry - ua.OSVersion = tokens.get("BlackBerry") + ua.OSVersion = tokens.get(BlackBerry) + ua.Mobile = true + + case tokens.exists("OpenHarmony"): + ua.OS = Harmony + ua.OSVersion = tokens.get("OpenHarmony") ua.Mobile = true } switch { - case tokens.exists("Googlebot"): + case tokens.exists(Googlebot): ua.Name = Googlebot ua.Version = tokens.get(Googlebot) ua.Bot = true - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") + ua.Mobile = tokens.existsAny(Mobile, MobileSafari) case tokens.existsAny("GoogleProber", "GoogleProducer"): if name := tokens.findBestMatch(false); name != "" { @@ -147,14 +159,19 @@ func Parse(userAgent string) UserAgent { } ua.Bot = true - case tokens.exists("Applebot"): + case tokens.exists("Bytespider"): + ua.Name = "Bytespider" + ua.Mobile = tokens.exists("Mobile Safari") + ua.Bot = true + + case tokens.exists(Applebot): ua.Name = Applebot ua.Version = tokens.get(Applebot) ua.Bot = true - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") + ua.Mobile = tokens.existsAny(Mobile, MobileSafari) ua.OS = "" - case tokens.get("Opera Mini") != "": + case tokens.get(OperaMini) != "": ua.Name = OperaMini ua.Version = tokens.get(OperaMini) ua.Mobile = true @@ -162,84 +179,92 @@ func Parse(userAgent string) UserAgent { case tokens.get("OPR") != "": ua.Name = Opera ua.Version = tokens.get("OPR") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") + ua.Mobile = tokens.existsAny(Mobile, MobileSafari) case tokens.get("OPT") != "": ua.Name = OperaTouch ua.Version = tokens.get("OPT") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") + ua.Mobile = tokens.existsAny(Mobile, MobileSafari) // Opera on iOS case tokens.get("OPiOS") != "": ua.Name = Opera ua.Version = tokens.get("OPiOS") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") + ua.Mobile = tokens.existsAny(Mobile, MobileSafari) // Chrome on iOS case tokens.get("CriOS") != "": ua.Name = Chrome ua.Version = tokens.get("CriOS") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") + ua.Mobile = tokens.existsAny(Mobile, MobileSafari) // Firefox on iOS case tokens.get("FxiOS") != "": ua.Name = Firefox ua.Version = tokens.get("FxiOS") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") + ua.Mobile = tokens.existsAny(Mobile, MobileSafari) - case tokens.get("Firefox") != "": + case tokens.get(Firefox) != "": ua.Name = Firefox ua.Version = tokens.get(Firefox) - ua.Mobile = tokens.exists("Mobile") - ua.Tablet = tokens.exists("Tablet") + ua.Mobile = tokens.exists(Mobile) + ua.Tablet = tokens.exists(Tablet) - case tokens.get("Vivaldi") != "": + case tokens.get(Vivaldi) != "": ua.Name = Vivaldi ua.Version = tokens.get(Vivaldi) - case tokens.exists("MSIE"): + case tokens.exists(Msie): ua.Name = InternetExplorer - ua.Version = tokens.get("MSIE") + ua.Version = tokens.get(Msie) case tokens.get("EdgiOS") != "": ua.Name = Edge ua.Version = tokens.get("EdgiOS") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") + ua.Mobile = tokens.existsAny(Mobile, MobileSafari) - case tokens.get("Edge") != "": + case tokens.get(Edge) != "": ua.Name = Edge - ua.Version = tokens.get("Edge") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") + ua.Version = tokens.get(Edge) + ua.Mobile = tokens.existsAny(Mobile, MobileSafari) case tokens.get("Edg") != "": ua.Name = Edge ua.Version = tokens.get("Edg") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") + ua.Mobile = tokens.existsAny(Mobile, MobileSafari) case tokens.get("EdgA") != "": ua.Name = Edge ua.Version = tokens.get("EdgA") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") + ua.Mobile = tokens.existsAny(Mobile, MobileSafari) case tokens.get("bingbot") != "": ua.Name = Bingbot ua.Version = tokens.get("bingbot") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") + ua.Mobile = tokens.existsAny(Mobile, MobileSafari) - case tokens.get("YandexBot") != "": - ua.Name = "YandexBot" - ua.Version = tokens.get("YandexBot") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") + case tokens.get(YandexBot) != "": + ua.Name = YandexBot + ua.Version = tokens.get(YandexBot) + ua.Mobile = tokens.existsAny(Mobile, MobileSafari) + ua.Bot = true + + case tokens.get(YandexAdNet) != "": + ua.Name = YandexAdNet + ua.Version = tokens.get(YandexAdNet) + ua.Mobile = tokens.existsAny(Mobile, MobileSafari) + ua.Bot = true case tokens.get("SamsungBrowser") != "": - ua.Name = "Samsung Browser" + ua.Name = SamsungBrowser ua.Version = tokens.get("SamsungBrowser") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") + ua.Mobile = tokens.existsAny(Mobile, MobileSafari) + ua.OS = Android case tokens.get("HeadlessChrome") != "": ua.Name = HeadlessChrome ua.Version = tokens.get("HeadlessChrome") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") + ua.Mobile = tokens.existsAny(Mobile, MobileSafari) ua.Bot = true case tokens.existsAny("AdsBot-Google-Mobile", "Mediapartners-Google", "AdsBot-Google"): @@ -279,18 +304,18 @@ func Parse(userAgent string) UserAgent { case tokens.get("HuaweiBrowser") != "": ua.Name = "Huawei Browser" ua.Version = tokens.get("HuaweiBrowser") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") + ua.Mobile = tokens.existsAny(Mobile, MobileSafari) - case tokens.exists("BlackBerry"): - ua.Name = "BlackBerry" - ua.Version = tokens.get("Version") + case tokens.exists(BlackBerry): + ua.Name = BlackBerry + ua.Version = tokens.get(Version) - case tokens.exists("NetFront"): - ua.Name = "NetFront" - ua.Version = tokens.get("NetFront") + case tokens.exists(NetFront): + ua.Name = NetFront + ua.Version = tokens.get(NetFront) ua.Mobile = true - // if chrome and Safari defined, find any other token sent descr + // if Chrome and Safari defined, find any other token sent descr case tokens.exists(Chrome) && tokens.exists(Safari): name := tokens.findBestMatch(true) if name != "" { @@ -300,30 +325,30 @@ func Parse(userAgent string) UserAgent { } fallthrough - case tokens.exists("Chrome"): + case tokens.exists(Chrome): ua.Name = Chrome - ua.Version = tokens.get("Chrome") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") + ua.Version = tokens.get(Chrome) + ua.Mobile = tokens.existsAny(Mobile, MobileSafari) case tokens.exists("Brave Chrome"): ua.Name = Chrome ua.Version = tokens.get("Brave Chrome") - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") + ua.Mobile = tokens.existsAny(Mobile, MobileSafari) - case tokens.exists("Safari"): + case tokens.exists(Safari): ua.Name = Safari - v := tokens.get("Version") + v := tokens.get(Version) if v != "" { ua.Version = v } else { - ua.Version = tokens.get("Safari") + ua.Version = tokens.get(Safari) } - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") + ua.Mobile = tokens.existsAny(Mobile, MobileSafari) default: - if ua.OS == "Android" && tokens.get("Version") != "" { + if ua.IsAndroid() && tokens.get(Version) != "" { ua.Name = "Android browser" - ua.Version = tokens.get("Version") + ua.Version = tokens.get(Version) ua.Mobile = true } else { if name := tokens.findBestMatch(false); name != "" { @@ -335,7 +360,7 @@ func Parse(userAgent string) UserAgent { ua.Bot = strings.Contains(strings.ToLower(ua.Name), "bot") // If mobile flag has already been set, don't override it. if !ua.Mobile { - ua.Mobile = tokens.existsAny("Mobile", "Mobile Safari") + ua.Mobile = tokens.existsAny(Mobile, MobileSafari) } } } @@ -349,45 +374,53 @@ func Parse(userAgent string) UserAgent { ua.Mobile = false } - // if not already bot, check some popular bots and wether URL is set - if !ua.Bot { - ua.Bot = ua.URL != "" - } - + // if not already bot, check some popular bots and whether URL is set if !ua.Bot { switch ua.Name { - case Twitterbot, FacebookExternalHit: + case Twitterbot, FacebookExternalHit, "facebookcatalog": ua.Bot = true + default: + ua.Bot = ua.URL != "" } } - parseVersion(ua.Version, &ua.VersionNo) - parseVersion(ua.OSVersion, &ua.OSVersionNo) + ua.VersionNo = parseVersion(ua.Version) + ua.OSVersionNo = parseVersion(ua.OSVersion) return ua } -func parse(userAgent string) properties { +// var buffPool = sync.Pool{New: func() interface{} { +// return bytes.NewBuffer(make([]byte, 0, 30)) +// }} + +func parse(userAgent []byte) properties { clients := properties{ list: make([]property, 0, 8), } slash := false isURL := false - var buff, val bytes.Buffer + // buff := buffPool.Get().(*bytes.Buffer) + // val := buffPool.Get().(*bytes.Buffer) + // buff.Reset() + // val.Reset() + + buff := bytes.NewBuffer(make([]byte, 0, 30)) + val := bytes.NewBuffer(make([]byte, 0, 30)) + addToken := func() { if buff.Len() != 0 { - s := strings.TrimSpace(buff.String()) + s := string(bytes.TrimSpace(buff.Bytes())) if !ignore(s) { if isURL { - s = strings.TrimPrefix(s, "+") + clients.url = strings.TrimPrefix(s, "+") + return } - - 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) + if val.Len() == 0 { + // if value don't exists, try to get version from the token + clients.list = append(clients.list, checkVer(s)) } else { - clients.add(s, strings.TrimSpace(val.String())) + clients.list = append(clients.list, property{Key: s, Value: string(bytes.TrimSpace(val.Bytes()))}) } } } @@ -400,10 +433,7 @@ func parse(userAgent string) properties { parOpen := false braOpen := false - bua := []byte(userAgent) - for i, c := range bua { - - //fmt.Println(string(c), c) + for i, c := range userAgent { switch { case c == 41: // ) addToken() @@ -430,11 +460,11 @@ func parse(userAgent string) properties { if bytes.HasSuffix(buff.Bytes(), []byte("http")) || bytes.HasSuffix(buff.Bytes(), []byte("https")) { // If we are part of a URL just write the character. buff.WriteByte(c) - } else if i != len(bua)-1 && bua[i+1] != ' ' { + } else if i != len(userAgent)-1 && userAgent[i+1] != ' ' { // If the following character is not a space, change to a space. buff.WriteByte(' ') } - // Otherwise don't write as its probably a badly formatted key value separator. + // Otherwise don't write as it's probably a badly formatted key value separator. case slash && c == 32: addToken() @@ -443,7 +473,7 @@ func parse(userAgent string) properties { 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:"))) { + if i != len(userAgent)-1 && userAgent[i+1] == 47 && (bytes.HasSuffix(buff.Bytes(), []byte("http:")) || bytes.HasSuffix(buff.Bytes(), []byte("https:"))) { buff.WriteByte(c) isURL = true } else { @@ -460,40 +490,32 @@ func parse(userAgent string) properties { } addToken() + // buffPool.Put(buff) + // buffPool.Put(val) return clients } -func checkVer(s string) (name, v string) { +func checkVer(s string) property { i := strings.LastIndex(s, " ") if i == -1 { - return s, "" + return property{Key: s, Value: ""} } - //v = s[i+1:] - switch s[:i] { - case "Linux", "Windows NT", "Windows Phone OS", "MSIE", "Android": - return s[:i], s[i+1:] + case Linux, WindowsNT, WindowsPhoneOS, Msie, Android, "OpenHarmony": + return property{Key: s[:i], Value: s[i+1:]} case "CrOS x86_64", "CrOS aarch64", "CrOS armv7l": j := strings.LastIndex(s[:i], " ") - return s[:j], s[j+1 : i] + return property{Key: s[:j], Value: s[j+1 : i]} default: - return s, "" + return property{Key: s, Value: ""} } - - // for _, c := range v { - // if (c >= 48 && c <= 57) || c == 46 { - // } else { - // return s, "" - // } - // } - // return s[:i], s[i+1:] } -// ignore retursn true if token should be ignored +// ignore returns true if token should be ignored func ignore(s string) bool { switch s { - case "KHTML, like Gecko", "U", "compatible", "Mozilla", "WOW64", "en", "en-us", "en-gb", "ru-ru", "Browser": + case "KHTML, like Gecko", "U", "compatible", Mozilla, "WOW64", "en", "en-us", "en-gb", "ru-ru", "Browser": return true default: return false @@ -506,10 +528,7 @@ type property struct { } type properties struct { list []property -} - -func (p *properties) add(key, value string) { - p.list = append(p.list, property{Key: key, Value: value}) + url string } func (p properties) get(key string) string { @@ -559,6 +578,17 @@ func (p properties) existsAny(keys ...string) bool { return false } +func (p properties) getAny(keys ...string) (key, value string) { + for _, k := range keys { + for _, prop := range p.list { + if prop.Key == k { + return prop.Key, prop.Value + } + } + } + return "", "" +} + func (p properties) findMacOSVersion() string { for _, token := range p.list { if strings.Contains(token.Key, "OS") { @@ -607,9 +637,9 @@ func (p properties) findBestMatch(withVerOnly bool) string { 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", "Tablet": + case Chrome, Firefox, Safari, Version, Mobile, MobileSafari, Mozilla, "AppleWebKit", WindowsNT, WindowsPhoneOS, Android, "Macintosh", Linux, "GSA", CrOS, Tablet, "OpenHarmony": default: - // don' pick if starts with number + // don't pick if starts with number if len(prop.Key) != 0 && prop.Key[0] >= 48 && prop.Key[0] <= 57 { break } @@ -641,15 +671,15 @@ func (p *properties) findAndroidDevice(startIndex int) string { if len(p.list) > i+1 { dev := p.list[i+1].Key if len(dev) == 2 || (len(dev) == 5 && dev[2] == '-') { - // probably langage tag (en-us etc..), ignore and continue loop + // probably language tag (en-us etc..), ignore and continue loop continue } switch dev { - case Chrome, Firefox, Safari, "Opera Mini", "Presto", "Version", "Mobile", "Mobile Safari", "Mozilla", "AppleWebKit", "Windows NT", "Windows Phone OS", Android, "Macintosh", Linux, "CrOS": - // ignore this tokens, not device names + case Chrome, Firefox, Safari, OperaMini, "Presto", Version, Mobile, MobileSafari, Mozilla, "AppleWebKit", WindowsNT, WindowsPhoneOS, Android, "Macintosh", Linux, CrOS: + // ignore these tokens, not device names default: - if strings.Contains(strings.ToLower(dev), "tablet") { - p.list[i+1].Key = "Tablet" // leave Tablet tag for later table detection + if strings.Contains(strings.ToLower(dev), tablet) { + p.list[i+1].Key = Tablet // leave Tablet tag for later table detection } else { p.list = append(p.list[:i+1], p.list[i+2:]...) } diff --git a/ua_test.go b/ua_test.go index ba35c5f..01f43b4 100644 --- a/ua_test.go +++ b/ua_test.go @@ -54,6 +54,7 @@ var testTable = [][]string{ {"Mozilla/5.0 (Linux; U; Android 11; ru-ru; Redmi Note 10S Build/RP1A.200720.011) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/89.0.4389.116 Mobile Safari/537.36 XiaoMi/MiuiBrowser/12.13.2-gn", "Miui Browser", "12.13.2-gn", "mobile", ua.Android, "Redmi Note 10S"}, {"Mozilla/5.0 (Linux; Android 10; MED-LX9N; HMSCore 6.6.0.311) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.105 HuaweiBrowser/12.1.0.303 Mobile Safari/537.36", "Huawei Browser", "12.1.0.303", "mobile", "Android"}, + {"Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) SamsungBrowser/22.0 Chrome/111.0.5563.116 Safari/537.36", ua.SamsungBrowser, "22.0", "mobile", ua.Android}, // useragent, name, version, mobile, os {"Mozilla/5.0 (Linux; Android 9; ONEPLUS A6003) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.99 Mobile Safari/537.36", ua.Chrome, "71.0.3578.99", "mobile", ua.Android}, @@ -90,6 +91,8 @@ var testTable = [][]string{ {"Mozilla/5.0 (compatible; Yahoo Ad monitoring; https://help.yahoo.com/kb/yahoo-ad-monitoring-SLN24857.html) cnv.aws-prod---sieve.hlfs-rest_client/1681346790-0", "Yahoo Ad monitoring", "", "bot", ""}, {"GoogleProber", "GoogleProber", "", "bot", ""}, {"GoogleProducer; (+http://goo.gl/7y4SX)", "GoogleProducer", "", "bot", ""}, + {"Mozilla/5.0 (compatible; Bytespider; spider-feedback@bytedance.com) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.0.0 Safari/537.36", "Bytespider", "", "bot", ""}, + {"Mozilla/5.0 (Linux; Android 5.0) AppleWebKit/537.36 (KHTML, like Gecko) Mobile Safari/537.36 (compatible; Bytespider; spider-feedback@bytedance.com)", "Bytespider", "", "bot", ua.Android}, // Google ads bots {"Mozilla/5.0 (Linux; Android 4.0.0; Galaxy Nexus Build/IMM76B) AppleWebKit/537.36 (KHTML, like Gecko; Mediapartners-Google) Chrome/104.0.0.0 Mobile Safari/537.36", ua.GoogleAdsBot, "", "bot", ua.Android}, @@ -153,6 +156,8 @@ var testTable = [][]string{ //{`${jndi:ldap://log4shell-generic-8ZnJfq2XFL3GWyaLyOpT${lower:ten}.w.nessus.org/nessus}`, "", "mobile", ua.Android}, // + {"Mozilla/5.0 (Phone; OpenHarmony 5.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 ArkWeb/4.1.6.1 Mobile", "ArkWeb", "4.1.6.1", "mobile", ua.Harmony, ""}, + // // ${jndi:ldap://log4shell-generic-8ZnJfq2XFL3GWyaLyOpT${lower:ten}.w.nessus.org/nessus} @@ -195,6 +200,10 @@ func TestParse(t *testing.T) { t.Error("\n", ua.String, "should be tablet") fmt.Printf("%+v", ua) } + if test[3] == "bot" && !ua.Bot { + t.Error("\n", ua.String, "should be bot") + fmt.Printf("%+v", ua) + } } if len(test) > 4 && test[4] != ua.OS { @@ -210,11 +219,6 @@ func TestParse(t *testing.T) { } } -func TestSingle(t *testing.T) { - agent := ua.Parse("SonyEricssonK310iv/R4DA Browser/NetFront/3.3 Profile/MIDP-2.0 Configuration/CLDC-1.1 UP.Link/6.3.1.13.0") - fmt.Printf("\n%+v\n", agent) -} - var testUA ua.UserAgent func BenchmarkUserAgent(b *testing.B) { @@ -225,6 +229,12 @@ func BenchmarkUserAgent(b *testing.B) { } } +func TestSingle(t *testing.T) { + //agent := ua.Parse("SonyEricssonK310iv/R4DA Browser/NetFront/3.3 Profile/MIDP-2.0 Configuration/CLDC-1.1 UP.Link/6.3.1.13.0") + agent := ua.Parse("Mozilla/5.0 (Linux; Android 5.0) AppleWebKit/537.36 (KHTML, like Gecko) Mobile Safari/537.36 (compatible; Bytespider; spider-feedback@bytedance.com)") + fmt.Printf("\n%+v\n", agent) +} + func ExampleParse() { userAgents := []string{ // Mac @@ -258,6 +268,9 @@ func ExampleParse() { "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", + + // Harmony + "Mozilla/5.0 (Phone; OpenHarmony 5.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 ArkWeb/4.1.6.1 Mobile", } for _, s := range userAgents { diff --git a/version.go b/version.go index 30c0eb1..1a487d5 100644 --- a/version.go +++ b/version.go @@ -12,24 +12,26 @@ type VersionNo struct { Patch int } -func parseVersion(ver string, verno *VersionNo) { +// parseVersion parse version string into Major.Minor.Patch struct +func parseVersion(ver string) (verno VersionNo) { var err error parts := strings.Split(ver, ".") if len(parts) > 0 { if verno.Major, err = strconv.Atoi(parts[0]); err != nil { - return + return verno } - } - if len(parts) > 1 { - if verno.Minor, err = strconv.Atoi(parts[1]); err != nil { - return - } - if len(parts) > 2 { - if verno.Patch, err = strconv.Atoi(parts[2]); err != nil { - return + if len(parts) > 1 { + if verno.Minor, err = strconv.Atoi(parts[1]); err != nil { + return verno + } + if len(parts) > 2 { + if verno.Patch, err = strconv.Atoi(parts[2]); err != nil { + return verno + } } } } + return verno } // VersionNoShort return version string in format .