premailer 1.32.0 → 1.33.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2186c26ccc50f22227a6dbbf53b4cd3b5ae5f8bebdbdc190b0e7fd0e5a152565
4
- data.tar.gz: 84becf9c1329983be14a427eef560806fb23ceb05847b7c5484033fe2fda0248
3
+ metadata.gz: b30285e2878d409bc44b84777bc055096cd023b666dd7453164586fb8e97a7dd
4
+ data.tar.gz: 47bfb72f292747619e4067332275f40f85bd225725977507a57e97bebffbdd71
5
5
  SHA512:
6
- metadata.gz: c489d4a76330f66eeb60793faebc2839591870ee261179c0d9369ef95e5194d967f2b0f1aec31f16edb9355e363766fcb9245e67f13d2bb39b53cf1598ab8e36
7
- data.tar.gz: 48a0cf85f5d2c2e3cbc2e7a5b21e70bf0e9e9215b2067703b9f9e5574342be25901aa639b1fcbdc3889e1d883905ad30e1e300d188fee68fd043f0de1588e2f2
6
+ metadata.gz: 77556f76f97fa85e0047b1e45ebdd530853d3cc33794366b3979b4475920a6b87163853d8349b2f507e6fbafa24a2a747529f448f3335b3b03446713266b3ab2
7
+ data.tar.gz: 50ac02894d5fef9ccc6e5b8df0e75feac8dcedff61eacdca4fac43230f9802319907648377666f132e19da46585965020adf128d70d0a7e7d12348d70103f7d9
data/README.md CHANGED
@@ -39,6 +39,9 @@ premailer.warnings.each do |w|
39
39
  end
40
40
  ```
41
41
 
42
+ The plain-text version excludes the contents of `<style>` and `<script>`
43
+ elements, including styles retained in HTML with `preserve_styles: true`.
44
+
42
45
  ## Adapters
43
46
 
44
47
  1. nokogiri (default)
@@ -53,6 +56,10 @@ Picking an adapter:
53
56
  Premailer::Adapter.use = :nokogiri_fast
54
57
  ```
55
58
 
59
+ When converting image CSS to HTML attributes, only `float: left` and
60
+ `float: right` become `align` attributes. Other values, such as `float: none`,
61
+ remain in the inline style because they have no equivalent HTML alignment.
62
+
56
63
  ## Ruby Compatibility
57
64
 
58
65
  See .github/workflows/actions.yml for which ruby versions are tested. JRuby support is close, contributors are welcome.
@@ -72,6 +79,9 @@ Premailer looks for a few CSS attributes that make working with tables a bit eas
72
79
 
73
80
  Each of these CSS declarations will be copied to appropriate element's attribute.
74
81
 
82
+ Background image URLs on tables are copied to the HTML `background` attribute.
83
+ Gradients remain in inline CSS because that attribute only supports image URLs.
84
+
75
85
  For example
76
86
 
77
87
  ```css
@@ -127,6 +137,10 @@ Premailer.new(
127
137
 
128
138
  [available options](https://premailer.github.io/premailer/Premailer.html#initialize-instance_method)
129
139
 
140
+ For the current options, see the [constructor source](https://github.com/premailer/premailer/blob/master/lib/premailer/premailer.rb).
141
+
142
+ Verbose diagnostics are written to stderr. The command-line tool keeps its generated HTML on stdout, including when `--verbose` is enabled.
143
+
130
144
  ### Loading CSS from local/private hosts
131
145
 
132
146
  css_parser 3+ blocks fetching remote CSS (`<link>`, `@import`) from hosts that resolve to loopback/private IPs
@@ -137,6 +151,11 @@ To allow them, when the html is trusted:
137
151
  Premailer.new(html, with_html_string: true, allow_local_network: true)
138
152
  ```
139
153
 
154
+ ### Subresource Integrity
155
+
156
+ Remote `<link rel="stylesheet">` tags with an `integrity` attribute are verified before inlining.
157
+ A stylesheet whose hash does not match is dropped (or raises `CssParser::IntegrityError` with `io_exceptions: true`).
158
+
140
159
  ## Support for CSS variables
141
160
 
142
161
  The gem does not automatically replace CSS variables with their static values.
@@ -219,6 +238,29 @@ A few areas that are particularly in need of love:
219
238
  * Improved test coverage
220
239
  * Move un-repeated background images defined in CSS for Outlook
221
240
 
241
+ ## Generating API documentation
242
+
243
+ Generate the API reference with [YARD](https://yardoc.org/) from the repository root:
244
+
245
+ ```sh
246
+ bundle install
247
+ bundle exec yard doc
248
+ ```
249
+
250
+ Open the generated files in a browser. On macOS:
251
+
252
+ ```sh
253
+ open doc/index.html # all docs
254
+ open doc/Premailer.html # constructor and its options
255
+ ```
256
+
257
+ The generated `doc/` and `.yardoc/` directories are ignored by Git.
258
+
259
+ To update the [hosted documentation](https://premailer.github.io/premailer/), a maintainer
260
+ regenerates the reference for the desired version and copies the contents of `doc/` to
261
+ the root of the [`gh-pages` branch](https://github.com/premailer/premailer/tree/gh-pages).
262
+ After reviewing, committing, and pushing those changes, GitHub Pages publishes that branch's root.
263
+
222
264
  ## Credits and code
223
265
 
224
266
  Thanks to [all the wonderful contributors](https://github.com/premailer/premailer/contributors) for their updates.
@@ -6,6 +6,11 @@ class Premailer
6
6
  # Nokogiri adapter
7
7
  module Nokogiri
8
8
  WIDTH_AND_HIGHT = ['width', 'height'].freeze
9
+ # Selectors made only of element names, classes, ids, combinators and
10
+ # pseudo-classes without arguments: no attribute, string, function or escape
11
+ # can hide a "." that is not a class.
12
+ PLAIN_SELECTOR = /\A[\w\s.#>+~*:-]+\z/
13
+ CLASS_IN_SELECTOR = /\.([\w-]+)/
9
14
 
10
15
  include AdapterHelper::RgbToHex
11
16
  # Merge CSS into the HTML document.
@@ -31,6 +36,10 @@ class Premailer
31
36
  rs = rule_set_cache.fetch(style) { rule_set_cache[style] = build_rule_set(style, 1000) }
32
37
  rules_by_node[el] = rs ? [rs] : []
33
38
  end
39
+ # A selector requiring a class that no element carries cannot match:
40
+ # skip its (costly) document-wide search.
41
+ classes_in_doc = doc.xpath('.//@class').flat_map { |attribute| attribute.value.split }.to_set
42
+
34
43
  # Iterate through the rules and merge them into the HTML
35
44
  @css_parser.each_selector(:all) do |selector, declaration, specificity, media_types|
36
45
  # Save un-mergable rules separately
@@ -49,6 +58,8 @@ class Premailer
49
58
  @unmergable_rules.add_rule_set!(CssParser::RuleSet.new(selectors: selector, block: declaration))
50
59
  end
51
60
 
61
+ next if selector_requires_absent_class?(selector, classes_in_doc)
62
+
52
63
  # Change single ID CSS selectors into xpath so that we can match more
53
64
  # than one element. Added to work around dodgy generated code.
54
65
  selector.gsub!(/\A\#([\w_\-]+)\Z/, '*[@id=\1]')
@@ -176,6 +187,9 @@ class Premailer
176
187
  # Duplicate CSS attributes as HTML attributes
177
188
  if related_el_name
178
189
  Premailer::RELATED_ATTRIBUTES[related_el_name].each do |css_attr, html_attr|
190
+ next if css_gradient?(css_attr, merged)
191
+ next if css_float_without_alignment?(css_attr, merged)
192
+
179
193
  unless merged[css_attr].empty?
180
194
  new_val = merged[css_attr].dup
181
195
 
@@ -209,6 +223,11 @@ class Premailer
209
223
  [merged.declarations_to_s, attributes]
210
224
  end
211
225
 
226
+ def selector_requires_absent_class?(selector, classes_in_doc) # :nodoc:
227
+ PLAIN_SELECTOR.match?(selector) &&
228
+ selector.scan(CLASS_IN_SELECTOR).any? { |(name)| !classes_in_doc.include?(name) }
229
+ end
230
+
212
231
  # Build an expanded rule set for folding. Returns nil (and optionally
213
232
  # swallows the error, mirroring the old fold-time rescue) on bad CSS.
214
233
  def build_rule_set(block, specificity) # :nodoc:
@@ -302,7 +321,9 @@ class Premailer
302
321
  end
303
322
  encoding = @options[:input_encoding] || (RUBY_PLATFORM == 'java' ? nil : 'BINARY')
304
323
  doc = if @options[:html_fragment]
305
- ::Nokogiri::HTML.fragment(thing, encoding)
324
+ # libxml2 cannot serialize a fragment parsed as binary (sparklemotion/nokogiri#553):
325
+ # without an encoding, Nokogiri uses the string's own, or UTF-8 if it is binary.
326
+ ::Nokogiri::HTML.fragment(thing, (encoding unless encoding.to_s.match?(/\A(ASCII-8BIT|BINARY)\z/i)))
306
327
  else
307
328
  ::Nokogiri::HTML(thing, nil, encoding, &:recover)
308
329
  end
@@ -85,6 +85,9 @@ class Premailer
85
85
  # Duplicate CSS attributes as HTML attributes
86
86
  if Premailer::RELATED_ATTRIBUTES.key?(el.name) && @options[:css_to_attributes]
87
87
  Premailer::RELATED_ATTRIBUTES[el.name].each do |css_attr, html_attr|
88
+ next if css_gradient?(css_attr, merged)
89
+ next if css_float_without_alignment?(css_attr, merged)
90
+
88
91
  if el[html_attr].nil? && !merged[css_attr].empty?
89
92
  new_val = merged[css_attr].dup
90
93
 
@@ -245,7 +248,9 @@ class Premailer
245
248
  end
246
249
  encoding = @options[:input_encoding] || (RUBY_PLATFORM == 'java' ? nil : 'BINARY')
247
250
  doc = if @options[:html_fragment]
248
- ::Nokogiri::HTML.fragment(thing, encoding)
251
+ # libxml2 cannot serialize a fragment parsed as binary (sparklemotion/nokogiri#553):
252
+ # without an encoding, Nokogiri uses the string's own, or UTF-8 if it is binary.
253
+ ::Nokogiri::HTML.fragment(thing, (encoding unless encoding.to_s.match?(/\A(ASCII-8BIT|BINARY)\z/i)))
249
254
  else
250
255
  ::Nokogiri::HTML(thing, nil, encoding, &:recover)
251
256
  end
@@ -80,6 +80,9 @@ class Premailer
80
80
  # Duplicate CSS attributes as HTML attributes
81
81
  if Premailer::RELATED_ATTRIBUTES.key?(el.name) && @options[:css_to_attributes]
82
82
  Premailer::RELATED_ATTRIBUTES[el.name].each do |css_attr, html_attr|
83
+ next if css_gradient?(css_attr, merged)
84
+ next if css_float_without_alignment?(css_attr, merged)
85
+
83
86
  if el[html_attr].nil? && !merged[css_attr].empty?
84
87
  new_val = merged[css_attr].dup
85
88
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
- require 'htmlentities'
2
+ require 'nokogiri'
3
3
 
4
4
  # Support functions for Premailer
5
5
  module HtmlToPlainText
@@ -20,6 +20,9 @@ module HtmlToPlainText
20
20
  # text version
21
21
  txt.gsub!(/<!-- start text\/html -->.*?<!-- end text\/html -->/m, '')
22
22
 
23
+ # Metadata is not readable text, even when styles are preserved in HTML.
24
+ txt.gsub!(/<(script|style)\b[^>]*>.*?<\/\1\s*>/im, '')
25
+
23
26
  # replace images with their alt attributes
24
27
  # for img tags with "" for attribute quotes
25
28
  # with or without closing tag
@@ -35,9 +38,6 @@ module HtmlToPlainText
35
38
  # <img alt=''>
36
39
  txt.gsub!(/<img[^>]+?alt='([^']*)'[^>]*>/i, '\1')
37
40
 
38
- # remove script tags and content
39
- txt.gsub!(/<script.*?\/script>/m, '')
40
-
41
41
  # links
42
42
  txt.gsub!(/<a\s+([^>]+)>(.*?)<\/a>/im) do |s|
43
43
  text = $2.strip
@@ -101,9 +101,8 @@ module HtmlToPlainText
101
101
  # strip remaining tags
102
102
  txt.gsub!(/<\/?[^>]*>/, '')
103
103
 
104
- # decode HTML entities
105
- he = HTMLEntities.new
106
- txt = he.decode(txt)
104
+ # decode HTML entities; a textarea context parses as RCDATA, so a stray "<" stays text
105
+ txt = Nokogiri::HTML5::DocumentFragment.new(Nokogiri::HTML5::Document.new, txt, "textarea").text
107
106
 
108
107
  # word wrap
109
108
  txt = word_wrap(txt, line_length)
@@ -271,6 +271,29 @@ class Premailer
271
271
 
272
272
  protected
273
273
 
274
+ # Keep e.g. background-image: image-set(linear-gradient(red, blue), url(a.png))
275
+ # in CSS: a URL-only HTML background attribute cannot represent its gradient.
276
+ def css_gradient?(css_attr, merged) # :nodoc:
277
+ return false unless css_attr == 'background-image'
278
+
279
+ # Ignore gradient-like text inside URLs and quoted strings.
280
+ value = merged[css_attr].gsub(CssParser::URI_RX, '').gsub(CssParser::RE_STRING, '')
281
+ value.match?(
282
+ /(?:\A|[,(])\s* # Start of a value, list item, or nested function
283
+ (?:-[a-z]+-)? # Optional vendor prefix, e.g. -webkit-
284
+ (?:repeating-)? # Optional repeating gradient
285
+ (?:linear|radial|conic)-gradient\( # Supported gradient function names
286
+ /ix
287
+ )
288
+ end
289
+
290
+ # Only left/right floats have an equivalent legacy image alignment.
291
+ def css_float_without_alignment?(css_attr, merged) # :nodoc:
292
+ return false unless css_attr == 'float'
293
+
294
+ !merged[css_attr].match?(/\A\s*(?:left|right)\s*(?:!important\s*)?;?\s*\z/i)
295
+ end
296
+
274
297
  def load_css_from_local_file!(path)
275
298
  css_block = +''
276
299
  path.delete_prefix!('file:')
@@ -332,7 +355,7 @@ class Premailer
332
355
  load_css_from_local_file!(link_uri)
333
356
  else
334
357
  warn "Loading css from uri: " + link_uri if @options[:verbose]
335
- @css_parser.load_uri!(link_uri, { :only_media_types => [:screen, :handheld] })
358
+ @css_parser.load_uri!(link_uri, { :only_media_types => [:screen, :handheld], :integrity => tag.attributes['integrity']&.to_s })
336
359
  end
337
360
 
338
361
  elsif tag.to_s.strip =~ /^<style/i && @options[:include_style_tags]
@@ -414,7 +437,7 @@ class Premailer
414
437
  end
415
438
 
416
439
  if href.scheme && (href.scheme != 'http') && (href.scheme != 'https')
417
- puts "Skipping append_query_string for: #{href} because scheme is no good" if @options[:verbose]
440
+ warn "Skipping append_query_string for: #{href} because scheme is no good" if @options[:verbose]
418
441
  next
419
442
  end
420
443
 
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
  class Premailer
3
3
  # Premailer version.
4
- VERSION = '1.32.0'
4
+ VERSION = '1.33.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: premailer
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.32.0
4
+ version: 1.33.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Dunae
@@ -29,28 +29,14 @@ dependencies:
29
29
  requirements:
30
30
  - - ">="
31
31
  - !ruby/object:Gem::Version
32
- version: 1.19.0
32
+ version: 3.1.0
33
33
  type: :runtime
34
34
  prerelease: false
35
35
  version_requirements: !ruby/object:Gem::Requirement
36
36
  requirements:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
- version: 1.19.0
40
- - !ruby/object:Gem::Dependency
41
- name: htmlentities
42
- requirement: !ruby/object:Gem::Requirement
43
- requirements:
44
- - - ">="
45
- - !ruby/object:Gem::Version
46
- version: 4.0.0
47
- type: :runtime
48
- prerelease: false
49
- version_requirements: !ruby/object:Gem::Requirement
50
- requirements:
51
- - - ">="
52
- - !ruby/object:Gem::Version
53
- version: 4.0.0
39
+ version: 3.1.0
54
40
  description: Improve the rendering of HTML emails by making CSS inline, converting
55
41
  links and warning about unsupported code.
56
42
  email: akzhan.abdulin@gmail.com