useragent_parser 0.0.1

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/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in useragent_parser.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,13 @@
1
+ Copyright 2011 Morton Jonuschat
2
+
3
+ Licensed under the Apache License, Version 2.0 (the "License");
4
+ you may not use this file except in compliance with the License.
5
+ You may obtain a copy of the License at
6
+
7
+ http://www.apache.org/licenses/LICENSE-2.0
8
+
9
+ Unless required by applicable law or agreed to in writing, software
10
+ distributed under the License is distributed on an "AS IS" BASIS,
11
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ See the License for the specific language governing permissions and
13
+ limitations under the License.
data/README.md ADDED
@@ -0,0 +1,49 @@
1
+ # UseragentParser
2
+
3
+ UseragentParser is a library to detect useful information contained
4
+ within the Useragent Header transmitted by modern web clients.
5
+
6
+ # Usage
7
+
8
+ require 'useragent_parser'
9
+ ua = UseragentParser.parse('Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_4; en-US) AppleWebKit/525.13 (KHTML, like Gecko) Chrome/8.0.552.237 Safari/525.13')
10
+
11
+ This will return a hash containing the following keys:
12
+
13
+ **family** - The browser family
14
+ **v1** - The major browser version
15
+ **v2** - The minor browser version
16
+ **v3** - The patch level of the browser
17
+ **os_family** - The operating system
18
+ **os_v1** - The major operating system version
19
+ **os_v2** - The minor operating system version
20
+ **os_v3** - The patch level of the operating system
21
+
22
+ In the case that the **family** or **os_family** is unknown, 'Other' will be
23
+ returned. The version components will return _nil_ in the case that the
24
+ respective version level in undetectable.
25
+
26
+ # Additional information
27
+
28
+ This is a port of the Python [ua-parser](http://code.google.com/p/ua-parser/) library.
29
+
30
+ ## Maintainers
31
+
32
+ * Morton Jonuschat ([github.com/yabawock](https://github.com/yabawock))
33
+
34
+ # License
35
+
36
+ Copyright 2011 Morton Jonuschat
37
+
38
+ Licensed under the Apache License, Version 2.0 (the "License");
39
+ you may not use this file except in compliance with the License.
40
+ You may obtain a copy of the License at
41
+
42
+ http://www.apache.org/licenses/LICENSE-2.0
43
+
44
+ Unless required by applicable law or agreed to in writing, software
45
+ distributed under the License is distributed on an "AS IS" BASIS,
46
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
47
+ See the License for the specific language governing permissions and
48
+ limitations under the License.
49
+
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ desc "Run all RSpec tests"
5
+ RSpec::Core::RakeTask.new(:spec)
@@ -0,0 +1,259 @@
1
+ user_agent_parsers:
2
+ #### SPECIAL CASES TOP ####
3
+
4
+ # must go before Opera
5
+ - regex: '^(Opera)/(\d+)\.(\d+) \(Nintendo Wii'
6
+ family_replacement: 'Wii'
7
+
8
+ # must go before Firefox to catch SeaMonkey.
9
+ - regex: '(SeaMonkey|Fennec|Camino)/(\d+)\.(\d+)\.?([ab]?\d+[a-z]*)'
10
+
11
+ # must go before Firefox
12
+
13
+ # must go before Browser/major_version.minor_version - eg: Minefield/3.1a1pre
14
+ - regex: '(Namoroka|Shiretoko|Minefield)/(\d+)\.(\d+)\.(\d+(?:pre)?)'
15
+ family_replacement: 'Firefox ($1)'
16
+
17
+ - regex: '(Firefox)/(\d+)\.(\d+)([ab]\d+[a-z]*)'
18
+ family_replacement: 'Firefox Beta'
19
+
20
+ - regex: '(Firefox)-(?:\d+\.\d+)?/(\d+)\.(\d+)([ab]\d+[a-z]*)'
21
+ family_replacement: 'Firefox Beta'
22
+
23
+ - regex: '(Namoroka|Shiretoko|Minefield)/(\d+)\.(\d+)([ab]\d+[a-z]*)?'
24
+ family_replacement: 'Firefox ($1)'
25
+
26
+ - regex: '(Firefox).*Tablet browser (\d+)\.(\d+)\.(\d+)'
27
+ family_replacement: 'MicroB'
28
+
29
+ - regex: '(MozillaDeveloperPreview)/(\d+)\.(\d+)([ab]\d+[a-z]*)?'
30
+
31
+ # e.g.: Flock/2.0b2
32
+ - regex: '(Flock)/(\d+)\.(\d+)(b\d+?)'
33
+
34
+ # RockMelt
35
+ - regex: '(RockMelt)/(\d+)\.(\d+)\.(\d+)'
36
+
37
+ # e.g.: Fennec/0.9pre
38
+ - regex: '(Fennec)/(\d+)\.(\d+)(pre)'
39
+
40
+ - regex: '(Navigator)/(\d+)\.(\d+)\.(\d+)'
41
+ family_replacement: 'Netscape'
42
+
43
+ - regex: '(Navigator)/(\d+)\.(\d+)([ab]\d+)'
44
+ family_replacement: 'Netscape'
45
+
46
+ - regex: '(Netscape6)/(\d+)\.(\d+)\.(\d+)'
47
+ family_replacement: 'Netscape'
48
+
49
+ - regex: '(MyIBrow)/(\d+)\.(\d+)'
50
+ family_replacement: 'My Internet Browser'
51
+
52
+ # Opera will stop at 9.80 and hide the real version in the Version string.
53
+ # see: http://dev.opera.com/articles/view/opera-ua-string-changes/
54
+ - regex: '(Opera Tablet).*Version\/(\d+)\.(\d+)(?:\.(\d+))?'
55
+
56
+ - regex: '(Opera)/.+Opera Mobi.+Version/(\d+)\.(\d+)'
57
+ family_replacement: 'Opera Mobile'
58
+
59
+ - regex: '(Opera Mini)/(\d+)\.(\d+)'
60
+
61
+ - regex: '(Opera)/9.80.*Version\/(\d+)\.(\d+)(?:\.(\d+))?'
62
+
63
+ # Palm WebOS looks a lot like Safari.
64
+ - regex: '(webOS)/(\d+)\.(\d+)'
65
+ family_replacement: 'Palm webOS'
66
+
67
+ # LuaKit has no version info.
68
+ # http://luakit.org/projects/luakit/
69
+ - regex: '(luakit)'
70
+ family_replacement: 'LuaKit'
71
+
72
+ # Lightning (for Thunderbird)
73
+ # http://www.mozilla.org/projects/calendar/lightning/
74
+ - regex: '(Lightning)/(\d+)\.(\d+)([ab]?\d+[a-z]*)'
75
+
76
+ # Swiftfox
77
+ - regex: '(Firefox)/(\d+)\.(\d+)\.(\d+(?:pre)?) \(Swiftfox\)'
78
+ family_replacement: 'Swiftfox'
79
+ - regex: '(Firefox)/(\d+)\.(\d+)([ab]\d+[a-z]*)? \(Swiftfox\)'
80
+ family_replacement: 'Swiftfox'
81
+
82
+ # Rekonq
83
+ - regex: 'rekonq'
84
+ family_replacement: 'Rekonq'
85
+
86
+ # Conkeror lowercase/uppercase
87
+ # http://conkeror.org/
88
+ - regex: '(conkeror|Conkeror)/(\d+)\.(\d+)\.?(\d+)?'
89
+ family_replacement: 'Conkeror'
90
+
91
+ # catches lower case konqueror
92
+ - regex: '(konqueror)/(\d+)\.(\d+)\.(\d+)'
93
+ family_replacement: 'Konqueror'
94
+
95
+ - regex: '(PlayBook).+RIM Tablet OS (\d+)\.(\d+)\.(\d+)'
96
+
97
+ - regex: '(WeTab)-Browser'
98
+
99
+ - regex: '(wOSBrowser).+TouchPad/(\d+)\.(\d+)'
100
+ family_replacement: 'webOS TouchPad'
101
+
102
+ - regex: '(Comodo_Dragon)/(\d+)\.(\d+)\.(\d+)'
103
+ family_replacement: 'Comodo Dragon'
104
+
105
+ # YottaaMonitor bot
106
+ - regex: '(YottaaMonitor)'
107
+
108
+ # must go before NetFront below
109
+ - regex: '(Kindle)/(\d+)\.(\d+)'
110
+
111
+ # Maxthon Googlebot
112
+ - regex: 'Googlebot\/\d+\.\d+.+(Maxthon)'
113
+
114
+ # Mobile Safari on Android
115
+ - regex: 'Android.*\).*(Version)/(\d+)\.(\d+)(?:\.(\d+))?.*Safari/'
116
+ family_replacement: 'Mobile Safari'
117
+
118
+ # Mobile Safari on iOS
119
+ - regex: '(Version)/(\d+)\.(\d+)(?:\.(\d+))?.*Mobile/\w+ Safari/\d+'
120
+ family_replacement: 'Mobile Safari'
121
+
122
+ #### END SPECIAL CASES TOP ####
123
+
124
+ #### MAIN CASES - this catches > 50% of all browsers ####
125
+
126
+ # Browser/major_version.minor_version.beta_version
127
+ - regex: '(AdobeAIR|Chromium|FireWeb|Jasmine|ANTGalio|Midori|Fresco|Lobo|PaleMoon|Maxthon|Lynx|OmniWeb|Dillo|Camino|Demeter|Fluid|Fennec|Shiira|Sunrise|Chrome|Flock|Netscape|Lunascape|Epiphany|WebPilot|Vodafone|NetFront|Konqueror|SeaMonkey|Kazehakase|Vienna|Iceape|Iceweasel|IceWeasel|Iron|K-Meleon|Sleipnir|Galeon|GranParadiso|Opera Mini|iCab|NetNewsWire|ThunderBrowse|Thunderbird|Iron|Iris)/(\d+)\.(\d+)\.(\d+)'
128
+
129
+ # Browser/major_version.minor_version
130
+ - regex: '(Bolt|Jasmine|IEMobile|IceCat|Skyfire|Midori|Maxthon|Lynx|Arora|IBrowse|Dillo|Camino|Shiira|Fennec|Phoenix|Chrome|Flock|Netscape|Lunascape|Epiphany|WebPilot|Opera Mini|Opera|Vodafone|NetFront|Konqueror|Googlebot|SeaMonkey|Kazehakase|Vienna|Iceape|Iceweasel|IceWeasel|Iron|K-Meleon|Sleipnir|Galeon|GranParadiso|iCab|NetNewsWire|Iron|Space Bison|Stainless|Orca|Dolfin|BOLT)/(\d+)\.(\d+)'
131
+
132
+ # Browser major_version.minor_version.beta_version (space instead of slash)
133
+ - regex: '(iRider|Crazy Browser|SkipStone|iCab|Lunascape|Sleipnir|Maemo Browser) (\d+)\.(\d+)\.(\d+)'
134
+ # Browser major_version.minor_version (space instead of slash)
135
+ - regex: '(iCab|Lunascape|Opera|Android) (\d+)\.(\d+)\.?(\d+)?'
136
+
137
+ - regex: '(IEMobile) (\d+)\.(\d+)'
138
+ family_replacement: 'IE Mobile'
139
+
140
+ # AFTER THE EDGE CASES ABOVE!
141
+ - regex: '(Firefox)/(\d+)\.(\d+)\.(\d+)'
142
+
143
+ - regex: '(Firefox)/(\d+)\.(\d+)(pre|[ab]\d+[a-z]*)?'
144
+ #### END MAIN CASES ####
145
+
146
+ #### SPECIAL CASES ####
147
+ - regex: '(Obigo|OBIGO)[^\d]*(\d+)(?:.(\d+))?'
148
+ family_replacement: 'Obigo'
149
+
150
+ - regex: '(MAXTHON|Maxthon) (\d+)\.(\d+)'
151
+ family_replacement: 'Maxthon'
152
+
153
+ - regex: '(Maxthon|MyIE2|Uzbl|Shiira)'
154
+ major_version_replacement: '0'
155
+
156
+ - regex: '(PLAYSTATION) (\d+)'
157
+ family_replacement: 'PlayStation'
158
+
159
+ - regex: '(PlayStation Portable)[^\d]+(\d+).(\d+)'
160
+
161
+ - regex: '(BrowseX) \((\d+)\.(\d+)\.(\d+)'
162
+
163
+ - regex: '(POLARIS)/(\d+)\.(\d+)'
164
+ family_replacement: 'Polaris'
165
+
166
+ - regex: '(BonEcho)/(\d+)\.(\d+)\.(\d+)'
167
+ family_replacement: 'Bon Echo'
168
+
169
+ - regex: '(iPod).+Version/(\d+)\.(\d+)\.(\d+)'
170
+
171
+ - regex: '(iPhone) OS (\d+)_(\d+)(?:_(\d+))?'
172
+
173
+ - regex: '(iPad).+ OS (\d+)_(\d+)(?:_(\d+))?'
174
+
175
+ - regex: '(Avant)'
176
+ major_version_replacement: '1'
177
+
178
+ - regex: '(Nokia)[EN]?(\d+)'
179
+
180
+ # BlackBerry devices
181
+ - regex: '(Black[bB]erry).+Version\/(\d+)\.(\d+)\.(\d+)'
182
+ family_replacement: 'Blackberry'
183
+
184
+ - regex: '(Black[bB]erry)\s?(\d+)'
185
+ family_replacement: 'Blackberry'
186
+
187
+ - regex: '(OmniWeb)/v(\d+)\.(\d+)'
188
+
189
+ - regex: '(Blazer)/(\d+)\.(\d+)'
190
+ family_replacement: 'Palm Blazer'
191
+
192
+ - regex: '(Pre)/(\d+)\.(\d+)'
193
+ family_replacement: 'Palm Pre'
194
+
195
+ - regex: '(Links) \((\d+)\.(\d+)'
196
+
197
+ - regex: '(QtWeb) Internet Browser/(\d+)\.(\d+)'
198
+
199
+ #- regex: '\(iPad;.+(Version)/(\d+)\.(\d+)(?:\.(\d+))?.*Safari/'
200
+ # family_replacement: 'iPad'
201
+
202
+ # Safari
203
+ - regex: '(Version)/(\d+)\.(\d+)(?:\.(\d+))?.*Safari/'
204
+ family_replacement: 'Safari'
205
+ # Safari didn't provide "Version/d.d.d" prior to 3.0
206
+ - regex: '(Safari)/\d+'
207
+
208
+ - regex: '(OLPC)/Update(\d+)\.(\d+)'
209
+
210
+ - regex: '(OLPC)/Update()\.(\d+)'
211
+ major_version_replacement: '0'
212
+
213
+ - regex: '(SamsungSGHi560)'
214
+ family_replacement: 'Samsung SGHi560'
215
+
216
+ - regex: '^(SonyEricssonK800i)'
217
+ family_replacement: 'Sony Ericsson K800i'
218
+
219
+ - regex: '(Teleca Q7)'
220
+
221
+ - regex: '(MSIE) (\d+)\.(\d+)'
222
+ family_replacement: 'IE'
223
+
224
+
225
+ os_parsers:
226
+ - regex: '(Windows NT|Windows) (\d+)\.(\d+)'
227
+ family_replacement: 'Windows'
228
+ - regex: '(Windows Phone OS) (\d+)\.(\d+)'
229
+ family_replacement: 'Windows Phone'
230
+ - regex: '(Windows|Win) (XP|9x|95|98|ME)'
231
+ family_replacement: 'Windows'
232
+ - regex: '(Windows CE)'
233
+ - regex: '(Win)(95|98|9x)'
234
+ family_replacement: 'Windows'
235
+ - regex: '(Windows)'
236
+
237
+ - regex: '(Android) (\d+)\.(\d+)\.(\d+)'
238
+ - regex: '(Android) (\d+)\.(\d+)'
239
+
240
+ - regex: '(CPU OS|CPU iPhone OS) (\d+)_(\d+)_(\d+) like Mac OS X'
241
+ family_replacement: 'iOS'
242
+ - regex: '(CPU OS|CPU iPhone OS) (\d+)_(\d+) like Mac OS X'
243
+ family_replacement: 'iOS'
244
+
245
+ - regex: '(Mac OS X) v?(\d+)\.(\d+)\.?(\d+)?'
246
+ - regex: '(Mac OS X) v?(\d+)_(\d+)_?(\d+)?'
247
+ - regex: '(Mac OS X)/(\d+)\.(\d+)\.?(\d+)?'
248
+ - regex: '(os=Mac) (\d+)\.(\d+)\.(\d+)'
249
+ family_replacement: 'Mac OS X'
250
+ - regex: '(Mac OS X|Mac_PowerPC|iMac|MacBook|Macintosh|PowerMac)'
251
+ family_replacement: 'Mac OS X'
252
+
253
+ - regex: '(FreeBSD)'
254
+ - regex: '(iPhone Simulator)'
255
+
256
+ - regex: '(Linux)'
257
+
258
+ product_parsers:
259
+ - regex: '(iPhone)'
@@ -0,0 +1,51 @@
1
+ # encoding: utf-8
2
+
3
+ module UseragentParser
4
+ class Parser
5
+ attr_accessor :pattern, :user_agent_re, :family_replacement, :v1_replacement
6
+
7
+ def initialize(pattern, family_replacement = nil, v1_replacement = nil)
8
+ @pattern = pattern
9
+ @user_agent_re = Regexp.compile(pattern)
10
+ @family_replacement = family_replacement
11
+ @v1_replacement = v1_replacement
12
+ end
13
+
14
+ def match_spans(user_agent_string)
15
+ match_spans = []
16
+ match = @user_agent_re.match(user_agent_string)
17
+ if match
18
+ # Return the offsets
19
+ end
20
+ end
21
+
22
+ def parse(user_agent_string)
23
+ family, v1, v2, v3 = nil, nil, nil, nil
24
+ match = @user_agent_re.match(user_agent_string)
25
+ if match
26
+ family = match[1]
27
+ if @family_replacement
28
+ if %r'\$1'.match @family_replacement
29
+ family = @family_replacement.gsub(%r'\$1', match[1])
30
+ else
31
+ family = @family_replacement
32
+ end
33
+ end
34
+
35
+ if @v1_replacement
36
+ v1 = @v1_replacement
37
+ else
38
+ v1 = match[2]
39
+ end
40
+
41
+ if match.size >= 4
42
+ v2 = match[3]
43
+ if match.size >= 5
44
+ v3 = match[4]
45
+ end
46
+ end
47
+ end
48
+ return family, v1, v2, v3
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,3 @@
1
+ module UseragentParser
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,79 @@
1
+ require "useragent_parser/version"
2
+ require "useragent_parser/parser"
3
+
4
+ module UseragentParser
5
+ USER_AGENT_PARSERS = []
6
+ OS_PARSERS = []
7
+
8
+ def UseragentParser.load_parsers!
9
+ YAML.load_file(File.expand_path(File.dirname(__FILE__)) + "/../config/user_agent_parser.yaml")['user_agent_parsers'].each do |parser|
10
+ regex = parser['regex']
11
+ family_replacement, v1_replacement = nil, nil
12
+ if parser.has_key?('family_replacement')
13
+ family_replacement = parser['family_replacement']
14
+ end
15
+
16
+ if parser.has_key?('v1_replacement')
17
+ v1_replacement = parser['v1_replacement']
18
+ end
19
+
20
+ USER_AGENT_PARSERS.push UseragentParser::Parser.new(regex, family_replacement, v1_replacement)
21
+ end
22
+
23
+ YAML.load_file(File.expand_path(File.dirname(__FILE__)) + "/../config/user_agent_parser.yaml")['os_parsers'].each do |parser|
24
+ regex = parser['regex']
25
+ family_replacement, v1_replacement, code_name = nil, nil, nil
26
+ if parser.has_key?('family_replacement')
27
+ family_replacement = parser['family_replacement']
28
+ end
29
+
30
+ if parser.has_key?('v1_replacement')
31
+ v1_replacement = parser['v1_replacement']
32
+ end
33
+
34
+ OS_PARSERS.push UseragentParser::Parser.new(regex, family_replacement, v1_replacement)
35
+ end
36
+ end
37
+
38
+ def UseragentParser.parse(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)
39
+ family, v1, v2, v3 = nil, nil, nil, nil
40
+ os_family, os_v1, os_v2, os_v3 = nil, nil, nil, nil
41
+
42
+ # Override via JS properties.
43
+ if js_user_agent_family.nil?
44
+ USER_AGENT_PARSERS.each do |parser|
45
+ family, v1, v2, v3 = parser.parse(user_agent_string)
46
+ break unless family.nil?
47
+ end
48
+ else
49
+ family = js_user_agent_family
50
+ v1 = js_user_agent_v1 unless js_user_agent_v1.nil?
51
+ v2 = js_user_agent_v2 unless js_user_agent_v3.nil?
52
+ v3 = js_user_agent_v3 unless js_user_agent_v3.nil?
53
+ end
54
+
55
+ # Override for Chrome Frame IFF Chrome is enabled.
56
+ if js_user_agent_string && js_user_agent_string.include?('Chrome/') && user_agent_string.include?('chromeframe')
57
+ family = 'Chrome Frame (%{family} %{v1})' % { :family => family, :v1 => v1 }
58
+ cf_family, v1, v2, v3 = UseragentParser.parse(js_user_agent_string).values
59
+ end
60
+
61
+ OS_PARSERS.each do |parser|
62
+ os_family, os_v1, os_v2, os_v3 = parser.parse(user_agent_string)
63
+ break unless os_family.nil?
64
+ end
65
+
66
+ {
67
+ 'family' => family || 'Other',
68
+ 'v1' => v1,
69
+ 'v2' => v2,
70
+ 'v3' => v3,
71
+ 'os_family' => os_family || 'Other',
72
+ 'os_v1' => os_v1,
73
+ 'os_v2' => os_v2,
74
+ 'os_v3' => os_v3
75
+ }
76
+ end
77
+ end
78
+
79
+ UseragentParser.load_parsers!