Class: Abqari::Audit::Links

Inherits:
Rule
  • Object
show all
Defined in:
lib/abqari/audit/links.rb

Overview

Broken internal-link / missing-asset detector. Walks every href, src, and srcset in rendered HTML and flags any same-origin reference that doesn't resolve to a file we actually emitted. External (http(s):// to another host) references are skipped here — Privacy covers them — as are anchors / mailto / tel / data.

Instance Method Summary collapse

Methods inherited from Rule

#initialize

Constructor Details

This class inherits a constructor from Abqari::Audit::Rule

Instance Method Details

#runObject



13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
# File 'lib/abqari/audit/links.rb', line 13

def run
  output_paths = build_output_path_set
  site_host    = host_for(@site.config['url'])

  html_files.each do |file|
    url  = page_url(file)
    html = File.read(file, encoding: 'UTF-8')

    # Strip `<pre>…</pre>` and inline `<code>…</code>` blocks
    # before scanning. Rouge's HTML-lexer highlighting of an
    # ERB or HTML example wraps the attribute value in
    # `<span class="s">...</span>`, which a naive `href="…"`
    # regex picks up as a "broken link" pointing at
    # `</span><span class=` and similar fragments. Code
    # samples aren't navigation — skip them.
    scan_html = html.gsub(%r{<pre\b[^>]*>.*?</pre>}m, '')
                    .gsub(%r{<code\b[^>]*>.*?</code>}m, '')

    references(scan_html).each do |link|
      next if link.empty?
      next if link.start_with?('mailto:', 'tel:', 'data:', 'javascript:', '#')

      # Same-origin absolute URLs (`https://mysite/gone/`) are
      # checked like internal links; only cross-host ones are
      # skipped (Privacy covers those). Previously BOTH branches
      # fell through, so a broken absolute link to your own domain
      # was invisible to every rule.
      if link.start_with?('http://', 'https://')
        link_host = host_for(link)
        next unless site_host && link_host == site_host

        link = (URI(link).path.to_s.empty? ? '/' : URI(link).path)
      end

      target = resolve_target(link, url)
      next if output_paths.include?(target) ||
              output_paths.include?("#{target}index.html")

      add(category: 'links', severity: 'error', location: url,
          message: "Broken link/asset: #{link}")
    end
  end
end