site-inspector 3.2.0 → 4.0.0
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 +4 -4
- data/.github/workflows/ci.yml +30 -0
- data/.rubocop.yml +1 -5
- data/.rubocop_todo.yml +185 -79
- data/.ruby-version +1 -1
- data/CHANGELOG.md +71 -0
- data/Gemfile +0 -2
- data/README.md +8 -14
- data/bin/site-inspector +13 -13
- data/lib/cliver/dependency_ext.rb +2 -2
- data/lib/data/well-known.yml +51 -0
- data/lib/site-inspector/domain.rb +27 -15
- data/lib/site-inspector/domain_parser.rb +11 -0
- data/lib/site-inspector/{checks → endpoint}/accessibility.rb +6 -4
- data/lib/site-inspector/{checks → endpoint}/check.rb +5 -1
- data/lib/site-inspector/endpoint/content.rb +151 -0
- data/lib/site-inspector/{checks → endpoint}/cookies.rb +13 -5
- data/lib/site-inspector/{checks → endpoint}/dns.rb +42 -18
- data/lib/site-inspector/endpoint/headers.rb +55 -0
- data/lib/site-inspector/{checks → endpoint}/hsts.rb +36 -5
- data/lib/site-inspector/endpoint/wappalyzer.rb +68 -0
- data/lib/site-inspector/endpoint/well_known.rb +95 -0
- data/lib/site-inspector/endpoint/whois.rb +56 -0
- data/lib/site-inspector/endpoint.rb +60 -23
- data/lib/site-inspector/formatter.rb +118 -0
- data/lib/site-inspector/version.rb +1 -1
- data/lib/site-inspector.rb +34 -30
- data/package-lock.json +670 -23
- data/package.json +2 -1
- data/renovate.json +4 -0
- data/script/benchmark +8 -0
- data/script/cibuild +2 -2
- data/script/console +1 -1
- data/script/pa11y-version +1 -1
- data/script/release +11 -4
- data/script/update-well-known +18 -0
- data/site-inspector.gemspec +16 -9
- data/spec/cliver/dependency_ext_spec.rb +21 -0
- data/spec/{site_inspector_domain_spec.rb → site_inspector/domain_spec.rb} +71 -9
- data/spec/{checks/site_inspector_endpoint_accessibility_spec.rb → site_inspector/endpoint/accessibility_spec.rb} +9 -7
- data/spec/{checks/site_inspector_endpoint_content_spec.rb → site_inspector/endpoint/content_spec.rb} +14 -4
- data/spec/{checks/site_inspector_endpoint_cookies_spec.rb → site_inspector/endpoint/cookies_spec.rb} +27 -14
- data/spec/{checks/site_inspector_endpoint_dns_spec.rb → site_inspector/endpoint/dns_spec.rb} +87 -9
- data/spec/{checks/site_inspector_endpoint_headers_spec.rb → site_inspector/endpoint/headers_spec.rb} +0 -7
- data/spec/{checks/site_inspector_endpoint_hsts_spec.rb → site_inspector/endpoint/hsts_spec.rb} +8 -8
- data/spec/site_inspector/endpoint/wappalyzer_spec.rb +20 -0
- data/spec/site_inspector/endpoint/well_known_spec.rb +12 -0
- data/spec/{checks/site_inspector_endpoint_whois_spec.rb → site_inspector/endpoint/whois_spec.rb} +2 -1
- data/spec/{site_inspector_endpoint_spec.rb → site_inspector/endpoint_spec.rb} +59 -3
- data/spec/site_inspector_spec.rb +58 -11
- data/spec/spec_helper.rb +9 -2
- metadata +100 -92
- data/.travis.yml +0 -9
- data/lib/site-inspector/cache.rb +0 -17
- data/lib/site-inspector/checks/content.rb +0 -85
- data/lib/site-inspector/checks/headers.rb +0 -68
- data/lib/site-inspector/checks/sniffer.rb +0 -67
- data/lib/site-inspector/checks/wappalyzer.rb +0 -62
- data/lib/site-inspector/checks/whois.rb +0 -36
- data/lib/site-inspector/disk_cache.rb +0 -42
- data/lib/site-inspector/rails_cache.rb +0 -13
- data/spec/checks/site_inspector_endpoint_sniffer_spec.rb +0 -150
- data/spec/checks/site_inspector_endpoint_wappalyzer_spec.rb +0 -34
- data/spec/site_inspector_cache_spec.rb +0 -15
- data/spec/site_inspector_disk_cache_spec.rb +0 -39
- /data/lib/site-inspector/{checks → endpoint}/https.rb +0 -0
- /data/spec/{checks/site_inspector_endpoint_check_spec.rb → site_inspector/endpoint/check_spec.rb} +0 -0
- /data/spec/{checks/site_inspector_endpoint_https_spec.rb → site_inspector/endpoint/https_spec.rb} +0 -0
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
class SiteInspector
|
|
6
|
+
class Endpoint
|
|
7
|
+
class WellKnown < Check
|
|
8
|
+
BASE_PATH = './.well-known/'
|
|
9
|
+
|
|
10
|
+
class << self
|
|
11
|
+
def well_knowns
|
|
12
|
+
data = File.expand_path '../../data/well-known.yml', File.dirname(__FILE__)
|
|
13
|
+
wks = YAML.load_file(data)
|
|
14
|
+
wks.reject { |wk| wk.include?('deprecated') }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def keys
|
|
18
|
+
well_knowns.map { |wk| key_for(wk) }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def key_paths
|
|
22
|
+
@key_paths ||= begin
|
|
23
|
+
key_paths = well_knowns.map do |wk|
|
|
24
|
+
[key_for(wk), path_for(wk)]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
key_paths.to_h
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def path_for(wk)
|
|
32
|
+
return unless well_knowns.include?(wk)
|
|
33
|
+
|
|
34
|
+
"#{BASE_PATH}#{wk}"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def key_for(well_known)
|
|
38
|
+
well_known.gsub(/(\.|-)/, '_').to_sym
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def exists?(path)
|
|
43
|
+
endpoint.content.path_exists?(path)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def uri_for(key)
|
|
47
|
+
path = self.class.key_paths[key]
|
|
48
|
+
endpoint.join(path) if path
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def to_h
|
|
52
|
+
return {} unless endpoint.up?
|
|
53
|
+
return {} if endpoint.redirect?
|
|
54
|
+
return @hash if defined?(@hash)
|
|
55
|
+
|
|
56
|
+
prefetch
|
|
57
|
+
@hash = {}
|
|
58
|
+
|
|
59
|
+
self.class.keys.each do |key|
|
|
60
|
+
@hash[key] = public_send(key)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
@hash
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def method_missing(method_sym, *arguments, &)
|
|
67
|
+
if respond_to_missing?(method_sym)
|
|
68
|
+
exists?(self.class.key_paths[method_sym])
|
|
69
|
+
else
|
|
70
|
+
super
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def respond_to_missing?(method_sym, include_private = false)
|
|
75
|
+
if self.class.keys.include?(method_sym)
|
|
76
|
+
true
|
|
77
|
+
else
|
|
78
|
+
super
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def prefetch
|
|
85
|
+
options = SiteInspector.typhoeus_defaults.merge(followlocation: true)
|
|
86
|
+
self.class.key_paths.each do |_key, path|
|
|
87
|
+
uri = endpoint.join(path)
|
|
88
|
+
request = Typhoeus::Request.new(uri, options)
|
|
89
|
+
SiteInspector.hydra.queue(request)
|
|
90
|
+
end
|
|
91
|
+
SiteInspector.hydra.run
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class SiteInspector
|
|
4
|
+
class Endpoint
|
|
5
|
+
class Whois < Check
|
|
6
|
+
def domain
|
|
7
|
+
return @domain if defined? @domain
|
|
8
|
+
|
|
9
|
+
@domain = begin
|
|
10
|
+
whois.lookup host
|
|
11
|
+
rescue Timeout::Error
|
|
12
|
+
nil
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def ip
|
|
17
|
+
return @ip if defined? @ip
|
|
18
|
+
|
|
19
|
+
@ip = begin
|
|
20
|
+
whois.lookup ip_address if ip_address
|
|
21
|
+
rescue Timeout::Error
|
|
22
|
+
nil
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_h
|
|
27
|
+
{
|
|
28
|
+
domain: record_to_h(domain),
|
|
29
|
+
ip: record_to_h(ip)
|
|
30
|
+
}
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def record_to_h(record)
|
|
36
|
+
return unless record
|
|
37
|
+
|
|
38
|
+
record.content.scan(/^\s*(.*?):\s*(.*?)\r?\n/).to_h
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def ip_address
|
|
42
|
+
return @ip_address if defined? @ip_address
|
|
43
|
+
|
|
44
|
+
@ip_address = begin
|
|
45
|
+
Resolv.getaddress host
|
|
46
|
+
rescue Resolv::ResolvError
|
|
47
|
+
nil
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def whois
|
|
52
|
+
@whois ||= ::Whois::Client.new
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'site-inspector/endpoint/check'
|
|
4
|
+
require 'site-inspector/endpoint/accessibility'
|
|
5
|
+
require 'site-inspector/endpoint/content'
|
|
6
|
+
require 'site-inspector/endpoint/dns'
|
|
7
|
+
require 'site-inspector/endpoint/headers'
|
|
8
|
+
require 'site-inspector/endpoint/hsts'
|
|
9
|
+
require 'site-inspector/endpoint/https'
|
|
10
|
+
require 'site-inspector/endpoint/cookies'
|
|
11
|
+
require 'site-inspector/endpoint/well_known'
|
|
12
|
+
require 'site-inspector/endpoint/whois'
|
|
13
|
+
require 'site-inspector/endpoint/wappalyzer'
|
|
14
|
+
|
|
3
15
|
class SiteInspector
|
|
4
16
|
# Every domain has four possible "endpoints" to evaluate
|
|
5
17
|
#
|
|
@@ -14,23 +26,32 @@ class SiteInspector
|
|
|
14
26
|
class Endpoint
|
|
15
27
|
attr_accessor :host, :uri, :domain
|
|
16
28
|
|
|
29
|
+
def self.build(domain, https: true, www: false)
|
|
30
|
+
uri = Addressable::URI.new
|
|
31
|
+
uri.host = www ? "www.#{domain.host}" : domain.host.to_s
|
|
32
|
+
uri.scheme = https ? 'https' : 'http'
|
|
33
|
+
|
|
34
|
+
new(uri.to_s, domain:)
|
|
35
|
+
end
|
|
36
|
+
|
|
17
37
|
# Initatiate a new Endpoint object
|
|
18
38
|
#
|
|
19
39
|
# endpoint - (string) the endpoint to query (e.g., `https://example.com`)
|
|
20
40
|
# options - A hash of options
|
|
21
41
|
# domain - the parent domain object, if passed, facilitates caching of redirects
|
|
22
42
|
def initialize(host, options = {})
|
|
23
|
-
|
|
43
|
+
host = host.downcase if host.is_a?(String)
|
|
44
|
+
@uri = Addressable::URI.parse(host)
|
|
24
45
|
# The root URL always has an implict path of "/", even if not requested
|
|
25
46
|
# Make it explicit to facilitate caching and prevent a potential redirect
|
|
26
47
|
@uri.path = '/'
|
|
27
|
-
@host =
|
|
48
|
+
@host = DomainParser.parse(host)
|
|
28
49
|
@checks = {}
|
|
29
50
|
@domain = options[:domain]
|
|
30
51
|
end
|
|
31
52
|
|
|
32
53
|
def www?
|
|
33
|
-
|
|
54
|
+
@host.trd == 'www'
|
|
34
55
|
end
|
|
35
56
|
|
|
36
57
|
def root?
|
|
@@ -50,13 +71,21 @@ class SiteInspector
|
|
|
50
71
|
end
|
|
51
72
|
|
|
52
73
|
def request(options = {})
|
|
53
|
-
|
|
54
|
-
request = Typhoeus::Request.new(target, SiteInspector.typhoeus_defaults.merge(options))
|
|
74
|
+
request = build_request(options)
|
|
55
75
|
hydra.queue(request)
|
|
56
76
|
hydra.run
|
|
57
77
|
request.response
|
|
58
78
|
end
|
|
59
79
|
|
|
80
|
+
def build_request(options)
|
|
81
|
+
target = options[:path] ? join(options.delete(:path)) : uri
|
|
82
|
+
Typhoeus::Request.new(target, SiteInspector.typhoeus_defaults.merge(options))
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def join(*args)
|
|
86
|
+
URI.join(uri, *args)
|
|
87
|
+
end
|
|
88
|
+
|
|
60
89
|
# Makes a GET request of the given host
|
|
61
90
|
#
|
|
62
91
|
# Retutns the Typhoeus::Response object
|
|
@@ -74,7 +103,9 @@ class SiteInspector
|
|
|
74
103
|
|
|
75
104
|
# Does the endpoint return a 2xx or 3xx response code?
|
|
76
105
|
def up?
|
|
77
|
-
|
|
106
|
+
return false unless response
|
|
107
|
+
|
|
108
|
+
response_code.start_with?('2', '3')
|
|
78
109
|
end
|
|
79
110
|
|
|
80
111
|
# Does the server respond at all?
|
|
@@ -86,18 +117,16 @@ class SiteInspector
|
|
|
86
117
|
def redirect
|
|
87
118
|
return unless response && response_code.start_with?('3')
|
|
88
119
|
|
|
89
|
-
|
|
90
|
-
|
|
120
|
+
location = headers['location']
|
|
121
|
+
return if location.nil? || location.empty?
|
|
91
122
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
redirect.scheme = scheme
|
|
97
|
-
end
|
|
123
|
+
@redirect ||= begin
|
|
124
|
+
# Resolve per RFC 3986, so protocol-relative (`//host`), query-only
|
|
125
|
+
# (`?q`), and dot-segment (`../x`) locations become absolute URIs
|
|
126
|
+
redirect = uri.join(location)
|
|
98
127
|
|
|
99
128
|
# This was a redirect to a subpath or back to itself, which we don't care about
|
|
100
|
-
return if redirect.host == host && redirect.scheme == scheme
|
|
129
|
+
return if redirect.host == host.to_s && redirect.scheme == scheme
|
|
101
130
|
|
|
102
131
|
# Init a new endpoint representing the redirect
|
|
103
132
|
find_or_create_by_uri(redirect.to_s)
|
|
@@ -132,7 +161,7 @@ class SiteInspector
|
|
|
132
161
|
end
|
|
133
162
|
|
|
134
163
|
def external_redirect?
|
|
135
|
-
host != resolves_to.host
|
|
164
|
+
host.to_s != resolves_to.host.to_s
|
|
136
165
|
end
|
|
137
166
|
|
|
138
167
|
def to_s
|
|
@@ -153,16 +182,19 @@ class SiteInspector
|
|
|
153
182
|
#
|
|
154
183
|
# Returns the hash representing the endpoint and its checks
|
|
155
184
|
def to_h(options = {})
|
|
156
|
-
hash
|
|
185
|
+
return @hash if defined?(@hash) && options == {}
|
|
186
|
+
|
|
187
|
+
@hash = {
|
|
157
188
|
uri: uri.to_s,
|
|
158
|
-
host: host,
|
|
189
|
+
host: host.to_s,
|
|
159
190
|
www: www?,
|
|
160
191
|
https: https?,
|
|
161
|
-
scheme
|
|
192
|
+
scheme:,
|
|
162
193
|
up: up?,
|
|
163
194
|
responds: responds?,
|
|
164
195
|
timed_out: timed_out?,
|
|
165
196
|
redirect: redirect?,
|
|
197
|
+
resolves_to: resolves_to.to_s,
|
|
166
198
|
external_redirect: external_redirect?
|
|
167
199
|
}
|
|
168
200
|
|
|
@@ -170,11 +202,11 @@ class SiteInspector
|
|
|
170
202
|
checks = SiteInspector::Endpoint.checks.select { |c| options.key?(c.name) }
|
|
171
203
|
checks = SiteInspector::Endpoint.checks if checks.empty?
|
|
172
204
|
|
|
173
|
-
Parallel.each(checks,
|
|
174
|
-
hash[check.name] = send(check.name).to_h
|
|
205
|
+
Parallel.each(checks, in_processes: 0) do |check|
|
|
206
|
+
@hash[check.name] = send(check.name).to_h
|
|
175
207
|
end
|
|
176
208
|
|
|
177
|
-
hash
|
|
209
|
+
@hash
|
|
178
210
|
end
|
|
179
211
|
|
|
180
212
|
def self.checks
|
|
@@ -183,7 +215,7 @@ class SiteInspector
|
|
|
183
215
|
@checks = ObjectSpace.each_object(Class).select { |klass| klass < Check }.select(&:enabled?).sort_by(&:name)
|
|
184
216
|
end
|
|
185
217
|
|
|
186
|
-
def method_missing(method_sym, *arguments, &
|
|
218
|
+
def method_missing(method_sym, *arguments, &)
|
|
187
219
|
check = SiteInspector::Endpoint.checks.find { |c| c.name == method_sym }
|
|
188
220
|
if check
|
|
189
221
|
@checks[method_sym] ||= check.new(self)
|
|
@@ -200,6 +232,11 @@ class SiteInspector
|
|
|
200
232
|
end
|
|
201
233
|
end
|
|
202
234
|
|
|
235
|
+
def eql?(other)
|
|
236
|
+
other.class == self.class && other.uri == uri
|
|
237
|
+
end
|
|
238
|
+
alias == eql?
|
|
239
|
+
|
|
203
240
|
private
|
|
204
241
|
|
|
205
242
|
def hydra
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/core_ext/string/inflections'
|
|
4
|
+
require 'nokogiri'
|
|
5
|
+
|
|
6
|
+
class SiteInspector
|
|
7
|
+
module Formatter
|
|
8
|
+
ACRONYMS = %w[
|
|
9
|
+
acme cdn cms crm dns dnssec dnt hsts http https id ip ipv6 json paas pki sld ssl tld tls trd txt ui uri url whois www xml xss
|
|
10
|
+
].freeze
|
|
11
|
+
|
|
12
|
+
def format_table(hash)
|
|
13
|
+
build_fragment do |f|
|
|
14
|
+
f.table(class: 'table table-striped') do
|
|
15
|
+
hash.each do |key, value|
|
|
16
|
+
f << format_key_value(key, value)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def format_key(string)
|
|
23
|
+
capitalize_acronyms(string.to_s.gsub(/^x-/, '').tr('-', ' ').humanize)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def format_value(value)
|
|
27
|
+
case value
|
|
28
|
+
when Hash
|
|
29
|
+
format_hash(value)
|
|
30
|
+
when Array
|
|
31
|
+
format_array(value)
|
|
32
|
+
else
|
|
33
|
+
format_string(value)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def format_key_value(key, value, check = nil)
|
|
38
|
+
c = class_for_value(value)
|
|
39
|
+
|
|
40
|
+
build_fragment do |f|
|
|
41
|
+
f.tr do
|
|
42
|
+
f.th { f.text format_key(key) }
|
|
43
|
+
f.td(c) do
|
|
44
|
+
uri = check&.uri_for(key)
|
|
45
|
+
|
|
46
|
+
if value == true && uri
|
|
47
|
+
f.a({ href: uri }.merge(c)) { f << format_value(value) }
|
|
48
|
+
else
|
|
49
|
+
f << format_value(value)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def class_for_value(value)
|
|
59
|
+
if value.instance_of?(TrueClass)
|
|
60
|
+
{ class: 'text-success' }
|
|
61
|
+
elsif value.instance_of?(FalseClass)
|
|
62
|
+
{ class: 'text-danger' }
|
|
63
|
+
else
|
|
64
|
+
{}
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def build_fragment(&block)
|
|
69
|
+
fragment = Nokogiri::HTML::DocumentFragment.parse ''
|
|
70
|
+
Nokogiri::HTML::Builder.with(fragment, &block)
|
|
71
|
+
fragment.to_s
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def capitalize_acronyms(string)
|
|
75
|
+
string.gsub(/\b(#{ACRONYMS.join("|")})\b/i) do
|
|
76
|
+
Regexp.last_match(1).to_s.upcase
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def format_string(value)
|
|
81
|
+
value = CGI.escapeHTML(value.to_s)
|
|
82
|
+
|
|
83
|
+
if %r{^https?:/}.match?(value)
|
|
84
|
+
value = build_fragment do |f|
|
|
85
|
+
f.a(href: value) { f.text value }
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
value
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def format_hash(hash)
|
|
93
|
+
build_fragment do |f|
|
|
94
|
+
f.ul do
|
|
95
|
+
hash.each do |key, value|
|
|
96
|
+
f.li do
|
|
97
|
+
f.span(class: 'font-weight-bold') { f.text format_key(key) }
|
|
98
|
+
f.text ': '
|
|
99
|
+
f << format_value(value)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def format_array(array)
|
|
107
|
+
return format_value(array[0]) if array.length == 1
|
|
108
|
+
|
|
109
|
+
build_fragment do |f|
|
|
110
|
+
f.ol do
|
|
111
|
+
array.each do |el|
|
|
112
|
+
f.li { f << format_value(el) }
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
data/lib/site-inspector.rb
CHANGED
|
@@ -4,43 +4,37 @@ require 'open-uri'
|
|
|
4
4
|
require 'addressable/uri'
|
|
5
5
|
require 'public_suffix'
|
|
6
6
|
require 'typhoeus'
|
|
7
|
+
require 'typhoeus/cache/rails'
|
|
8
|
+
require 'active_support'
|
|
9
|
+
require 'active_support/cache'
|
|
7
10
|
require 'parallel'
|
|
8
11
|
require 'cliver'
|
|
9
12
|
require 'whois'
|
|
10
13
|
require 'cgi'
|
|
11
14
|
require 'resolv'
|
|
12
|
-
require '
|
|
13
|
-
|
|
14
|
-
require_relative 'site-inspector/cache'
|
|
15
|
-
require_relative 'site-inspector/disk_cache'
|
|
16
|
-
require_relative 'site-inspector/rails_cache'
|
|
17
|
-
require_relative 'site-inspector/domain'
|
|
18
|
-
require_relative 'site-inspector/checks/check'
|
|
19
|
-
require_relative 'site-inspector/checks/accessibility'
|
|
20
|
-
require_relative 'site-inspector/checks/content'
|
|
21
|
-
require_relative 'site-inspector/checks/dns'
|
|
22
|
-
require_relative 'site-inspector/checks/headers'
|
|
23
|
-
require_relative 'site-inspector/checks/hsts'
|
|
24
|
-
require_relative 'site-inspector/checks/https'
|
|
25
|
-
require_relative 'site-inspector/checks/sniffer'
|
|
26
|
-
require_relative 'site-inspector/checks/cookies'
|
|
27
|
-
require_relative 'site-inspector/checks/whois'
|
|
28
|
-
require_relative 'site-inspector/checks/wappalyzer'
|
|
29
|
-
require_relative 'site-inspector/endpoint'
|
|
30
|
-
require_relative 'site-inspector/version'
|
|
15
|
+
require 'naughty_or_nice'
|
|
31
16
|
require_relative 'cliver/dependency_ext'
|
|
32
17
|
|
|
33
18
|
class SiteInspector
|
|
19
|
+
autoload :Formatter, 'site-inspector/formatter'
|
|
20
|
+
autoload :Domain, 'site-inspector/domain'
|
|
21
|
+
autoload :DomainParser, 'site-inspector/domain_parser'
|
|
22
|
+
autoload :Endpoint, 'site-inspector/endpoint'
|
|
23
|
+
autoload :VERSION, 'site-inspector/version'
|
|
24
|
+
|
|
34
25
|
class << self
|
|
35
26
|
attr_writer :timeout, :cache, :typhoeus_options
|
|
36
27
|
|
|
28
|
+
# The Typhoeus response cache. Set CACHE to a directory to persist
|
|
29
|
+
# responses to disk; inside Rails, Rails.cache is used; otherwise
|
|
30
|
+
# responses are kept in memory for the life of the process.
|
|
37
31
|
def cache
|
|
38
32
|
@cache ||= if ENV['CACHE']
|
|
39
|
-
|
|
40
|
-
elsif Object.const_defined?(
|
|
41
|
-
|
|
33
|
+
Typhoeus::Cache::Rails.new(ActiveSupport::Cache::FileStore.new(ENV.fetch('CACHE')))
|
|
34
|
+
elsif Object.const_defined?(:Rails)
|
|
35
|
+
Typhoeus::Cache::Rails.new
|
|
42
36
|
else
|
|
43
|
-
|
|
37
|
+
Typhoeus::Cache::Rails.new(ActiveSupport::Cache::MemoryStore.new)
|
|
44
38
|
end
|
|
45
39
|
end
|
|
46
40
|
|
|
@@ -57,10 +51,7 @@ class SiteInspector
|
|
|
57
51
|
followlocation: false,
|
|
58
52
|
timeout: SiteInspector.timeout,
|
|
59
53
|
accept_encoding: 'gzip',
|
|
60
|
-
method: :head
|
|
61
|
-
headers: {
|
|
62
|
-
'User-Agent' => "Mozilla/5.0 (compatible; SiteInspector/#{SiteInspector::VERSION}; +https://github.com/benbalter/site-inspector)"
|
|
63
|
-
}
|
|
54
|
+
method: :head
|
|
64
55
|
}
|
|
65
56
|
defaults.merge! @typhoeus_options if @typhoeus_options
|
|
66
57
|
defaults
|
|
@@ -68,16 +59,29 @@ class SiteInspector
|
|
|
68
59
|
|
|
69
60
|
# Returns a thread-safe, memoized hydra instance
|
|
70
61
|
def hydra
|
|
71
|
-
Typhoeus::Hydra.hydra
|
|
62
|
+
@hydra ||= Typhoeus::Hydra.hydra
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def logger
|
|
66
|
+
@logger ||= Logger.new($stdout)
|
|
72
67
|
end
|
|
73
68
|
end
|
|
74
69
|
end
|
|
75
70
|
|
|
76
71
|
if ENV['DEBUG']
|
|
77
|
-
Ethon.logger =
|
|
72
|
+
Ethon.logger = SiteInspector.logger
|
|
78
73
|
Ethon.logger.level = Logger::DEBUG
|
|
79
74
|
Typhoeus::Config.verbose = true
|
|
80
75
|
end
|
|
81
76
|
|
|
82
|
-
Typhoeus::Config.memoize = true
|
|
83
77
|
Typhoeus::Config.cache = SiteInspector.cache
|
|
78
|
+
Typhoeus::Config.memoize = true
|
|
79
|
+
Typhoeus::Config.user_agent = "Mozilla/5.0 (compatible; SiteInspector/#{SiteInspector::VERSION}; +https://github.com/benbalter/site-inspector)"
|
|
80
|
+
|
|
81
|
+
if ENV['VERBOSE']
|
|
82
|
+
Typhoeus.before do |req|
|
|
83
|
+
hit = Typhoeus::Config.cache.get(req)
|
|
84
|
+
SiteInspector.logger.info "#{req.options[:method]} #{req.base_url} (CACHE #{hit ? 'HIT' : 'MISS'})"
|
|
85
|
+
req
|
|
86
|
+
end
|
|
87
|
+
end
|