validate_html 0.1.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 +7 -0
- data/CHANGELOG.md +1 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +21 -0
- data/README.md +42 -0
- data/lib/validate_html/active_support_notification_handler.rb +22 -0
- data/lib/validate_html/configuration.rb +100 -0
- data/lib/validate_html/mailer_observer.rb +34 -0
- data/lib/validate_html/rack_middleware.rb +57 -0
- data/lib/validate_html/railtie.rb +42 -0
- data/lib/validate_html/version.rb +5 -0
- data/lib/validate_html.rb +187 -0
- data/validate_html.gemspec +53 -0
- metadata +311 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 6a9d52bd97f5669d92f8d0536e3cef55fb5d5247c0e88b6555f375fc1d64ef11
|
4
|
+
data.tar.gz: e44041a0066907734a64ff8062f1bcf30bec36fe61f157b7296925a610e7b1aa
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 594282963b8803f2487f624960f3e67f9fd589ac5c30ff2d3d74b64af038f153b535f08912e20d402452d228ad9f2d65fb08c19a784b18da08fc76918b1547cc
|
7
|
+
data.tar.gz: d366535dff5ca823fbca68303f3ae2055957e5e9c1b4ba8c9d7419cfa3b6689fcb88e17cc4f57cb7d55fa2c9bd1d83cf1d08a75e57d806d4d7b192c5c295b94f
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
Unreleased
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 Ackama
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# ValidateHTML
|
2
|
+
|
3
|
+
Validate HTML as it leaves your rails application.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
```ruby
|
10
|
+
gem 'validate_html'
|
11
|
+
```
|
12
|
+
|
13
|
+
And then execute:
|
14
|
+
|
15
|
+
$ bundle
|
16
|
+
|
17
|
+
Or install it yourself as:
|
18
|
+
|
19
|
+
$ gem install validate_html
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
When included in your Gemfile, when in `development` & `test` environments ValidateHTML will automatically check html leaving your app via rack or html emails or turbo streams for invalid HTML using nokogiri.
|
24
|
+
|
25
|
+
To validate a block of HTML outside of these contexts (including outside of rails entirely), you can use
|
26
|
+
```ruby
|
27
|
+
ValidateHTML.validate_html(my_html_here, **options)
|
28
|
+
```
|
29
|
+
|
30
|
+
## Development
|
31
|
+
|
32
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
33
|
+
|
34
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
35
|
+
|
36
|
+
## Contributing
|
37
|
+
|
38
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/ackama/validate_html.
|
39
|
+
|
40
|
+
## License
|
41
|
+
|
42
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ValidateHTML
|
4
|
+
# Validate HTML from Turbo::StreamsChannel
|
5
|
+
# as called by ActiveSupport::Notifications.instrument
|
6
|
+
module ActiveSupportNotificationHandler
|
7
|
+
# Validate HTML from Turbo::StreamsChannel
|
8
|
+
# as called by ActiveSupport::Notifications.instrument
|
9
|
+
#
|
10
|
+
# @option payload :channel_class [String] if this is Turbo::StreamsChannel the validation will happen
|
11
|
+
# @option payload :data [String] the html to validate
|
12
|
+
# @return [Boolean] true if there are no validation errors
|
13
|
+
# @raise {InvalidHTMLError} if the data of the payload is invalid
|
14
|
+
# and {Configuration#raise_on_invalid_html} is true
|
15
|
+
# @see ValidateHTML.validate_html
|
16
|
+
def self.call(_name, _start, _finish, _id, payload)
|
17
|
+
return unless payload && payload[:channel_class] == 'Turbo::StreamsChannel'
|
18
|
+
|
19
|
+
ValidateHTML.validate_html(payload[:data])
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tmpdir'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
module ValidateHTML
|
7
|
+
# Configuration attributes for ValidateHTML
|
8
|
+
#
|
9
|
+
# @see ValidateHTML.configure
|
10
|
+
class Configuration
|
11
|
+
# Set to false to not raise {InvalidHTMLError} by default
|
12
|
+
#
|
13
|
+
# Defaults to true
|
14
|
+
#
|
15
|
+
# @return [Boolean]
|
16
|
+
# @see ValidateHTML.validate_html
|
17
|
+
# @see ValidateHTML.raise_remembered_messages
|
18
|
+
# @see #remember_messages
|
19
|
+
attr_accessor :raise_on_invalid_html
|
20
|
+
alias_method :raise_on_invalid_html?, :raise_on_invalid_html
|
21
|
+
|
22
|
+
# Set to true to allow using {ValidateHTML.raise_remembered_messages}
|
23
|
+
#
|
24
|
+
# Defaults to false
|
25
|
+
#
|
26
|
+
# @return [Boolean]
|
27
|
+
# @see ValidateHTML.raise_remembered_messages
|
28
|
+
attr_accessor :remember_messages
|
29
|
+
alias_method :remember_messages?, :remember_messages
|
30
|
+
|
31
|
+
# Error messages to ignore
|
32
|
+
# @return [Array<String, Regexp>]
|
33
|
+
attr_reader :ignored_errors
|
34
|
+
|
35
|
+
# App-relative paths to skip automatic validation
|
36
|
+
# @return [Array<String, Regexp>]
|
37
|
+
attr_reader :ignored_paths
|
38
|
+
|
39
|
+
# The rails environments to initialize automatic validation
|
40
|
+
#
|
41
|
+
# Defaults to ["development", "test"]
|
42
|
+
#
|
43
|
+
# This won't take any effect if changed after the app is initialized
|
44
|
+
#
|
45
|
+
# @return [Array<String>]
|
46
|
+
attr_accessor :environments
|
47
|
+
|
48
|
+
def initialize
|
49
|
+
@raise_on_invalid_html = true
|
50
|
+
@ignored_errors = []
|
51
|
+
@ignored_paths = []
|
52
|
+
@environments = %w[development test]
|
53
|
+
@remember_messages = false
|
54
|
+
@snapshot_path = nil
|
55
|
+
end
|
56
|
+
|
57
|
+
# The directory to use for snapshots with invalid HTML
|
58
|
+
#
|
59
|
+
# @default
|
60
|
+
# if Rails is present, this will default to "tmp/invalid_html"
|
61
|
+
# otherwise it will default to a new directory created with Dir.mktmpdir
|
62
|
+
#
|
63
|
+
# @!attribute snapshot_path [rw]
|
64
|
+
# @return [Pathname]
|
65
|
+
# @param [Pathname, String] path
|
66
|
+
def snapshot_path
|
67
|
+
@snapshot_path ||= defined?(::Rails) ? ::Rails.root.join('tmp/invalid_html') : ::Pathname.new(::Dir.mktmpdir)
|
68
|
+
end
|
69
|
+
|
70
|
+
def snapshot_path=(path)
|
71
|
+
@snapshot_path = path.is_a?(Pathname) ? path : ::Pathname.new(path)
|
72
|
+
end
|
73
|
+
|
74
|
+
def ignored_errors=(errors)
|
75
|
+
@ignored_errors = errors
|
76
|
+
@ignored_errors_re = nil
|
77
|
+
end
|
78
|
+
|
79
|
+
def ignored_paths=(paths)
|
80
|
+
@ignored_paths = paths
|
81
|
+
@ignored_paths_re = nil
|
82
|
+
end
|
83
|
+
|
84
|
+
# @!visibility private
|
85
|
+
def ignored_errors_re
|
86
|
+
@ignored_errors_re ||= list_to_re(ignored_errors)
|
87
|
+
end
|
88
|
+
|
89
|
+
# @!visibility private
|
90
|
+
def ignored_paths_re
|
91
|
+
@ignored_paths_re ||= list_to_re(ignored_paths)
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def list_to_re(list)
|
97
|
+
Regexp.union(list.map { |i| i.is_a?(Regexp) ? i : /\A#{Regexp.escape(i)}\z/ })
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ValidateHTML
|
4
|
+
# Validate the HTML of outgoing mail
|
5
|
+
# Use standalone or as an ActionMailer interceptor, observer, or preview_interceptor
|
6
|
+
module MailerObserver
|
7
|
+
class << self
|
8
|
+
# Validate the HTML of outgoing mail
|
9
|
+
# Use standalone or as an ActionMailer interceptor, observer, or preview_interceptor
|
10
|
+
#
|
11
|
+
# @param email [Mail]
|
12
|
+
# @return [Mail] unmodified from the passed in param
|
13
|
+
# @raise {InvalidHTMLError} if the html_part of the email is invalid
|
14
|
+
# and {Configuration#raise_on_invalid_html} is true
|
15
|
+
# @see ValidateHTML.validate_html
|
16
|
+
def perform(email)
|
17
|
+
html_part = email.html_part
|
18
|
+
return email unless html_part
|
19
|
+
|
20
|
+
ValidateHTML.validate_html(
|
21
|
+
html_part.body.raw_source,
|
22
|
+
content_type: html_part.content_type,
|
23
|
+
name: "email #{email.subject}"
|
24
|
+
)
|
25
|
+
|
26
|
+
email
|
27
|
+
end
|
28
|
+
|
29
|
+
alias_method :delivering_email, :perform
|
30
|
+
alias_method :delivered_email, :perform
|
31
|
+
alias_method :previewing_email, :perform
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ValidateHTML
|
4
|
+
# Rack Middleware to validate the HTML of outgoing responses
|
5
|
+
#
|
6
|
+
# This can be used with any rack app
|
7
|
+
class RackMiddleware
|
8
|
+
def initialize(app)
|
9
|
+
@app = app
|
10
|
+
end
|
11
|
+
|
12
|
+
# @param env
|
13
|
+
# @return [Array<(status, headers, response)>]
|
14
|
+
# @raise {InvalidHTMLError} if the response has an HTML content type
|
15
|
+
# and the html is invalid
|
16
|
+
# and {Configuration#raise_on_invalid_html} is true
|
17
|
+
# and the request path isn't ignored by {Configuration#ignored_paths}
|
18
|
+
# @see ValidateHTML.validate_html
|
19
|
+
def call(env)
|
20
|
+
status, headers, response = @app.call(env)
|
21
|
+
path = ::Rack::Request.new(env).path
|
22
|
+
return [status, headers, response] unless checkable_path?(path)
|
23
|
+
|
24
|
+
body = find_body(response)
|
25
|
+
|
26
|
+
return [status, headers, response] unless html_content_type?(headers)
|
27
|
+
|
28
|
+
ValidateHTML.validate_html(body, content_type: headers['Content-Type'], name: path)
|
29
|
+
|
30
|
+
[status, headers, response]
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def checkable_path?(path)
|
36
|
+
!ValidateHTML.configuration.ignored_paths_re.match?(path)
|
37
|
+
end
|
38
|
+
|
39
|
+
def html_content_type?(headers)
|
40
|
+
headers['Content-Type']&.match?(%r{\Atext/(?:vnd\.turbo-stream\.html|html)\b})
|
41
|
+
end
|
42
|
+
|
43
|
+
def find_body(response)
|
44
|
+
if response.respond_to?(:body)
|
45
|
+
find_body(response.body)
|
46
|
+
elsif response.respond_to?(:each) # request specs
|
47
|
+
response.each.to_a.join
|
48
|
+
elsif response.respond_to?(:to_str)
|
49
|
+
response.to_str
|
50
|
+
else
|
51
|
+
''
|
52
|
+
end
|
53
|
+
rescue NoMethodError # sometimes things say they respond to body, then don't.
|
54
|
+
''
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ValidateHTML
|
4
|
+
# Automatic integration of ValidateHTML with Rails
|
5
|
+
# require 'validate_html/railtie'
|
6
|
+
# or just ensure rails is required before validate_html
|
7
|
+
class Railtie < ::Rails::Railtie
|
8
|
+
initializer 'html_validator.configure_rails_initialization' do |app|
|
9
|
+
next unless ::ValidateHTML.configuration.environments.any? { |e| e.to_s == Rails.env }
|
10
|
+
|
11
|
+
app.configure do
|
12
|
+
config.middleware.use(::ValidateHTML::RackMiddleware)
|
13
|
+
|
14
|
+
next unless config.respond_to?(:action_mailer)
|
15
|
+
|
16
|
+
# run first to run before e.g. premailer, which does a parse-then-output step
|
17
|
+
# and will make assumptions with the output that we may want to correct
|
18
|
+
config.action_mailer.interceptors = [
|
19
|
+
::ValidateHTML::MailerObserver,
|
20
|
+
*config.action_mailer.interceptors
|
21
|
+
]
|
22
|
+
|
23
|
+
# and run again after
|
24
|
+
config.action_mailer.observers = [
|
25
|
+
*config.action_mailer.observers,
|
26
|
+
::ValidateHTML::MailerObserver
|
27
|
+
]
|
28
|
+
|
29
|
+
# for completeness, this might be overkill
|
30
|
+
config.action_mailer.preview_interceptors = [
|
31
|
+
*config.action_mailer.preview_interceptors,
|
32
|
+
::ValidateHTML::MailerObserver
|
33
|
+
]
|
34
|
+
end
|
35
|
+
|
36
|
+
::ActiveSupport::Notifications.subscribe(
|
37
|
+
'transmit.action_cable',
|
38
|
+
::ValidateHTML::ActiveSupportNotificationHandler
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# frozen_string_literals: true
|
4
|
+
|
5
|
+
require_relative 'validate_html/version'
|
6
|
+
require_relative 'validate_html/configuration'
|
7
|
+
require_relative 'validate_html/rack_middleware'
|
8
|
+
require_relative 'validate_html/mailer_observer'
|
9
|
+
require_relative 'validate_html/active_support_notification_handler'
|
10
|
+
require_relative 'validate_html/railtie' if defined?(::Rails::Railtie)
|
11
|
+
require 'nokogiri'
|
12
|
+
require 'digest'
|
13
|
+
|
14
|
+
# Validate HTML as it leaves your rails application
|
15
|
+
module ValidateHTML
|
16
|
+
class Error < StandardError; end
|
17
|
+
# This error message will include the full html validation details,
|
18
|
+
# with a path to the snapshot to assist resolving the invalid html
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
# ValidateHTML.validate_html('<strong><em>Very Emphasized</strong></em>', name: 'My Emphasized Fragment')
|
22
|
+
# # raises: ValidateHTML::InvalidHTMLError with this message:
|
23
|
+
# #
|
24
|
+
# # Invalid html from My Emphasized Fragment (ValidateHTML::InvalidHTMLError)
|
25
|
+
# # Parsed using Nokogiri::HTML5::DocumentFragment
|
26
|
+
# # document saved at: [Configuration#snapshot_path]/1a8ce99806ddeccc3a5f2904ba07c7fa5ae4659d.html
|
27
|
+
# #
|
28
|
+
# # 1:28: ERROR: That tag isn't allowed here Currently open tags: html, strong, em.
|
29
|
+
# # <strong><em>Very Emphasized</strong></em>
|
30
|
+
# # ^
|
31
|
+
# # 1:37: ERROR: That tag isn't allowed here Currently open tags: html.
|
32
|
+
# # <strong><em>Very Emphasized</strong></em>
|
33
|
+
# # ^
|
34
|
+
# @see ValidateHTML.validate_html
|
35
|
+
# @see ValidateHTML.raise_remembered_messages
|
36
|
+
class InvalidHTMLError < Error; end
|
37
|
+
|
38
|
+
# This error will be raised when calling {ValidateHTML.raise_remembered_messages}
|
39
|
+
# when {Configuration#remember_messages} is false
|
40
|
+
#
|
41
|
+
# @see ValidateHTML.raise_remembered_messages
|
42
|
+
# @see Configuration#remember_messages
|
43
|
+
class NotRememberingMessagesError < Error; end
|
44
|
+
|
45
|
+
class << self
|
46
|
+
# Validate the HTML using by parsing it with nokogiri
|
47
|
+
#
|
48
|
+
# skip any errors matching patterns in {Configuration#ignored_errors}
|
49
|
+
#
|
50
|
+
# if there are any errors remaining:
|
51
|
+
# remember the errors if {Configuration#remember_messages} is true,
|
52
|
+
# save the invalid html into the {Configuration#snapshot_path} directory,
|
53
|
+
# and raise {InvalidHTMLError} with the full messages if raise_on_invalid_html is true
|
54
|
+
# or return false
|
55
|
+
#
|
56
|
+
# if there are no errors, return true
|
57
|
+
#
|
58
|
+
# @example
|
59
|
+
# ValidateHTML.validate_html('<strong><em>Very Emphasized</strong></em>', name: 'My Emphasized Fragment')
|
60
|
+
# # raises: ValidateHTML::InvalidHTMLError with this message:
|
61
|
+
# #
|
62
|
+
# # Invalid html from My Emphasized Fragment (ValidateHTML::InvalidHTMLError)
|
63
|
+
# # Parsed using Nokogiri::HTML5::DocumentFragment
|
64
|
+
# # Document saved at: [Configuration#snapshot_path]/1a8ce99806ddeccc3a5f2904ba07c7fa5ae4659d.html
|
65
|
+
# #
|
66
|
+
# # 1:28: ERROR: That tag isn't allowed here Currently open tags: html, strong, em.
|
67
|
+
# # <strong><em>Very Emphasized</strong></em>
|
68
|
+
# # ^
|
69
|
+
# # 1:37: ERROR: That tag isn't allowed here Currently open tags: html.
|
70
|
+
# # <strong><em>Very Emphasized</strong></em>
|
71
|
+
# # ^
|
72
|
+
#
|
73
|
+
# @param html [String]
|
74
|
+
# @param name [String] filename or http path or email subject or etc to print in the error message
|
75
|
+
# @param content_type [String] mime type of the document to assist determining encoding
|
76
|
+
# @param raise_on_invalid_html [Boolean] override {Configuration#raise_on_invalid_html}
|
77
|
+
# @return [Boolean] true if there are no validation errors
|
78
|
+
# @raise [InvalidHTMLError] if the html is not valid and raise_on_invalid_html is true
|
79
|
+
def validate_html(html, name: nil, content_type: nil, raise_on_invalid_html: configuration.raise_on_invalid_html?)
|
80
|
+
return true if html.empty?
|
81
|
+
|
82
|
+
doc = parse_html(html, find_encoding(content_type))
|
83
|
+
|
84
|
+
errors = filter_errors(doc.errors)
|
85
|
+
|
86
|
+
return true if errors.empty?
|
87
|
+
|
88
|
+
handle_errors(name, doc, html, errors, raise_on_invalid_html)
|
89
|
+
|
90
|
+
false
|
91
|
+
end
|
92
|
+
|
93
|
+
# Raise any remembered messages
|
94
|
+
#
|
95
|
+
# @return [void]
|
96
|
+
# @raise [InvalidHTMLError] if there are remembered messages
|
97
|
+
# @raise [NotRememberingMessagesError] if {Configuration#remember_messages} is false
|
98
|
+
def raise_remembered_messages
|
99
|
+
raise NotRememberingMessagesError unless configuration.remember_messages?
|
100
|
+
return if remembered_messages.empty?
|
101
|
+
|
102
|
+
messages = remembered_messages
|
103
|
+
forget_messages
|
104
|
+
raise InvalidHTMLError, messages.uniq.join("---\n")
|
105
|
+
end
|
106
|
+
|
107
|
+
# @!attribute [r] remembered_messages
|
108
|
+
# @return [Array<String>]
|
109
|
+
def remembered_messages
|
110
|
+
@remembered_messages ||= []
|
111
|
+
end
|
112
|
+
|
113
|
+
# Clear any remembered messages
|
114
|
+
# @return [void]
|
115
|
+
def forget_messages
|
116
|
+
@remembered_messages = []
|
117
|
+
end
|
118
|
+
|
119
|
+
# @!attribute [r] configuration
|
120
|
+
# @return [Configuration]
|
121
|
+
def configuration
|
122
|
+
@configuration ||= Configuration.new
|
123
|
+
end
|
124
|
+
|
125
|
+
# Configure ValidateHTML
|
126
|
+
#
|
127
|
+
# @example
|
128
|
+
# ValidateHTML.configure do |c|
|
129
|
+
# c.remember_messages = true
|
130
|
+
# c.environments = ['test']
|
131
|
+
# end
|
132
|
+
# @yieldparam config [Configuration]
|
133
|
+
# @return [void]
|
134
|
+
def configure
|
135
|
+
yield configuration
|
136
|
+
end
|
137
|
+
|
138
|
+
private
|
139
|
+
|
140
|
+
def filter_errors(errors)
|
141
|
+
errors.map(&:to_s).grep_v(configuration.ignored_errors_re)
|
142
|
+
end
|
143
|
+
|
144
|
+
def handle_errors(name, doc, body, errors, raise_on_invalid_html)
|
145
|
+
configuration.snapshot_path.mkpath
|
146
|
+
path = configuration.snapshot_path.join("#{::Digest::SHA1.hexdigest(body)}.html")
|
147
|
+
path.write(body)
|
148
|
+
|
149
|
+
message = <<~ERROR
|
150
|
+
Invalid html#{" from #{name}" if name}
|
151
|
+
Parsed using #{doc.class}
|
152
|
+
document saved at: #{path}
|
153
|
+
|
154
|
+
#{errors.join("\n")}
|
155
|
+
ERROR
|
156
|
+
|
157
|
+
remembered_messages << message if configuration.remember_messages?
|
158
|
+
|
159
|
+
raise InvalidHTMLError, message if raise_on_invalid_html
|
160
|
+
|
161
|
+
warn message
|
162
|
+
end
|
163
|
+
|
164
|
+
def parse_html(body, encoding)
|
165
|
+
case body
|
166
|
+
when /\A\s*<!doctype html>/i
|
167
|
+
::Nokogiri::HTML5.parse(body, nil, encoding, max_errors: -1)
|
168
|
+
when /\A\s*<(!doctype|html)/i
|
169
|
+
::Nokogiri::HTML4.parse(
|
170
|
+
body,
|
171
|
+
nil,
|
172
|
+
encoding,
|
173
|
+
::Nokogiri::XML::ParseOptions::DEFAULT_HTML | ::Nokogiri::XML::ParseOptions::STRICT
|
174
|
+
)
|
175
|
+
else
|
176
|
+
::Nokogiri::HTML5.fragment(body, encoding, max_errors: -1)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def find_encoding(content_type)
|
181
|
+
return unless content_type
|
182
|
+
|
183
|
+
parts = content_type.split(/;\s*/)
|
184
|
+
parts.find { |part| part.start_with?('charset=') }&.delete_prefix('charset=')
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'validate_html/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'validate_html'
|
9
|
+
spec.version = ValidateHTML::VERSION
|
10
|
+
spec.authors = ['Ackama']
|
11
|
+
spec.email = ['opensource@ackama.com']
|
12
|
+
|
13
|
+
spec.summary = 'Validate HTML files as they leave your app by rack or by mail or by turbo-stream'
|
14
|
+
spec.homepage = 'https://github.com/ackama/validate_html'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.required_ruby_version = '>= 2.7.0'
|
18
|
+
|
19
|
+
if spec.respond_to?(:metadata)
|
20
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
21
|
+
spec.metadata['source_code_uri'] = spec.homepage
|
22
|
+
spec.metadata['changelog_uri'] = "#{spec.homepage}/blob/main/CHANGELOG.md"
|
23
|
+
spec.metadata['rubygems_mfa_required'] = 'true'
|
24
|
+
end
|
25
|
+
|
26
|
+
spec.files = Dir.glob('lib/**/{*,.*}') + %w[
|
27
|
+
CHANGELOG.md
|
28
|
+
Gemfile
|
29
|
+
LICENSE.txt
|
30
|
+
README.md
|
31
|
+
validate_html.gemspec
|
32
|
+
]
|
33
|
+
spec.require_paths = ['lib']
|
34
|
+
|
35
|
+
spec.add_development_dependency 'bundler', '>= 2.0'
|
36
|
+
spec.add_development_dependency 'rake', '>= 12.0'
|
37
|
+
spec.add_development_dependency 'rspec', '>= 3.0'
|
38
|
+
spec.add_development_dependency 'yard'
|
39
|
+
spec.add_dependency 'nokogiri'
|
40
|
+
spec.add_development_dependency 'actionmailer'
|
41
|
+
spec.add_development_dependency 'activesupport'
|
42
|
+
spec.add_development_dependency 'leftovers'
|
43
|
+
spec.add_development_dependency 'mail'
|
44
|
+
spec.add_development_dependency 'pry'
|
45
|
+
spec.add_development_dependency 'railties'
|
46
|
+
spec.add_development_dependency 'rubocop'
|
47
|
+
spec.add_development_dependency 'rubocop-performance'
|
48
|
+
spec.add_development_dependency 'rubocop-rake'
|
49
|
+
spec.add_development_dependency 'rubocop-rspec'
|
50
|
+
spec.add_development_dependency 'simplecov'
|
51
|
+
spec.add_development_dependency 'simplecov-console'
|
52
|
+
spec.add_development_dependency 'spellr'
|
53
|
+
end
|
metadata
ADDED
@@ -0,0 +1,311 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: validate_html
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ackama
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-10-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '12.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '12.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: yard
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: nokogiri
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: actionmailer
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: activesupport
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: leftovers
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: mail
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: pry
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: railties
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: rubocop
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - ">="
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - ">="
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '0'
|
181
|
+
- !ruby/object:Gem::Dependency
|
182
|
+
name: rubocop-performance
|
183
|
+
requirement: !ruby/object:Gem::Requirement
|
184
|
+
requirements:
|
185
|
+
- - ">="
|
186
|
+
- !ruby/object:Gem::Version
|
187
|
+
version: '0'
|
188
|
+
type: :development
|
189
|
+
prerelease: false
|
190
|
+
version_requirements: !ruby/object:Gem::Requirement
|
191
|
+
requirements:
|
192
|
+
- - ">="
|
193
|
+
- !ruby/object:Gem::Version
|
194
|
+
version: '0'
|
195
|
+
- !ruby/object:Gem::Dependency
|
196
|
+
name: rubocop-rake
|
197
|
+
requirement: !ruby/object:Gem::Requirement
|
198
|
+
requirements:
|
199
|
+
- - ">="
|
200
|
+
- !ruby/object:Gem::Version
|
201
|
+
version: '0'
|
202
|
+
type: :development
|
203
|
+
prerelease: false
|
204
|
+
version_requirements: !ruby/object:Gem::Requirement
|
205
|
+
requirements:
|
206
|
+
- - ">="
|
207
|
+
- !ruby/object:Gem::Version
|
208
|
+
version: '0'
|
209
|
+
- !ruby/object:Gem::Dependency
|
210
|
+
name: rubocop-rspec
|
211
|
+
requirement: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - ">="
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: '0'
|
216
|
+
type: :development
|
217
|
+
prerelease: false
|
218
|
+
version_requirements: !ruby/object:Gem::Requirement
|
219
|
+
requirements:
|
220
|
+
- - ">="
|
221
|
+
- !ruby/object:Gem::Version
|
222
|
+
version: '0'
|
223
|
+
- !ruby/object:Gem::Dependency
|
224
|
+
name: simplecov
|
225
|
+
requirement: !ruby/object:Gem::Requirement
|
226
|
+
requirements:
|
227
|
+
- - ">="
|
228
|
+
- !ruby/object:Gem::Version
|
229
|
+
version: '0'
|
230
|
+
type: :development
|
231
|
+
prerelease: false
|
232
|
+
version_requirements: !ruby/object:Gem::Requirement
|
233
|
+
requirements:
|
234
|
+
- - ">="
|
235
|
+
- !ruby/object:Gem::Version
|
236
|
+
version: '0'
|
237
|
+
- !ruby/object:Gem::Dependency
|
238
|
+
name: simplecov-console
|
239
|
+
requirement: !ruby/object:Gem::Requirement
|
240
|
+
requirements:
|
241
|
+
- - ">="
|
242
|
+
- !ruby/object:Gem::Version
|
243
|
+
version: '0'
|
244
|
+
type: :development
|
245
|
+
prerelease: false
|
246
|
+
version_requirements: !ruby/object:Gem::Requirement
|
247
|
+
requirements:
|
248
|
+
- - ">="
|
249
|
+
- !ruby/object:Gem::Version
|
250
|
+
version: '0'
|
251
|
+
- !ruby/object:Gem::Dependency
|
252
|
+
name: spellr
|
253
|
+
requirement: !ruby/object:Gem::Requirement
|
254
|
+
requirements:
|
255
|
+
- - ">="
|
256
|
+
- !ruby/object:Gem::Version
|
257
|
+
version: '0'
|
258
|
+
type: :development
|
259
|
+
prerelease: false
|
260
|
+
version_requirements: !ruby/object:Gem::Requirement
|
261
|
+
requirements:
|
262
|
+
- - ">="
|
263
|
+
- !ruby/object:Gem::Version
|
264
|
+
version: '0'
|
265
|
+
description:
|
266
|
+
email:
|
267
|
+
- opensource@ackama.com
|
268
|
+
executables: []
|
269
|
+
extensions: []
|
270
|
+
extra_rdoc_files: []
|
271
|
+
files:
|
272
|
+
- CHANGELOG.md
|
273
|
+
- Gemfile
|
274
|
+
- LICENSE.txt
|
275
|
+
- README.md
|
276
|
+
- lib/validate_html.rb
|
277
|
+
- lib/validate_html/active_support_notification_handler.rb
|
278
|
+
- lib/validate_html/configuration.rb
|
279
|
+
- lib/validate_html/mailer_observer.rb
|
280
|
+
- lib/validate_html/rack_middleware.rb
|
281
|
+
- lib/validate_html/railtie.rb
|
282
|
+
- lib/validate_html/version.rb
|
283
|
+
- validate_html.gemspec
|
284
|
+
homepage: https://github.com/ackama/validate_html
|
285
|
+
licenses:
|
286
|
+
- MIT
|
287
|
+
metadata:
|
288
|
+
homepage_uri: https://github.com/ackama/validate_html
|
289
|
+
source_code_uri: https://github.com/ackama/validate_html
|
290
|
+
changelog_uri: https://github.com/ackama/validate_html/blob/main/CHANGELOG.md
|
291
|
+
rubygems_mfa_required: 'true'
|
292
|
+
post_install_message:
|
293
|
+
rdoc_options: []
|
294
|
+
require_paths:
|
295
|
+
- lib
|
296
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
297
|
+
requirements:
|
298
|
+
- - ">="
|
299
|
+
- !ruby/object:Gem::Version
|
300
|
+
version: 2.7.0
|
301
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
302
|
+
requirements:
|
303
|
+
- - ">="
|
304
|
+
- !ruby/object:Gem::Version
|
305
|
+
version: '0'
|
306
|
+
requirements: []
|
307
|
+
rubygems_version: 3.3.7
|
308
|
+
signing_key:
|
309
|
+
specification_version: 4
|
310
|
+
summary: Validate HTML files as they leave your app by rack or by mail or by turbo-stream
|
311
|
+
test_files: []
|