crass 1.0.5 → 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: 335330d4d7d855e20733747cc6f01cf3513c176c37dd107c473f525b94c2bbfc
4
- data.tar.gz: aec721d58cd3f1017fc957f7ae9d8b47f0e811db22649535ab64da580a6db70f
3
+ metadata.gz: a90afad390dcbfa666bb15dcc55ff6bc7711a4fabd9e5a920c53783bc22d2c8b
4
+ data.tar.gz: ba5fe09c8233f06503da5bf0f89a9c716cb19aa6b867eb6e1bff3f3d7153446d
5
5
  SHA512:
6
- metadata.gz: df7fc13c7c363a4aaf44986f62eabbce1fe9da744797bdcdf3148628d1c8447e69c24b6df2b33325bebe88951e195ccb43c9a5e50b2e1e920fab782aa5f532dc
7
- data.tar.gz: 38cfc577fcd184f175ba37b54f7e8638086a4c9817eb54c24b1c53a62f0437d4e97f9b87b8e81422511bf20c5eb2452f85838c6f822b89634fa944211eab3071
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,121 +1,89 @@
1
- Crass Change History
2
- ====================
1
+ # Crass Changelog
3
2
 
4
- 1.0.5 (2019-10-15)
5
- ------------------
3
+ ## 1.0.7 (2026-06-25)
6
4
 
7
- * Removed test files from the gem. [@t-richards - #8][8]
5
+ ### Security
8
6
 
9
- [8]:https://github.com/rgrove/crass/pull/8
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)
10
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)
11
10
 
12
- 1.0.4 (2018-04-08)
13
- ------------------
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)
14
12
 
15
- * Fixed whitespace warnings. (#7 - @yahonda)
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)
16
14
 
15
+ ## 1.0.6 (2020-01-12)
17
16
 
18
- 1.0.3 (2017-11-13)
19
- ------------------
17
+ - Number values are now limited to a maximum of `Float::MAX` and a minimum of negative `Float::MAX`. (#11)
20
18
 
21
- * Added support for frozen string literals. (#3 - @flavorjones)
19
+ - Added project metadata to the gemspec. (#9 - @orien)
22
20
 
21
+ ## 1.0.5 (2019-10-15)
23
22
 
24
- 1.0.2 (2015-04-17)
25
- ------------------
23
+ - Removed test files from the gem. (#8 - @t-richards)
26
24
 
27
- * Fixed: An at-rule immediately followed by a `{}` simple block would have the
28
- block (and subsequent tokens until a semicolon) incorrectly appended to its
29
- prelude. This was super dumb and made me very sad.
25
+ ## 1.0.4 (2018-04-08)
30
26
 
27
+ - Fixed whitespace warnings. (#7 - @yahonda)
31
28
 
32
- 1.0.1 (2014-11-16)
33
- ------------------
29
+ ## 1.0.3 (2017-11-13)
34
30
 
35
- * Fixed: Modifications made to the block of an `:at_rule` node in a parse tree
36
- weren't reflected when that node was stringified. This was a regression
37
- introduced in 1.0.0.
31
+ - Added support for frozen string literals. (#3 - @flavorjones)
38
32
 
33
+ ## 1.0.2 (2015-04-17)
39
34
 
40
- 1.0.0 (2014-11-16)
41
- ------------------
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.
42
36
 
43
- * Many parsing and tokenization tweaks to bring us into full compliance with the
44
- [14 November 2014 editor's draft][css-syntax-draft] of the CSS syntax spec.
45
- The most significant outwardly visible change is that quoted URLs like
46
- `url("foo")` are now returned as `:function` tokens and not `:url` tokens due
47
- to a change in the tokenization spec.
37
+ ## 1.0.1 (2014-11-16)
48
38
 
49
- * Teensy tiny speed and memory usage improvements that you almost certainly
50
- won't notice.
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.
51
40
 
52
- * Fixed: A semicolon following a `@charset` rule would be omitted during
53
- serialization.
41
+ ## 1.0.0 (2014-11-16)
54
42
 
55
- * Fixed: A multibyte char at the beginning of an id token could trigger an
56
- encoding error because `StringScanner#peek` is a jerkface.
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.
57
44
 
58
- [css-syntax-draft]:http://dev.w3.org/csswg/css-syntax-3/
45
+ - Teensy tiny speed and memory usage improvements that you almost certainly won't notice.
59
46
 
47
+ - Fixed: A semicolon following a `@charset` rule would be omitted during serialization.
60
48
 
61
- 0.2.1 (2014-07-22)
62
- ------------------
49
+ - Fixed: A multibyte char at the beginning of an id token could trigger an encoding error because `StringScanner#peek` is a jerkface.
63
50
 
64
- * Fixed: Error when the last property of a rule has no value and no terminating
65
- semicolon. [#2][]
51
+ ## 0.2.1 (2014-07-22)
66
52
 
67
- [#2]:https://github.com/rgrove/crass/issues/2
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)
68
54
 
55
+ ## 0.2.0 (2013-10-10)
69
56
 
70
- 0.2.0 (2013-10-10)
71
- ------------------
57
+ - Added a `:children` field to `:property` nodes. It's an array containing all the nodes that make up the property's value.
72
58
 
73
- * Added a `:children` field to `:property` nodes. It's an array containing all
74
- the nodes that make up the property's value.
59
+ - Fixed: Incorrect value was given for `:property` nodes whose values contained functions.
75
60
 
76
- * Fixed: Incorrect value was given for `:property` nodes whose values contained
77
- functions.
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.
78
62
 
79
- * Fixed: When parsing the value of an at-rule's block as a list of rules, a
80
- selector containing a function (such as `#foo:not(.bar)`) would cause that
81
- property and the rest of the token stream to be discarded.
63
+ ## 0.1.0 (2013-10-04)
82
64
 
65
+ - Tokenization is a little over 50% faster.
83
66
 
84
- 0.1.0 (2013-10-04)
85
- ------------------
67
+ - Added tons of unit tests.
86
68
 
87
- * Tokenization is a little over 50% faster.
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.
88
70
 
89
- * Added tons of unit tests.
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
- * Added `Crass.parse_properties` and `Crass::Parser.parse_properties`, which can
92
- be used to parse the contents of an HTML element's `style` attribute.
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
- * Added `Crass::Parser.parse_rules`, which can be used to parse the contents of
95
- an `:at_rule` block like `@media` that may contain style rules.
75
+ - Fixed: On `:property` nodes, `:important` is now set to `true` when the property is followed by an "!important" declaration.
96
76
 
97
- * Fixed: `Crass::Parser#consume_at_rule` and `#consume_qualified_rule` didn't
98
- properly handle already-parsed `:simple_block` nodes in the input, which
99
- occurs when parsing rules in the value of an `:at_rule` block.
77
+ - Fixed: "!important" is no longer included in the value of a `:property` node.
100
78
 
101
- * Fixed: On `:property` nodes, `:important` is now set to `true` when the
102
- property is followed by an "!important" declaration.
79
+ - Fixed: A variety of tokenization bugs uncovered by tests.
103
80
 
104
- * Fixed: "!important" is no longer included in the value of a `:property` node.
81
+ - Fixed: Added a workaround for a possible spec bug when an `:at_keyword` is encountered while consuming declarations.
105
82
 
106
- * Fixed: A variety of tokenization bugs uncovered by tests.
83
+ ## 0.0.2 (2013-09-30)
107
84
 
108
- * Fixed: Added a workaround for a possible spec bug when an `:at_keyword` is
109
- encountered while consuming declarations.
85
+ - Fixed: `:at_rule` nodes now have a `:name` key.
110
86
 
87
+ ## 0.0.1 (2013-09-27)
111
88
 
112
- 0.0.2 (2013-09-30)
113
- ------------------
114
-
115
- * Fixed: `:at_rule` nodes now have a `:name` key.
116
-
117
-
118
- 0.0.1 (2013-09-27)
119
- ------------------
120
-
121
- * Initial release.
89
+ - Initial release.
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2017 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,64 +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
- ------------
163
-
164
- The best way to contribute is to use Crass and [create issues][issue] when you
165
- run into problems.
166
-
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.
170
-
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.
174
-
175
- [issue]: https://github.com/rgrove/crass/issues/new
176
-
177
- Acknowledgments
178
- ---------------
137
+ ## Versioning
179
138
 
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.
139
+ As of version 1.0.0, Crass adheres strictly to [SemVer 2.0](http://semver.org/spec/v2.0.0.html).
184
140
 
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.
141
+ ## Contributing
188
142
 
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/
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.
193
144
 
194
- License
195
- -------
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.
196
146
 
197
- Copyright (c) 2017 Ryan Grove (ryan@wonko.com)
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.
198
148
 
199
- Permission is hereby granted, free of charge, to any person obtaining a copy of
200
- this software and associated documentation files (the ‘Software’), to deal in
201
- the Software without restriction, including without limitation the rights to
202
- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
203
- the Software, and to permit persons to whom the Software is furnished to do so,
204
- subject to the following conditions:
149
+ ## Acknowledgments
205
150
 
206
- The above copyright notice and this permission notice shall be included in all
207
- copies or substantial portions of the Software.
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.
208
152
 
209
- THE SOFTWARE IS PROVIDED ‘AS IS’, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
210
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
211
- FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
212
- COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
213
- IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
214
- CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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,14 +2,22 @@
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
+
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",
17
+ 'documentation_uri' => "https://www.rubydoc.info/gems/crass/#{s.version}",
18
+ 'rubygems_mfa_required' => 'true',
19
+ 'source_code_uri' => "https://github.com/rgrove/crass/tree/v#{s.version}",
20
+ }
13
21
 
14
22
  s.platform = Gem::Platform::RUBY
15
23
  s.required_ruby_version = Gem::Requirement.new('>= 1.9.2')
@@ -19,6 +27,6 @@ Gem::Specification.new do |s|
19
27
  s.files = `git ls-files -z`.split("\x0").grep_v(%r{^test/})
20
28
 
21
29
  # Development dependencies.
22
- s.add_development_dependency 'minitest', '~> 5.0.8'
23
- 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'
24
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,37 +427,46 @@ 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
433
+ repr = number[0]
434
+ value = number[1]
435
+ type = number[2]
436
+
437
+ if type == :integer
438
+ value = value.to_i
439
+ else
440
+ value = value.to_f
441
+ end
432
442
 
433
443
  if start_identifier?(@s.peek(3))
434
444
  create_token(:dimension,
435
- :repr => number[0],
436
- :type => number[2],
437
- :unit => consume_name,
438
- :value => number[1])
445
+ :repr => repr,
446
+ :type => type,
447
+ :unit => consume_name,
448
+ :value => value)
439
449
 
440
450
  elsif @s.peek == '%'
441
451
  @s.consume
442
452
 
443
453
  create_token(:percentage,
444
- :repr => number[0],
445
- :type => number[2],
446
- :value => number[1])
454
+ :repr => repr,
455
+ :type => type,
456
+ :value => value)
447
457
 
448
458
  else
449
459
  create_token(:number,
450
- :repr => number[0],
451
- :type => number[2],
452
- :value => number[1])
460
+ :repr => repr,
461
+ :type => type,
462
+ :value => value)
453
463
  end
454
464
  end
455
465
 
456
466
  # Consumes a string token that ends at the given character, and returns the
457
467
  # token.
458
468
  #
459
- # 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
460
470
  def consume_string(ending = nil)
461
471
  ending = @s.current if ending.nil?
462
472
  value = String.new
@@ -497,7 +507,7 @@ module Crass
497
507
  # Consumes a Unicode range token and returns it. Assumes the initial "u+" or
498
508
  # "U+" has already been consumed.
499
509
  #
500
- # 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
501
511
  def consume_unicode_range
502
512
  value = @s.scan(RE_HEX) || String.new
503
513
 
@@ -529,7 +539,7 @@ module Crass
529
539
  # Consumes a URL token and returns it. Assumes the original "url(" has
530
540
  # already been consumed.
531
541
  #
532
- # 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
533
543
  def consume_url
534
544
  value = String.new
535
545
 
@@ -577,7 +587,7 @@ module Crass
577
587
 
578
588
  # Converts a valid CSS number string into a number and returns the number.
579
589
  #
580
- # 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
581
591
  def convert_string_to_number(str)
582
592
  matches = RE_NUMBER_STR.match(str)
583
593
 
@@ -588,9 +598,45 @@ module Crass
588
598
  t = matches[:exponent_sign] == '-' ? -1 : 1
589
599
  e = matches[:exponent].to_i
590
600
 
591
- # I know this looks nutty, but it's exactly what's defined in the spec,
592
- # and it works.
593
- 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
624
+
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
637
+ end
638
+
639
+ value
594
640
  end
595
641
 
596
642
  # Creates and returns a new token with the given _properties_.
@@ -604,7 +650,7 @@ module Crass
604
650
 
605
651
  # Preprocesses _input_ to prepare it for the tokenizer.
606
652
  #
607
- # 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
608
654
  def preprocess(input)
609
655
  input = input.to_s.encode('UTF-8',
610
656
  :invalid => :replace,
@@ -619,7 +665,7 @@ module Crass
619
665
  # identifier. If _text_ is `nil`, the current and next two characters in the
620
666
  # input stream will be checked, but will not be consumed.
621
667
  #
622
- # 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
623
669
  def start_identifier?(text = nil)
624
670
  text = @s.current + @s.peek(2) if text.nil?
625
671
 
@@ -643,7 +689,7 @@ module Crass
643
689
  # If _text_ is `nil`, the current and next two characters in the input
644
690
  # stream will be checked, but will not be consumed.
645
691
  #
646
- # 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
647
693
  def start_number?(text = nil)
648
694
  text = @s.current + @s.peek(2) if text.nil?
649
695
 
@@ -679,7 +725,7 @@ module Crass
679
725
  # valid escape sequence. If _text_ is `nil`, the current and next character
680
726
  # in the input stream will be checked, but will not be consumed.
681
727
  #
682
- # 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
683
729
  def valid_escape?(text = nil)
684
730
  text = @s.current + @s.peek if text.nil?
685
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.5'
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.5
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: 2019-10-16 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
@@ -63,8 +62,12 @@ files:
63
62
  homepage: https://github.com/rgrove/crass/
64
63
  licenses:
65
64
  - MIT
66
- metadata: {}
67
- post_install_message:
65
+ metadata:
66
+ bug_tracker_uri: https://github.com/rgrove/crass/issues
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
68
71
  rdoc_options: []
69
72
  require_paths:
70
73
  - lib
@@ -79,8 +82,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
79
82
  - !ruby/object:Gem::Version
80
83
  version: '0'
81
84
  requirements: []
82
- rubygems_version: 3.0.3
83
- signing_key:
85
+ rubygems_version: 4.0.10
84
86
  specification_version: 4
85
87
  summary: CSS parser based on the CSS Syntax Level 3 spec.
86
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