ronin-web-user_agents 0.1.0.beta1
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.
- checksums.yaml +7 -0
- data/.document +5 -0
- data/.github/workflows/ruby.yml +31 -0
- data/.gitignore +14 -0
- data/.rspec +1 -0
- data/.ruby-version +1 -0
- data/.yardopts +1 -0
- data/COPYING.txt +165 -0
- data/ChangeLog.md +5 -0
- data/Gemfile +26 -0
- data/README.md +177 -0
- data/Rakefile +34 -0
- data/corpus/.gitkeep +0 -0
- data/data/android/devices.txt +1393 -0
- data/data/chrome/versions.txt +394 -0
- data/data/firefox/langs.txt +125 -0
- data/data/firefox/versions.txt +584 -0
- data/gemspec.yml +21 -0
- data/lib/ronin/web/user_agents/chrome.rb +301 -0
- data/lib/ronin/web/user_agents/data_dir.rb +30 -0
- data/lib/ronin/web/user_agents/firefox.rb +360 -0
- data/lib/ronin/web/user_agents/google_bot.rb +140 -0
- data/lib/ronin/web/user_agents/os/android.rb +79 -0
- data/lib/ronin/web/user_agents/os/linux.rb +52 -0
- data/lib/ronin/web/user_agents/os/mac_os.rb +116 -0
- data/lib/ronin/web/user_agents/os/windows.rb +53 -0
- data/lib/ronin/web/user_agents/version.rb +28 -0
- data/lib/ronin/web/user_agents.rb +142 -0
- data/ronin-web-user_agents.gemspec +61 -0
- data/scripts/index_user_agents +281 -0
- data/spec/chrome_spec.rb +511 -0
- data/spec/firefox_spec.rb +414 -0
- data/spec/google_bot_spec.rb +231 -0
- data/spec/os/android_spec.rb +55 -0
- data/spec/os/linux_spec.rb +48 -0
- data/spec/os/mac_os_spec.rb +73 -0
- data/spec/os/windows_spec.rb +52 -0
- data/spec/spec_helper.rb +4 -0
- data/spec/user_agents_spec.rb +49 -0
- metadata +110 -0
@@ -0,0 +1,281 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
class UserAgentsFile
|
6
|
+
|
7
|
+
attr_reader :path
|
8
|
+
|
9
|
+
def initialize(path)
|
10
|
+
@path = path
|
11
|
+
end
|
12
|
+
|
13
|
+
def each_line
|
14
|
+
File.open(@path) do |file|
|
15
|
+
file.each_line do |line|
|
16
|
+
line.chomp!
|
17
|
+
yield line
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
PRODUCT_REGEXP = /[A-Z][A-Za-z ]+/
|
23
|
+
|
24
|
+
PRODUCT_VERSION_REGEXP = /[\d\.]+/
|
25
|
+
|
26
|
+
EXTENSIONS_REGEXP = /[^\(\)]+(?:\([^\)]+\)[^\(\)]+)?/
|
27
|
+
|
28
|
+
EXTENSION_SEPARATOR = /\s*;\s+/
|
29
|
+
|
30
|
+
REGEXP = /(#{PRODUCT_REGEXP})\/(#{PRODUCT_VERSION_REGEXP})(?:\s+\((#{EXTENSIONS_REGEXP})\))?/
|
31
|
+
|
32
|
+
VERSION_REGEXP = /\d+(?:\.\d+)*/
|
33
|
+
|
34
|
+
def parse
|
35
|
+
each_line do |user_agent|
|
36
|
+
matches = user_agent.scan(REGEXP)
|
37
|
+
|
38
|
+
yield matches
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def index_product_combinations
|
43
|
+
product_combinations = Hash.new { |hash,key| hash[key] = 0 }
|
44
|
+
|
45
|
+
parse do |matches|
|
46
|
+
products = matches.map { |(product,_,_)| product }
|
47
|
+
|
48
|
+
product_combinations[products] += 1
|
49
|
+
end
|
50
|
+
|
51
|
+
return product_combinations
|
52
|
+
end
|
53
|
+
|
54
|
+
def index_product_versions(search_product)
|
55
|
+
product_versions = Hash.new { |hash,key| hash[key] = 0 }
|
56
|
+
|
57
|
+
parse do |matches|
|
58
|
+
_,product_version,_ = *matches.find do |(product,_,_)|
|
59
|
+
product == search_product
|
60
|
+
end
|
61
|
+
|
62
|
+
if product_version
|
63
|
+
product_versions[product_version] += 1
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
return product_versions
|
68
|
+
end
|
69
|
+
|
70
|
+
def index_product_extensions(target_product)
|
71
|
+
product_extensions = Hash.new { |hash,key| hash[key] = 0 }
|
72
|
+
|
73
|
+
parse do |matches|
|
74
|
+
_,_,extensions = matches.find { |(product,_,_)| product == target_product }
|
75
|
+
|
76
|
+
if extensions
|
77
|
+
tree_node = product_extensions
|
78
|
+
|
79
|
+
product_extensions[extensions.split(EXTENSION_SEPARATOR)] += 1
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
return product_extensions
|
84
|
+
end
|
85
|
+
|
86
|
+
def each_product(name)
|
87
|
+
parse do |matches|
|
88
|
+
product, product_version, extensions = matches.find do |(product,_,_)|
|
89
|
+
product == name
|
90
|
+
end
|
91
|
+
|
92
|
+
if product
|
93
|
+
yield product, product_version, extensions
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def index_macos_versions
|
99
|
+
macos_versions = Set.new
|
100
|
+
|
101
|
+
each_product('Mozilla') do |product,product_version,extensions|
|
102
|
+
if extensions
|
103
|
+
if (match = extensions.match(/Macintosh; Intel Mac OS X (\d+(?:_\d+)*)/))
|
104
|
+
macos_versions << match[1]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
return macos_versions
|
110
|
+
end
|
111
|
+
|
112
|
+
def index_windows_versions
|
113
|
+
windows_versions = Set.new
|
114
|
+
|
115
|
+
each_product('Mozilla') do |product,product_version,extensions|
|
116
|
+
if extensions
|
117
|
+
if (match = extensions.match(/Windows NT (#{VERSION_REGEXP});/))
|
118
|
+
windows_versions << match[1]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
return windows_versions
|
124
|
+
end
|
125
|
+
|
126
|
+
def index_android_versions
|
127
|
+
android_versions = Set.new
|
128
|
+
|
129
|
+
each_product('Mozilla') do |product,product_version,extensions|
|
130
|
+
if extensions
|
131
|
+
if (match = extensions.match(/; Android (#{VERSION_REGEXP});/))
|
132
|
+
android_versions << match[1]
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
return android_versions
|
138
|
+
end
|
139
|
+
|
140
|
+
def index_android_devices
|
141
|
+
android_devices = Set.new
|
142
|
+
|
143
|
+
each_product('Mozilla') do |product,product_version,extensions|
|
144
|
+
if (extensions && extensions =~ /; Android [^;]+; /)
|
145
|
+
device = extensions.split(EXTENSION_SEPARATOR).last
|
146
|
+
|
147
|
+
android_devices << device
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
return android_devices
|
152
|
+
end
|
153
|
+
|
154
|
+
def index_chrome_versions
|
155
|
+
chrome_versions = Set.new
|
156
|
+
|
157
|
+
each_product('Chrome') do |product,product_version,extensions|
|
158
|
+
if product_version
|
159
|
+
chrome_versions << product_version
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
return chrome_versions
|
164
|
+
end
|
165
|
+
|
166
|
+
def index_firefox_versions
|
167
|
+
firefox_versions = Set.new
|
168
|
+
|
169
|
+
each_product('Mozilla') do |product,product_version,extensions|
|
170
|
+
if extensions
|
171
|
+
if (match = extensions.match(/; rv:(#{VERSION_REGEXP})/))
|
172
|
+
firefox_versions << match[1]
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|
176
|
+
|
177
|
+
return firefox_versions
|
178
|
+
end
|
179
|
+
|
180
|
+
def index_gecko_versions
|
181
|
+
gecko_versions = Set.new
|
182
|
+
|
183
|
+
each_product('Gecko') do |product,product_version,_|
|
184
|
+
gecko_versions << product_version
|
185
|
+
end
|
186
|
+
|
187
|
+
return gecko_versions
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
if $0 == __FILE__
|
192
|
+
require 'optparse'
|
193
|
+
|
194
|
+
@method = nil
|
195
|
+
@method_args = []
|
196
|
+
@print_mode = nil
|
197
|
+
|
198
|
+
optparser = OptionParser.new do |opts|
|
199
|
+
opts.banner = './scripts/index_user_agents [options] FILE'
|
200
|
+
|
201
|
+
opts.on('--products', 'List all product names') do
|
202
|
+
@method = :index_product_combinations
|
203
|
+
@print_mode = :count
|
204
|
+
end
|
205
|
+
|
206
|
+
opts.on('--product-versions PRODUCT','List all versions for a given PRODUCT') do |product|
|
207
|
+
@method = :index_product_versions
|
208
|
+
@method_args = [product]
|
209
|
+
@print_mode = :count
|
210
|
+
end
|
211
|
+
|
212
|
+
opts.on('--product-extensions PRODUCT','List all extensions for a given PRODUCT') do |product|
|
213
|
+
@method = :index_product_extensions
|
214
|
+
@method_args = [product]
|
215
|
+
@print_mode = :count
|
216
|
+
end
|
217
|
+
|
218
|
+
opts.on('--macos-versions','List all macOS versions') do
|
219
|
+
@method = :index_macos_versions
|
220
|
+
@print_mode = :list
|
221
|
+
end
|
222
|
+
|
223
|
+
opts.on('--windows-versions','List all Windows versions') do
|
224
|
+
@method = :index_windows_versions
|
225
|
+
@print_mode = :list
|
226
|
+
end
|
227
|
+
|
228
|
+
opts.on('--android-versions','List all Android versions') do
|
229
|
+
@method = :index_android_versions
|
230
|
+
@print_mode = :list
|
231
|
+
end
|
232
|
+
|
233
|
+
opts.on('--chrome-versions','List all Chrome versions') do
|
234
|
+
@method = :index_chrome_versions
|
235
|
+
@print_mode = :list
|
236
|
+
end
|
237
|
+
|
238
|
+
opts.on('--firefox-versions','List all Firefox versions') do
|
239
|
+
@method = :index_firefox_versions
|
240
|
+
@print_mode = :list
|
241
|
+
end
|
242
|
+
|
243
|
+
opts.on('--gecko-versions','List all Gecko versions') do
|
244
|
+
@method = :index_gecko_versions
|
245
|
+
@print_mode = :list
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
args = begin
|
250
|
+
optparser.parse(ARGV)
|
251
|
+
rescue OptionParser::ParseError => error
|
252
|
+
$stderr.puts "./scripts/index_user_agents: #{error.message}"
|
253
|
+
exit -1
|
254
|
+
end
|
255
|
+
|
256
|
+
unless (file_path = args[0])
|
257
|
+
$stderr.puts "./scripts/index_user_agents: must specify a User-Agents FILE"
|
258
|
+
exit -1
|
259
|
+
end
|
260
|
+
|
261
|
+
user_agents = UserAgentsFile.new(file_path)
|
262
|
+
|
263
|
+
unless @method
|
264
|
+
$stderr.puts "./scripts/index_user_agents: must specify atleast one option"
|
265
|
+
$stderr.puts optparser
|
266
|
+
exit -1
|
267
|
+
end
|
268
|
+
|
269
|
+
results = user_agents.send(@method,*@method_args)
|
270
|
+
|
271
|
+
case @print_mode
|
272
|
+
when :count
|
273
|
+
results.sort_by { |value,count| -count }.each do |value,count|
|
274
|
+
puts "#{count}\t#{value}"
|
275
|
+
end
|
276
|
+
when :list
|
277
|
+
results.sort.each do |value|
|
278
|
+
puts " #{value}"
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|