wmap 2.7.6 → 2.8.2

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.
@@ -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