css_parser 3.0.0 → 3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7286523850595059190f244936748ce609fc7d22de85ef56d5c5c229bd9a4ba4
4
- data.tar.gz: 64fe512e21e1687c4221be94793eedbf603bd5723ef32a804177f70ba96e41b7
3
+ metadata.gz: 7c675c0d95c9efce004a49e17cf09c880c44c4c11375f754c50586052c03202d
4
+ data.tar.gz: 2d729de7e39e045d1e2594e4c847c495ef7765d34c37b76f53ef9348de0e1f96
5
5
  SHA512:
6
- metadata.gz: 63fa1631ae27fb97c375eb50d2e2b29f99e029872be0ad82191834818ad989470517c7d52cb77291a6a2a58fd920c3d1aa10a6250b5b569641016c77862b57cc
7
- data.tar.gz: e6c24be4780b12aff38d866e5f0bc6b2b17deb78a94d2bb5072ec23bd82fcd70e1bcab63e4e02e915d2796e09dcaf9f5a239614c5c792d33003bb8ef0ca89d00
6
+ metadata.gz: ab3178e90c357ad4ede9c90ac70526bbe4b90a475690afa60551d4acfc2bb41a70db125bdda040a1bbcfa8f4e3dfa454401116aec745e639af9244c71c3f7c6a
7
+ data.tar.gz: 15838f208ef82d6a2d5adbddcb5486ad5b13b0d88f32e3923b6f7bd064fbbe08dc4f6dc4af0a299a517ff7c4faec3010a044b59209b809248e58622b08a29c4a
@@ -1,11 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'strscan'
4
+ require 'digest'
5
+ require 'base64'
4
6
 
5
7
  module CssParser
6
8
  # Exception class used for any errors encountered while downloading remote files.
7
9
  class RemoteFileError < IOError; end
8
10
 
11
+ # Exception class used when a fetched remote file fails Subresource Integrity verification.
12
+ class IntegrityError < RemoteFileError; end
13
+
9
14
  # Exception class used if a request is made to load a CSS file more than once.
10
15
  class CircularReferenceError < StandardError; end
11
16
 
@@ -37,6 +42,15 @@ module CssParser
37
42
  # was GHSA-9pmc-p236-855h.
38
43
  REMOTE_ALLOWED_SCHEMES = %w[http https].freeze
39
44
 
45
+ # Subresource Integrity hash algorithms this library can verify, strongest first.
46
+ # Mirrors the SRI spec's "agility" rule (https://www.w3.org/TR/SRI/#agility):
47
+ # if `integrity` lists multiple algorithms, only the strongest is checked.
48
+ INTEGRITY_ALGORITHMS = {
49
+ 'sha512' => Digest::SHA512,
50
+ 'sha384' => Digest::SHA384,
51
+ 'sha256' => Digest::SHA256
52
+ }.freeze
53
+
40
54
  # Array of CSS files that have been loaded.
41
55
  attr_reader :loaded_uris
42
56
 
@@ -492,9 +506,7 @@ module CssParser
492
506
  #
493
507
  # You can also pass in file://test.css
494
508
  #
495
- # See add_block! for options.
496
- #
497
- # Deprecated: originally accepted three params: `uri`, `base_uri` and `media_types`
509
+ # See add_block! for options and integrity_matches? for integrity
498
510
  def load_uri!(uri, options = {}, deprecated = nil)
499
511
  uri = Addressable::URI.parse(uri) unless uri.respond_to? :scheme
500
512
 
@@ -502,7 +514,7 @@ module CssParser
502
514
 
503
515
  if options.is_a? Hash
504
516
  opts.merge!(options)
505
- else
517
+ else # TODO: start raising
506
518
  warn '[DEPRECATION] `load_uri!` with positional arguments is deprecated. ' \
507
519
  'Please use keyword arguments instead.', uplevel: 1
508
520
  opts[:base_uri] = options if options.is_a? String
@@ -538,7 +550,7 @@ module CssParser
538
550
  end
539
551
  read_local_file(uri)
540
552
  else
541
- src_and_charset, = read_remote_file(uri) # skip charset
553
+ src_and_charset, = read_remote_file(uri, integrity: opts[:integrity]) # skip charset
542
554
  src_and_charset
543
555
  end
544
556
 
@@ -677,10 +689,14 @@ module CssParser
677
689
  # is still validated on every redirect hop, so cross-scheme
678
690
  # redirect to `file://` (the original GHSA-9pmc-p236-855h sink)
679
691
  # remains closed even on this opt-in path.
692
+ #
693
+ # `integrity:`, when given, is verified against the raw response body
694
+ # (before charset decoding, matching Subresource Integrity semantics)
695
+ # -- see `integrity_matches?` and `load_uri!`'s documentation.
680
696
  #--
681
697
  # TODO: add option to fail silently or throw and exception on a 404
682
698
  #++
683
- def read_remote_file(uri) # :nodoc:
699
+ def read_remote_file(uri, integrity: nil) # :nodoc:
684
700
  uri = Addressable::URI.parse(uri.to_s)
685
701
 
686
702
  unless circular_reference_check(uri.to_s)
@@ -711,11 +727,19 @@ module CssParser
711
727
  return '', nil
712
728
  end
713
729
 
730
+ if integrity && !integrity_matches?(res.body, integrity)
731
+ raise IntegrityError, uri.to_s if @options[:io_exceptions]
732
+
733
+ return nil, nil
734
+ end
735
+
714
736
  charset = res.respond_to?(:charset) ? res.encoding : 'utf-8'
715
737
  src = res.body
716
738
  src.encode!('UTF-8', charset) if charset
717
739
 
718
740
  [src, charset]
741
+ rescue IntegrityError # already is a RemoteFileError
742
+ raise
719
743
  rescue
720
744
  raise RemoteFileError, uri.to_s if @options[:io_exceptions]
721
745
 
@@ -723,6 +747,28 @@ module CssParser
723
747
  end
724
748
  end
725
749
 
750
+ # Verifies +body+ (raw response bytes, not yet charset-decoded) against a
751
+ # Subresource Integrity value -- a `<algorithm>-<base64 digest>` token, or
752
+ # several whitespace-separated tokens (https://www.w3.org/TR/SRI/#the-integrity-attribute).
753
+ #
754
+ # - Tokens using an algorithm this library doesn't recognize are ignored
755
+ # - Multiple recognized algorithms only check the strongest one is checked
756
+ # - Value containing no recognized algorithm is treated as unverifiable and matches by default
757
+ def integrity_matches?(body, integrity) # :nodoc:
758
+ candidates = integrity.to_s.split.filter_map do |token|
759
+ algorithm, value = token.split('-', 2)
760
+ [algorithm, value] if algorithm && value && INTEGRITY_ALGORITHMS.key?(algorithm)
761
+ end
762
+ return true if candidates.empty?
763
+
764
+ algorithms_present = candidates.map(&:first)
765
+ algorithm = INTEGRITY_ALGORITHMS.each_key.find { |a| algorithms_present.include?(a) }
766
+ expected_values = candidates.select { |a, _v| a == algorithm }.map { |_a, v| v }
767
+ digest_class = INTEGRITY_ALGORITHMS.fetch(algorithm)
768
+
769
+ expected_values.include?(Base64.strict_encode64(digest_class.digest(body)))
770
+ end
771
+
726
772
  # Net::HTTP path used only when `allow_local_network: true`. Validates
727
773
  # the URI scheme on every redirect hop so a `Location: file://...`
728
774
  # cannot be followed even on this opt-in code path.
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CssParser
4
- VERSION = '3.0.0'.freeze
4
+ VERSION = '3.1.0'.freeze
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: css_parser
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex Dunae
@@ -23,6 +23,20 @@ dependencies:
23
23
  - - ">="
24
24
  - !ruby/object:Gem::Version
25
25
  version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: base64
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
26
40
  - !ruby/object:Gem::Dependency
27
41
  name: ssrf_filter
28
42
  requirement: !ruby/object:Gem::Requirement