Skip to content

Commit

Permalink
Added detection of OS version
Browse files Browse the repository at this point in the history
for macOS, iOS, Windows and Android at the moment. Backwards compatible.
  • Loading branch information
dwb committed Oct 13, 2016
1 parent 0d6d2e0 commit 7fb4bcb
Show file tree
Hide file tree
Showing 4 changed files with 137 additions and 13 deletions.
43 changes: 34 additions & 9 deletions browser.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,14 @@ var browsers = map[string]*url.URL{
"WebView": u("http://developer.android.com/guide/webapps/webview.html"),
}

const (
osAndroid = "Android"
osMacOS = "Mac OS X"
osIOS = "iOS"
osLinux = "GNU/Linux"
osWindows = "Windows"
)

func parseBrowser(l *lex) *UserAgent {
for _, f := range []parseFn{parseGecko, parseChromeSafari, parseIE1, parseIE2} {
if ua := f(newLex(l.s)); ua != nil {
Expand Down Expand Up @@ -67,7 +75,7 @@ func parseMozillaLike(l *lex, ua *UserAgent) bool {
parseUnixLike(l, ua)
case l.match("Android"):
ua.Security = parseSecurity(l)
ua.OS = "Android"
ua.OS = osAndroid
if l.match("; Mobile") {
ua.Mobile = true
} else if l.match("; Tablet") {
Expand All @@ -76,16 +84,16 @@ func parseMozillaLike(l *lex, ua *UserAgent) bool {
case l.match("Linux; "):
ua.Security = parseSecurity(l)
if l.match("Android") {
ua.OS = "Android"
ua.OS = osAndroid
} else {
return false
}
case l.match("Windows"):
ua.Security = parseSecurity(l)
ua.OS = "Windows"
ua.OS = osWindows
case l.match("Macintosh"):
ua.Security = parseSecurity(l)
ua.OS = "Mac OS X"
ua.OS = osMacOS
case l.match("Mobile; "):
ua.Security = parseSecurity(l)
ua.OS = "Firefox OS"
Expand All @@ -96,11 +104,11 @@ func parseMozillaLike(l *lex, ua *UserAgent) bool {
ua.Tablet = true
case l.match("iPad; "):
ua.Security = parseSecurity(l)
ua.OS = "iOS"
ua.OS = osIOS
ua.Tablet = true
case l.match("iPhone; ") || l.match("iPod; ") || l.match("iPod touch; "):
ua.Security = parseSecurity(l)
ua.OS = "iOS"
ua.OS = osIOS
ua.Mobile = true
case l.match("Unknown; "):
ua.Security = parseSecurity(l)
Expand All @@ -109,6 +117,9 @@ func parseMozillaLike(l *lex, ua *UserAgent) bool {
return false
}

// swallow the error to preserve backwards compatibility
_ = parseOSVersion(l, ua)

if _, ok := l.span(") "); !ok {
return false
}
Expand All @@ -120,7 +131,7 @@ func parseMozillaLike(l *lex, ua *UserAgent) bool {
func parseUnixLike(l *lex, ua *UserAgent) bool {
switch {
case l.match("Linux") || l.match("Ubuntu"):
ua.OS = "GNU/Linux"
ua.OS = osLinux
case l.match("FreeBSD"):
ua.OS = "FreeBSD"
case l.match("OpenBSD"):
Expand Down Expand Up @@ -236,11 +247,18 @@ func parseIE1(l *lex) *UserAgent {
return nil
}
ua.Name = "MSIE"
ua.OS = "Windows"
if !parseVersion(l, ua, ";") {
return nil
}

if !l.match(" Windows NT") {
return nil
}

ua.OS = osWindows
// swallow the error to preserve backwards compatibility
_ = parseOSVersion(l, ua)

return ua
}

Expand All @@ -252,14 +270,21 @@ func parseIE2(l *lex) *UserAgent {
if !l.match("Mozilla") {
return nil
}
if _, ok := l.span("(Windows NT"); !ok {
return nil
}
ua.OS = osWindows

// swallow the error to preserve backwards compatibility
_ = parseOSVersion(l, ua)

if _, ok := l.span("Trident/"); !ok {
return nil
}
if _, ok := l.span("rv:"); !ok {
return nil
}
ua.Name = "MSIE"
ua.OS = "Windows"
if !parseVersion(l, ua, ")") {
return nil
}
Expand Down
15 changes: 15 additions & 0 deletions lex.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package useragent

import (
"regexp"
"strings"
)

Expand Down Expand Up @@ -56,3 +57,17 @@ func (l *lex) spanAny(chars string) (string, bool) {
l.p += i + len(chars)
return s, true
}

// assumes the first group is the bit we want
func (l *lex) spanRegexp(re *regexp.Regexp) (before string, match string, success bool) {
loc := re.FindStringSubmatchIndex(l.s[l.p:])
if loc == nil {
return "", "", false
}
success = true
start, end := loc[2], loc[3]
before = l.s[l.p : l.p+start]
match = l.s[l.p:][start:end]
l.p += end
return
}
49 changes: 46 additions & 3 deletions parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"fmt"
"github.com/blang/semver"
"net/url"
"regexp"
"strings"
)

Expand Down Expand Up @@ -107,8 +108,9 @@ type UserAgent struct {
// CrOS
// etc.
// If the os is not known, OS will be `unknown'.
OS string
Security Security
OS string
OSVersion semver.Version
Security Security
// URL with more information about the user agent (in most cases it's the home page).
// If unknown is nil.
URL *url.URL
Expand All @@ -123,9 +125,10 @@ func (ua *UserAgent) String() string {
Name: %v
Version: %v
OS: %v
OSVersion: %v
Security: %v
Mobile: %v
Tablet: %v`, ua.Type, ua.Name, ua.Version, ua.OS, ua.Security, ua.Mobile, ua.Tablet)
Tablet: %v`, ua.Type, ua.Name, ua.Version, ua.OS, ua.OSVersion, ua.Security, ua.Mobile, ua.Tablet)
}

func new() *UserAgent {
Expand Down Expand Up @@ -201,6 +204,46 @@ func parseVersion(l *lex, ua *UserAgent, sep string) bool {
return true
}

var appleVersionRegexp = regexp.MustCompile(`(?:[^\)]+?)\b(\d+_\d+(_\d+)?)\b`)
var genericVersionRegexp = regexp.MustCompile(`(?:[^\)]*?) (\d+\.\d+(\.\d+)?)\b`)

func parseOSVersion(l *lex, ua *UserAgent) bool {
switch ua.OS {
case osMacOS, osIOS:
_, s, ok := l.spanRegexp(appleVersionRegexp)
if !ok {
return true
}

s = strings.Replace(s, "_", ".", -1)

v, err := semver.ParseTolerant(s)
if err != nil {
return false
}

ua.OSVersion = v
return true

case osAndroid, osWindows:
_, s, ok := l.spanRegexp(genericVersionRegexp)
if !ok {
return true
}

v, err := semver.ParseTolerant(s)
if err != nil {
return false
}

ua.OSVersion = v
return true

default:
return false
}
}

func parseNameVersion(l *lex, ua *UserAgent) bool {
var s string
var ok bool
Expand Down
Loading

0 comments on commit 7fb4bcb

Please sign in to comment.