ronin-web-browser 0.1.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.document +4 -0
- data/.github/workflows/ruby.yml +51 -0
- data/.gitignore +13 -0
- data/.rspec +1 -0
- data/.rubocop.yml +12 -0
- data/.ruby-version +1 -0
- data/.yardopts +1 -0
- data/COPYING.txt +165 -0
- data/ChangeLog.md +13 -0
- data/Gemfile +32 -0
- data/README.md +186 -0
- data/Rakefile +35 -0
- data/gemspec.yml +28 -0
- data/lib/ronin/web/browser/agent.rb +430 -0
- data/lib/ronin/web/browser/cookie.rb +140 -0
- data/lib/ronin/web/browser/cookie_file.rb +91 -0
- data/lib/ronin/web/browser/version.rb +28 -0
- data/lib/ronin/web/browser.rb +148 -0
- data/ronin-web-browser.gemspec +60 -0
- metadata +109 -0
@@ -0,0 +1,430 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# ronin-web-browser - An automated Chrome API.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2022-2024 Hal Brodigan (postmodern.mod3@gmail.com)
|
6
|
+
#
|
7
|
+
# ronin-web-browser is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU Lesser General Public License as published
|
9
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
#
|
12
|
+
# ronin-web-browser is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU Lesser General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Lesser General Public License
|
18
|
+
# along with ronin-web-browser. If not, see <https://www.gnu.org/licenses/>.
|
19
|
+
#
|
20
|
+
|
21
|
+
require 'ronin/web/browser/cookie'
|
22
|
+
require 'ronin/web/browser/cookie_file'
|
23
|
+
require 'ronin/support/network/http'
|
24
|
+
|
25
|
+
require 'ferrum'
|
26
|
+
require 'uri'
|
27
|
+
|
28
|
+
module Ronin
|
29
|
+
module Web
|
30
|
+
module Browser
|
31
|
+
#
|
32
|
+
# Represents an instance of a Chrome headless or visible browser.
|
33
|
+
#
|
34
|
+
class Agent < Ferrum::Browser
|
35
|
+
|
36
|
+
# The configured proxy information.
|
37
|
+
#
|
38
|
+
# @return [Hash{Symbol => Object}, nil]
|
39
|
+
attr_reader :proxy
|
40
|
+
|
41
|
+
#
|
42
|
+
# Initializes the browser agent.
|
43
|
+
#
|
44
|
+
# @param [Boolean] visible
|
45
|
+
# Controls whether the browser will start in visible or headless mode.
|
46
|
+
#
|
47
|
+
# @param [Boolean] headless
|
48
|
+
# Controls whether the browser will start in headless or visible mode.
|
49
|
+
#
|
50
|
+
# @param [String, URI::HTTP, Addressible::URI, Hash, nil] proxy
|
51
|
+
# The proxy to send all browser requests through.
|
52
|
+
#
|
53
|
+
# @param [Hash{Symbol => Object}] kwargs
|
54
|
+
# Additional keyword arguments for `Ferrum::Browser#initialize`.
|
55
|
+
#
|
56
|
+
def initialize(visible: false,
|
57
|
+
headless: !visible,
|
58
|
+
proxy: Ronin::Support::Network::HTTP.proxy,
|
59
|
+
**kwargs)
|
60
|
+
proxy = case proxy
|
61
|
+
when Hash, nil then proxy
|
62
|
+
when URI::HTTP, Addressable::URI
|
63
|
+
{
|
64
|
+
host: proxy.host,
|
65
|
+
port: proxy.port,
|
66
|
+
user: proxy.user,
|
67
|
+
password: proxy.password
|
68
|
+
}
|
69
|
+
when String
|
70
|
+
uri = URI(proxy)
|
71
|
+
|
72
|
+
{
|
73
|
+
host: uri.host,
|
74
|
+
port: uri.port,
|
75
|
+
user: uri.user,
|
76
|
+
password: uri.password
|
77
|
+
}
|
78
|
+
else
|
79
|
+
raise(ArgumentError,"invalid proxy value (#{proxy.inspect}), must be either a Hash, URI::HTTP, String, or nil")
|
80
|
+
end
|
81
|
+
|
82
|
+
@headless = headless
|
83
|
+
@proxy = proxy
|
84
|
+
|
85
|
+
super(headless: headless, proxy: proxy, **kwargs)
|
86
|
+
end
|
87
|
+
|
88
|
+
#
|
89
|
+
# Opens a new browser.
|
90
|
+
#
|
91
|
+
# @param [Hash{Symbol => Object}] kwargs
|
92
|
+
# Additional keyword arguments for {#initialize}.
|
93
|
+
#
|
94
|
+
# @yield [browser]
|
95
|
+
# If a block is given, it will be passed the new browser object.
|
96
|
+
# Once the block returns, `quit` will be called on the browser object.
|
97
|
+
#
|
98
|
+
# @yieldparam [Agent] browser
|
99
|
+
# The newly created browser object.
|
100
|
+
#
|
101
|
+
# @return [Agent]
|
102
|
+
# The opened browser object.
|
103
|
+
#
|
104
|
+
def self.open(**kwargs)
|
105
|
+
browser = new(**kwargs)
|
106
|
+
|
107
|
+
if block_given?
|
108
|
+
yield browser
|
109
|
+
browser.quit
|
110
|
+
end
|
111
|
+
|
112
|
+
return browser
|
113
|
+
end
|
114
|
+
|
115
|
+
#
|
116
|
+
# Determines whether the browser was opened in headless mode.
|
117
|
+
#
|
118
|
+
# @return [Boolean]
|
119
|
+
#
|
120
|
+
def headless?
|
121
|
+
@headless
|
122
|
+
end
|
123
|
+
|
124
|
+
#
|
125
|
+
# Determines whether the browser was opened in visible mode.
|
126
|
+
#
|
127
|
+
# @return [Boolean]
|
128
|
+
#
|
129
|
+
def visible?
|
130
|
+
!@headless
|
131
|
+
end
|
132
|
+
|
133
|
+
#
|
134
|
+
# Determines whether the proxy was initialized with a proxy.
|
135
|
+
#
|
136
|
+
def proxy?
|
137
|
+
!@proxy.nil?
|
138
|
+
end
|
139
|
+
|
140
|
+
#
|
141
|
+
# Enables or disables bypassing CSP.
|
142
|
+
#
|
143
|
+
# @param [Boolean] mode
|
144
|
+
# Controls whether to enable or disable CSP bypassing.
|
145
|
+
#
|
146
|
+
def bypass_csp=(mode)
|
147
|
+
if mode then bypass_csp(enabled: true)
|
148
|
+
else bypass_csp(enabled: false)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
#
|
153
|
+
# Registers a callback for the given event type.
|
154
|
+
#
|
155
|
+
# @param [:request, :response, :dialog, String] event
|
156
|
+
# The event to register a callback for.
|
157
|
+
# For an exhaustive list of event String names, see the
|
158
|
+
# [Chrome DevTools Protocol documentation](https://chromedevtools.github.io/devtools-protocol/1-3/)
|
159
|
+
#
|
160
|
+
# @yield [request]
|
161
|
+
# If the event type is `:request` the given block will be passed the
|
162
|
+
# request object.
|
163
|
+
#
|
164
|
+
# @yield [exchange]
|
165
|
+
# If the event type is `:response` the given block will be passed the
|
166
|
+
# network exchange object containing both the request and the response
|
167
|
+
# objects.
|
168
|
+
#
|
169
|
+
# @yield [params, index, total]
|
170
|
+
# Other event types will be passed a params Hash, index, and total.
|
171
|
+
#
|
172
|
+
# @yieldparam [Ferrum::Network::InterceptedRequest] request
|
173
|
+
# A network request object.
|
174
|
+
#
|
175
|
+
# @yieldparam [Ferrum::Network::Exchange] exchange
|
176
|
+
# A network exchange object containing both the request and respoonse
|
177
|
+
# objects.
|
178
|
+
#
|
179
|
+
# @yieldparam [Hash{String => Object}] params
|
180
|
+
# A params Hash containing the return value(s).
|
181
|
+
#
|
182
|
+
# @yieldparam [Integer] index
|
183
|
+
#
|
184
|
+
# @yieldparam [Integer] total
|
185
|
+
#
|
186
|
+
def on(event,&block)
|
187
|
+
case event
|
188
|
+
when :response
|
189
|
+
super('Network.responseReceived') do |params,index,total|
|
190
|
+
exchange = network.select(params['requestId']).last
|
191
|
+
|
192
|
+
if exchange
|
193
|
+
block.call(exchange,index,total)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
when :close
|
197
|
+
super('Inspector.detached',&block)
|
198
|
+
else
|
199
|
+
super(event,&block)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
#
|
204
|
+
# Passes every request to the given block.
|
205
|
+
#
|
206
|
+
# @yield [request]
|
207
|
+
# The given block will be passed each request before it's sent.
|
208
|
+
#
|
209
|
+
# @yieldparam [Ferrum::Network::InterceptRequest] request
|
210
|
+
# A network request object.
|
211
|
+
#
|
212
|
+
def every_request
|
213
|
+
network.intercept
|
214
|
+
|
215
|
+
on(:request) do |request|
|
216
|
+
yield request
|
217
|
+
request.continue
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
221
|
+
#
|
222
|
+
# Passes every response to the given block.
|
223
|
+
#
|
224
|
+
# @yield [response]
|
225
|
+
# If the given block accepts a single argument, it will be passed
|
226
|
+
# each response object.
|
227
|
+
#
|
228
|
+
# @yield [response, request]
|
229
|
+
# If the given block accepts two arguments, it will be passed the
|
230
|
+
# response and the request objects.
|
231
|
+
#
|
232
|
+
# @yieldparam [Ferrum::Network::Response] response
|
233
|
+
# A respone object returned for a request.
|
234
|
+
#
|
235
|
+
# @yieldparam [Ferrum::Network::Request] request
|
236
|
+
# The request object for the response.
|
237
|
+
#
|
238
|
+
def every_response(&block)
|
239
|
+
on(:response) do |exchange,index,total|
|
240
|
+
if block.arity == 2
|
241
|
+
yield exchange.response, exchange.request
|
242
|
+
else
|
243
|
+
yield exchange.response
|
244
|
+
end
|
245
|
+
end
|
246
|
+
end
|
247
|
+
|
248
|
+
#
|
249
|
+
# Passes every requested URL to the given block.
|
250
|
+
#
|
251
|
+
# @yield [url]
|
252
|
+
# The given block will be passed every URL.
|
253
|
+
#
|
254
|
+
# @yieldparam [String] url
|
255
|
+
# A URL requested by the browser.
|
256
|
+
#
|
257
|
+
def every_url
|
258
|
+
every_request do |request|
|
259
|
+
yield request.url
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
#
|
264
|
+
# Passes every requested URL that matches the given pattern to the given
|
265
|
+
# block.
|
266
|
+
#
|
267
|
+
# @param [String, Regexp] pattern
|
268
|
+
# The pattern to filter the URLs by.
|
269
|
+
#
|
270
|
+
# @yield [url]
|
271
|
+
# The given block will be passed every URL that matches the pattern.
|
272
|
+
#
|
273
|
+
# @yieldparam [String] url
|
274
|
+
# A matching URL requested by the browser.
|
275
|
+
#
|
276
|
+
def every_url_like(pattern)
|
277
|
+
every_url do |url|
|
278
|
+
if pattern.match(url)
|
279
|
+
yield url
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
#
|
285
|
+
# The page's current URI.
|
286
|
+
#
|
287
|
+
# @return [URI::HTTP]
|
288
|
+
#
|
289
|
+
def page_uri
|
290
|
+
URI.parse(url)
|
291
|
+
end
|
292
|
+
|
293
|
+
#
|
294
|
+
# Queries the XPath or CSS-path query and returns the matching nodes.
|
295
|
+
#
|
296
|
+
# @return [Array<Ferrum::Node>]
|
297
|
+
# The matching node.
|
298
|
+
#
|
299
|
+
def search(query)
|
300
|
+
if query.start_with?('/')
|
301
|
+
xpath(query)
|
302
|
+
else
|
303
|
+
css(query)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
#
|
308
|
+
# Queries the XPath or CSS-path query and returns the first match.
|
309
|
+
#
|
310
|
+
# @return [Ferrum::Node, nil]
|
311
|
+
# The first matching node.
|
312
|
+
#
|
313
|
+
def at(query)
|
314
|
+
if query.start_with?('/')
|
315
|
+
at_xpath(query)
|
316
|
+
else
|
317
|
+
at_css(query)
|
318
|
+
end
|
319
|
+
end
|
320
|
+
|
321
|
+
#
|
322
|
+
# Queries all `<a href="...">` links in the current page.
|
323
|
+
#
|
324
|
+
# @return [Array<String>]
|
325
|
+
#
|
326
|
+
def links
|
327
|
+
xpath('//a/@href').map(&:value)
|
328
|
+
end
|
329
|
+
|
330
|
+
#
|
331
|
+
# All link URLs in the current page.
|
332
|
+
#
|
333
|
+
# @return [Array<URI::HTTP, URI::HTTPS>]
|
334
|
+
#
|
335
|
+
def urls
|
336
|
+
page_uri = self.page_uri
|
337
|
+
|
338
|
+
links.map { |link| page_uri.merge(link) }
|
339
|
+
end
|
340
|
+
|
341
|
+
alias eval_js evaluate
|
342
|
+
alias load_js add_script_tag
|
343
|
+
alias inject_js evaluate_on_new_document
|
344
|
+
alias load_css add_style_tag
|
345
|
+
|
346
|
+
#
|
347
|
+
# Enumerates over all session cookies.
|
348
|
+
#
|
349
|
+
# @yield [cookie]
|
350
|
+
# The given block will be passed each session cookie.
|
351
|
+
#
|
352
|
+
# @yieldparam [Ferrum::Cookies::Cookie] cookie
|
353
|
+
# A cookie that ends with `sess` or `session`.
|
354
|
+
#
|
355
|
+
# @return [Enumerator]
|
356
|
+
# If no block is given, then an Enumerator object will be returned.
|
357
|
+
#
|
358
|
+
def each_session_cookie
|
359
|
+
return enum_for(__method__) unless block_given?
|
360
|
+
|
361
|
+
cookies.each do |cookie|
|
362
|
+
yield cookie if cookie.session?
|
363
|
+
end
|
364
|
+
end
|
365
|
+
|
366
|
+
#
|
367
|
+
# Fetches all session cookies.
|
368
|
+
#
|
369
|
+
# @return [Array<Ferrum::Cookie>]
|
370
|
+
# The matching session cookies.
|
371
|
+
#
|
372
|
+
def session_cookies
|
373
|
+
each_session_cookie.to_a
|
374
|
+
end
|
375
|
+
|
376
|
+
#
|
377
|
+
# Sets a cookie.
|
378
|
+
#
|
379
|
+
# @param [String] name
|
380
|
+
# The cookie name.
|
381
|
+
#
|
382
|
+
# @param [String] value
|
383
|
+
# The cookie value.
|
384
|
+
#
|
385
|
+
# @param [Hash{Symbol => Object}] options
|
386
|
+
# Additional cookie attributes.
|
387
|
+
#
|
388
|
+
def set_cookie(name,value,**options)
|
389
|
+
cookies.set(name: name, value: value, **options)
|
390
|
+
end
|
391
|
+
|
392
|
+
#
|
393
|
+
# Loads the cookies from the cookie file.
|
394
|
+
#
|
395
|
+
# @param [String] path
|
396
|
+
# The path to the cookie file.
|
397
|
+
#
|
398
|
+
def load_cookies(path)
|
399
|
+
CookieFile.new(path).each do |cookie|
|
400
|
+
cookies.set(cookie)
|
401
|
+
end
|
402
|
+
end
|
403
|
+
|
404
|
+
#
|
405
|
+
# Saves the cookies to a cookie file.
|
406
|
+
#
|
407
|
+
# @param [String] path
|
408
|
+
# The path to the output cookie file.
|
409
|
+
#
|
410
|
+
def save_cookies(path)
|
411
|
+
CookieFile.save(path,cookies)
|
412
|
+
end
|
413
|
+
|
414
|
+
#
|
415
|
+
# Waits indefinitely until the browser window is closed.
|
416
|
+
#
|
417
|
+
def wait_until_closed
|
418
|
+
window_closed = false
|
419
|
+
|
420
|
+
on('Inspector.detached') do
|
421
|
+
window_closed = true
|
422
|
+
end
|
423
|
+
|
424
|
+
sleep(1) until window_closed
|
425
|
+
end
|
426
|
+
|
427
|
+
end
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
@@ -0,0 +1,140 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# ronin-web-browser - An automated Chrome API.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2022-2024 Hal Brodigan (postmodern.mod3@gmail.com)
|
6
|
+
#
|
7
|
+
# ronin-web-browser is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU Lesser General Public License as published
|
9
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
#
|
12
|
+
# ronin-web-browser is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU Lesser General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Lesser General Public License
|
18
|
+
# along with ronin-web-browser. If not, see <https://www.gnu.org/licenses/>.
|
19
|
+
#
|
20
|
+
|
21
|
+
require 'ferrum/cookies/cookie'
|
22
|
+
|
23
|
+
module Ronin
|
24
|
+
module Web
|
25
|
+
module Browser
|
26
|
+
#
|
27
|
+
# Represents a browser cookie.
|
28
|
+
#
|
29
|
+
class Cookie < Ferrum::Cookies::Cookie
|
30
|
+
|
31
|
+
#
|
32
|
+
# Parses a browser cookie from a raw String.
|
33
|
+
#
|
34
|
+
# @param [String] string
|
35
|
+
# The raw cookie String to parse.
|
36
|
+
#
|
37
|
+
# @return [Cookie]
|
38
|
+
# The parsed cookie.
|
39
|
+
#
|
40
|
+
# @raise [ArgumentError]
|
41
|
+
# The string was empty or contains an unknown field.
|
42
|
+
#
|
43
|
+
def self.parse(string)
|
44
|
+
fields = string.split(/;\s+/)
|
45
|
+
|
46
|
+
if fields.empty?
|
47
|
+
raise(ArgumentError,"cookie must not be empty: #{string.inspect}")
|
48
|
+
end
|
49
|
+
|
50
|
+
name, value = fields.shift.split('=',2)
|
51
|
+
attributes = {
|
52
|
+
'name' => name,
|
53
|
+
'value' => value
|
54
|
+
}
|
55
|
+
|
56
|
+
fields.each do |field|
|
57
|
+
if field.include?('=')
|
58
|
+
key, value = field.split('=',2)
|
59
|
+
|
60
|
+
case key
|
61
|
+
when 'Expires', 'Max-Age'
|
62
|
+
attributes['expires'] = Time.parse(value).to_i
|
63
|
+
when 'Path' then attributes['path'] = value
|
64
|
+
when 'Domain' then attributes['domain'] = value
|
65
|
+
when 'SameSite'then attributes['sameSite'] = value
|
66
|
+
else
|
67
|
+
raise(ArgumentError,"unrecognized Cookie field: #{field.inspect}")
|
68
|
+
end
|
69
|
+
else
|
70
|
+
case field
|
71
|
+
when 'HttpOnly' then attributes['httpOnly'] = true
|
72
|
+
when 'Secure' then attributes['secure'] = true
|
73
|
+
else
|
74
|
+
raise(ArgumentError,"unrecognized Cookie flag: #{field.inspect}")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
return new(attributes)
|
80
|
+
end
|
81
|
+
|
82
|
+
#
|
83
|
+
# The priority of the cookie.
|
84
|
+
#
|
85
|
+
# @return [String]
|
86
|
+
#
|
87
|
+
def priority
|
88
|
+
@attributes['priority']
|
89
|
+
end
|
90
|
+
|
91
|
+
#
|
92
|
+
# @return [Boolean]
|
93
|
+
#
|
94
|
+
def sameparty?
|
95
|
+
@attributes['sameParty']
|
96
|
+
end
|
97
|
+
|
98
|
+
alias same_party? sameparty?
|
99
|
+
|
100
|
+
#
|
101
|
+
# @return [String]
|
102
|
+
#
|
103
|
+
def source_scheme
|
104
|
+
@attributes['sourceScheme']
|
105
|
+
end
|
106
|
+
|
107
|
+
#
|
108
|
+
# @return [Integer]
|
109
|
+
#
|
110
|
+
def source_port
|
111
|
+
@attributes['sourcePort']
|
112
|
+
end
|
113
|
+
|
114
|
+
#
|
115
|
+
# Converts the cookie back into a raw cookie String.
|
116
|
+
#
|
117
|
+
# @return [String]
|
118
|
+
# The raw cookie string.
|
119
|
+
#
|
120
|
+
def to_s
|
121
|
+
string = "#{@attributes['name']}=#{@attributes['value']}"
|
122
|
+
|
123
|
+
@attributes.each do |key,value|
|
124
|
+
case key
|
125
|
+
when 'name', 'value' # no-op
|
126
|
+
when 'domain' then string << "; Domain=#{value}"
|
127
|
+
when 'path' then string << "; Path=#{value}"
|
128
|
+
when 'expires' then string << "; Expires=#{Time.at(value).httpdate}"
|
129
|
+
when 'httpOnly' then string << "; httpOnly" if value
|
130
|
+
when 'secure' then string << "; Secure" if value
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
return string
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# ronin-web-browser - An automated Chrome API.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2022-2024 Hal Brodigan (postmodern.mod3@gmail.com)
|
6
|
+
#
|
7
|
+
# ronin-web-browser is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU Lesser General Public License as published
|
9
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
#
|
12
|
+
# ronin-web-browser is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU Lesser General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Lesser General Public License
|
18
|
+
# along with ronin-web-browser. If not, see <https://www.gnu.org/licenses/>.
|
19
|
+
#
|
20
|
+
|
21
|
+
require 'ronin/web/browser/cookie'
|
22
|
+
|
23
|
+
module Ronin
|
24
|
+
module Web
|
25
|
+
module Browser
|
26
|
+
#
|
27
|
+
# Represents a file of cookies.
|
28
|
+
#
|
29
|
+
class CookieFile
|
30
|
+
|
31
|
+
include Enumerable
|
32
|
+
|
33
|
+
# The path to the file.
|
34
|
+
#
|
35
|
+
# @return [String]
|
36
|
+
attr_reader :path
|
37
|
+
|
38
|
+
#
|
39
|
+
# Initializes a cookie file.
|
40
|
+
#
|
41
|
+
# @param [String] path
|
42
|
+
# The path to the cookie file.
|
43
|
+
#
|
44
|
+
def initialize(path)
|
45
|
+
@path = File.expand_path(path)
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Writes the cookies to the cookie file.
|
50
|
+
#
|
51
|
+
# @param [String] path
|
52
|
+
# The path to the cookie file to write to.
|
53
|
+
#
|
54
|
+
# @param [Array<Cookie>, Enumerator<Cookie>] cookies
|
55
|
+
# The cookies to write.
|
56
|
+
#
|
57
|
+
def self.save(path,cookies)
|
58
|
+
File.open(path,'w') do |file|
|
59
|
+
cookies.each do |cookie|
|
60
|
+
file.puts(cookie)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Parses each cookie in the cookie file.
|
67
|
+
#
|
68
|
+
# @yield [cookie]
|
69
|
+
# The given block will be passed each cookie parsed from each line of
|
70
|
+
# the file.
|
71
|
+
#
|
72
|
+
# @yieldparam [Cookie] cookie
|
73
|
+
# A cookie parsed from the file.
|
74
|
+
#
|
75
|
+
# @return [Enumerator]
|
76
|
+
# If no block is given, then an Enumerator will be returned.
|
77
|
+
#
|
78
|
+
def each
|
79
|
+
return enum_for(__method__) unless block_given?
|
80
|
+
|
81
|
+
File.open(@path) do |file|
|
82
|
+
file.each_line(chomp: true) do |line|
|
83
|
+
yield Cookie.parse(line)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# ronin-web-browser - An automated Chrome API.
|
4
|
+
#
|
5
|
+
# Copyright (c) 2022-2024 Hal Brodigan (postmodern.mod3@gmail.com)
|
6
|
+
#
|
7
|
+
# ronin-web-browser is free software: you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU Lesser General Public License as published
|
9
|
+
# by the Free Software Foundation, either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
#
|
12
|
+
# ronin-web-browser is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU Lesser General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Lesser General Public License
|
18
|
+
# along with ronin-web-browser. If not, see <https://www.gnu.org/licenses/>.
|
19
|
+
#
|
20
|
+
|
21
|
+
module Ronin
|
22
|
+
module Web
|
23
|
+
module Browser
|
24
|
+
# ronin-web-browser version
|
25
|
+
VERSION = '0.1.0.rc1'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|