site-inspector 3.1.0 → 3.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +34 -0
  3. data/.ruby-version +1 -1
  4. data/Gemfile +1 -1
  5. data/Guardfile +1 -1
  6. data/README.md +6 -1
  7. data/Rakefile +2 -2
  8. data/bin/site-inspector +15 -15
  9. data/lib/cliver/dependency_ext.rb +21 -0
  10. data/lib/site-inspector.rb +13 -11
  11. data/lib/site-inspector/checks/accessibility.rb +27 -17
  12. data/lib/site-inspector/checks/check.rb +1 -3
  13. data/lib/site-inspector/checks/content.rb +6 -6
  14. data/lib/site-inspector/checks/cookies.rb +6 -8
  15. data/lib/site-inspector/checks/dns.rb +21 -20
  16. data/lib/site-inspector/checks/headers.rb +12 -13
  17. data/lib/site-inspector/checks/hsts.rb +8 -9
  18. data/lib/site-inspector/checks/https.rb +3 -5
  19. data/lib/site-inspector/checks/sniffer.rb +8 -9
  20. data/lib/site-inspector/domain.rb +28 -32
  21. data/lib/site-inspector/endpoint.rb +31 -32
  22. data/lib/site-inspector/version.rb +1 -1
  23. data/script/cibuild +3 -1
  24. data/script/pa11y-version +9 -0
  25. data/site-inspector.gemspec +25 -25
  26. data/spec/checks/site_inspector_endpoint_accessibility_spec.rb +31 -30
  27. data/spec/checks/site_inspector_endpoint_check_spec.rb +10 -11
  28. data/spec/checks/site_inspector_endpoint_content_spec.rb +43 -44
  29. data/spec/checks/site_inspector_endpoint_cookies_spec.rb +30 -31
  30. data/spec/checks/site_inspector_endpoint_dns_spec.rb +72 -77
  31. data/spec/checks/site_inspector_endpoint_headers_spec.rb +26 -27
  32. data/spec/checks/site_inspector_endpoint_hsts_spec.rb +26 -27
  33. data/spec/checks/site_inspector_endpoint_https_spec.rb +11 -12
  34. data/spec/checks/site_inspector_endpoint_sniffer_spec.rb +56 -57
  35. data/spec/site_inspector_cache_spec.rb +6 -6
  36. data/spec/site_inspector_disk_cache_spec.rb +9 -9
  37. data/spec/site_inspector_domain_spec.rb +132 -136
  38. data/spec/site_inspector_endpoint_spec.rb +108 -108
  39. data/spec/site_inspector_spec.rb +17 -18
  40. data/spec/spec_helper.rb +3 -3
  41. metadata +21 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 56a9e64300435fa494e8ae17ca710b46238bcffd
4
- data.tar.gz: 1e552e430d8f1fea10eba7d873e1ae17aab00415
3
+ metadata.gz: 6884b52732376fcd208b761e6b441f52c8ff383d
4
+ data.tar.gz: a6d6268ce73a44108f4ec490da07d7b23257981d
5
5
  SHA512:
6
- metadata.gz: 96eaaa8846783ef2b9dfe5b76e84ebd3c160139c425c69edc25761828690b2d508770aa842e234cbb63e9f6ecaa8bb7ffc7b2121a9449d934928938a9a9ec637
7
- data.tar.gz: b3b4d86a6b6ead26b17eb380b27a5da37afd645c582b94b53860eb1948e28c8cf6baec1c088ace2a53ebdb4e755a316bb635236b9b46c36cb60f1012581c5500
6
+ metadata.gz: c0c672eb141da7b5522b4ea9191024a13d08f37c3d91b11b8771f649596ff38f587cc98a95ce6cf6bb80f6f060d71068a51c45d132517e7a6a4237f7cb54d9d8
7
+ data.tar.gz: a1251d75b522286e2ff9889b347b4bf73440b2932f89acc9b4840d1b2c40ad7cbded89feca80e278f8120f9b1976d658860a75bd747c25921df4b55efcd365e8
@@ -0,0 +1,34 @@
1
+ Metrics/LineLength:
2
+ Enabled: false
3
+
4
+ Style/AlignHash:
5
+ EnforcedHashRocketStyle: table
6
+ EnforcedColonStyle: table
7
+
8
+ Lint/EndAlignment:
9
+ AlignWith: variable
10
+ AutoCorrect: true
11
+
12
+ Metrics/MethodLength:
13
+ Enabled: false
14
+
15
+ Metrics/AbcSize:
16
+ Enabled: false
17
+
18
+ Style/Documentation:
19
+ Enabled: false
20
+
21
+ Style/FileName:
22
+ Enabled: false
23
+
24
+ Metrics/CyclomaticComplexity:
25
+ Enabled: false
26
+
27
+ Style/DoubleNegation:
28
+ Enabled: false
29
+
30
+ Metrics/ClassLength:
31
+ Enabled: false
32
+
33
+ Style/ClassVars:
34
+ Enabled: false
@@ -1 +1 @@
1
- 2.1.4
1
+ 2.3.0
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
- source "https://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
3
  gemspec
data/Guardfile CHANGED
@@ -1,7 +1,7 @@
1
1
  # A sample Guardfile
2
2
  # More info at https://github.com/guard/guard#readme
3
3
 
4
- guard 'rake', :task => 'test' do
4
+ guard 'rake', task: 'test' do
5
5
  watch(%r{^test/.+$})
6
6
  watch(%r{^lib/.+$})
7
7
  watch(%r{^views/.+$})
data/README.md CHANGED
@@ -150,7 +150,7 @@ Uses the `pa11y` CLI to run automated accessibility tests. Requires `node`. To i
150
150
  ```ruby
151
151
  class SiteInspector
152
152
  class Endpoint
153
- class Mention
153
+ class Mention < Check
154
154
  def mentions_ben?
155
155
  endpoint.content.body =~ /ben/i
156
156
  end
@@ -159,6 +159,11 @@ class SiteInspector
159
159
  end
160
160
  ```
161
161
 
162
+ This check can then be used as follows:
163
+ ```
164
+ domain.canonical_endpoint.mention.mentions_ben?
165
+ ```
166
+
162
167
  Checks can call the `endpoint` object, which, contains the request, response, and other checks. Custom checks are automatically exposed as endpoint methods.
163
168
 
164
169
  ## Contributing
data/Rakefile CHANGED
@@ -1,8 +1,8 @@
1
1
  require './lib/site-inspector'
2
2
  require 'rspec/core/rake_task'
3
3
 
4
- desc "Run specs"
4
+ desc 'Run specs'
5
5
  RSpec::Core::RakeTask.new do |t|
6
6
  t.pattern = 'spec/**/*_spec.rb'
7
- t.rspec_opts = ["--order", "rand", "--color"]
7
+ t.rspec_opts = ['--order', 'rand', '--color']
8
8
  end
@@ -1,27 +1,27 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- require "mercenary"
4
- require "oj"
3
+ require 'mercenary'
4
+ require 'oj'
5
5
  require 'yaml'
6
6
  require 'colorator'
7
- require_relative "../lib/site-inspector"
7
+ require_relative '../lib/site-inspector'
8
8
 
9
9
  def stringify_keys_deep!(h)
10
- h.keys.each do |k|
11
- ks = k.respond_to?(:to_s) ? k.to_s : k
12
- h[ks] = h.delete k # Preserve order even when k == ks
13
- stringify_keys_deep! h[ks] if h[ks].kind_of? Hash
14
- end
10
+ h.keys.each do |k|
11
+ ks = k.respond_to?(:to_s) ? k.to_s : k
12
+ h[ks] = h.delete k # Preserve order even when k == ks
13
+ stringify_keys_deep! h[ks] if h[ks].is_a? Hash
14
+ end
15
15
  end
16
16
 
17
17
  Mercenary.program(:"site-inspector") do |p|
18
18
  p.version SiteInspector::VERSION
19
19
  p.description "Returns information about a domain's technology and capabilities"
20
- p.syntax "site-inspector <command> <domain> [options]"
20
+ p.syntax 'site-inspector <command> <domain> [options]'
21
21
 
22
22
  p.command(:inspect) do |c|
23
- c.syntax "inspect <domain> [options]"
24
- c.description "inspects a domain"
23
+ c.syntax 'inspect <domain> [options]'
24
+ c.description 'inspects a domain'
25
25
  c.option 'json', '-j', '--json', 'JSON encode the output'
26
26
  c.option 'all', '-a', '--all', 'return results for all endpoints (defaults to only the canonical endpoint)'
27
27
 
@@ -30,7 +30,7 @@ Mercenary.program(:"site-inspector") do |p|
30
30
  end
31
31
 
32
32
  c.action do |args, options|
33
- next c.logger.fatal "Must specify a domain" if args.length != 1
33
+ next c.logger.fatal 'Must specify a domain' if args.length != 1
34
34
 
35
35
  # Build our domain hash as requested
36
36
  domain = SiteInspector.inspect(args[0])
@@ -38,15 +38,15 @@ Mercenary.program(:"site-inspector") do |p|
38
38
  json = Oj.dump(hash, indent: 2, mode: :compat)
39
39
 
40
40
  # Dump the JSON and run
41
- next puts json if options["json"]
41
+ next puts json if options['json']
42
42
 
43
43
  # This is a dirty, dirty hack, but it's a simple way to stringify keys recursively
44
44
  # And format the output in a human-readable way
45
45
  yaml = YAML.dump Oj.load(json)
46
46
 
47
47
  # Colorize bools
48
- yaml.gsub! /\: (true|ok)$/, ": " + "true".green
49
- yaml.gsub! /\: false$/, ": " + "false".red
48
+ yaml.gsub!(/\: (true|ok)$/, ': ' + 'true'.green)
49
+ yaml.gsub!(/\: false$/, ': ' + 'false'.red)
50
50
 
51
51
  puts yaml
52
52
  end
@@ -0,0 +1,21 @@
1
+ module Cliver
2
+ class Dependency
3
+ # Memoized shortcut for detect
4
+ # Returns the path to the detected dependency
5
+ # Raises an error if the dependency was not satisfied
6
+ def path
7
+ @detected_path ||= detect!
8
+ end
9
+
10
+ # Returns the version of the resolved dependency
11
+ def version
12
+ return @detected_version if defined? @detected_version
13
+ version = installed_versions.find { |p, _v| p == path }
14
+ @detected_version = version.nil? ? nil : version[1]
15
+ end
16
+
17
+ def major_version
18
+ version.split('.').first if version
19
+ end
20
+ end
21
+ end
@@ -21,11 +21,11 @@ require_relative 'site-inspector/checks/sniffer'
21
21
  require_relative 'site-inspector/checks/cookies'
22
22
  require_relative 'site-inspector/endpoint'
23
23
  require_relative 'site-inspector/version'
24
+ require_relative 'cliver/dependency_ext'
24
25
 
25
26
  class SiteInspector
26
27
  class << self
27
-
28
- attr_writer :timeout, :cache
28
+ attr_writer :timeout, :cache, :typhoeus_options
29
29
 
30
30
  def cache
31
31
  @cache ||= if ENV['CACHE']
@@ -46,15 +46,17 @@ class SiteInspector
46
46
  end
47
47
 
48
48
  def typhoeus_defaults
49
- {
50
- :followlocation => false,
51
- :timeout => SiteInspector.timeout,
52
- :accept_encoding => "gzip",
53
- :method => :head,
54
- :headers => {
55
- "User-Agent" => "Mozilla/5.0 (compatible; SiteInspector/#{SiteInspector::VERSION}; +https://github.com/benbalter/site-inspector)"
49
+ defaults = {
50
+ followlocation: false,
51
+ timeout: SiteInspector.timeout,
52
+ accept_encoding: 'gzip',
53
+ method: :head,
54
+ headers: {
55
+ 'User-Agent' => "Mozilla/5.0 (compatible; SiteInspector/#{SiteInspector::VERSION}; +https://github.com/benbalter/site-inspector)"
56
56
  }
57
57
  }
58
+ defaults.merge! @typhoeus_options if @typhoeus_options
59
+ defaults
58
60
  end
59
61
 
60
62
  # Returns a thread-safe, memoized hydra instance
@@ -64,8 +66,8 @@ class SiteInspector
64
66
  end
65
67
  end
66
68
 
67
- if ENV["DEBUG"]
68
- Ethon.logger = Logger.new(STDOUT);
69
+ if ENV['DEBUG']
70
+ Ethon.logger = Logger.new(STDOUT)
69
71
  Ethon.logger.level = Logger::DEBUG
70
72
  Typhoeus::Config.verbose = true
71
73
  end
@@ -15,19 +15,29 @@ class SiteInspector
15
15
 
16
16
  DEFAULT_LEVEL = :error
17
17
 
18
+ REQUIRED_PA11Y_VERSION = '~> 2.1'
19
+
18
20
  class << self
19
21
  def pa11y_version
20
- output, status = Open3.capture2e("pa11y", "--version")
21
- output.strip if status == 0
22
+ @pa11y_version ||= pa11y.version
22
23
  end
23
24
 
24
25
  def pa11y?
25
- !!(Cliver.detect('pa11y'))
26
+ return @pa11y_detected if defined? @pa11y_detected
27
+ @pa11y_detected = !!(pa11y.detect)
26
28
  end
27
29
 
28
30
  def enabled?
29
31
  @@enabled && pa11y?
30
32
  end
33
+
34
+ def pa11y
35
+ @pa11y ||= begin
36
+ node_bin = File.expand_path('../../../node_modules/pa11y/bin', File.dirname(__FILE__))
37
+ path = ['*', node_bin].join(File::PATH_SEPARATOR)
38
+ Cliver::Dependency.new('pa11y', REQUIRED_PA11Y_VERSION, path: path)
39
+ end
40
+ end
31
41
  end
32
42
 
33
43
  def level
@@ -35,7 +45,7 @@ class SiteInspector
35
45
  end
36
46
 
37
47
  def level=(level)
38
- raise ArgumentError, "Invalid level '#{level}'" unless [:error, :warning, :notice].include?(level)
48
+ fail ArgumentError, "Invalid level '#{level}'" unless [:error, :warning, :notice].include?(level)
39
49
  @level = level
40
50
  end
41
51
 
@@ -48,7 +58,7 @@ class SiteInspector
48
58
  end
49
59
 
50
60
  def standard=(standard)
51
- raise ArgumentError, "Unknown standard '#{standard}'" unless standard?(standard)
61
+ fail ArgumentError, "Unknown standard '#{standard}'" unless standard?(standard)
52
62
  @standard = standard
53
63
  end
54
64
 
@@ -57,11 +67,11 @@ class SiteInspector
57
67
  end
58
68
 
59
69
  def errors
60
- check[:results].count { |r| r["type"] == "error" } if check
70
+ check[:results].count { |r| r['type'] == 'error' } if check
61
71
  end
62
72
 
63
73
  def check
64
- @check ||= pa11y(standard)
74
+ @check ||= run_pa11y(standard)
65
75
  rescue Pa11yError
66
76
  nil
67
77
  end
@@ -69,7 +79,7 @@ class SiteInspector
69
79
 
70
80
  def method_missing(method_sym, *arguments, &block)
71
81
  if standard?(method_sym)
72
- pa11y(method_sym)
82
+ run_pa11y(method_sym)
73
83
  else
74
84
  super
75
85
  end
@@ -85,32 +95,32 @@ class SiteInspector
85
95
 
86
96
  private
87
97
 
88
- def pa11y(standard)
89
- Cliver.assert('pa11y')
90
- raise ArgumentError, "Unknown standard '#{standard}'" unless standard?(standard)
98
+ def run_pa11y(standard)
99
+ self.class.pa11y.detect! unless ENV['SKIP_PA11Y_CHECK']
100
+ fail ArgumentError, "Unknown standard '#{standard}'" unless standard?(standard)
91
101
 
92
102
  args = [
93
- "--standard", STANDARDS[standard],
94
- "--reporter", "json",
95
- "--level", level.to_s,
103
+ '--standard', STANDARDS[standard],
104
+ '--reporter', 'json',
105
+ '--level', level.to_s,
96
106
  endpoint.uri.to_s
97
107
  ]
98
108
  output, status = run_command(args)
99
109
 
100
110
  # Pa11y exit codes: https://github.com/nature/pa11y#exit-codes
101
111
  # 0: No errors, 1: Technical error within pa11y, 2: accessibility error (configurable via --level)
102
- raise Pa11yError if status == 1
112
+ fail Pa11yError if status == 1
103
113
 
104
114
  {
105
115
  valid: status == 0,
106
116
  results: JSON.parse(output)
107
117
  }
108
118
  rescue Pa11yError, JSON::ParserError
109
- raise Pa11yError, "Command `pa11y #{args.join(" ")}` failed: #{output}"
119
+ raise Pa11yError, "Command `pa11y #{args.join(' ')}` failed: #{output}"
110
120
  end
111
121
 
112
122
  def run_command(args)
113
- Open3.capture2e("pa11y", *args)
123
+ Open3.capture2e(self.class.pa11y.path, *args)
114
124
  end
115
125
  end
116
126
  end
@@ -1,7 +1,6 @@
1
1
  class SiteInspector
2
2
  class Endpoint
3
3
  class Check
4
-
5
4
  attr_reader :endpoint
6
5
 
7
6
  # A check is an abstract class that takes an Endpoint object
@@ -34,11 +33,10 @@ class SiteInspector
34
33
  end
35
34
 
36
35
  class << self
37
-
38
36
  @@enabled = true
39
37
 
40
38
  def name
41
- self.to_s.split('::').last.downcase.to_sym
39
+ to_s.split('::').last.downcase.to_sym
42
40
  end
43
41
 
44
42
  def enabled?
@@ -9,7 +9,7 @@ class SiteInspector
9
9
  # The default Check#response method is from a HEAD request
10
10
  # The content check has a special response which includes the body from a GET request
11
11
  def response
12
- @response ||= endpoint.request(:method => :get)
12
+ @response ||= endpoint.request(method: :get)
13
13
  end
14
14
 
15
15
  def document
@@ -19,19 +19,19 @@ class SiteInspector
19
19
  alias_method :doc, :document
20
20
 
21
21
  def body
22
- @body ||= document.to_s.force_encoding("UTF-8").encode("UTF-8", :invalid => :replace, :replace => "")
22
+ @body ||= document.to_s.force_encoding('UTF-8').encode('UTF-8', invalid: :replace, replace: '')
23
23
  end
24
24
 
25
25
  def robots_txt?
26
- @bodts_txt ||= path_exists?("robots.txt") if proper_404s?
26
+ @bodts_txt ||= path_exists?('robots.txt') if proper_404s?
27
27
  end
28
28
 
29
29
  def sitemap_xml?
30
- @sitemap_xml ||= path_exists?("sitemap.xml") if proper_404s?
30
+ @sitemap_xml ||= path_exists?('sitemap.xml') if proper_404s?
31
31
  end
32
32
 
33
33
  def humans_txt?
34
- @humans_txt ||= path_exists?("humans.txt") if proper_404s?
34
+ @humans_txt ||= path_exists?('humans.txt') if proper_404s?
35
35
  end
36
36
 
37
37
  def doctype
@@ -41,7 +41,7 @@ class SiteInspector
41
41
  def prefetch
42
42
  return unless endpoint.up?
43
43
  options = SiteInspector.typhoeus_defaults.merge(followlocation: true)
44
- ["robots.txt", "sitemap.xml", "humans.txt", random_path].each do |path|
44
+ ['robots.txt', 'sitemap.xml', 'humans.txt', random_path].each do |path|
45
45
  request = Typhoeus::Request.new(URI.join(endpoint.uri, path), options)
46
46
  SiteInspector.hydra.queue(request)
47
47
  end
@@ -1,7 +1,6 @@
1
1
  class SiteInspector
2
2
  class Endpoint
3
3
  class Cookies < Check
4
-
5
4
  def any?(&block)
6
5
  if cookie_header.nil? || cookie_header.empty?
7
6
  false
@@ -14,7 +13,7 @@ class SiteInspector
14
13
  alias_method :cookies?, :any?
15
14
 
16
15
  def all
17
- @cookies ||= cookie_header.map { |c| CGI::Cookie::parse(c) } if cookies?
16
+ @cookies ||= cookie_header.map { |c| CGI::Cookie.parse(c) } if cookies?
18
17
  end
19
18
 
20
19
  def [](key)
@@ -22,14 +21,14 @@ class SiteInspector
22
21
  end
23
22
 
24
23
  def secure?
25
- pairs = cookie_header.join("; ").split("; ") # CGI::Cookies#Parse doesn't seem to like secure headers
26
- pairs.any? { |c| c.downcase == "secure" } && pairs.any? { |c| c.downcase == "httponly" }
24
+ pairs = cookie_header.join('; ').split('; ') # CGI::Cookies#Parse doesn't seem to like secure headers
25
+ pairs.any? { |c| c.downcase == 'secure' } && pairs.any? { |c| c.downcase == 'httponly' }
27
26
  end
28
27
 
29
28
  def to_h
30
29
  {
31
- :cookie? => any?,
32
- :secure? => secure?
30
+ cookie?: any?,
31
+ secure?: secure?
33
32
  }
34
33
  end
35
34
 
@@ -37,9 +36,8 @@ class SiteInspector
37
36
 
38
37
  def cookie_header
39
38
  # Cookie header may be an array or string, always return an array
40
- [endpoint.headers.all["set-cookie"]].flatten.compact
39
+ [endpoint.headers.all['set-cookie']].flatten.compact
41
40
  end
42
-
43
41
  end
44
42
  end
45
43
  end