useragent_parser 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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!