wmap 2.7.6 → 2.8.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -17,7 +17,8 @@ require "parallel"
17
17
  class Wmap::UrlCrawler
18
18
  include Wmap::Utils
19
19
 
20
- attr_accessor :http_timeout, :crawl_page_limit, :crawl_depth, :max_parallel, :verbose, :data_dir
20
+ attr_accessor :http_timeout, :crawl_page_limit, :crawl_depth, :max_parallel, \
21
+ :verbose, :data_dir, :user_agent
21
22
  attr_reader :discovered_urls_by_crawler, :visited_urls_by_crawler, :crawl_start, :crawl_done
22
23
  # Global variable used to store the combined result of all the forked child processes. Note that class variable
23
24
  # would not be able to pass the result due the limitation of IO Pipe communication mechanism used by 'parallel' fork manager
@@ -35,13 +36,16 @@ class Wmap::UrlCrawler
35
36
  @crawl_depth=params.fetch(:crawl_depth, 4)
36
37
  @crawl_page_limit=params.fetch(:crawl_page_limit, 1000)
37
38
  @max_parallel=params.fetch(:max_parallel, 40)
39
+ @user_agent=params.fetch(:user_agent, "OWASP WMAP Spider")
38
40
  # Discovered data store
39
41
  @discovered_urls_by_crawler=Hash.new
40
42
  @visited_urls_by_crawler=Hash.new
41
43
  @crawl_start=Hash.new
42
44
  @crawl_done=Hash.new
43
45
  Dir.mkdir(@data_dir) unless Dir.exist?(@data_dir)
44
- @log_file=@data_dir + "/../logs/crawler.log"
46
+ @log_dir=@data_dir + "/logs/"
47
+ Dir.mkdir(@log_dir) unless Dir.exist?(@log_dir)
48
+ @log_file=@log_dir + "crawler.log"
45
49
  end
46
50
 
47
51
  # Pre-crawl profiler, to be used for network profiling to maximum the crawler performance.
@@ -216,14 +220,14 @@ class Wmap::UrlCrawler
216
220
  alias_method :crawl_file, :crawl_workers_on_file
217
221
 
218
222
  # Wrapper for the OpenURI open method - create an open_uri object and return the reference upon success
219
- def open_url(url)
223
+ def open_url(url,user_agent=@user_agent)
220
224
  puts "Open url #{url} by creating an open_uri object. Return the reference upon success." if @verbose
221
225
  if url =~ /http\:/i
222
226
  # patch for allow the 'un-safe' URL redirection i.e. https://www.example.com -> http://www.example.com
223
- url_object = open(url, :allow_redirections=>:safe, :read_timeout=>Max_http_timeout/1000)
227
+ url_object = open(url, :allow_redirections=>:safe, :read_timeout=>Max_http_timeout/1000, "User-Agent"=>user_agent)
224
228
  #url_object = open(url)
225
229
  elsif url =~ /https\:/i
226
- url_object = open(url,:ssl_verify_mode => 0, :allow_redirections =>:safe, :read_timeout=>Max_http_timeout/1000)
230
+ url_object = open(url, :ssl_verify_mode=>0, :allow_redirections=>:safe, :read_timeout=>Max_http_timeout/1000, "User-Agent"=>user_agent)
227
231
  #url_object = open(url,:ssl_verify_mode => 0)
228
232
  else
229
233
  raise "Invalid URL format - please specify the protocol prefix http(s) in the URL: #{url}"
@@ -258,22 +262,6 @@ class Wmap::UrlCrawler
258
262
  return nil
259
263
  end
260
264
 
261
- =begin
262
- # Wrapper for the Nokogiri DOM parser
263
- def parse_html(html_body)
264
- begin
265
- #puts "Parsing the html content: #{html_body}. Return DOM " if @verbose
266
- doc = Nokogiri::HTML(html_body)
267
- #puts "Successfully crawling the url: #{url_object.base_uri.to_s}" if @verbose
268
- #puts "doc: #{doc}" if @verbose
269
- return doc
270
- rescue => ee
271
- puts "Exception on method #{__method__}: #{ee}" if @verbose
272
- return nil
273
- end
274
- end
275
- =end
276
-
277
265
  # Search 'current_url' and return found URLs under the same domain
278
266
  def find_urls_on_page(doc, current_url)
279
267
  puts "Search and return URLs within the doc: #{doc}" if @verbose
@@ -192,7 +192,7 @@ module Wmap
192
192
  # Function to print instance variable - General top level domain list
193
193
  def print_gtld
194
194
  puts @gtld
195
- return @gtld
195
+ return @gtld
196
196
  end
197
197
 
198
198
  # Function to print instance variable - Country code top-level domain list
@@ -8,46 +8,43 @@
8
8
 
9
9
 
10
10
  module Wmap
11
- module Utils
11
+ module Utils
12
12
  # Module to log debugging and other messages
13
- module Logger
13
+ module Logger
14
14
  extend self
15
15
  # Append information into the log file for the trouble-shooting purpose
16
16
  def wlog (obj, agent, file)
17
17
  puts "Writing #{obj} into log file: #{file}" if @verbose
18
- begin
19
- return false if obj.nil?
20
- # 01/27/2015, implementing singleton pattern for the logger
21
- @@f=File.open(file,'a')
22
- timestamp=Time.now
23
- case obj
24
- when Array
25
- if obj.size >= 0
26
- @@f.write "#{timestamp}: #{agent}: \n"
27
- obj.map { |x| @@f.write " #{x}\n" }
28
- puts "The list is successfully saved into the log file: #{file} " if @verbose
29
- end
30
- when Hash
31
- if obj.length >= 0
32
- @@f.write "#{timestamp}: #{agent}: \n"
33
- obj.each_value { |value| @@f.write " #{value}\n" }
34
- puts "The hash is successfully saved into the log file: #{file} " if @verbose
35
- end
36
- when String
37
- @@f.write "#{timestamp}: #{agent}: #{obj}\n"
38
- puts "The string is successfully saved into the log file: #{file} " if @verbose
39
- else
40
- #do nothing
41
- puts "Un-handled exception on: #{obj}" if @verbose
18
+ return false if obj.nil?
19
+ @@f=File.open(file,'a')
20
+ timestamp=Time.now
21
+ case obj
22
+ when Array
23
+ if obj.size >= 0
24
+ @@f.write "#{timestamp}: #{agent}: \n"
25
+ obj.map { |x| @@f.write " #{x}\n" }
26
+ puts "The list is successfully saved into the log file: #{file} " if @verbose
42
27
  end
43
- @@f.close
44
- return true
45
- rescue => ee
46
- puts "Exception on method #{__method__}: #{ee}" if @verbose
47
- return false
48
- end
28
+ when Hash
29
+ if obj.length >= 0
30
+ @@f.write "#{timestamp}: #{agent}: \n"
31
+ obj.each_value { |value| @@f.write " #{value}\n" }
32
+ puts "The hash is successfully saved into the log file: #{file} " if @verbose
33
+ end
34
+ when String
35
+ @@f.write "#{timestamp}: #{agent}: #{obj}\n"
36
+ puts "The string is successfully saved into the log file: #{file} " if @verbose
37
+ else
38
+ #do nothing
39
+ puts "Un-handled exception on: #{obj}" if @verbose
40
+ end
41
+ @@f.close
42
+ return true
43
+ rescue => ee
44
+ puts "Exception on method #{__method__}: #{ee}" if @verbose
45
+ return false
49
46
  end
50
-
51
- end
47
+
48
+ end
52
49
  end
53
50
  end
@@ -15,6 +15,7 @@ module Wmap
15
15
 
16
16
  # set hard stop limit of http time-out to 8 seconds, in order to avoid severe performance penalty for certain 'weird' site(s)
17
17
  Max_http_timeout=15000
18
+ User_agent = "OWASP WMAP Spider"
18
19
 
19
20
  # Simple sanity check on a 'claimed' URL string.
20
21
  def is_url?(url)
@@ -263,7 +264,7 @@ module Wmap
263
264
  return absolute_url
264
265
  rescue => ee
265
266
  puts "Exception on method #{__method__}: #{ee}" if @verbose
266
- return nil
267
+ return nil
267
268
  end
268
269
 
269
270
  # Normalize the URL to a consistent manner in order to determine if a link has been visited or cached before
@@ -292,7 +293,6 @@ module Wmap
292
293
  return url
293
294
  end
294
295
 
295
-
296
296
  # Test the URL and return the response code
297
297
  def response_code (url)
298
298
  puts "Check the http response code on the url: #{url}" if @verbose
@@ -344,9 +344,42 @@ module Wmap
344
344
  return code
345
345
  end
346
346
 
347
+ # Test the URL and return the response headers
348
+ def response_headers (url)
349
+ puts "Check the http response headers on the url: #{url}" if @verbose
350
+ raise "Invalid url: #{url}" unless is_url?(url)
351
+ headers = Hash.new
352
+ url=url.strip.downcase
353
+ timeo = Max_http_timeout/1000.0
354
+ uri = URI.parse(url)
355
+ http = Net::HTTP.new(uri.host, uri.port)
356
+ http.open_timeout = timeo
357
+ http.read_timeout = timeo
358
+ if (url =~ /https\:/i)
359
+ http.use_ssl = true
360
+ #http.ssl_version = :SSLv3
361
+ # Bypass the remote web server cert validation test
362
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
363
+ end
364
+ request = Net::HTTP::Get.new(uri.request_uri)
365
+ response = http.request(request)
366
+ puts "Server response the following: #{response}" if @verbose
367
+ response.each_header do |key,val|
368
+ puts "#{key} => #{val}" if @verbose
369
+ headers.merge!({key => val})
370
+ end
371
+ puts "Response headers on #{url}: #{headers}" if @verbose
372
+ return headers
373
+ rescue => ee
374
+ puts "Exception on method #{__method__}: #{ee}" if @verbose
375
+ return nil
376
+ end
377
+
378
+
347
379
  # Given an URL, open the page, then return the DOM text from a normal user perspective
348
380
  def open_page(url)
349
- args = {ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE, allow_redirections: :safe, read_timeout: Max_http_timeout/1000}
381
+ args = {ssl_verify_mode: OpenSSL::SSL::VERIFY_NONE, allow_redirections: :safe, \
382
+ read_timeout: Max_http_timeout/1000, "User-Agent"=>User_agent}
350
383
  doc = Nokogiri::HTML(open(url, args))
351
384
  if doc.text.include?("Please enable JavaScript to view the page content")
352
385
  puts "Invoke headless chrome through webdriver ..." if @verbose
@@ -354,7 +387,7 @@ module Wmap
354
387
  #driver = Selenium::WebDriver.for :chrome
355
388
  # http://watir.com/guides/chrome/
356
389
  args = ['--ignore-certificate-errors', '--disable-popup-blocking', '--disable-translate', '--disk-cache-size 8192']
357
- browser = Watir::Browser.new :chrome, headless: true, options: {args: args}
390
+ browser = Watir::Browser.new :chrome, headless: true, switches: %w[--user-agent=OWASP\ WMAP\ Spider]
358
391
  browser.goto(url)
359
392
  sleep(2) # wait for the loading
360
393
  doc = Nokogiri::HTML(browser.html)
@@ -159,20 +159,18 @@ module Wmap
159
159
  # Simple test a host string format. Return true if it contains a valid internet domain sub-string. Note: Don't be confused with another method 'valid_dns_record?', which is a stricter and time-consuming test on the DNS server for a resolvable internet host.
160
160
  def is_fqdn? (host)
161
161
  puts "Validate the host-name format is valid: #{host}" if @verbose
162
- begin
163
- return false if is_ip?(host) or is_url?(host)
164
- domain=get_domain_root(host)
165
- if domain.nil?
166
- return false
167
- elsif is_domain_root?(domain)
168
- return true
169
- else
170
- return false
171
- end
172
- rescue => ee
173
- puts "Exception on method is_fqdn? for #{host}: #{ee}" if @verbose
162
+ return false if is_ip?(host) or is_url?(host)
163
+ domain=get_domain_root(host)
164
+ if domain.nil?
165
+ return false
166
+ elsif is_domain_root?(domain)
167
+ return true
168
+ else
174
169
  return false
175
170
  end
171
+ # rescue => ee
172
+ # puts "Exception on method is_fqdn? for #{host}: #{ee}" if @verbose
173
+ # return false
176
174
  end
177
175
  alias_method :is_host?, :is_fqdn?
178
176
 
@@ -0,0 +1,358 @@
1
+ #--
2
+ # Wmap
3
+ #
4
+ # A pure Ruby library for Internet web application discovery and tracking.
5
+ #
6
+ # Copyright (c) 2012-2015 Yang Li <yang.li@owasp.org>
7
+ #++
8
+
9
+ # Utilities for wp_tracker class only; must use with other Utils modules.
10
+ module Wmap
11
+ module Utils
12
+ module WpDetect
13
+ extend self
14
+
15
+ # Main method to detect if it's a wordpress site
16
+ def is_wp?(url)
17
+ site=url_2_site(url)
18
+ if wp_readme?(site)
19
+ return true
20
+ elsif wp_css?(site)
21
+ return true
22
+ elsif wp_meta?(site)
23
+ return true
24
+ elsif wp_login?(site)
25
+ return true
26
+ elsif wp_rpc?(site)
27
+ return true
28
+ elsif wp_gen?(site)
29
+ return true
30
+ elsif wp_load_styles?(site)
31
+ return true
32
+ else
33
+ return false
34
+ end
35
+ rescue => ee
36
+ puts "Exception on method #{__method__}: #{ee}: #{url}" if @verbose
37
+ end
38
+
39
+ # Main method to extract the WordPress version
40
+ def wp_ver(url)
41
+ if !wp_ver_readme(url).nil?
42
+ puts "WordPress version found by wp_ver_readme method. " if @verbose
43
+ return wp_ver_readme(url)
44
+ elsif !wp_ver_login(url,"login.min.css").nil?
45
+ puts "WordPress version found by login.min.css file. " if @verbose
46
+ return wp_ver_login(url,"login.min.css")
47
+ elsif !wp_ver_login(url,"buttons.min.css").nil?
48
+ puts "WordPress version found by buttons.min.css file. " if @verbose
49
+ return wp_ver_login(url,"buttons.min.css")
50
+ elsif !wp_ver_login(url,"wp-admin.min.css").nil?
51
+ puts "WordPress version found by wp-admin.min.css file. " if @verbose
52
+ return wp_ver_login(url,"wp-admin.min.css")
53
+ elsif !wp_ver_meta(url).nil?
54
+ puts "WordPress version found by wp_ver_meta method. " if @verbose
55
+ return wp_ver_meta(url)
56
+ elsif !wp_ver_generator(url).nil?
57
+ puts "WordPress version found by wp_ver_generator method. " if @verbose
58
+ return wp_ver_generator(url)
59
+ elsif !wp_ver_load_styles(url).nil?
60
+ puts "WordPress version found by wp_ver_load_styles method. " if @verbose
61
+ return wp_ver_load_styles(url)
62
+ else
63
+ return nil
64
+ end
65
+ rescue => ee
66
+ puts "Exception on method #{__method__} for url #{url}: #{ee}" if @verbose
67
+ return nil
68
+ end
69
+
70
+ # Wordpress detection checkpoint - readme.html
71
+ def wp_readme?(url)
72
+ site = url_2_site(url)
73
+ readme_url=site + "readme.html"
74
+ k=Wmap::UrlChecker.new
75
+ if k.response_code(readme_url) == 200
76
+ k=nil
77
+ doc=open_page(readme_url)
78
+ title=doc.css('title')
79
+ if title.to_s =~ /wordpress/i
80
+ return true
81
+ else
82
+ return false
83
+ end
84
+ else
85
+ k=nil
86
+ return false
87
+ end
88
+ rescue => ee
89
+ puts "Exception on method #{__method__} for site #{url}: #{ee}" if @verbose
90
+ return false
91
+ end
92
+
93
+ # Wordpress detection checkpoint - install.css
94
+ def wp_css?(url)
95
+ site = url_2_site(url)
96
+ css_url = site + "wp-admin/css/install.css"
97
+ k=Wmap::UrlChecker.new
98
+ if k.response_code(css_url) == 200
99
+ k=nil
100
+ parser = CssParser::Parser.new
101
+ parser.load_uri!(css_url)
102
+ rule = parser.find_by_selector('#logo a')
103
+ if rule.length >0
104
+ if rule[0] =~ /wordpress/i
105
+ return true
106
+ end
107
+ end
108
+ else
109
+ k=nil
110
+ return false
111
+ end
112
+ return false
113
+ rescue => ee
114
+ puts "Exception on method #{__method__} for site #{url}: #{ee}" if @verbose
115
+ return false
116
+ end
117
+
118
+ # Wordpress detection checkpoint - WP meta tag
119
+ def wp_meta?(url)
120
+ site=url_2_site(url)
121
+ k=Wmap::UrlChecker.new
122
+ if k.response_code(site) == 200
123
+ k=nil
124
+ doc=open_page(site)
125
+ meta=doc.css('meta')
126
+ if meta.to_s =~ /wordpress/i
127
+ return true
128
+ else
129
+ return false
130
+ end
131
+ end
132
+ return false
133
+ rescue => ee
134
+ puts "Exception on method #{__method__} for url #{url}: #{ee}" if @verbose
135
+ return false
136
+ end
137
+
138
+ # Wordpress detection checkpoint - WP generator tag
139
+ def wp_gen?(url)
140
+ puts "#{__method__} check for #{url}" if @verbose
141
+ site = url_2_site(url)
142
+ gen_url_1 = site + "feed/"
143
+ gen_url_2 = site + "comments/feed"
144
+ k=Wmap::UrlChecker.new
145
+ if k.response_code(gen_url_1) == 200
146
+ doc=open_page(gen_url_1)
147
+ elsif k.response_code(gen_url_2) == 200
148
+ doc=open_page(gen_url_2)
149
+ else
150
+ k=nil
151
+ return false
152
+ end
153
+ #puts doc.inspect
154
+ gens=doc.css('generator')
155
+ if gens.nil?
156
+ k=nil
157
+ return false
158
+ end
159
+ gens.each do |gen|
160
+ if gen.text.to_s =~ /wordpress/i
161
+ k=doc=nil
162
+ return true
163
+ end
164
+ end
165
+ k=doc=nil
166
+ return false
167
+ rescue => ee
168
+ puts "Exception on method #{__method__} for url #{url}: #{ee}" if @verbose
169
+ return false
170
+ end
171
+
172
+ # Wordpress detection checkpoint - wp-login
173
+ def wp_login?(url)
174
+ site=url_2_site(url)
175
+ login_url=site + "wp-login.php"
176
+ k=Wmap::UrlChecker.new
177
+ if k.response_code(login_url) == 200
178
+ k=nil
179
+ doc=open_page(login_url)
180
+ links=doc.css('link')
181
+ if links.to_s =~ /login.min.css/i
182
+ return true
183
+ else
184
+ return false
185
+ end
186
+ end
187
+ return false
188
+ rescue => ee
189
+ puts "Exception on method #{__method__} for url #{url}: #{ee}" if @verbose
190
+ return false
191
+ end
192
+
193
+ # Wordpress detection checkpoint - xml-rpc
194
+ def wp_rpc?(url)
195
+ site=url_2_site(url)
196
+ rpc_url=site + "xmlrpc.php"
197
+ k=Wmap::UrlChecker.new
198
+ #puts "res code", k.response_code(rpc_url)
199
+ if k.response_code(rpc_url) == 405 # method not allowed
200
+ k=nil
201
+ return true
202
+ end
203
+ return false
204
+ rescue => ee
205
+ puts "Exception on method #{__method__} for url #{url}: #{ee}" if @verbose
206
+ return false
207
+ end
208
+
209
+ # Wordpress detection checkpoint - /wp-admin/load-styles.php
210
+ def wp_load_styles?(url)
211
+ site = url_2_site(url)
212
+ load_styles_url=site + "wp-admin/load-styles.php"
213
+ k=Wmap::UrlChecker.new
214
+ if k.response_code(load_styles_url) == 200 && k.response_headers(load_styles_url).keys.include?("etag")
215
+ k=nil
216
+ return true
217
+ else
218
+ k=nil
219
+ return false
220
+ end
221
+ rescue => ee
222
+ puts "Exception on method #{__method__} for site #{url}: #{ee}" if @verbose
223
+ return false
224
+ end
225
+
226
+ # Identify wordpress version through the login page
227
+ def wp_ver_login(url,pattern)
228
+ puts "Check for #{pattern}" if @verbose
229
+ site=url_2_site(url)
230
+ login_url=site + "wp-login.php"
231
+ k=Wmap::UrlChecker.new
232
+ #puts "Res code: #{k.response_code(login_url)}" if @verbose
233
+ if k.response_code(login_url) == 200
234
+ doc=open_page(login_url)
235
+ #puts doc.inspect
236
+ links=doc.css('link')
237
+ #puts links.inspect if @verbose
238
+ links.each do |tag|
239
+ if tag.to_s.include?(pattern)
240
+ puts tag.to_s if @verbose
241
+ k=nil
242
+ if tag.to_s.scan(/[\d+\.]+\d+/).first =~ /\d+\./
243
+ return tag.to_s.scan(/[\d+\.]+\d+/).first
244
+ else
245
+ return nil
246
+ end
247
+ end
248
+ end
249
+ end
250
+ k=nil
251
+ return nil
252
+ rescue => ee
253
+ puts "Exception on method #{__method__} for url #{url}: #{ee}" if @verbose
254
+ return nil
255
+ end
256
+
257
+ # Identify wordpress version through the meta link
258
+ def wp_ver_meta(url)
259
+ site=url_2_site(url)
260
+ k=Wmap::UrlChecker.new
261
+ if k.response_code(site) == 200
262
+ doc=open_page(site)
263
+ #puts doc.inspect
264
+ meta=doc.css('meta')
265
+ #puts meta.inspect
266
+ meta.each do |tag|
267
+ if tag['content'].to_s =~ /wordpress/i
268
+ #puts tag.to_s
269
+ k=nil
270
+ return tag['content'].to_s.scan(/[\d+\.]+\d+/).first
271
+ end
272
+ end
273
+ end
274
+ k=nil
275
+ return nil
276
+ rescue => ee
277
+ puts "Exception on method #{__method__} for url #{url}: #{ee}" if @verbose
278
+ return nil
279
+ end
280
+
281
+ # Identify wordpress version through the generator tag: <generator>https://wordpress.org/?v=4.9.8</generator>
282
+ def wp_ver_generator(url)
283
+ puts "#{__method__} check for #{url}" if @verbose
284
+ site = url_2_site(url)
285
+ gen_url_1 = site + "feed/"
286
+ gen_url_2 = site + "comments/feed"
287
+ k=Wmap::UrlChecker.new
288
+ if k.response_code(gen_url_1) == 200
289
+ doc=open_page(gen_url_1)
290
+ elsif k.response_code(gen_url_2) == 200
291
+ doc=open_page(gen_url_2)
292
+ else
293
+ k=nil
294
+ return nil
295
+ end
296
+ #puts doc.inspect
297
+ gens=doc.css('generator')
298
+ if gens.nil?
299
+ k=nil
300
+ return nil
301
+ end
302
+ gens.each do |gen|
303
+ if gen.text.to_s =~ /wordpress/i
304
+ k=nil
305
+ return gen.text.to_s.scan(/[\d+\.]+\d+/).first
306
+ end
307
+ end
308
+ k=doc=nil
309
+ return nil
310
+ rescue => ee
311
+ puts "Exception on method #{__method__} for url #{url}: #{ee}" if @verbose
312
+ return nil
313
+ end
314
+
315
+ # Wordpress version detection via - readme.html
316
+ def wp_ver_readme(url)
317
+ site=url_2_site(url)
318
+ readme_url=site + "readme.html"
319
+ k=Wmap::UrlChecker.new
320
+ puts "Res code: #{k.response_code(readme_url)}" if @verbose
321
+ if k.response_code(readme_url) == 200
322
+ k=nil
323
+ doc=open_page(readme_url)
324
+ puts doc if @verbose
325
+ logo=doc.css('h1#logo')[0]
326
+ puts logo.inspect if @verbose
327
+ return logo.to_s.scan(/[\d+\.]+\d+/).first
328
+ end
329
+ k=nil
330
+ return nil
331
+ rescue => ee
332
+ puts "Exception on method #{__method__} for url #{url}: #{ee}" if @verbose
333
+ return nil
334
+ end
335
+
336
+ # Wordpress version detection via - /wp-admin/load-styles.php
337
+ def wp_ver_load_styles(url)
338
+ site=url_2_site(url)
339
+ load_styles_url = site + "wp-admin/load-styles.php"
340
+ k=Wmap::UrlChecker.new
341
+ if k.response_code(load_styles_url) == 200
342
+ headers = k.response_headers(load_styles_url)
343
+ if headers.keys.include?("etag")
344
+ k=nil
345
+ return headers["etag"]
346
+ end
347
+ end
348
+ k=nil
349
+ return nil
350
+ rescue => ee
351
+ puts "Exception on method #{__method__} for url #{url}: #{ee}" if @verbose
352
+ return nil
353
+ end
354
+
355
+
356
+ end
357
+ end
358
+ end