useragent_parser 0.0.4 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|