forked from mssola/user_agent
-
Notifications
You must be signed in to change notification settings - Fork 0
/
user_agent.go
170 lines (152 loc) · 4.49 KB
/
user_agent.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
// Copyright (C) 2012-2014 Miquel Sabaté Solà <mikisabate@gmail.com>
// This file is licensed under the MIT license.
// See the LICENSE file.
// Package user_agent 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 user_agent
import (
"regexp"
"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 {
mozilla string
platform string
os string
localization string
browser Browser
bot bool
mobile 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) {
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++
}
return s
}
// Parse 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.mobile = false
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 {
p.mozilla = sections[0].version
if !p.bot {
for _, v := range sections {
p.checkBot(v.comment)
}
if !p.bot {
p.detectBrowser(sections)
p.detectOS(sections[0])
}
}
}
}
// Check if we're dealing with a Bot.
func (p *UserAgent) checkBot(comment []string) {
// Regular bots (Google, Bing, ...).
reg, _ := regexp.Compile("(?i)bot")
for _, v := range comment {
if reg.Match([]byte(v)) {
p.bot = true
return
}
}
// Special case for the Baidu bot.
if len(comment) > 1 && strings.HasPrefix(comment[1], "Baidu") {
p.bot = true
}
}
// 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
}
// Returns true if it's a bot, false otherwise.
func (p *UserAgent) Bot() bool {
return p.bot
}
// Returns true if it's a mobile device, false otherwise.
func (p *UserAgent) Mobile() bool {
return p.mobile
}