useragent_parser 0.0.4 → 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/config/regexes.yaml +829 -0
- data/lib/useragent_parser/parsers/device_parser.rb +38 -0
- data/lib/useragent_parser/parsers/os_parser.rb +39 -0
- data/lib/useragent_parser/{parser.rb → parsers/user_agent_parser.rb} +3 -7
- data/lib/useragent_parser/user_agent.rb +31 -9
- data/lib/useragent_parser/version.rb +1 -1
- data/lib/useragent_parser.rb +70 -29
- data/spec/fixtures/additional_os_tests.yaml +134 -0
- data/spec/fixtures/firefox_user_agent_strings.yaml +804 -804
- data/spec/fixtures/pgts_browser_list.yaml +37489 -37489
- data/spec/fixtures/test_device.yaml +31 -0
- data/spec/fixtures/test_user_agent_parser.yaml +216 -687
- data/spec/fixtures/test_user_agent_parser_os.yaml +346 -0
- data/spec/parsers/device_parser_spec.rb +18 -0
- data/spec/parsers/os_parser_spec.rb +35 -0
- data/spec/{useragent_parser → parsers}/useragent_parser_spec.rb +16 -20
- data/spec/user_agent_spec.rb +190 -0
- data/spec/useragent_parser_spec.rb +34 -0
- metadata +44 -21
- data/config/user_agent_parser.yaml +0 -259
- data/spec/useragent_parser/user_agent_spec.rb +0 -169
@@ -0,0 +1,38 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module UseragentParser
|
4
|
+
class DeviceParser
|
5
|
+
attr_accessor :pattern, :user_agent_re, :device_replacement
|
6
|
+
|
7
|
+
def initialize(pattern, device_replacement = nil)
|
8
|
+
@pattern = pattern
|
9
|
+
@user_agent_re = Regexp.compile(pattern)
|
10
|
+
@device_replacement = device_replacement
|
11
|
+
end
|
12
|
+
|
13
|
+
def match_spans(user_agent_string)
|
14
|
+
match_spans = []
|
15
|
+
match = @user_agent_re.match(user_agent_string)
|
16
|
+
if match
|
17
|
+
# Return the offsets
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse(user_agent_string)
|
22
|
+
device = nil
|
23
|
+
match = @user_agent_re.match(user_agent_string)
|
24
|
+
if match
|
25
|
+
if @device_replacement
|
26
|
+
if %r'\$1'.match @device_replacement
|
27
|
+
device = @device_replacement.gsub(%r'\$1', match[1])
|
28
|
+
else
|
29
|
+
device = @device_replacement
|
30
|
+
end
|
31
|
+
else
|
32
|
+
device = match[1]
|
33
|
+
end
|
34
|
+
end
|
35
|
+
return device
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module UseragentParser
|
4
|
+
class OSParser
|
5
|
+
attr_accessor :pattern, :user_agent_re, :family_replacement, :major_replacement
|
6
|
+
|
7
|
+
def initialize(pattern, os_replacement = nil)
|
8
|
+
@pattern = pattern
|
9
|
+
@user_agent_re = Regexp.compile(pattern)
|
10
|
+
@os_replacement = os_replacement
|
11
|
+
end
|
12
|
+
|
13
|
+
def match_spans(user_agent_string)
|
14
|
+
match_spans = []
|
15
|
+
match = @user_agent_re.match(user_agent_string)
|
16
|
+
if match
|
17
|
+
# Return the offsets
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def parse(user_agent_string)
|
22
|
+
os, os_v1, os_v2, os_v3, os_v4 = nil, nil, nil, nil, nil
|
23
|
+
match = @user_agent_re.match(user_agent_string)
|
24
|
+
if match
|
25
|
+
if @os_replacement
|
26
|
+
os = @os_replacement
|
27
|
+
else
|
28
|
+
os = match[1]
|
29
|
+
end
|
30
|
+
|
31
|
+
os_v1 = match[2] if match.size >= 3
|
32
|
+
os_v2 = match[3] if match.size >= 4
|
33
|
+
os_v3 = match[4] if match.size >= 5
|
34
|
+
os_v4 = match[5] if match.size >= 6
|
35
|
+
end
|
36
|
+
return os, os_v1, os_v2, os_v3, os_v4
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# encoding: utf-8
|
2
2
|
|
3
3
|
module UseragentParser
|
4
|
-
class
|
4
|
+
class UserAgentParser
|
5
5
|
attr_accessor :pattern, :user_agent_re, :family_replacement, :v1_replacement
|
6
6
|
|
7
7
|
def initialize(pattern, family_replacement = nil, v1_replacement = nil)
|
@@ -38,12 +38,8 @@ module UseragentParser
|
|
38
38
|
v1 = match[2]
|
39
39
|
end
|
40
40
|
|
41
|
-
if match.size >= 4
|
42
|
-
|
43
|
-
if match.size >= 5
|
44
|
-
v3 = match[4]
|
45
|
-
end
|
46
|
-
end
|
41
|
+
v2 = match[3] if match.size >= 4
|
42
|
+
v3 = match[4] if match.size >= 5
|
47
43
|
end
|
48
44
|
return family, v1, v2, v3
|
49
45
|
end
|
@@ -4,15 +4,37 @@ module UseragentParser
|
|
4
4
|
attr_reader :os, :os_family, :os_version, :os_major_version, :os_minor_version, :os_patch_version
|
5
5
|
|
6
6
|
def initialize(details = {})
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
7
|
+
if user_agent = details['user_agent']
|
8
|
+
@browser_family = user_agent['family'] || 'Other'
|
9
|
+
@browser_major_version = user_agent['major']
|
10
|
+
@browser_minor_version = user_agent['minor']
|
11
|
+
@browser_patch_version = user_agent['patch']
|
12
|
+
end
|
13
|
+
|
14
|
+
if os = details['os']
|
15
|
+
@os_family = os['family'] || 'Other'
|
16
|
+
@os_major_version = os['major']
|
17
|
+
@os_minor_version = os['minor']
|
18
|
+
@os_patch_version = os['patch']
|
19
|
+
end
|
20
|
+
|
21
|
+
if device = details['device']
|
22
|
+
@device = device['family']
|
23
|
+
@is_mobile = device['is_mobile']
|
24
|
+
@is_spider = device['is_spider']
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def device
|
29
|
+
@device
|
30
|
+
end
|
31
|
+
|
32
|
+
def is_mobile?
|
33
|
+
!!@is_mobile
|
34
|
+
end
|
35
|
+
|
36
|
+
def is_spider?
|
37
|
+
!!@is_spider
|
16
38
|
end
|
17
39
|
|
18
40
|
def browser_version
|
data/lib/useragent_parser.rb
CHANGED
@@ -1,45 +1,56 @@
|
|
1
1
|
require "useragent_parser/version"
|
2
|
-
require "useragent_parser/
|
2
|
+
require "useragent_parser/parsers/user_agent_parser"
|
3
|
+
require "useragent_parser/parsers/os_parser"
|
4
|
+
require "useragent_parser/parsers/device_parser"
|
3
5
|
require "useragent_parser/user_agent"
|
4
6
|
|
5
7
|
module UseragentParser
|
6
8
|
USER_AGENT_PARSERS = []
|
7
9
|
OS_PARSERS = []
|
10
|
+
DEVICE_PARSERS = []
|
11
|
+
MOBILE_USER_AGENT_FAMILIES = []
|
12
|
+
MOBILE_OS_FAMILIES = []
|
8
13
|
|
9
|
-
def
|
10
|
-
YAML.load_file(File.expand_path(File.dirname(__FILE__)) + "/../config/
|
14
|
+
def self.load_parsers!
|
15
|
+
yaml = YAML.load_file(File.expand_path(File.dirname(__FILE__)) + "/../config/regexes.yaml")
|
16
|
+
yaml['user_agent_parsers'].each do |parser|
|
11
17
|
regex = parser['regex']
|
12
|
-
family_replacement
|
13
|
-
|
14
|
-
family_replacement = parser['family_replacement']
|
15
|
-
end
|
16
|
-
|
17
|
-
if parser.has_key?('v1_replacement')
|
18
|
-
v1_replacement = parser['v1_replacement']
|
19
|
-
end
|
18
|
+
family_replacement = parser.fetch('family_replacement', nil)
|
19
|
+
v1_replacement = parser.fetch('v1_replacement', nil)
|
20
20
|
|
21
|
-
USER_AGENT_PARSERS.push UseragentParser::
|
21
|
+
USER_AGENT_PARSERS.push UseragentParser::UserAgentParser.new(regex, family_replacement, v1_replacement)
|
22
22
|
end
|
23
23
|
|
24
|
-
|
24
|
+
yaml['os_parsers'].each do |parser|
|
25
25
|
regex = parser['regex']
|
26
|
-
|
27
|
-
if parser.has_key?('family_replacement')
|
28
|
-
family_replacement = parser['family_replacement']
|
29
|
-
end
|
26
|
+
os_replacement = parser.fetch('os_replacement', nil)
|
30
27
|
|
31
|
-
|
32
|
-
|
33
|
-
end
|
28
|
+
OS_PARSERS.push UseragentParser::OSParser.new(regex, os_replacement)
|
29
|
+
end
|
34
30
|
|
35
|
-
|
31
|
+
yaml['device_parsers'].each do |parser|
|
32
|
+
regex = parser['regex']
|
33
|
+
device_replacement = parser.fetch('device_replacement', nil)
|
34
|
+
|
35
|
+
DEVICE_PARSERS.push UseragentParser::DeviceParser.new(regex, device_replacement)
|
36
36
|
end
|
37
|
+
|
38
|
+
MOBILE_USER_AGENT_FAMILIES.push *yaml['mobile_user_agent_families']
|
39
|
+
MOBILE_OS_FAMILIES.push *yaml['mobile_os_families']
|
37
40
|
end
|
38
41
|
|
39
|
-
def
|
40
|
-
|
41
|
-
|
42
|
+
def self.parse_all(user_agent_string, *js_args)
|
43
|
+
# UseragentParser::UserAgent.new{
|
44
|
+
{
|
45
|
+
'user_agent' => self.parse_user_agent(user_agent_string, *js_args),
|
46
|
+
'os' => self.parse_os(user_agent_string, *js_args),
|
47
|
+
'device' => self.parse_device(user_agent_string, *js_args),
|
48
|
+
'string' => user_agent_string
|
49
|
+
}
|
50
|
+
end
|
42
51
|
|
52
|
+
def self.parse_user_agent(user_agent_string, js_user_agent_string = nil, js_user_agent_family = nil, js_user_agent_v1 = nil, js_user_agent_v2 = nil, js_user_agent_v3 = nil)
|
53
|
+
family, v1, v2, v3 = nil
|
43
54
|
# Override via JS properties.
|
44
55
|
if js_user_agent_family.nil?
|
45
56
|
USER_AGENT_PARSERS.each do |parser|
|
@@ -56,16 +67,46 @@ module UseragentParser
|
|
56
67
|
# Override for Chrome Frame IFF Chrome is enabled.
|
57
68
|
if js_user_agent_string && js_user_agent_string.include?('Chrome/') && user_agent_string.include?('chromeframe')
|
58
69
|
family = 'Chrome Frame (%s %s)' % [ family, v1 ]
|
59
|
-
js_ua =
|
60
|
-
cf_family, v1, v2, v3 = js_ua
|
70
|
+
js_ua = self.parse_user_agent(js_user_agent_string)
|
71
|
+
cf_family, v1, v2, v3 = js_ua['family'], js_ua['major'], js_ua['minor'], js_ua['patch']
|
61
72
|
end
|
62
73
|
|
74
|
+
family ||= 'Other'
|
75
|
+
{ 'family' => family, 'major' => v1, 'minor' => v2, 'patch' => v3 }
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.parse_os(user_agent_string, js_user_agent_string = nil, js_user_agent_family = nil, js_user_agent_v1 = nil, js_user_agent_v2 = nil, js_user_agent_v3 = nil)
|
79
|
+
os, os_v1, os_v2, os_v3, os_v4 = nil, nil, nil, nil, nil
|
63
80
|
OS_PARSERS.each do |parser|
|
64
|
-
|
65
|
-
break unless
|
81
|
+
os, os_v1, os_v2, os_v3, os_v4 = parser.parse(user_agent_string)
|
82
|
+
break unless os.nil?
|
83
|
+
end
|
84
|
+
|
85
|
+
os ||= 'Other'
|
86
|
+
{ 'family' => os, 'major' => os_v1, 'minor' => os_v2, 'patch' => os_v3, 'patch_minor' => os_v4 }
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.parse_device(user_agent_string, ua_family = nil, os_family = nil)
|
90
|
+
device = nil
|
91
|
+
DEVICE_PARSERS.each do |parser|
|
92
|
+
device = parser.parse(user_agent_string)
|
93
|
+
break unless device.nil?
|
94
|
+
end
|
95
|
+
|
96
|
+
os_family = device || 'Other'
|
97
|
+
|
98
|
+
ua_family = self.parse_user_agent(user_agent_string)['family'] if ua_family.nil?
|
99
|
+
os_family = self.parse_os(user_agent_string)['family'] if os_family.nil?
|
100
|
+
|
101
|
+
if MOBILE_USER_AGENT_FAMILIES.include?(ua_family)
|
102
|
+
is_mobile = true
|
103
|
+
elsif MOBILE_OS_FAMILIES.include?(os_family)
|
104
|
+
is_mobile = true
|
105
|
+
else
|
106
|
+
is_mobile = false
|
66
107
|
end
|
67
108
|
|
68
|
-
|
109
|
+
{ 'family' => device, 'is_mobile' => is_mobile, 'is_spider' => (device == 'Spider') }
|
69
110
|
end
|
70
111
|
end
|
71
112
|
|
@@ -0,0 +1,134 @@
|
|
1
|
+
test_cases:
|
2
|
+
|
3
|
+
- user_agent_string: 'Mozilla/5.0 (Linux; U; Android 2.1-update1; en-us; Nexus One Build/ERE27) AppleWebKit/530.17 (KHTML, like Gecko) Version/4.0 Mobile Safari/530.17,gzip(gfe),gzip(gfe)'
|
4
|
+
family: 'Android'
|
5
|
+
major: '2'
|
6
|
+
minor: '1'
|
7
|
+
patch: 'update1'
|
8
|
+
patch_minor:
|
9
|
+
|
10
|
+
- user_agent_string: 'Mozilla/5.0 (Macintosh; U; Intel Mac OS X Mach-O; en-en; rv:1.9.0.12) Gecko/2009070609 Firefox/3.0.12,gzip(gfe),gzip(gfe)'
|
11
|
+
family: 'Mac OS X'
|
12
|
+
major:
|
13
|
+
minor:
|
14
|
+
patch:
|
15
|
+
patch_minor:
|
16
|
+
|
17
|
+
- user_agent_string: 'Opera/9.80 (iPhone; Opera Mini/5.0.019802/21.572; U; en) Presto/2.5.25 Version/10.54'
|
18
|
+
family: 'iOS'
|
19
|
+
major:
|
20
|
+
minor:
|
21
|
+
patch:
|
22
|
+
patch_minor:
|
23
|
+
|
24
|
+
- user_agent_string: 'Mozilla/5.0 (SymbianOS/9.2; U; Series60/3.1 NokiaN95/12.0.013; Profile/MIDP-2.0 Configuration/CLDC-1.1 ) AppleWebKit/413 (KHTML, like Gecko) Safari/413'
|
25
|
+
family: 'Symbian OS'
|
26
|
+
major: '9'
|
27
|
+
minor: '2'
|
28
|
+
patch:
|
29
|
+
patch_minor:
|
30
|
+
|
31
|
+
- user_agent_string: 'Opera/9.80 (S60; SymbOS; Opera Mobi/499; U; de) Presto/2.4.18 Version/10.00,gzip(gfe),gzip(gfe)'
|
32
|
+
family: 'Symbian OS'
|
33
|
+
major:
|
34
|
+
minor:
|
35
|
+
patch:
|
36
|
+
patch_minor:
|
37
|
+
|
38
|
+
- user_agent_string: 'Mozilla/5.0 (Symbian/3; Series60/5.2 NokiaN8-00/010.022; Profile/MIDP-2.1 Configuration/CLDC-1.1 ) AppleWebKit/525 (KHTML, like Gecko) Version/3.0 BrowserNG/7.2.6.3 3gpp-gba,gzip(gfe),gzip(gfe)'
|
39
|
+
family: 'Symbian^3'
|
40
|
+
major:
|
41
|
+
minor:
|
42
|
+
patch:
|
43
|
+
patch_minor:
|
44
|
+
|
45
|
+
- user_agent_string: 'BlackBerry9000/4.6.0.167 Profile/MIDP-2.0 Configuration/CLDC-1.1 VendorID/102'
|
46
|
+
family: 'BlackBerry OS'
|
47
|
+
major: '4'
|
48
|
+
minor: '6'
|
49
|
+
patch: '0'
|
50
|
+
patch_minor: '167'
|
51
|
+
|
52
|
+
- user_agent_string: 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.8) Gecko/20100723 Linux Mint/9 (Isadora) Firefox/3.6.8'
|
53
|
+
family: 'Linux Mint'
|
54
|
+
major: '9'
|
55
|
+
minor:
|
56
|
+
patch:
|
57
|
+
patch_minor:
|
58
|
+
|
59
|
+
- user_agent_string: 'Mozilla/5.0 (X11; CrOS i686 13.587.80) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/13.0.782.99 Safari/535.1'
|
60
|
+
family: 'Chrome OS'
|
61
|
+
major: '13'
|
62
|
+
minor: '587'
|
63
|
+
patch: '80'
|
64
|
+
patch_minor:
|
65
|
+
|
66
|
+
- user_agent_string: 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.1) Gecko/2008072310 Red Hat/3.0.1-3.el4 Firefox/3.0.1,gzip(gfe),gzip(gfe)'
|
67
|
+
family: 'Red Hat'
|
68
|
+
major: '3'
|
69
|
+
minor: '0'
|
70
|
+
patch: '1'
|
71
|
+
patch_minor:
|
72
|
+
|
73
|
+
- user_agent_string: 'Mozilla/5.0 ( U; Linux x86_32; en-US; rv:1.0) Gecko/20090723 Puppy/3.6.8-0.1.1 Firefox/3.6.7,gzip(gfe),gzip(gfe)'
|
74
|
+
family: 'Puppy'
|
75
|
+
major: '3'
|
76
|
+
minor: '6'
|
77
|
+
patch: '8'
|
78
|
+
patch_minor:
|
79
|
+
|
80
|
+
- user_agent_string: 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.2.14) Gecko/20110301 PCLinuxOS/1.9.2.14-1pclos2011 (2011) Firefox/3.6.14,gzip(gfe),gzip(gfe),gzip(gfe)'
|
81
|
+
family: 'PCLinuxOS'
|
82
|
+
major: '1'
|
83
|
+
minor: '9'
|
84
|
+
patch: '2'
|
85
|
+
patch_minor: '14'
|
86
|
+
|
87
|
+
- user_agent_string: 'Mozilla/5.0 (X11; U; Linux x86_64; cs-CZ; rv:1.9.0.4) Gecko/2008111217 Fedora/3.0.4-1.fc9 Firefox/3.0.4'
|
88
|
+
family: 'Fedora'
|
89
|
+
major: '3'
|
90
|
+
minor: '0'
|
91
|
+
patch: '4'
|
92
|
+
patch_minor:
|
93
|
+
|
94
|
+
- user_agent_string: 'Mozilla/5.0 (X11; 78; CentOS; US-en) AppleWebKit/527+ (KHTML, like Gecko) Bolt/0.862 Version/3.0 Safari/523.15'
|
95
|
+
family: 'CentOS'
|
96
|
+
major:
|
97
|
+
minor:
|
98
|
+
patch:
|
99
|
+
patch_minor:
|
100
|
+
|
101
|
+
- user_agent_string: 'Mozilla/5.0 (BlackBerry; U; BlackBerry 9780; en) AppleWebKit/534.8+ (KHTML, like Gecko) Version/6.0.0.526 Mobile Safari/534.8+,gzip(gfe),gzip(gfe),gzip(gfe)'
|
102
|
+
family: 'BlackBerry OS'
|
103
|
+
major: '6'
|
104
|
+
minor: '0'
|
105
|
+
patch: '0'
|
106
|
+
patch_minor: '526'
|
107
|
+
|
108
|
+
- user_agent_string: 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.1.2) Gecko/20090807 Mandriva Linux/1.9.1.2-1.1mud2009.1 (2009.1) Firefox/3.5.2 FirePHP/0.3,gzip(gfe),gzip(gfe)'
|
109
|
+
family: 'Mandriva'
|
110
|
+
major: '1'
|
111
|
+
minor: '9'
|
112
|
+
patch: '1'
|
113
|
+
patch_minor: '2'
|
114
|
+
|
115
|
+
- user_agent_string: 'Opera/9.80 (X11; Linux x86_64; U; Slackware; lt) Presto/2.8.131 Version/11.11'
|
116
|
+
family: 'Slackware'
|
117
|
+
major:
|
118
|
+
minor:
|
119
|
+
patch:
|
120
|
+
patch_minor:
|
121
|
+
|
122
|
+
- user_agent_string: 'Mozilla/5.0 (compatible; MSIE 9.0; Windows Phone OS 7.5; Trident/5.0; IEMobile/9.0; SAMSUNG; SGH-i917)'
|
123
|
+
family: 'Windows Phone OS'
|
124
|
+
major: '7'
|
125
|
+
minor: '5'
|
126
|
+
patch:
|
127
|
+
patch_minor:
|
128
|
+
|
129
|
+
- user_agent_string: ''
|
130
|
+
family: 'Other'
|
131
|
+
major:
|
132
|
+
minor:
|
133
|
+
patch:
|
134
|
+
patch_minor:
|