crass 1.0.6 → 1.0.7

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: def285e9f3d16e7222dce13f004b8ad8b936c76ecec909306cf15078f6ec8999
4
- data.tar.gz: 6fbb08644b3ff290d66012bbfa9afa249ab565f9e67240e13d433715d1b535b7
3
+ metadata.gz: a90afad390dcbfa666bb15dcc55ff6bc7711a4fabd9e5a920c53783bc22d2c8b
4
+ data.tar.gz: ba5fe09c8233f06503da5bf0f89a9c716cb19aa6b867eb6e1bff3f3d7153446d
5
5
  SHA512:
6
- metadata.gz: 43daba64a022574e3ac4b439c5dca3cc329e2217e47df9eeaf79987748fa949feeaccb4745a212ebb9e91f01d4b5ec433947b57380efda233d078a9f1f1ea8ab
7
- data.tar.gz: aa88544b1374fa5fb774970e5b714e6913c23e750a0e84767548e9c8076d25851f9f8861b38dfef0c9262a8c7785bce82436ad19c951ea4a9836fcee4a163199
6
+ metadata.gz: efc02970ebefe0e0baf624b97c4aa43f9ce95c39acb7c3a8ccd8e5e74d56545bf9ee4efb0b5d67ba4c5e80f1c13563e8fbc1332feea7cd0ac6c207312c57cab4
7
+ data.tar.gz: 999316893f90a5f3a7fc9cd48dd20f2f1afd8c3087e772bec065bde19bfe08ad59d7430e786b232bd03f78a3f8054bc79cb4800130cab66ddaf93aff1dcc4a47
@@ -0,0 +1,25 @@
1
+ name: Tests
2
+ on: [ push, pull_request ]
3
+ jobs:
4
+ test:
5
+ strategy:
6
+ fail-fast: false
7
+ matrix:
8
+ os:
9
+ - ubuntu-latest
10
+ - macos-latest
11
+ ruby:
12
+ - '3.3'
13
+ - '3.4'
14
+ - jruby
15
+ - ruby
16
+ - ruby-head
17
+ continue-on-error: ${{ endsWith(matrix.ruby, 'head') }}
18
+ runs-on: ${{ matrix.os }}
19
+ steps:
20
+ - uses: actions/checkout@v7
21
+ - uses: ruby/setup-ruby@9eb537ca036ebaed86729dcb9309076e4c5c3b74 # v1.314.0
22
+ with:
23
+ bundler-cache: true
24
+ ruby-version: ${{ matrix.ruby }}
25
+ - run: bundle exec rake
data/HISTORY.md CHANGED
@@ -1,128 +1,89 @@
1
- Crass Change History
2
- ====================
1
+ # Crass Changelog
3
2
 
4
- 1.0.6 (2020-01-12)
5
- ------------------
3
+ ## 1.0.7 (2026-06-25)
6
4
 
7
- * Number values are now limited to a maximum of `Float::MAX` and a minimum of
8
- negative `Float::MAX`. (#11)
5
+ ### Security
9
6
 
10
- * Added project metadata to the gemspec. (#9 - @orien)
7
+ - High: Fixed a denial of service vulnerability in which a large numeric exponent could consume disproportionate CPU and memory before the value was clamped. Exponents are now bounded before `10**exponent` is computed. (GHSA-6wmf-3r64-vcwv)
11
8
 
9
+ - Moderate: Fixed a scenario in which deeply nested simple blocks or functions could exhaust the Ruby stack and raise `SystemStackError`, or could result in excessive memory usage. Parser nesting is now limited to a configurable maximum depth via a new option (`:maximum_depth`, with a conservative default of 25). Constructs nested more deeply are discarded as an `:error` node with the value "maximum-depth-exceeded". (GHSA-6jxj-px6v-747w)
12
10
 
13
- 1.0.5 (2019-10-15)
14
- ------------------
11
+ - Moderate: Fixed a scenario in which a long run of adjacent comments could exhaust the Ruby stack and raise `SystemStackError`. Discarded comments are now skipped iteratively rather than recursively. (GHSA-wwpr-jff3-395c)
15
12
 
16
- * Removed test files from the gem. (#8 - @t-richards)
13
+ - Moderate: Fixed a denial of service vulnerability in which inputs containing many non-ASCII characters could cause excessive CPU usage due to inefficient handling of multi-byte characters during tokenization. (GHSA-8vfg-2r28-hvhj)
17
14
 
15
+ ## 1.0.6 (2020-01-12)
18
16
 
19
- 1.0.4 (2018-04-08)
20
- ------------------
17
+ - Number values are now limited to a maximum of `Float::MAX` and a minimum of negative `Float::MAX`. (#11)
21
18
 
22
- * Fixed whitespace warnings. (#7 - @yahonda)
19
+ - Added project metadata to the gemspec. (#9 - @orien)
23
20
 
21
+ ## 1.0.5 (2019-10-15)
24
22
 
25
- 1.0.3 (2017-11-13)
26
- ------------------
23
+ - Removed test files from the gem. (#8 - @t-richards)
27
24
 
28
- * Added support for frozen string literals. (#3 - @flavorjones)
25
+ ## 1.0.4 (2018-04-08)
29
26
 
27
+ - Fixed whitespace warnings. (#7 - @yahonda)
30
28
 
31
- 1.0.2 (2015-04-17)
32
- ------------------
29
+ ## 1.0.3 (2017-11-13)
33
30
 
34
- * Fixed: An at-rule immediately followed by a `{}` simple block would have the
35
- block (and subsequent tokens until a semicolon) incorrectly appended to its
36
- prelude. This was super dumb and made me very sad.
31
+ - Added support for frozen string literals. (#3 - @flavorjones)
37
32
 
33
+ ## 1.0.2 (2015-04-17)
38
34
 
39
- 1.0.1 (2014-11-16)
40
- ------------------
35
+ - Fixed: An at-rule immediately followed by a `{}` simple block would have the block (and subsequent tokens until a semicolon) incorrectly appended to its prelude. This was super dumb and made me very sad.
41
36
 
42
- * Fixed: Modifications made to the block of an `:at_rule` node in a parse tree
43
- weren't reflected when that node was stringified. This was a regression
44
- introduced in 1.0.0.
37
+ ## 1.0.1 (2014-11-16)
45
38
 
39
+ - Fixed: Modifications made to the block of an `:at_rule` node in a parse tree weren't reflected when that node was stringified. This was a regression introduced in 1.0.0.
46
40
 
47
- 1.0.0 (2014-11-16)
48
- ------------------
41
+ ## 1.0.0 (2014-11-16)
49
42
 
50
- * Many parsing and tokenization tweaks to bring us into full compliance with the
51
- [14 November 2014 editor's draft][css-syntax-draft] of the CSS syntax spec.
52
- The most significant outwardly visible change is that quoted URLs like
53
- `url("foo")` are now returned as `:function` tokens and not `:url` tokens due
54
- to a change in the tokenization spec.
43
+ - Many parsing and tokenization tweaks to bring us into full compliance with the [14 November 2014 editor's draft](http://dev.w3.org/csswg/css-syntax-3/) of the CSS syntax spec. The most significant outwardly visible change is that quoted URLs like `url("foo")` are now returned as `:function` tokens and not `:url` tokens due to a change in the tokenization spec.
55
44
 
56
- * Teensy tiny speed and memory usage improvements that you almost certainly
57
- won't notice.
45
+ - Teensy tiny speed and memory usage improvements that you almost certainly won't notice.
58
46
 
59
- * Fixed: A semicolon following a `@charset` rule would be omitted during
60
- serialization.
47
+ - Fixed: A semicolon following a `@charset` rule would be omitted during serialization.
61
48
 
62
- * Fixed: A multibyte char at the beginning of an id token could trigger an
63
- encoding error because `StringScanner#peek` is a jerkface.
49
+ - Fixed: A multibyte char at the beginning of an id token could trigger an encoding error because `StringScanner#peek` is a jerkface.
64
50
 
65
- [css-syntax-draft]:http://dev.w3.org/csswg/css-syntax-3/
51
+ ## 0.2.1 (2014-07-22)
66
52
 
53
+ - Fixed: Error when the last property of a rule has no value and no terminating semicolon. [#2](https://github.com/rgrove/crass/issues/2)
67
54
 
68
- 0.2.1 (2014-07-22)
69
- ------------------
55
+ ## 0.2.0 (2013-10-10)
70
56
 
71
- * Fixed: Error when the last property of a rule has no value and no terminating
72
- semicolon. [#2][]
57
+ - Added a `:children` field to `:property` nodes. It's an array containing all the nodes that make up the property's value.
73
58
 
74
- [#2]:https://github.com/rgrove/crass/issues/2
59
+ - Fixed: Incorrect value was given for `:property` nodes whose values contained functions.
75
60
 
61
+ - Fixed: When parsing the value of an at-rule's block as a list of rules, a selector containing a function (such as `#foo:not(.bar)`) would cause that property and the rest of the token stream to be discarded.
76
62
 
77
- 0.2.0 (2013-10-10)
78
- ------------------
63
+ ## 0.1.0 (2013-10-04)
79
64
 
80
- * Added a `:children` field to `:property` nodes. It's an array containing all
81
- the nodes that make up the property's value.
65
+ - Tokenization is a little over 50% faster.
82
66
 
83
- * Fixed: Incorrect value was given for `:property` nodes whose values contained
84
- functions.
67
+ - Added tons of unit tests.
85
68
 
86
- * Fixed: When parsing the value of an at-rule's block as a list of rules, a
87
- selector containing a function (such as `#foo:not(.bar)`) would cause that
88
- property and the rest of the token stream to be discarded.
69
+ - Added `Crass.parse_properties` and `Crass::Parser.parse_properties`, which can be used to parse the contents of an HTML element's `style` attribute.
89
70
 
71
+ - Added `Crass::Parser.parse_rules`, which can be used to parse the contents of an `:at_rule` block like `@media` that may contain style rules.
90
72
 
91
- 0.1.0 (2013-10-04)
92
- ------------------
73
+ - Fixed: `Crass::Parser#consume_at_rule` and `#consume_qualified_rule` didn't properly handle already-parsed `:simple_block` nodes in the input, which occurs when parsing rules in the value of an `:at_rule` block.
93
74
 
94
- * Tokenization is a little over 50% faster.
75
+ - Fixed: On `:property` nodes, `:important` is now set to `true` when the property is followed by an "!important" declaration.
95
76
 
96
- * Added tons of unit tests.
77
+ - Fixed: "!important" is no longer included in the value of a `:property` node.
97
78
 
98
- * Added `Crass.parse_properties` and `Crass::Parser.parse_properties`, which can
99
- be used to parse the contents of an HTML element's `style` attribute.
79
+ - Fixed: A variety of tokenization bugs uncovered by tests.
100
80
 
101
- * Added `Crass::Parser.parse_rules`, which can be used to parse the contents of
102
- an `:at_rule` block like `@media` that may contain style rules.
81
+ - Fixed: Added a workaround for a possible spec bug when an `:at_keyword` is encountered while consuming declarations.
103
82
 
104
- * Fixed: `Crass::Parser#consume_at_rule` and `#consume_qualified_rule` didn't
105
- properly handle already-parsed `:simple_block` nodes in the input, which
106
- occurs when parsing rules in the value of an `:at_rule` block.
83
+ ## 0.0.2 (2013-09-30)
107
84
 
108
- * Fixed: On `:property` nodes, `:important` is now set to `true` when the
109
- property is followed by an "!important" declaration.
85
+ - Fixed: `:at_rule` nodes now have a `:name` key.
110
86
 
111
- * Fixed: "!important" is no longer included in the value of a `:property` node.
87
+ ## 0.0.1 (2013-09-27)
112
88
 
113
- * Fixed: A variety of tokenization bugs uncovered by tests.
114
-
115
- * Fixed: Added a workaround for a possible spec bug when an `:at_keyword` is
116
- encountered while consuming declarations.
117
-
118
-
119
- 0.0.2 (2013-09-30)
120
- ------------------
121
-
122
- * Fixed: `:at_rule` nodes now have a `:name` key.
123
-
124
-
125
- 0.0.1 (2013-09-27)
126
- ------------------
127
-
128
- * Initial release.
89
+ - Initial release.
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2020 Ryan Grove (ryan@wonko.com)
1
+ Copyright Ryan Grove <ryan@wonko.com>
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy of
4
4
  this software and associated documentation files (the ‘Software’), to deal in
data/README.md CHANGED
@@ -1,64 +1,47 @@
1
- Crass
2
- =====
1
+ # Crass
3
2
 
4
- Crass is a Ruby CSS parser that's fully compliant with the
5
- [CSS Syntax Level 3][css] specification.
3
+ Crass is a Ruby CSS parser that's fully compliant with the [CSS Syntax Level 3](http://dev.w3.org/csswg/css-syntax/) specification.
6
4
 
7
- * [Home](https://github.com/rgrove/crass/)
8
- * [API Docs](http://rubydoc.info/github/rgrove/crass/master)
9
-
10
- [![Build Status](https://travis-ci.org/rgrove/crass.svg?branch=master)](https://travis-ci.org/rgrove/crass)
11
5
  [![Gem Version](https://badge.fury.io/rb/crass.svg)](http://badge.fury.io/rb/crass)
6
+ [![Tests](https://github.com/rgrove/crass/actions/workflows/tests.yml/badge.svg)](https://github.com/rgrove/crass/actions/workflows/tests.yml)
7
+
8
+ ## Links
9
+
10
+ - [Home](https://github.com/rgrove/crass/)
11
+ - [API Docs](https://rubydoc.info/github/rgrove/crass/Crass)
12
12
 
13
- Features
14
- --------
15
13
 
16
- * Pure Ruby, with no runtime dependencies other than Ruby 1.9.x or higher.
14
+ ## Features
17
15
 
18
- * Tokenizes and parses CSS according to the rules defined in the 14 November
19
- 2014 editor's draft of the [CSS Syntax Level 3][css] specification.
16
+ - Pure Ruby, with no runtime dependencies other than Ruby 1.9.x or higher.
20
17
 
21
- * Extremely tolerant of broken or invalid CSS. If a browser can handle it, Crass
22
- should be able to handle it too.
18
+ - Tokenizes and parses CSS according to the rules defined in the 14 November 2014 editor's draft of the [CSS Syntax Level 3](http://dev.w3.org/csswg/css-syntax/) specification.
23
19
 
24
- * Optionally includes comments in the token stream.
20
+ - Extremely tolerant of broken or invalid CSS. If a browser can handle it, Crass should be able to handle it too.
25
21
 
26
- * Optionally preserves certain CSS hacks, such as the IE "*" hack, which would
27
- otherwise be discarded according to CSS3 tokenizing rules.
22
+ - Optionally includes comments in the token stream.
28
23
 
29
- * Capable of serializing the parse tree back to CSS while maintaining all
30
- original whitespace, comments, and indentation.
24
+ - Optionally preserves certain CSS hacks, such as the IE "*" hack, which would otherwise be discarded according to CSS3 tokenizing rules.
31
25
 
32
- [css]: http://dev.w3.org/csswg/css-syntax/
26
+ - Capable of serializing the parse tree back to CSS while maintaining all original whitespace, comments, and indentation.
33
27
 
34
- Problems
35
- --------
28
+ ## Problems
36
29
 
37
- * Crass isn't terribly fast. I mean, it's Ruby, and it's not really slow by Ruby
38
- standards. But compared to the CSS parser in your average browser? Yeah, it's
39
- slow.
30
+ - Crass isn't terribly fast. I mean, it's Ruby, and it's not really slow by Ruby standards. But compared to the CSS parser in your average browser? Yeah, it's slow.
40
31
 
41
- * Crass only parses the CSS syntax; it doesn't understand what any of it means,
42
- doesn't coalesce selectors, etc. You can do this yourself by consuming the
43
- parse tree, though.
32
+ - Crass only parses the CSS syntax; it doesn't understand what any of it means, doesn't coalesce selectors, etc. You can do this yourself by consuming the parse tree, though.
44
33
 
45
- * While any node in the parse tree (or the parse tree as a whole) can be
46
- serialized back to CSS with perfect fidelity, changes made to those nodes
47
- (except for wholesale removal of nodes) are not reflected in the serialized
48
- output.
34
+ - While any node in the parse tree (or the parse tree as a whole) can be serialized back to CSS with perfect fidelity, changes made to those nodes (except for wholesale removal of nodes) are not reflected in the serialized output.
49
35
 
50
- * Crass only supports UTF-8 input and doesn't respect `@charset` rules. Input in
51
- any other encoding will be converted to UTF-8.
36
+ - Crass only supports UTF-8 input and doesn't respect `@charset` rules. Input in any other encoding will be converted to UTF-8.
52
37
 
53
- Installing
54
- ----------
38
+ ## Installing
55
39
 
56
40
  ```
57
41
  gem install crass
58
42
  ```
59
43
 
60
- Examples
61
- --------
44
+ ## Examples
62
45
 
63
46
  Say you have a string containing some CSS:
64
47
 
@@ -76,7 +59,7 @@ Parsing it is simple:
76
59
  tree = Crass.parse(css, :preserve_comments => true)
77
60
  ```
78
61
 
79
- This returns a big fat beautiful parse tree, which looks like this:
62
+ This returns a big beautiful parse tree, which looks like this:
80
63
 
81
64
  ```ruby
82
65
  [{:node=>:comment, :pos=>0, :raw=>"/* Comment! */", :value=>" Comment! "},
@@ -151,42 +134,20 @@ a:hover {
151
134
 
152
135
  Wasn't that exciting?
153
136
 
154
- A Note on Versioning
155
- --------------------
156
-
157
- As of version 1.0.0, Crass adheres strictly to [SemVer 2.0][semver].
158
-
159
- [semver]:http://semver.org/spec/v2.0.0.html
160
-
161
- Contributing
162
- ------------
137
+ ## Versioning
163
138
 
164
- The best way to contribute is to use Crass and [create issues][issue] when you
165
- run into problems.
139
+ As of version 1.0.0, Crass adheres strictly to [SemVer 2.0](http://semver.org/spec/v2.0.0.html).
166
140
 
167
- Pull requests that fix bugs are more than welcome as long as they include tests.
168
- Please adhere to the style and format of the surrounding code, or I might ask
169
- you to change things.
141
+ ## Contributing
170
142
 
171
- If you want to add a feature or refactor something, please get in touch first to
172
- make sure I'm on board with your idea and approach; I'm pretty picky, and I'd
173
- hate to have to turn down a pull request you spent a lot of time on.
143
+ The best way to contribute is to use Crass and [create issues](https://github.com/rgrove/crass/issues/new) when you run into problems.
174
144
 
175
- [issue]: https://github.com/rgrove/crass/issues/new
145
+ Pull requests that fix bugs are more than welcome as long as they include tests. Please adhere to the style and format of the surrounding code, or I might ask you to change things.
176
146
 
177
- Acknowledgments
178
- ---------------
147
+ If you want to add a feature or refactor something, please get in touch first to make sure I'm on board with your idea and approach; I'm pretty picky, and I'd hate to have to turn down a pull request you spent a lot of time on.
179
148
 
180
- I'm deeply, deeply grateful to [Simon Sapin][simon] for his wonderfully
181
- comprehensive [CSS parsing tests][css-tests], which I adapted to create many of
182
- Crass's tests. They've been invaluable in helping me fix bugs and handle weird
183
- edge cases, and Crass would be much crappier without them.
149
+ ## Acknowledgments
184
150
 
185
- I'm also grateful to [Tab Atkins Jr.][tab] and Simon Sapin (again!) for their
186
- work on the [CSS Syntax Level 3][spec] specification, which defines the
187
- tokenizing and parsing rules that Crass implements.
151
+ I'm deeply grateful to [Simon Sapin](http://exyr.org/about/) for his wonderfully comprehensive [CSS parsing tests](https://github.com/SimonSapin/css-parsing-tests/), which I adapted to create many of Crass's tests. They've been invaluable in helping me fix bugs and handle weird edge cases, and Crass would be much crappier without them.
188
152
 
189
- [css-tests]:https://github.com/SimonSapin/css-parsing-tests/
190
- [simon]:http://exyr.org/about/
191
- [spec]:http://www.w3.org/TR/css-syntax-3/
192
- [tab]:http://www.xanthir.com/contact/
153
+ I'm also grateful to [Tab Atkins-Bittner](https://www.xanthir.com/contact/) and Simon Sapin (again!) for their work on the [CSS Syntax Level 3](http://www.w3.org/TR/css-syntax-3/) specification, which defines the tokenizing and parsing rules that Crass implements.
data/crass.gemspec CHANGED
@@ -2,20 +2,21 @@
2
2
  require './lib/crass/version'
3
3
 
4
4
  Gem::Specification.new do |s|
5
- s.name = 'crass'
6
- s.summary = 'CSS parser based on the CSS Syntax Level 3 spec.'
5
+ s.name = 'crass'
6
+ s.summary = 'CSS parser based on the CSS Syntax Level 3 spec.'
7
7
  s.description = 'Crass is a pure Ruby CSS parser based on the CSS Syntax Level 3 spec.'
8
- s.version = Crass::VERSION
9
- s.authors = ['Ryan Grove']
10
- s.email = ['ryan@wonko.com']
11
- s.homepage = 'https://github.com/rgrove/crass/'
12
- s.license = 'MIT'
8
+ s.version = Crass::VERSION
9
+ s.authors = ['Ryan Grove']
10
+ s.email = ['ryan@wonko.com']
11
+ s.homepage = 'https://github.com/rgrove/crass/'
12
+ s.license = 'MIT'
13
13
 
14
14
  s.metadata = {
15
- 'bug_tracker_uri' => 'https://github.com/rgrove/crass/issues',
16
- 'changelog_uri' => "https://github.com/rgrove/crass/blob/v#{s.version}/HISTORY.md",
15
+ 'bug_tracker_uri' => 'https://github.com/rgrove/crass/issues',
16
+ 'changelog_uri' => "https://github.com/rgrove/crass/blob/v#{s.version}/HISTORY.md",
17
17
  'documentation_uri' => "https://www.rubydoc.info/gems/crass/#{s.version}",
18
- 'source_code_uri' => "https://github.com/rgrove/crass/tree/v#{s.version}",
18
+ 'rubygems_mfa_required' => 'true',
19
+ 'source_code_uri' => "https://github.com/rgrove/crass/tree/v#{s.version}",
19
20
  }
20
21
 
21
22
  s.platform = Gem::Platform::RUBY
@@ -26,6 +27,6 @@ Gem::Specification.new do |s|
26
27
  s.files = `git ls-files -z`.split("\x0").grep_v(%r{^test/})
27
28
 
28
29
  # Development dependencies.
29
- s.add_development_dependency 'minitest', '~> 5.0.8'
30
- s.add_development_dependency 'rake', '~> 10.1.0'
30
+ s.add_development_dependency 'minitest', '~> 6.0.6'
31
+ s.add_development_dependency 'rake', '~> 13.4.2'
31
32
  end
data/lib/crass/parser.rb CHANGED
@@ -6,7 +6,7 @@ module Crass
6
6
 
7
7
  # Parses a CSS string or list of tokens.
8
8
  #
9
- # 5. http://dev.w3.org/csswg/css-syntax/#parsing
9
+ # 5. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#parsing
10
10
  class Parser
11
11
  BLOCK_END_TOKENS = {
12
12
  :'{' => :'}',
@@ -14,14 +14,28 @@ module Crass
14
14
  :'(' => :')'
15
15
  }
16
16
 
17
+ # Default maximum nesting depth for simple blocks and functions. This is far
18
+ # higher than any legitimate CSS needs, but far below the depth at which
19
+ # Ruby would raise `SystemStackError` while recursively parsing nested
20
+ # constructs.
21
+ #
22
+ # Keeping this low also bounds memory usage: each nested simple block and
23
+ # function retains a `:tokens` array spanning its descendants for
24
+ # serialization, so the total serialization metadata grows with nesting
25
+ # depth. A modest limit prevents deeply nested (but otherwise valid) input
26
+ # from amplifying memory disproportionately.
27
+ #
28
+ # It can be overridden with the `:maximum_depth` option.
29
+ DEFAULT_MAXIMUM_DEPTH = 25
30
+
17
31
  # -- Class Methods ---------------------------------------------------------
18
32
 
19
33
  # Parses CSS properties (such as the contents of an HTML element's `style`
20
34
  # attribute) and returns a parse tree.
21
35
  #
22
- # See {Tokenizer#initialize} for _options_.
36
+ # See {Crass.parse} for _options_.
23
37
  #
24
- # 5.3.6. http://dev.w3.org/csswg/css-syntax/#parse-a-list-of-declarations
38
+ # 5.3.6. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#parse-a-list-of-declarations
25
39
  def self.parse_properties(input, options = {})
26
40
  Parser.new(input, options).parse_properties
27
41
  end
@@ -30,9 +44,9 @@ module Crass
30
44
  # parse tree. The only difference from {parse_stylesheet} is that CDO/CDC
31
45
  # nodes (`<!--` and `-->`) aren't ignored.
32
46
  #
33
- # See {Tokenizer#initialize} for _options_.
47
+ # See {Crass.parse} for _options_.
34
48
  #
35
- # 5.3.3. http://dev.w3.org/csswg/css-syntax/#parse-a-list-of-rules
49
+ # 5.3.3. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#parse-a-list-of-rules
36
50
  def self.parse_rules(input, options = {})
37
51
  parser = Parser.new(input, options)
38
52
  rules = parser.consume_rules
@@ -48,9 +62,9 @@ module Crass
48
62
 
49
63
  # Parses a CSS stylesheet and returns a parse tree.
50
64
  #
51
- # See {Tokenizer#initialize} for _options_.
65
+ # See {Crass.parse} for _options_.
52
66
  #
53
- # 5.3.2. http://dev.w3.org/csswg/css-syntax/#parse-a-stylesheet
67
+ # 5.3.2. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#parse-a-stylesheet
54
68
  def self.parse_stylesheet(input, options = {})
55
69
  parser = Parser.new(input, options)
56
70
  rules = parser.consume_rules(:top_level => true)
@@ -122,18 +136,20 @@ module Crass
122
136
  # Initializes a parser based on the given _input_, which may be a CSS string
123
137
  # or an array of tokens.
124
138
  #
125
- # See {Tokenizer#initialize} for _options_.
139
+ # See {Crass.parse} for _options_.
126
140
  def initialize(input, options = {})
127
141
  unless input.kind_of?(Enumerable)
128
142
  input = Tokenizer.tokenize(input, options)
129
143
  end
130
144
 
145
+ @depth = 0
146
+ @maximum_depth = options[:maximum_depth] || DEFAULT_MAXIMUM_DEPTH
131
147
  @tokens = TokenScanner.new(input)
132
148
  end
133
149
 
134
150
  # Consumes an at-rule and returns it.
135
151
  #
136
- # 5.4.2. http://dev.w3.org/csswg/css-syntax-3/#consume-at-rule
152
+ # 5.4.2. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-at-rule
137
153
  def consume_at_rule(input = @tokens)
138
154
  rule = {}
139
155
 
@@ -180,7 +196,7 @@ module Crass
180
196
  # Consumes a component value and returns it, or `nil` if there are no more
181
197
  # tokens.
182
198
  #
183
- # 5.4.6. http://dev.w3.org/csswg/css-syntax-3/#consume-a-component-value
199
+ # 5.4.6. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-component-value
184
200
  def consume_component_value(input = @tokens)
185
201
  return nil unless token = input.consume
186
202
 
@@ -205,7 +221,7 @@ module Crass
205
221
 
206
222
  # Consumes a declaration and returns it, or `nil` on parse error.
207
223
  #
208
- # 5.4.5. http://dev.w3.org/csswg/css-syntax-3/#consume-a-declaration
224
+ # 5.4.5. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-declaration
209
225
  def consume_declaration(input = @tokens)
210
226
  declaration = {}
211
227
  value = []
@@ -272,7 +288,7 @@ module Crass
272
288
  # * **:strict** - Set to `true` to exclude non-standard `:comment`,
273
289
  # `:semicolon`, and `:whitespace` nodes.
274
290
  #
275
- # 5.4.4. http://dev.w3.org/csswg/css-syntax/#consume-a-list-of-declarations
291
+ # 5.4.4. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-list-of-declarations
276
292
  def consume_declarations(input = @tokens, options = {})
277
293
  declarations = []
278
294
 
@@ -322,38 +338,48 @@ module Crass
322
338
 
323
339
  # Consumes a function and returns it.
324
340
  #
325
- # 5.4.8. http://dev.w3.org/csswg/css-syntax-3/#consume-a-function
341
+ # 5.4.8. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-function
326
342
  def consume_function(input = @tokens)
327
- function = {
328
- :name => input.current[:value],
329
- :value => [],
330
- :tokens => [input.current] # Non-standard, used for serialization.
331
- }
332
-
333
- function[:tokens].concat(input.collect {
334
- while token = input.consume
335
- case token[:node]
336
- when :')'
337
- break
338
-
339
- # Non-standard.
340
- when :comment
341
- next
342
-
343
- else
344
- input.reconsume
345
- function[:value] << consume_component_value(input)
343
+ @depth += 1
344
+
345
+ begin
346
+ # Discard functions nested more deeply than the maximum allowed depth to
347
+ # avoid exhausting the Ruby stack on maliciously nested input.
348
+ return discard_block(input) if @depth > @maximum_depth
349
+
350
+ function = {
351
+ :name => input.current[:value],
352
+ :value => [],
353
+ :tokens => [input.current] # Non-standard, used for serialization.
354
+ }
355
+
356
+ function[:tokens].concat(input.collect {
357
+ while token = input.consume
358
+ case token[:node]
359
+ when :')'
360
+ break
361
+
362
+ # Non-standard.
363
+ when :comment
364
+ next
365
+
366
+ else
367
+ input.reconsume
368
+ function[:value] << consume_component_value(input)
369
+ end
346
370
  end
347
- end
348
- })
371
+ })
349
372
 
350
- create_node(:function, function)
373
+ create_node(:function, function)
374
+ ensure
375
+ @depth -= 1
376
+ end
351
377
  end
352
378
 
353
379
  # Consumes a qualified rule and returns it, or `nil` if a parse error
354
380
  # occurs.
355
381
  #
356
- # 5.4.3. http://dev.w3.org/csswg/css-syntax-3/#consume-a-qualified-rule
382
+ # 5.4.3. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-qualified-rule
357
383
  def consume_qualified_rule(input = @tokens)
358
384
  rule = {:prelude => []}
359
385
 
@@ -394,7 +420,7 @@ module Crass
394
420
 
395
421
  # Consumes a list of rules and returns them.
396
422
  #
397
- # 5.4.1. http://dev.w3.org/csswg/css-syntax/#consume-a-list-of-rules
423
+ # 5.4.1. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-list-of-rules
398
424
  def consume_rules(flags = {})
399
425
  rules = []
400
426
 
@@ -430,28 +456,70 @@ module Crass
430
456
  # Consumes and returns a simple block associated with the current input
431
457
  # token.
432
458
  #
433
- # 5.4.7. http://dev.w3.org/csswg/css-syntax/#consume-a-simple-block
459
+ # 5.4.7. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-simple-block
434
460
  def consume_simple_block(input = @tokens)
435
- start_token = input.current[:node]
436
- end_token = BLOCK_END_TOKENS[start_token]
461
+ @depth += 1
437
462
 
438
- block = {
439
- :start => start_token.to_s,
440
- :end => end_token.to_s,
441
- :value => [],
442
- :tokens => [input.current] # Non-standard. Used for serialization.
443
- }
463
+ begin
464
+ # Discard blocks nested more deeply than the maximum allowed depth to
465
+ # avoid exhausting the Ruby stack on maliciously nested input.
466
+ return discard_block(input) if @depth > @maximum_depth
444
467
 
445
- block[:tokens].concat(input.collect do
446
- while token = input.consume
447
- break if token[:node] == end_token
468
+ start_token = input.current[:node]
469
+ end_token = BLOCK_END_TOKENS[start_token]
448
470
 
449
- input.reconsume
450
- block[:value] << consume_component_value(input)
471
+ block = {
472
+ :start => start_token.to_s,
473
+ :end => end_token.to_s,
474
+ :value => [],
475
+ :tokens => [input.current] # Non-standard. Used for serialization.
476
+ }
477
+
478
+ block[:tokens].concat(input.collect do
479
+ while token = input.consume
480
+ break if token[:node] == end_token
481
+
482
+ input.reconsume
483
+ block[:value] << consume_component_value(input)
484
+ end
485
+ end)
486
+
487
+ create_node(:simple_block, block)
488
+ ensure
489
+ @depth -= 1
490
+ end
491
+ end
492
+
493
+ # Discards an over-nested simple block or function without recursing, then
494
+ # returns an `:error` node. Assumes `input.current` is the opening token (a
495
+ # `{`, `[`, `(`, or function token).
496
+ #
497
+ # This is reached only when the configured maximum nesting depth is
498
+ # exceeded. It iteratively consumes tokens up to the matching closing token
499
+ # (tracking nested blocks and functions with an explicit stack) so that a
500
+ # deeply nested construct can't exhaust the Ruby stack.
501
+ def discard_block(input)
502
+ opening = input.current
503
+
504
+ stack = [opening[:node] == :function ? :')' : BLOCK_END_TOKENS[opening[:node]]]
505
+ tokens = [opening] # Non-standard. Used for serialization.
506
+
507
+ tokens.concat(input.collect do
508
+ until stack.empty?
509
+ break unless token = input.consume
510
+
511
+ case token[:node]
512
+ when :'{', :'[', :'('
513
+ stack.push(BLOCK_END_TOKENS[token[:node]])
514
+ when :function
515
+ stack.push(:')')
516
+ when stack.last
517
+ stack.pop
518
+ end
451
519
  end
452
520
  end)
453
521
 
454
- create_node(:simple_block, block)
522
+ create_node(:error, :value => 'maximum-depth-exceeded', :tokens => tokens)
455
523
  end
456
524
 
457
525
  # Creates and returns a new parse node with the given _properties_.
@@ -479,7 +547,7 @@ module Crass
479
547
 
480
548
  # Parses a single component value and returns it.
481
549
  #
482
- # 5.3.7. http://dev.w3.org/csswg/css-syntax-3/#parse-a-component-value
550
+ # 5.3.7. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#parse-a-component-value
483
551
  def parse_component_value(input = @tokens)
484
552
  input = TokenScanner.new(input) unless input.is_a?(TokenScanner)
485
553
 
@@ -506,7 +574,7 @@ module Crass
506
574
 
507
575
  # Parses a list of component values and returns an array of parsed tokens.
508
576
  #
509
- # 5.3.8. http://dev.w3.org/csswg/css-syntax/#parse-a-list-of-component-values
577
+ # 5.3.8. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#parse-a-list-of-component-values
510
578
  def parse_component_values(input = @tokens)
511
579
  input = TokenScanner.new(input) unless input.is_a?(TokenScanner)
512
580
  tokens = []
@@ -520,7 +588,7 @@ module Crass
520
588
 
521
589
  # Parses a single declaration and returns it.
522
590
  #
523
- # 5.3.5. http://dev.w3.org/csswg/css-syntax/#parse-a-declaration
591
+ # 5.3.5. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#parse-a-declaration
524
592
  def parse_declaration(input = @tokens)
525
593
  input = TokenScanner.new(input) unless input.is_a?(TokenScanner)
526
594
 
@@ -548,7 +616,7 @@ module Crass
548
616
  #
549
617
  # See {#consume_declarations} for _options_.
550
618
  #
551
- # 5.3.6. http://dev.w3.org/csswg/css-syntax/#parse-a-list-of-declarations
619
+ # 5.3.6. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#parse-a-list-of-declarations
552
620
  def parse_declarations(input = @tokens, options = {})
553
621
  input = TokenScanner.new(input) unless input.is_a?(TokenScanner)
554
622
  consume_declarations(input, options)
@@ -582,7 +650,7 @@ module Crass
582
650
 
583
651
  # Parses a single rule and returns it.
584
652
  #
585
- # 5.3.4. http://dev.w3.org/csswg/css-syntax-3/#parse-a-rule
653
+ # 5.3.4. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#parse-a-rule
586
654
  def parse_rule(input = @tokens)
587
655
  input = TokenScanner.new(input) unless input.is_a?(TokenScanner)
588
656
 
data/lib/crass/scanner.rb CHANGED
@@ -16,6 +16,10 @@ module Crass
16
16
 
17
17
  # Position of the next character that will be consumed. This is a character
18
18
  # position, not a byte position, so it accounts for multi-byte characters.
19
+ #
20
+ # Byte offsets (used internally for fast substring extraction) are tracked
21
+ # separately by the underlying StringScanner, whose `pos` always reflects
22
+ # the byte offset corresponding to this character position.
19
23
  attr_accessor :pos
20
24
 
21
25
  # String being scanned.
@@ -46,6 +50,11 @@ module Crass
46
50
  def consume_rest
47
51
  result = @scanner.rest
48
52
 
53
+ # `StringScanner#rest` does not advance the scan pointer, so move it to
54
+ # the end of the input to keep the byte offset in sync with {#pos}. This
55
+ # ensures a subsequent {#marked} extracts the correct substring.
56
+ @scanner.terminate
57
+
49
58
  @current = result[-1]
50
59
  @pos = @len
51
60
 
@@ -61,24 +70,31 @@ module Crass
61
70
  # Sets the marker to the position of the next character that will be
62
71
  # consumed.
63
72
  def mark
64
- @marker = @pos
73
+ @byte_marker = @scanner.pos
74
+ @marker = @pos
65
75
  end
66
76
 
67
77
  # Returns the substring between {#marker} and {#pos}, without altering the
68
78
  # pointer.
69
79
  def marked
70
- if result = @string[@marker, @pos - @marker]
71
- result
72
- else
73
- ''
74
- end
80
+ # Extract the marked text using byte offsets rather than character
81
+ # offsets. Slicing the original string by character offset is O(n) on
82
+ # multi-byte input (Ruby must translate the character index into a byte
83
+ # index), which makes tokenizing non-ASCII input superlinear. Byte slicing
84
+ # is O(length) regardless of how far into the string we are.
85
+ @string.byteslice(@byte_marker, @scanner.pos - @byte_marker) || ''
75
86
  end
76
87
 
77
88
  # Returns up to _length_ characters starting at the current position, but
78
89
  # doesn't consume them. The number of characters returned may be less than
79
90
  # _length_ if the end of the string is reached.
80
91
  def peek(length = 1)
81
- @string[pos, length]
92
+ # Grab the bytes for up to _length_ characters and then take the first
93
+ # _length_ characters. A UTF-8 character is at most four bytes, so `length
94
+ # * 4` bytes always contains at least _length_ whole characters when that
95
+ # many remain. This avoids the O(n) character-offset slice that
96
+ # `@string[pos, length]` would otherwise perform on multi-byte input.
97
+ @string.byteslice(@scanner.pos, length * 4).slice(0, length) || ''
82
98
  end
83
99
 
84
100
  # Moves the pointer back one character without changing the value of
@@ -91,10 +107,13 @@ module Crass
91
107
 
92
108
  # Resets the pointer to the beginning of the string.
93
109
  def reset
94
- @current = nil
95
- @len = @string.size
96
- @marker = 0
97
- @pos = 0
110
+ @scanner.reset
111
+
112
+ @byte_marker = 0
113
+ @current = nil
114
+ @len = @string.size
115
+ @marker = 0
116
+ @pos = 0
98
117
  end
99
118
 
100
119
  # Tries to match _pattern_ at the current position. If it matches, the
@@ -5,7 +5,7 @@ module Crass
5
5
 
6
6
  # Tokenizes a CSS string.
7
7
  #
8
- # 4. http://dev.w3.org/csswg/css-syntax/#tokenization
8
+ # 4. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#tokenization
9
9
  class Tokenizer
10
10
  RE_COMMENT_CLOSE = /\*\//
11
11
  RE_DIGIT = /[0-9]+/
@@ -66,19 +66,20 @@ module Crass
66
66
 
67
67
  # Consumes a token and returns the token that was consumed.
68
68
  #
69
- # 4.3.1. http://dev.w3.org/csswg/css-syntax/#consume-a-token
69
+ # 4.3.1. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-token
70
70
  def consume
71
- return nil if @s.eos?
71
+ # Skip over any comments. When comments aren't being preserved this is
72
+ # done iteratively rather than recursively so that a long run of adjacent
73
+ # comments can't exhaust the Ruby stack and raise a `SystemStackError`.
74
+ loop do
75
+ return nil if @s.eos?
72
76
 
73
- @s.mark
77
+ @s.mark
74
78
 
75
- # Consume comments.
76
- if comment_token = consume_comments
77
- if @options[:preserve_comments]
78
- return comment_token
79
- else
80
- return consume
81
- end
79
+ comment_token = consume_comments
80
+ break unless comment_token
81
+
82
+ return comment_token if @options[:preserve_comments]
82
83
  end
83
84
 
84
85
  # Consume whitespace.
@@ -271,7 +272,7 @@ module Crass
271
272
 
272
273
  # Consumes the remnants of a bad URL and returns the consumed text.
273
274
  #
274
- # 4.3.15. http://dev.w3.org/csswg/css-syntax/#consume-the-remnants-of-a-bad-url
275
+ # 4.3.15. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-the-remnants-of-a-bad-url
275
276
  def consume_bad_url
276
277
  text = String.new
277
278
 
@@ -297,7 +298,7 @@ module Crass
297
298
 
298
299
  # Consumes comments and returns them, or `nil` if no comments were consumed.
299
300
  #
300
- # 4.3.2. http://dev.w3.org/csswg/css-syntax/#consume-comments
301
+ # 4.3.2. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-comments
301
302
  def consume_comments
302
303
  if @s.peek(2) == '/*'
303
304
  @s.consume
@@ -322,7 +323,7 @@ module Crass
322
323
  # next character in the input has already been verified not to be a newline
323
324
  # or EOF.
324
325
  #
325
- # 4.3.8. http://dev.w3.org/csswg/css-syntax/#consume-an-escaped-code-point
326
+ # 4.3.8. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-an-escaped-code-point
326
327
  def consume_escaped
327
328
  return "\ufffd" if @s.eos?
328
329
 
@@ -346,7 +347,7 @@ module Crass
346
347
 
347
348
  # Consumes an ident-like token and returns it.
348
349
  #
349
- # 4.3.4. http://dev.w3.org/csswg/css-syntax/#consume-an-ident-like-token
350
+ # 4.3.4. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-an-ident-like-token
350
351
  def consume_ident
351
352
  value = consume_name
352
353
 
@@ -371,7 +372,7 @@ module Crass
371
372
 
372
373
  # Consumes a name and returns it.
373
374
  #
374
- # 4.3.12. http://dev.w3.org/csswg/css-syntax/#consume-a-name
375
+ # 4.3.12. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-name
375
376
  def consume_name
376
377
  result = String.new
377
378
 
@@ -403,7 +404,7 @@ module Crass
403
404
  # original representation, its numeric value, and its type (either
404
405
  # `:integer` or `:number`).
405
406
  #
406
- # 4.3.13. http://dev.w3.org/csswg/css-syntax/#consume-a-number
407
+ # 4.3.13. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-number
407
408
  def consume_number
408
409
  repr = String.new
409
410
  type = :integer
@@ -426,7 +427,7 @@ module Crass
426
427
 
427
428
  # Consumes a numeric token and returns it.
428
429
  #
429
- # 4.3.3. http://dev.w3.org/csswg/css-syntax/#consume-a-numeric-token
430
+ # 4.3.3. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-numeric-token
430
431
  def consume_numeric
431
432
  number = consume_number
432
433
  repr = number[0]
@@ -465,7 +466,7 @@ module Crass
465
466
  # Consumes a string token that ends at the given character, and returns the
466
467
  # token.
467
468
  #
468
- # 4.3.5. http://dev.w3.org/csswg/css-syntax/#consume-a-string-token
469
+ # 4.3.5. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-string-token
469
470
  def consume_string(ending = nil)
470
471
  ending = @s.current if ending.nil?
471
472
  value = String.new
@@ -506,7 +507,7 @@ module Crass
506
507
  # Consumes a Unicode range token and returns it. Assumes the initial "u+" or
507
508
  # "U+" has already been consumed.
508
509
  #
509
- # 4.3.7. http://dev.w3.org/csswg/css-syntax/#consume-a-unicode-range-token
510
+ # 4.3.7. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-unicode-range-token
510
511
  def consume_unicode_range
511
512
  value = @s.scan(RE_HEX) || String.new
512
513
 
@@ -538,7 +539,7 @@ module Crass
538
539
  # Consumes a URL token and returns it. Assumes the original "url(" has
539
540
  # already been consumed.
540
541
  #
541
- # 4.3.6. http://dev.w3.org/csswg/css-syntax/#consume-a-url-token
542
+ # 4.3.6. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#consume-a-url-token
542
543
  def consume_url
543
544
  value = String.new
544
545
 
@@ -586,7 +587,7 @@ module Crass
586
587
 
587
588
  # Converts a valid CSS number string into a number and returns the number.
588
589
  #
589
- # 4.3.14. http://dev.w3.org/csswg/css-syntax/#convert-a-string-to-a-number
590
+ # 4.3.14. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#convert-a-string-to-a-number
590
591
  def convert_string_to_number(str)
591
592
  matches = RE_NUMBER_STR.match(str)
592
593
 
@@ -597,16 +598,42 @@ module Crass
597
598
  t = matches[:exponent_sign] == '-' ? -1 : 1
598
599
  e = matches[:exponent].to_i
599
600
 
600
- # I know this formula looks nutty, but it's exactly what's defined in the
601
- # spec, and it works.
602
- value = s * (i + f * 10**-d) * 10**(t * e)
601
+ exponent = t * e
602
+
603
+ # Guard against denial of service via attacker-controlled exponentiation.
604
+ # A tiny input like "1e5000000" would otherwise force Ruby to compute
605
+ # `10**exponent` (an enormous integer) before the value is clamped below,
606
+ # consuming disproportionate CPU and memory. When the exponent's magnitude
607
+ # is far outside the range a finite double can represent, we saturate or
608
+ # underflow without performing the exponentiation.
609
+ if i == 0 && f == 0
610
+ # The mantissa is zero, so the value is zero regardless of the exponent.
611
+ value = s * 0.0
612
+
613
+ elsif exponent - d > Float::MAX_10_EXP + 1
614
+ # The value is larger than `Float::MAX` and would be clamped anyway.
615
+ value = s * Float::MAX
616
+
617
+ elsif exponent + matches[:integer].length < -325
618
+ # The value is smaller than the smallest representable double and would
619
+ # round to zero. (-325 is one less than the smallest subnormal base-10
620
+ # exponent of roughly -324; `matches[:integer].length` is an upper bound
621
+ # on the mantissa's magnitude, ensuring we never zero a representable
622
+ # value.)
623
+ value = s * 0.0
603
624
 
604
- # Maximum and minimum values aren't defined in the spec, but are enforced
605
- # here for sanity.
606
- if value > Float::MAX
607
- value = Float::MAX
608
- elsif value < -Float::MAX
609
- value = -Float::MAX
625
+ else
626
+ # I know this formula looks nutty, but it's exactly what's defined in
627
+ # the spec, and it works.
628
+ value = s * (i + f * 10**-d) * 10**exponent
629
+
630
+ # Maximum and minimum values aren't defined in the spec, but are
631
+ # enforced here for sanity.
632
+ if value > Float::MAX
633
+ value = Float::MAX
634
+ elsif value < -Float::MAX
635
+ value = -Float::MAX
636
+ end
610
637
  end
611
638
 
612
639
  value
@@ -623,7 +650,7 @@ module Crass
623
650
 
624
651
  # Preprocesses _input_ to prepare it for the tokenizer.
625
652
  #
626
- # 3.3. http://dev.w3.org/csswg/css-syntax/#input-preprocessing
653
+ # 3.3. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#input-preprocessing
627
654
  def preprocess(input)
628
655
  input = input.to_s.encode('UTF-8',
629
656
  :invalid => :replace,
@@ -638,7 +665,7 @@ module Crass
638
665
  # identifier. If _text_ is `nil`, the current and next two characters in the
639
666
  # input stream will be checked, but will not be consumed.
640
667
  #
641
- # 4.3.10. http://dev.w3.org/csswg/css-syntax/#would-start-an-identifier
668
+ # 4.3.10. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#would-start-an-identifier
642
669
  def start_identifier?(text = nil)
643
670
  text = @s.current + @s.peek(2) if text.nil?
644
671
 
@@ -662,7 +689,7 @@ module Crass
662
689
  # If _text_ is `nil`, the current and next two characters in the input
663
690
  # stream will be checked, but will not be consumed.
664
691
  #
665
- # 4.3.11. http://dev.w3.org/csswg/css-syntax/#starts-with-a-number
692
+ # 4.3.11. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#starts-with-a-number
666
693
  def start_number?(text = nil)
667
694
  text = @s.current + @s.peek(2) if text.nil?
668
695
 
@@ -698,7 +725,7 @@ module Crass
698
725
  # valid escape sequence. If _text_ is `nil`, the current and next character
699
726
  # in the input stream will be checked, but will not be consumed.
700
727
  #
701
- # 4.3.9. http://dev.w3.org/csswg/css-syntax/#starts-with-a-valid-escape
728
+ # 4.3.9. https://www.w3.org/TR/2013/WD-css-syntax-3-20130919/#starts-with-a-valid-escape
702
729
  def valid_escape?(text = nil)
703
730
  text = @s.current + @s.peek if text.nil?
704
731
  !!(text[0] == '\\' && text[1] != "\n")
data/lib/crass/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # encoding: utf-8
2
2
 
3
3
  module Crass
4
- VERSION = '1.0.6'
4
+ VERSION = '1.0.7'
5
5
  end
data/lib/crass.rb CHANGED
@@ -6,7 +6,19 @@ module Crass
6
6
 
7
7
  # Parses _input_ as a CSS stylesheet and returns a parse tree.
8
8
  #
9
- # See {Tokenizer#initialize} for _options_.
9
+ # Options:
10
+ #
11
+ # * **:maximum_depth** - Maximum nesting depth for simple blocks and
12
+ # functions. Constructs nested more deeply than this are discarded to
13
+ # prevent stack exhaustion. Defaults to {Parser::DEFAULT_MAXIMUM_DEPTH}.
14
+ #
15
+ # * **:preserve_comments** - If `true`, comments will be preserved as
16
+ # `:comment` tokens.
17
+ #
18
+ # * **:preserve_hacks** - If `true`, certain non-standard browser hacks
19
+ # such as the IE "*" hack will be preserved even though they violate
20
+ # CSS 3 syntax rules.
21
+ #
10
22
  def self.parse(input, options = {})
11
23
  Parser.parse_stylesheet(input, options)
12
24
  end
@@ -14,7 +26,7 @@ module Crass
14
26
  # Parses _input_ as a string of CSS properties (such as the contents of an
15
27
  # HTML element's `style` attribute) and returns a parse tree.
16
28
  #
17
- # See {Tokenizer#initialize} for _options_.
29
+ # See {Crass.parse} for _options_.
18
30
  def self.parse_properties(input, options = {})
19
31
  Parser.parse_properties(input, options)
20
32
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: crass
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.6
4
+ version: 1.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ryan Grove
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2020-01-12 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: minitest
@@ -16,28 +15,28 @@ dependencies:
16
15
  requirements:
17
16
  - - "~>"
18
17
  - !ruby/object:Gem::Version
19
- version: 5.0.8
18
+ version: 6.0.6
20
19
  type: :development
21
20
  prerelease: false
22
21
  version_requirements: !ruby/object:Gem::Requirement
23
22
  requirements:
24
23
  - - "~>"
25
24
  - !ruby/object:Gem::Version
26
- version: 5.0.8
25
+ version: 6.0.6
27
26
  - !ruby/object:Gem::Dependency
28
27
  name: rake
29
28
  requirement: !ruby/object:Gem::Requirement
30
29
  requirements:
31
30
  - - "~>"
32
31
  - !ruby/object:Gem::Version
33
- version: 10.1.0
32
+ version: 13.4.2
34
33
  type: :development
35
34
  prerelease: false
36
35
  version_requirements: !ruby/object:Gem::Requirement
37
36
  requirements:
38
37
  - - "~>"
39
38
  - !ruby/object:Gem::Version
40
- version: 10.1.0
39
+ version: 13.4.2
41
40
  description: Crass is a pure Ruby CSS parser based on the CSS Syntax Level 3 spec.
42
41
  email:
43
42
  - ryan@wonko.com
@@ -45,8 +44,8 @@ executables: []
45
44
  extensions: []
46
45
  extra_rdoc_files: []
47
46
  files:
47
+ - ".github/workflows/tests.yml"
48
48
  - ".gitignore"
49
- - ".travis.yml"
50
49
  - ".yardopts"
51
50
  - Gemfile
52
51
  - HISTORY.md
@@ -65,10 +64,10 @@ licenses:
65
64
  - MIT
66
65
  metadata:
67
66
  bug_tracker_uri: https://github.com/rgrove/crass/issues
68
- changelog_uri: https://github.com/rgrove/crass/blob/v1.0.6/HISTORY.md
69
- documentation_uri: https://www.rubydoc.info/gems/crass/1.0.6
70
- source_code_uri: https://github.com/rgrove/crass/tree/v1.0.6
71
- post_install_message:
67
+ changelog_uri: https://github.com/rgrove/crass/blob/v1.0.7/HISTORY.md
68
+ documentation_uri: https://www.rubydoc.info/gems/crass/1.0.7
69
+ rubygems_mfa_required: 'true'
70
+ source_code_uri: https://github.com/rgrove/crass/tree/v1.0.7
72
71
  rdoc_options: []
73
72
  require_paths:
74
73
  - lib
@@ -83,8 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
83
82
  - !ruby/object:Gem::Version
84
83
  version: '0'
85
84
  requirements: []
86
- rubygems_version: 3.0.3
87
- signing_key:
85
+ rubygems_version: 4.0.10
88
86
  specification_version: 4
89
87
  summary: CSS parser based on the CSS Syntax Level 3 spec.
90
88
  test_files: []
data/.travis.yml DELETED
@@ -1,11 +0,0 @@
1
- language: ruby
2
- rvm:
3
- - 2.3
4
- - 2.4
5
- - 2.5
6
- - 2.6
7
- - ruby-head
8
-
9
- matrix:
10
- allow_failures:
11
- - rvm: ruby-head