html-proofer 5.0.0 → 5.2.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.
- checksums.yaml +4 -4
- data/lib/html_proofer/attribute/url.rb +58 -38
- data/lib/html_proofer/cache.rb +16 -9
- data/lib/html_proofer/check/favicon.rb +9 -5
- data/lib/html_proofer/check/images.rb +36 -22
- data/lib/html_proofer/check/links.rb +38 -23
- data/lib/html_proofer/check/open_graph.rb +12 -8
- data/lib/html_proofer/check/scripts.rb +22 -12
- data/lib/html_proofer/check.rb +17 -9
- data/lib/html_proofer/configuration.rb +72 -31
- data/lib/html_proofer/element.rb +24 -9
- data/lib/html_proofer/failure.rb +3 -2
- data/lib/html_proofer/log.rb +7 -5
- data/lib/html_proofer/reporter.rb +2 -2
- data/lib/html_proofer/runner.rb +18 -10
- data/lib/html_proofer/url_validator/external.rb +44 -5
- data/lib/html_proofer/url_validator/internal.rb +48 -27
- data/lib/html_proofer/version.rb +1 -1
- data/lib/html_proofer/xpath_functions.rb +1 -1
- data/lib/html_proofer.rb +4 -1
- metadata +27 -142
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1081048f56ee5d0b41f5ba9278d0757d1a1eef6fdb8c50148eb2b47a183b5572
|
|
4
|
+
data.tar.gz: 9a60b977a473c77c12085062b0438eb175751a0a013206ec4dbd1baf03deffb6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0402cc78e35cd4440bf95e9a9d0372f68677d28ad0e901867457c4583caebb4cbd4eefdc50e27e270d85e756ee402c0e2ba6898c3bf1b074fef6f69f8b30ae7a
|
|
7
|
+
data.tar.gz: f5754b9c79336f792cb2064baab958b7c27e62d13a6ae225a9fe5cde286d773e9427251ccd7b44afa7d258e517a5838af5ad6fdff3c63a0b6bf21b8cba08c75f
|
|
@@ -3,13 +3,16 @@
|
|
|
3
3
|
module HTMLProofer
|
|
4
4
|
class Attribute
|
|
5
5
|
class Url < HTMLProofer::Attribute
|
|
6
|
-
attr_reader :url, :size
|
|
6
|
+
attr_reader :url, :size, :source, :filename
|
|
7
7
|
|
|
8
8
|
REMOTE_SCHEMES = ["http", "https"].freeze
|
|
9
9
|
|
|
10
|
-
def initialize(runner, link_attribute, base_url: nil, extract_size: false)
|
|
10
|
+
def initialize(runner, link_attribute, base_url: nil, source: nil, filename: nil, extract_size: false)
|
|
11
11
|
super
|
|
12
12
|
|
|
13
|
+
@source = source
|
|
14
|
+
@filename = filename
|
|
15
|
+
|
|
13
16
|
if @raw_attribute.nil?
|
|
14
17
|
@url = nil
|
|
15
18
|
else
|
|
@@ -49,7 +52,8 @@ module HTMLProofer
|
|
|
49
52
|
|
|
50
53
|
def ignore?
|
|
51
54
|
return true if /^javascript:/.match?(@url)
|
|
52
|
-
|
|
55
|
+
|
|
56
|
+
true if ignores_pattern?(@runner.options[:ignore_urls])
|
|
53
57
|
end
|
|
54
58
|
|
|
55
59
|
def valid?
|
|
@@ -115,9 +119,30 @@ module HTMLProofer
|
|
|
115
119
|
def exists?
|
|
116
120
|
return true if base64?
|
|
117
121
|
|
|
118
|
-
|
|
122
|
+
!resolved_path.nil?
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def resolved_path
|
|
126
|
+
path_to_resolve = absolute_path
|
|
119
127
|
|
|
120
|
-
@runner.
|
|
128
|
+
return @runner.resolved_paths[path_to_resolve] if @runner.resolved_paths.key?(path_to_resolve)
|
|
129
|
+
|
|
130
|
+
# extensionless URLs
|
|
131
|
+
path_with_extension = "#{path_to_resolve}#{@runner.options[:assume_extension]}"
|
|
132
|
+
resolved = if @runner.options[:assume_extension] && File.file?(path_with_extension)
|
|
133
|
+
path_with_extension # existence checked implicitly by File.file?
|
|
134
|
+
# implicit index support
|
|
135
|
+
elsif File.directory?(path_to_resolve) && !unslashed_directory?(path_to_resolve)
|
|
136
|
+
@runner.options[:directory_index_files]
|
|
137
|
+
.map { |dif| File.join(path_to_resolve, dif) }
|
|
138
|
+
.find { |path_with_index| File.file?(path_with_index) }
|
|
139
|
+
# explicit file or directory
|
|
140
|
+
elsif File.exist?(path_to_resolve)
|
|
141
|
+
path_to_resolve
|
|
142
|
+
end
|
|
143
|
+
@runner.resolved_paths[path_to_resolve] = resolved
|
|
144
|
+
|
|
145
|
+
resolved
|
|
121
146
|
end
|
|
122
147
|
|
|
123
148
|
def base64?
|
|
@@ -125,49 +150,31 @@ module HTMLProofer
|
|
|
125
150
|
end
|
|
126
151
|
|
|
127
152
|
def absolute_path
|
|
128
|
-
path =
|
|
153
|
+
path = full_path || @filename
|
|
129
154
|
|
|
130
155
|
File.expand_path(path, Dir.pwd)
|
|
131
156
|
end
|
|
132
157
|
|
|
133
|
-
def
|
|
158
|
+
def full_path
|
|
134
159
|
return if path.nil? || path.empty?
|
|
135
160
|
|
|
136
|
-
path_dot_ext = ""
|
|
137
|
-
|
|
138
|
-
path_dot_ext = path + @runner.options[:assume_extension] unless blank?(@runner.options[:assume_extension])
|
|
139
|
-
|
|
140
161
|
base = if absolute_path?(path) # path relative to root
|
|
141
|
-
# either overwrite with root_dir; or, if source is directory, use that; or, just get the
|
|
142
|
-
@runner.options[:root_dir] || (File.directory?(@
|
|
143
|
-
# relative links, path is a file
|
|
144
|
-
elsif File.exist?(File.expand_path(path,
|
|
145
|
-
@runner.current_source)) || File.exist?(File.expand_path(path_dot_ext, @runner.current_source))
|
|
146
|
-
File.dirname(@runner.current_filename)
|
|
147
|
-
# relative links in nested dir, path is a file
|
|
148
|
-
elsif File.exist?(File.join(File.dirname(@runner.current_filename),
|
|
149
|
-
path)) || File.exist?(File.join(File.dirname(@runner.current_filename), path_dot_ext))
|
|
150
|
-
File.dirname(@runner.current_filename)
|
|
151
|
-
# relative link, path is a directory
|
|
162
|
+
# either overwrite with root_dir; or, if source is directory, use that; or, just get the source file's dirname
|
|
163
|
+
@runner.options[:root_dir] || (File.directory?(@source) ? @source : File.dirname(@source))
|
|
152
164
|
else
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
file = File.join(base, path)
|
|
157
|
-
|
|
158
|
-
if @runner.options[:assume_extension] && File.file?("#{file}#{@runner.options[:assume_extension]}")
|
|
159
|
-
file = "#{file}#{@runner.options[:assume_extension]}"
|
|
160
|
-
elsif File.directory?(file) && !unslashed_directory?(file) # implicit index support
|
|
161
|
-
file = File.join(file, @runner.options[:directory_index_file])
|
|
165
|
+
# path relative to the file where the link is defined
|
|
166
|
+
File.dirname(@filename)
|
|
162
167
|
end
|
|
163
168
|
|
|
164
|
-
|
|
169
|
+
File.join(base, path)
|
|
165
170
|
end
|
|
166
171
|
|
|
167
172
|
def unslashed_directory?(file)
|
|
168
173
|
return false unless File.directory?(file)
|
|
169
174
|
|
|
170
|
-
|
|
175
|
+
# the trailing slash must be read off the URL, not off `file`: the latter
|
|
176
|
+
# is produced by File.expand_path, which strips trailing separators
|
|
177
|
+
!path.to_s.end_with?("/") && !follow_location?
|
|
171
178
|
end
|
|
172
179
|
|
|
173
180
|
def follow_location?
|
|
@@ -213,15 +220,28 @@ module HTMLProofer
|
|
|
213
220
|
url.start_with?("?")
|
|
214
221
|
end
|
|
215
222
|
|
|
216
|
-
def
|
|
223
|
+
def without_hash
|
|
217
224
|
@url.to_s.sub(/##{hash}/, "")
|
|
218
225
|
end
|
|
219
226
|
|
|
220
|
-
# catch any obvious issues
|
|
227
|
+
# catch any obvious issues
|
|
221
228
|
private def clean_url!
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
229
|
+
parsed_url = Addressable::URI.parse(@url)
|
|
230
|
+
url = if parsed_url.scheme.nil?
|
|
231
|
+
parsed_url
|
|
232
|
+
else
|
|
233
|
+
parsed_url.normalize
|
|
234
|
+
end.to_s
|
|
235
|
+
|
|
236
|
+
# normalize strips this off, which causes issues with cache
|
|
237
|
+
@url = if @url.end_with?("/") && !url.end_with?("/")
|
|
238
|
+
"#{url}/"
|
|
239
|
+
elsif !@url.end_with?("/") && url.end_with?("/")
|
|
240
|
+
url.chop
|
|
241
|
+
else
|
|
242
|
+
url
|
|
243
|
+
end
|
|
244
|
+
rescue Addressable::URI::InvalidURIError # -- error will be reported at check time
|
|
225
245
|
end
|
|
226
246
|
|
|
227
247
|
private def swap_urls!
|
data/lib/html_proofer/cache.rb
CHANGED
|
@@ -34,14 +34,14 @@ module HTMLProofer
|
|
|
34
34
|
setup_cache!(options)
|
|
35
35
|
|
|
36
36
|
@external_timeframe = parsed_timeframe(options[:timeframe][:external])
|
|
37
|
-
define_singleton_method(:external_enabled?) { !@external_timeframe.nil? }
|
|
37
|
+
define_singleton_method(:external_enabled?) { !@external_timeframe.nil? } # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
38
38
|
@internal_timeframe = parsed_timeframe(options[:timeframe][:internal])
|
|
39
|
-
define_singleton_method(:internal_enabled?) { !@internal_timeframe.nil? }
|
|
39
|
+
define_singleton_method(:internal_enabled?) { !@internal_timeframe.nil? } # rubocop:disable ThreadSafety/ClassInstanceVariable
|
|
40
40
|
end
|
|
41
41
|
end
|
|
42
42
|
|
|
43
43
|
def parsed_timeframe(timeframe)
|
|
44
|
-
return
|
|
44
|
+
return if timeframe.nil?
|
|
45
45
|
|
|
46
46
|
time, date = timeframe.match(/(\d+)(\D)/).captures
|
|
47
47
|
time = time.to_i
|
|
@@ -71,8 +71,10 @@ module HTMLProofer
|
|
|
71
71
|
return unless external_enabled?
|
|
72
72
|
|
|
73
73
|
clean_url = cleaned_url(url)
|
|
74
|
+
# Filter out non-serializable element from metadata
|
|
75
|
+
cleaned_filenames = filenames.map { |f| f.except(:element) }
|
|
74
76
|
@cache_log[:external][clean_url] =
|
|
75
|
-
{ time: @cache_time.to_s, found: found, status_code: status_code, message: msg, metadata:
|
|
77
|
+
{ time: @cache_time.to_s, found: found, status_code: status_code, message: msg, metadata: cleaned_filenames }
|
|
76
78
|
end
|
|
77
79
|
|
|
78
80
|
def detect_url_changes(urls_detected, type)
|
|
@@ -93,8 +95,10 @@ module HTMLProofer
|
|
|
93
95
|
# if there are no urls, bail
|
|
94
96
|
return {} if urls_detected.empty?
|
|
95
97
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
+
if type == :external
|
|
99
|
+
urls_detected = urls_detected.transform_keys do |url|
|
|
100
|
+
cleaned_url(url)
|
|
101
|
+
end
|
|
98
102
|
end
|
|
99
103
|
|
|
100
104
|
urls_to_check = detect_url_changes(urls_detected, type)
|
|
@@ -184,7 +188,9 @@ module HTMLProofer
|
|
|
184
188
|
hsh[url] = metadata_additions
|
|
185
189
|
# remove from the cache the detected metadata additions as they correspond to failures to be rechecked
|
|
186
190
|
# (this works assuming the detected url metadata have "found" set to false)
|
|
187
|
-
|
|
191
|
+
# Compare without the :element key since cached metadata doesn't have it
|
|
192
|
+
metadata_additions_for_diff = metadata_additions.map { |m| m.except(:element) }
|
|
193
|
+
@cache_log[:internal][url][:metadata] = cache_metadata.difference(metadata_additions_for_diff)
|
|
188
194
|
end
|
|
189
195
|
end
|
|
190
196
|
|
|
@@ -250,7 +256,7 @@ module HTMLProofer
|
|
|
250
256
|
SECONDS_PER_HOUR = 3600
|
|
251
257
|
SECONDS_PER_DAY = 86400
|
|
252
258
|
SECONDS_PER_WEEK = 604800
|
|
253
|
-
SECONDS_PER_MONTH = 2629746
|
|
259
|
+
SECONDS_PER_MONTH = 2629746 # 1/12 of a gregorian year
|
|
254
260
|
|
|
255
261
|
private def time_ago(measurement, unit)
|
|
256
262
|
case unit
|
|
@@ -267,7 +273,8 @@ module HTMLProofer
|
|
|
267
273
|
|
|
268
274
|
private def url_matches_type?(url, type)
|
|
269
275
|
return true if type == :internal && url !~ URI_REGEXP
|
|
270
|
-
|
|
276
|
+
|
|
277
|
+
true if type == :external && url =~ URI_REGEXP
|
|
271
278
|
end
|
|
272
279
|
|
|
273
280
|
private def cleaned_url(url)
|
|
@@ -17,13 +17,17 @@ module HTMLProofer
|
|
|
17
17
|
|
|
18
18
|
if found
|
|
19
19
|
if @favicon.url.protocol_relative?
|
|
20
|
-
add_failure(
|
|
21
|
-
|
|
20
|
+
add_failure(
|
|
21
|
+
"favicon link #{@favicon.url} is a protocol-relative URL, use explicit https:// instead",
|
|
22
|
+
element: @favicon,
|
|
23
|
+
)
|
|
22
24
|
elsif @favicon.url.remote?
|
|
23
|
-
add_to_external_urls(@favicon.url, @favicon
|
|
25
|
+
add_to_external_urls(@favicon.url, @favicon)
|
|
24
26
|
elsif !@favicon.url.exists?
|
|
25
|
-
add_failure(
|
|
26
|
-
|
|
27
|
+
add_failure(
|
|
28
|
+
"internal favicon #{@favicon.url.raw_attribute} does not exist",
|
|
29
|
+
element: @favicon,
|
|
30
|
+
)
|
|
27
31
|
end
|
|
28
32
|
else
|
|
29
33
|
add_failure("no favicon provided")
|
|
@@ -12,48 +12,62 @@ module HTMLProofer
|
|
|
12
12
|
next if @img.ignore?
|
|
13
13
|
|
|
14
14
|
# screenshot filenames should return because of terrible names
|
|
15
|
-
add_failure(
|
|
16
|
-
|
|
15
|
+
add_failure(
|
|
16
|
+
"image has a terrible filename (#{@img.url.raw_attribute})",
|
|
17
|
+
element: @img,
|
|
18
|
+
) if terrible_filename?
|
|
17
19
|
|
|
18
20
|
# does the image exist?
|
|
19
21
|
if missing_src?
|
|
20
|
-
add_failure("image has no src or srcset attribute",
|
|
21
|
-
elsif @img.url.protocol_relative?
|
|
22
|
-
add_failure("image link #{@img.url} is a protocol-relative URL, use explicit https:// instead",
|
|
23
|
-
line: @img.line, content: @img.content)
|
|
24
|
-
elsif @img.url.remote?
|
|
25
|
-
add_to_external_urls(@img.url, @img.line)
|
|
26
|
-
elsif !@img.url.exists? && !@img.multiple_srcsets? && !@img.multiple_sizes?
|
|
27
|
-
add_failure("internal image #{@img.url.raw_attribute} does not exist", line: @img.line,
|
|
28
|
-
content: @img.content)
|
|
22
|
+
add_failure("image has no src or srcset attribute", element: @img)
|
|
29
23
|
elsif @img.multiple_srcsets? || @img.multiple_sizes?
|
|
30
24
|
@img.srcsets_wo_sizes.each do |srcset|
|
|
31
|
-
srcset_url = HTMLProofer::Attribute::Url.new(@runner, srcset, base_url: @img.base_url, extract_size: true)
|
|
25
|
+
srcset_url = HTMLProofer::Attribute::Url.new(@runner, srcset, base_url: @img.base_url, source: @img.url.source, filename: @img.url.filename, extract_size: true)
|
|
32
26
|
|
|
33
27
|
if srcset_url.protocol_relative?
|
|
34
|
-
add_failure(
|
|
35
|
-
|
|
28
|
+
add_failure(
|
|
29
|
+
"image link #{srcset_url.url} is a protocol-relative URL, use explicit https:// instead",
|
|
30
|
+
element: @img,
|
|
31
|
+
)
|
|
36
32
|
elsif srcset_url.remote?
|
|
37
|
-
add_to_external_urls(srcset_url.url, @img
|
|
33
|
+
add_to_external_urls(srcset_url.url, @img)
|
|
38
34
|
elsif !srcset_url.exists?
|
|
39
|
-
add_failure("internal image #{srcset} does not exist",
|
|
35
|
+
add_failure("internal image #{srcset} does not exist", element: @img)
|
|
40
36
|
end
|
|
41
37
|
end
|
|
38
|
+
elsif @img.url.protocol_relative?
|
|
39
|
+
add_failure(
|
|
40
|
+
"image link #{@img.url} is a protocol-relative URL, use explicit https:// instead",
|
|
41
|
+
element: @img,
|
|
42
|
+
)
|
|
43
|
+
elsif @img.url.remote?
|
|
44
|
+
add_to_external_urls(@img.url, @img)
|
|
45
|
+
elsif !@img.url.exists? && !@img.multiple_srcsets? && !@img.multiple_sizes?
|
|
46
|
+
add_failure(
|
|
47
|
+
"internal image #{@img.url.raw_attribute} does not exist",
|
|
48
|
+
element: @img,
|
|
49
|
+
)
|
|
42
50
|
end
|
|
43
51
|
|
|
44
52
|
# if this is an img element, check that the alt attribute is present
|
|
45
53
|
if @img.img_tag? && !ignore_element?
|
|
46
54
|
if missing_alt_tag? && !ignore_missing_alt?
|
|
47
|
-
add_failure(
|
|
48
|
-
|
|
55
|
+
add_failure(
|
|
56
|
+
"image #{@img.url.raw_attribute} does not have an alt attribute",
|
|
57
|
+
element: @img,
|
|
58
|
+
)
|
|
49
59
|
elsif (empty_alt_tag? || alt_all_spaces?) && !ignore_empty_alt?
|
|
50
|
-
add_failure(
|
|
51
|
-
|
|
60
|
+
add_failure(
|
|
61
|
+
"image #{@img.url.raw_attribute} has an alt attribute, but no content",
|
|
62
|
+
element: @img,
|
|
63
|
+
)
|
|
52
64
|
end
|
|
53
65
|
end
|
|
54
66
|
|
|
55
|
-
add_failure(
|
|
56
|
-
|
|
67
|
+
add_failure(
|
|
68
|
+
"image #{@img.url.raw_attribute} uses the http scheme",
|
|
69
|
+
element: @img,
|
|
70
|
+
) if @runner.enforce_https? && @img.url.http?
|
|
57
71
|
end
|
|
58
72
|
|
|
59
73
|
external_urls
|
|
@@ -10,7 +10,7 @@ module HTMLProofer
|
|
|
10
10
|
next if @link.ignore?
|
|
11
11
|
|
|
12
12
|
if !allow_hash_href? && @link.node["href"] == "#"
|
|
13
|
-
add_failure("linking to internal hash #, which points to nowhere",
|
|
13
|
+
add_failure("linking to internal hash #, which points to nowhere", element: @link)
|
|
14
14
|
next
|
|
15
15
|
end
|
|
16
16
|
|
|
@@ -18,19 +18,21 @@ module HTMLProofer
|
|
|
18
18
|
if blank?(@link.url.raw_attribute)
|
|
19
19
|
next if allow_missing_href?
|
|
20
20
|
|
|
21
|
-
add_failure("'#{@link.node.name}' tag is missing a reference",
|
|
21
|
+
add_failure("'#{@link.node.name}' tag is missing a reference", element: @link)
|
|
22
22
|
next
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
# is it even a valid URL?
|
|
26
26
|
unless @link.url.valid?
|
|
27
|
-
add_failure("#{@link.href} is an invalid URL",
|
|
27
|
+
add_failure("#{@link.href} is an invalid URL", element: @link)
|
|
28
28
|
next
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
if @link.url.protocol_relative?
|
|
32
|
-
add_failure(
|
|
33
|
-
|
|
32
|
+
add_failure(
|
|
33
|
+
"#{@link.url} is a protocol-relative URL, use explicit https:// instead",
|
|
34
|
+
element: @link,
|
|
35
|
+
)
|
|
34
36
|
next
|
|
35
37
|
end
|
|
36
38
|
|
|
@@ -47,20 +49,22 @@ module HTMLProofer
|
|
|
47
49
|
next if @link.node["rel"] == "dns-prefetch"
|
|
48
50
|
|
|
49
51
|
unless @link.url.path?
|
|
50
|
-
add_failure("#{@link.url.raw_attribute} is an invalid URL",
|
|
52
|
+
add_failure("#{@link.url.raw_attribute} is an invalid URL", element: @link)
|
|
51
53
|
next
|
|
52
54
|
end
|
|
53
55
|
|
|
54
|
-
add_to_external_urls(@link.url, @link
|
|
56
|
+
add_to_external_urls(@link.url, @link)
|
|
55
57
|
elsif @link.url.internal?
|
|
56
58
|
# does the local directory have a trailing slash?
|
|
57
59
|
if @link.url.unslashed_directory?(@link.url.absolute_path)
|
|
58
|
-
add_failure(
|
|
59
|
-
|
|
60
|
+
add_failure(
|
|
61
|
+
"internally linking to a directory #{@link.url.raw_attribute} without trailing slash",
|
|
62
|
+
element: @link,
|
|
63
|
+
)
|
|
60
64
|
next
|
|
61
65
|
end
|
|
62
66
|
|
|
63
|
-
add_to_internal_urls(@link.url, @link
|
|
67
|
+
add_to_internal_urls(@link.url, @link)
|
|
64
68
|
end
|
|
65
69
|
end
|
|
66
70
|
end
|
|
@@ -82,23 +86,30 @@ module HTMLProofer
|
|
|
82
86
|
when "http"
|
|
83
87
|
return unless @runner.options[:enforce_https]
|
|
84
88
|
|
|
85
|
-
add_failure("#{@link.url.raw_attribute} is not an HTTPS link",
|
|
89
|
+
add_failure("#{@link.url.raw_attribute} is not an HTTPS link", element: @link)
|
|
86
90
|
end
|
|
87
91
|
end
|
|
88
92
|
|
|
89
93
|
def handle_mailto
|
|
90
94
|
if @link.url.path.empty?
|
|
91
|
-
add_failure(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
add_failure(
|
|
96
|
+
"#{@link.url.raw_attribute} contains no email address",
|
|
97
|
+
element: @link,
|
|
98
|
+
) unless ignore_empty_mailto?
|
|
99
|
+
# eg., if any do not match a valid URL
|
|
100
|
+
elsif @link.url.path.split(",").any? { |email| !/#{URI::MailTo::EMAIL_REGEXP}/o.match?(email) }
|
|
101
|
+
add_failure(
|
|
102
|
+
"#{@link.url.raw_attribute} contains an invalid email address",
|
|
103
|
+
element: @link,
|
|
104
|
+
)
|
|
96
105
|
end
|
|
97
106
|
end
|
|
98
107
|
|
|
99
108
|
def handle_tel
|
|
100
|
-
add_failure(
|
|
101
|
-
|
|
109
|
+
add_failure(
|
|
110
|
+
"#{@link.url.raw_attribute} contains no phone number",
|
|
111
|
+
element: @link,
|
|
112
|
+
) if @link.url.path.empty?
|
|
102
113
|
end
|
|
103
114
|
|
|
104
115
|
def ignore_empty_mailto?
|
|
@@ -113,13 +124,17 @@ module HTMLProofer
|
|
|
113
124
|
return unless SRI_REL_TYPES.include?(@link.node["rel"])
|
|
114
125
|
|
|
115
126
|
if blank?(@link.node["integrity"]) && blank?(@link.node["crossorigin"])
|
|
116
|
-
add_failure(
|
|
117
|
-
|
|
127
|
+
add_failure(
|
|
128
|
+
"SRI and CORS not provided in: #{@link.url.raw_attribute}",
|
|
129
|
+
element: @link,
|
|
130
|
+
)
|
|
118
131
|
elsif blank?(@link.node["integrity"])
|
|
119
|
-
add_failure("Integrity is missing in: #{@link.url.raw_attribute}",
|
|
132
|
+
add_failure("Integrity is missing in: #{@link.url.raw_attribute}", element: @link)
|
|
120
133
|
elsif blank?(@link.node["crossorigin"])
|
|
121
|
-
add_failure(
|
|
122
|
-
|
|
134
|
+
add_failure(
|
|
135
|
+
"CORS not provided for external resource in: #{@link.link.url.raw_attribute}",
|
|
136
|
+
element: @link,
|
|
137
|
+
)
|
|
123
138
|
end
|
|
124
139
|
end
|
|
125
140
|
|
|
@@ -11,19 +11,23 @@ module HTMLProofer
|
|
|
11
11
|
|
|
12
12
|
# does the open_graph exist?
|
|
13
13
|
if missing_content?
|
|
14
|
-
add_failure("open graph has no content attribute",
|
|
14
|
+
add_failure("open graph has no content attribute", element: @open_graph)
|
|
15
15
|
elsif empty_content?
|
|
16
|
-
add_failure("open graph content attribute is empty",
|
|
16
|
+
add_failure("open graph content attribute is empty", element: @open_graph)
|
|
17
17
|
elsif !@open_graph.url.valid?
|
|
18
|
-
add_failure("#{@open_graph.src} is an invalid URL",
|
|
18
|
+
add_failure("#{@open_graph.src} is an invalid URL", element: @open_graph)
|
|
19
19
|
elsif @open_graph.url.protocol_relative?
|
|
20
|
-
add_failure(
|
|
21
|
-
|
|
20
|
+
add_failure(
|
|
21
|
+
"open graph link #{@open_graph.url} is a protocol-relative URL, use explicit https:// instead",
|
|
22
|
+
element: @open_graph,
|
|
23
|
+
)
|
|
22
24
|
elsif @open_graph.url.remote?
|
|
23
|
-
add_to_external_urls(@open_graph.url, @open_graph
|
|
25
|
+
add_to_external_urls(@open_graph.url, @open_graph)
|
|
24
26
|
else
|
|
25
|
-
add_failure(
|
|
26
|
-
|
|
27
|
+
add_failure(
|
|
28
|
+
"internal open graph #{@open_graph.url.raw_attribute} does not exist",
|
|
29
|
+
element: @open_graph,
|
|
30
|
+
) unless @open_graph.url.exists?
|
|
27
31
|
end
|
|
28
32
|
end
|
|
29
33
|
|
|
@@ -12,16 +12,20 @@ module HTMLProofer
|
|
|
12
12
|
|
|
13
13
|
# does the script exist?
|
|
14
14
|
if missing_src?
|
|
15
|
-
add_failure("script is empty and has no src attribute",
|
|
15
|
+
add_failure("script is empty and has no src attribute", element: @script)
|
|
16
16
|
elsif @script.url.protocol_relative?
|
|
17
|
-
add_failure(
|
|
18
|
-
|
|
17
|
+
add_failure(
|
|
18
|
+
"script link #{@script.url} is a protocol-relative URL, use explicit https:// instead",
|
|
19
|
+
element: @script,
|
|
20
|
+
)
|
|
19
21
|
elsif @script.url.remote?
|
|
20
|
-
add_to_external_urls(@script.url, @script
|
|
22
|
+
add_to_external_urls(@script.url, @script)
|
|
21
23
|
check_sri if @runner.check_sri?
|
|
22
24
|
elsif !@script.url.exists?
|
|
23
|
-
add_failure(
|
|
24
|
-
|
|
25
|
+
add_failure(
|
|
26
|
+
"internal script reference #{@script.src} does not exist",
|
|
27
|
+
element: @script,
|
|
28
|
+
)
|
|
25
29
|
end
|
|
26
30
|
end
|
|
27
31
|
|
|
@@ -34,14 +38,20 @@ module HTMLProofer
|
|
|
34
38
|
|
|
35
39
|
def check_sri
|
|
36
40
|
if blank?(@script.node["integrity"]) && blank?(@script.node["crossorigin"])
|
|
37
|
-
add_failure(
|
|
38
|
-
|
|
41
|
+
add_failure(
|
|
42
|
+
"SRI and CORS not provided in: #{@script.url.raw_attribute}",
|
|
43
|
+
element: @script,
|
|
44
|
+
)
|
|
39
45
|
elsif blank?(@script.node["integrity"])
|
|
40
|
-
add_failure(
|
|
41
|
-
|
|
46
|
+
add_failure(
|
|
47
|
+
"Integrity is missing in: #{@script.url.raw_attribute}",
|
|
48
|
+
element: @script,
|
|
49
|
+
)
|
|
42
50
|
elsif blank?(@script.node["crossorigin"])
|
|
43
|
-
add_failure(
|
|
44
|
-
|
|
51
|
+
add_failure(
|
|
52
|
+
"CORS not provided for external resource in: #{@script.url.raw_attribute}",
|
|
53
|
+
element: @script,
|
|
54
|
+
)
|
|
45
55
|
end
|
|
46
56
|
end
|
|
47
57
|
end
|
data/lib/html_proofer/check.rb
CHANGED
|
@@ -24,36 +24,44 @@ module HTMLProofer
|
|
|
24
24
|
raise NotImplementedError, "HTMLProofer::Check subclasses must implement #run"
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
def add_failure(description, line: nil, status: nil, content: nil)
|
|
28
|
-
@failures << Failure.new(
|
|
29
|
-
|
|
27
|
+
def add_failure(description, element: nil, line: nil, status: nil, content: nil)
|
|
28
|
+
@failures << Failure.new(
|
|
29
|
+
@runner.current_filename,
|
|
30
|
+
short_name,
|
|
31
|
+
description,
|
|
32
|
+
line: element.nil? ? line : element.line,
|
|
33
|
+
status: status,
|
|
34
|
+
content: element.nil? ? content : element.content,
|
|
35
|
+
element: element,
|
|
36
|
+
)
|
|
30
37
|
end
|
|
31
38
|
|
|
32
39
|
def short_name
|
|
33
40
|
self.class.name.split("::").last
|
|
34
41
|
end
|
|
35
42
|
|
|
36
|
-
def add_to_internal_urls(url,
|
|
43
|
+
def add_to_internal_urls(url, element)
|
|
37
44
|
url_string = url.raw_attribute
|
|
38
45
|
|
|
39
46
|
@internal_urls[url_string] = [] if @internal_urls[url_string].nil?
|
|
40
47
|
|
|
41
48
|
metadata = {
|
|
42
|
-
source:
|
|
43
|
-
filename:
|
|
44
|
-
line: line,
|
|
49
|
+
source: url.source,
|
|
50
|
+
filename: url.filename,
|
|
51
|
+
line: element.line,
|
|
45
52
|
base_url: base_url,
|
|
46
53
|
found: false,
|
|
54
|
+
element: element,
|
|
47
55
|
}
|
|
48
56
|
@internal_urls[url_string] << metadata
|
|
49
57
|
end
|
|
50
58
|
|
|
51
|
-
def add_to_external_urls(url,
|
|
59
|
+
def add_to_external_urls(url, element)
|
|
52
60
|
url_string = url.to_s
|
|
53
61
|
|
|
54
62
|
@external_urls[url_string] = [] if @external_urls[url_string].nil?
|
|
55
63
|
|
|
56
|
-
@external_urls[url_string] << { filename:
|
|
64
|
+
@external_urls[url_string] << { filename: url.filename, line: element.line, element: element }
|
|
57
65
|
end
|
|
58
66
|
|
|
59
67
|
class << self
|