csp_parser 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/ruby.yml +43 -0
- data/.gitignore +5 -0
- data/.rubocop.yml +6 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +91 -0
- data/LICENSE +21 -0
- data/README.md +152 -0
- data/csp_parser.gemspec +19 -0
- data/lib/csp.rb +4 -0
- data/lib/csp_parser.rb +19 -0
- data/lib/directive.rb +55 -0
- data/lib/directive_value/base.rb +18 -0
- data/lib/directive_value/default.rb +14 -0
- data/lib/directive_value/directive_value.rb +8 -0
- data/lib/directive_value/sandbox.rb +14 -0
- data/lib/directive_value/serialized_source_list.rb +42 -0
- data/lib/directive_value/source/base.rb +18 -0
- data/lib/directive_value/source/hash.rb +23 -0
- data/lib/directive_value/source/host.rb +31 -0
- data/lib/directive_value/source/http_host.rb +15 -0
- data/lib/directive_value/source/keyword.rb +15 -0
- data/lib/directive_value/source/nonce.rb +19 -0
- data/lib/directive_value/source/none.rb +15 -0
- data/lib/directive_value/source/scheme.rb +15 -0
- data/lib/directive_value/source/source.rb +6 -0
- data/lib/directive_value/token.rb +14 -0
- data/lib/grammar.rb +50 -0
- data/lib/serialized_policy.rb +32 -0
- metadata +70 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: cead6c85010ac7c384e7e49923be99ca0b975921dd6b427fabdc8639c632aaed
|
4
|
+
data.tar.gz: 15f2f45cc97f7c01751bf8b79afa9f4f624aeb37fb18d7acfa21b63d651b4d06
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a0e8076a7ad8c8fe3f6b999eaf5f29cc70948ddd65f747055512506bd9f1b17d5dd9f7ba196bae3b0d8018a86b277fff460a60bc0491ad3cc583767a97b66c9e
|
7
|
+
data.tar.gz: f89e0fa7162eb4151531f48a71c2210758afdf25e6ae24aebdadfa31228e74dc9f21c00f48d73d5a1b32b92769561227e21c9de80e326a60c461c4d7a5b57291
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# This workflow uses actions that are not certified by GitHub.
|
2
|
+
# They are provided by a third-party and are governed by
|
3
|
+
# separate terms of service, privacy policy, and support
|
4
|
+
# documentation.
|
5
|
+
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
|
6
|
+
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
|
7
|
+
|
8
|
+
name: Ruby
|
9
|
+
|
10
|
+
on:
|
11
|
+
push:
|
12
|
+
branches: [ master ]
|
13
|
+
pull_request:
|
14
|
+
branches: [ master ]
|
15
|
+
|
16
|
+
jobs:
|
17
|
+
test:
|
18
|
+
|
19
|
+
runs-on: ubuntu-latest
|
20
|
+
strategy:
|
21
|
+
matrix:
|
22
|
+
ruby-version: ['2.5']
|
23
|
+
|
24
|
+
steps:
|
25
|
+
- uses: actions/checkout@v2
|
26
|
+
- name: Set up Ruby
|
27
|
+
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
|
28
|
+
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
29
|
+
# uses: ruby/setup-ruby@v1
|
30
|
+
uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e
|
31
|
+
with:
|
32
|
+
ruby-version: ${{ matrix.ruby-version }}
|
33
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
34
|
+
- name: rubocop
|
35
|
+
run: bundle exec rubocop
|
36
|
+
- name: run tests
|
37
|
+
run: COVER=1 bundle exec rspec
|
38
|
+
- name: Upload coverage results
|
39
|
+
uses: actions/upload-artifact@master
|
40
|
+
if: always()
|
41
|
+
with:
|
42
|
+
name: coverage-report
|
43
|
+
path: coverage
|
data/.rubocop.yml
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
git_source(:github) { |repo_name| "https://github.com/#{repo_name}" }
|
6
|
+
|
7
|
+
gem "rspec", require: false, group: :test
|
8
|
+
gem "rubocop", require: false, group: :test
|
9
|
+
gem "rubocop-config-umbrellio", require: false, group: :test
|
10
|
+
gem "simplecov", require: false, group: :test
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
activesupport (6.1.3.2)
|
5
|
+
concurrent-ruby (~> 1.0, >= 1.0.2)
|
6
|
+
i18n (>= 1.6, < 2)
|
7
|
+
minitest (>= 5.1)
|
8
|
+
tzinfo (~> 2.0)
|
9
|
+
zeitwerk (~> 2.3)
|
10
|
+
ast (2.4.2)
|
11
|
+
concurrent-ruby (1.1.9)
|
12
|
+
diff-lcs (1.4.4)
|
13
|
+
docile (1.4.0)
|
14
|
+
i18n (1.8.10)
|
15
|
+
concurrent-ruby (~> 1.0)
|
16
|
+
minitest (5.14.4)
|
17
|
+
parallel (1.20.1)
|
18
|
+
parser (3.0.1.1)
|
19
|
+
ast (~> 2.4.1)
|
20
|
+
rack (2.2.3)
|
21
|
+
rainbow (3.0.0)
|
22
|
+
regexp_parser (2.1.1)
|
23
|
+
rexml (3.2.5)
|
24
|
+
rspec (3.10.0)
|
25
|
+
rspec-core (~> 3.10.0)
|
26
|
+
rspec-expectations (~> 3.10.0)
|
27
|
+
rspec-mocks (~> 3.10.0)
|
28
|
+
rspec-core (3.10.1)
|
29
|
+
rspec-support (~> 3.10.0)
|
30
|
+
rspec-expectations (3.10.1)
|
31
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
32
|
+
rspec-support (~> 3.10.0)
|
33
|
+
rspec-mocks (3.10.2)
|
34
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
35
|
+
rspec-support (~> 3.10.0)
|
36
|
+
rspec-support (3.10.2)
|
37
|
+
rubocop (1.11.0)
|
38
|
+
parallel (~> 1.10)
|
39
|
+
parser (>= 3.0.0.0)
|
40
|
+
rainbow (>= 2.2.2, < 4.0)
|
41
|
+
regexp_parser (>= 1.8, < 3.0)
|
42
|
+
rexml
|
43
|
+
rubocop-ast (>= 1.2.0, < 2.0)
|
44
|
+
ruby-progressbar (~> 1.7)
|
45
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
46
|
+
rubocop-ast (1.7.0)
|
47
|
+
parser (>= 3.0.1.1)
|
48
|
+
rubocop-config-umbrellio (1.11.0.51)
|
49
|
+
rubocop (= 1.11.0)
|
50
|
+
rubocop-performance (= 1.10.0)
|
51
|
+
rubocop-rails (= 2.9.1)
|
52
|
+
rubocop-rake (= 0.5.1)
|
53
|
+
rubocop-rspec (= 2.2.0)
|
54
|
+
rubocop-sequel (= 0.2.0)
|
55
|
+
rubocop-performance (1.10.0)
|
56
|
+
rubocop (>= 0.90.0, < 2.0)
|
57
|
+
rubocop-ast (>= 0.4.0)
|
58
|
+
rubocop-rails (2.9.1)
|
59
|
+
activesupport (>= 4.2.0)
|
60
|
+
rack (>= 1.1)
|
61
|
+
rubocop (>= 0.90.0, < 2.0)
|
62
|
+
rubocop-rake (0.5.1)
|
63
|
+
rubocop
|
64
|
+
rubocop-rspec (2.2.0)
|
65
|
+
rubocop (~> 1.0)
|
66
|
+
rubocop-ast (>= 1.1.0)
|
67
|
+
rubocop-sequel (0.2.0)
|
68
|
+
rubocop (~> 1.0)
|
69
|
+
ruby-progressbar (1.11.0)
|
70
|
+
simplecov (0.21.2)
|
71
|
+
docile (~> 1.1)
|
72
|
+
simplecov-html (~> 0.11)
|
73
|
+
simplecov_json_formatter (~> 0.1)
|
74
|
+
simplecov-html (0.12.3)
|
75
|
+
simplecov_json_formatter (0.1.3)
|
76
|
+
tzinfo (2.0.4)
|
77
|
+
concurrent-ruby (~> 1.0)
|
78
|
+
unicode-display_width (2.0.0)
|
79
|
+
zeitwerk (2.4.2)
|
80
|
+
|
81
|
+
PLATFORMS
|
82
|
+
ruby
|
83
|
+
|
84
|
+
DEPENDENCIES
|
85
|
+
rspec
|
86
|
+
rubocop
|
87
|
+
rubocop-config-umbrellio
|
88
|
+
simplecov
|
89
|
+
|
90
|
+
BUNDLED WITH
|
91
|
+
1.17.2
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2021 Igor Kirianov
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
# CSP::Parser ![Build Status](https://github.com/KirIgor/csp-parser/actions/workflows/ruby.yml/badge.svg)
|
2
|
+
|
3
|
+
CSP::Parser is a gem for parsing Content Security Policy.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add `gem "csp_parser"` to your Gemfile.
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
To parse Content Security Policy:
|
12
|
+
```ruby
|
13
|
+
require "csp_parser"
|
14
|
+
|
15
|
+
# Content-Security-Policy:
|
16
|
+
policy_str = "default-src 'self';" +
|
17
|
+
"img-src *;" +
|
18
|
+
"media-src https://*.media2.com:*/path ftp://media3.com;" +
|
19
|
+
"script-src 'sha256-RFWPLDbv2BY+rCkDzsE+0fr8ylGr2R2faWMhq4lfEQc=';" +
|
20
|
+
"font-src 'nonce-RFWPLDbv2BY+rCkDzsE+0fr8ylGr2R2faWMhq4lfEQc=';" +
|
21
|
+
"frame-src https:;" +
|
22
|
+
"object-src 'none'"
|
23
|
+
|
24
|
+
policy = CSP::Parser.parse(policy_str)
|
25
|
+
# => #<SerializedPolicy:0x00000001231da870 @directives=[
|
26
|
+
# #<Directive:0x00000001231da4d8 @value_str="default-src 'self'",
|
27
|
+
# @name="default-src",
|
28
|
+
# @value=#<DirectiveValue::SerializedSourceList:0x00000001231da280 @value_str="'self'",
|
29
|
+
# @sources=[#<DirectiveValue::Source::Keyword:0x00000001231d99c0 @value_str="'self'">]
|
30
|
+
# >
|
31
|
+
# >,
|
32
|
+
# #<Directive:0x00000001231d9768 @value_str="img-src *",
|
33
|
+
# @name="img-src",
|
34
|
+
# @value=#<DirectiveValue::SerializedSourceList:0x00000001231d9510 @value_str="*",
|
35
|
+
# @sources=[#<DirectiveValue::Source::HttpHost:0x00000001231d8390 @value_str="*">]
|
36
|
+
# >
|
37
|
+
# >,
|
38
|
+
# #<Directive:0x00000001231d8110 @value_str="media-src *.media2.com:* ftp://media3.com",
|
39
|
+
# @name="media-src",
|
40
|
+
# @value=#<DirectiveValue::SerializedSourceList:0x00000001231e3e70
|
41
|
+
# @value_str="media-src https://*.media2.com:*/path ftp://media3.com",
|
42
|
+
# @sources=[
|
43
|
+
# #<DirectiveValue::Source::HttpHost:0x00000001231e1ff8 @value_str="https://*.media2.com:*/path">,
|
44
|
+
# #<DirectiveValue::Source::Host:0x00000001231e0c70 @value_str="ftp://media3.com">
|
45
|
+
# ]
|
46
|
+
# >
|
47
|
+
# >,
|
48
|
+
# #<Directive:0x00000001231e09c8 @value_str="script-src 'sha256-RFWPLDbv2BY+rCkDzsE+0fr8ylGr2R2faWMhq4lfEQc='",
|
49
|
+
# @name="script-src",
|
50
|
+
# @value=#<DirectiveValue::SerializedSourceList:0x00000001231e0798
|
51
|
+
# @value_str="'sha256-RFWPLDbv2BY+rCkDzsE+0fr8ylGr2R2faWMhq4lfEQc='",
|
52
|
+
# @sources=[
|
53
|
+
# #<DirectiveValue::Source::Hash:0x00000001231ebc88
|
54
|
+
# @value_str="'sha256-RFWPLDbv2BY+rCkDzsE+0fr8ylGr2R2faWMhq4lfEQc='"
|
55
|
+
# >
|
56
|
+
# ]
|
57
|
+
# >
|
58
|
+
# >,
|
59
|
+
# #<Directive:0x00000001231eba30 @value_str="font-src 'nonce-RFWPLDbv2BY+rCkDzsE+0fr8ylGr2R2faWMhq4lfEQc='",
|
60
|
+
# @name="font-src",
|
61
|
+
# @value=#<DirectiveValue::SerializedSourceList:0x00000001231eb800
|
62
|
+
# @value_str="'nonce-RFWPLDbv2BY+rCkDzsE+0fr8ylGr2R2faWMhq4lfEQc='",
|
63
|
+
# @sources=[
|
64
|
+
# #<DirectiveValue::Source::Nonce:0x00000001231eaab8
|
65
|
+
# @value_str="'nonce-RFWPLDbv2BY+rCkDzsE+0fr8ylGr2R2faWMhq4lfEQc='",
|
66
|
+
# >
|
67
|
+
# ]
|
68
|
+
# >
|
69
|
+
# >,
|
70
|
+
# #<Directive:0x00000001231ea888 @value_str="frame-src https:",
|
71
|
+
# @name="frame-src",
|
72
|
+
# @value=#<DirectiveValue::SerializedSourceList:0x00000001231ea630 @value_str="https:",
|
73
|
+
# @sources=[
|
74
|
+
# #<DirectiveValue::Source::Scheme:0x00000001231e96e0 @value_str="https:">
|
75
|
+
# ]
|
76
|
+
# >
|
77
|
+
# >,
|
78
|
+
# #<Directive:0x00000001231e9488 @value_str="object-src 'none'",
|
79
|
+
# @name="object-src",
|
80
|
+
# @value=#<DirectiveValue::SerializedSourceList:0x00000001231e9230 @value_str="'none'",
|
81
|
+
# @sources=[#<DirectiveValue::Source::None:0x00000001231e8b78 @value_str="'none'">]
|
82
|
+
# >
|
83
|
+
# >
|
84
|
+
# ]>
|
85
|
+
|
86
|
+
policy.directives.map(&:name)
|
87
|
+
# => ["default-src", "img-src", "media-src", "script-src", "font-src", "frame-src", "object-src"]
|
88
|
+
|
89
|
+
default_src = policy.directives.find { |d| d.name == "default-src" } # DirectiveValue::SerializedSourceList
|
90
|
+
keyword = default_src.value.sources.first # DirectiveValue::Source::Keyword
|
91
|
+
keyword.to_s # => "'self'"
|
92
|
+
|
93
|
+
media_src = policy.directives.find { |d| d.name == "media-src" } # DirectiveValue::SerializedSourceList
|
94
|
+
http_host = media_src.value.sources.first # DirectiveValue::Source::HttpHost
|
95
|
+
http_host.scheme_part # => "https"
|
96
|
+
http_host.host_part # => "*.media2.com"
|
97
|
+
http_host.port_part # => "*"
|
98
|
+
http_host.path_part # => "/path"
|
99
|
+
http_host.to_s # => "https://*.media2.com:*/path"
|
100
|
+
|
101
|
+
script_src = policy.directives.find { |d| d.name == "script-src" } # DirectiveValue::SerializedSourceList
|
102
|
+
hash = script_src.value.sources.first # DirectiveValue::Source::Hash
|
103
|
+
hash.algorithm # => "sha256"
|
104
|
+
hash.value # => "RFWPLDbv2BY+rCkDzsE+0fr8ylGr2R2faWMhq4lfEQc="
|
105
|
+
|
106
|
+
font_src = policy.directives.find { |d| d.name == "font-src" } # DirectiveValue::SerializedSourceList
|
107
|
+
nonce = font_src.value.sources.first # DirectiveValue::Source::Nonce
|
108
|
+
nonce.value # => "RFWPLDbv2BY+rCkDzsE+0fr8ylGr2R2faWMhq4lfEQc="
|
109
|
+
|
110
|
+
frame_src = policy.directives.find { |d| d.name == "frame-src" } # DirectiveValue::SerializedSourceList
|
111
|
+
scheme = frame_src.value.sources.first # DirectiveValue::Source::Scheme
|
112
|
+
scheme.to_s # => "https:"
|
113
|
+
|
114
|
+
object_src = policy.directives.find { |d| d.name == "object-src" } # DirectiveValue::SerializedSourceList
|
115
|
+
none = object_src.value.sources.first # DirectiveValue::Source::None
|
116
|
+
none.to_s # => "'none'"
|
117
|
+
```
|
118
|
+
|
119
|
+
You can also parse individual source:
|
120
|
+
```ruby
|
121
|
+
http_host = CSP::DirectiveValue::Source::HttpHost.new("https://*.example.com:*/path")
|
122
|
+
http_host.scheme_part # => "https"
|
123
|
+
http_host.host_part # => "*.example.com"
|
124
|
+
http_host.port_part # => "*"
|
125
|
+
http_host.path_part # => "/path"
|
126
|
+
http_host.to_s # => "https://*.example.com:*/path"
|
127
|
+
```
|
128
|
+
|
129
|
+
Or just check if http host is valid:
|
130
|
+
```ruby
|
131
|
+
CSP::Parser.valid_http_host_source?("https://*.example.com:*/path")
|
132
|
+
# => true
|
133
|
+
CSP::Parser.valid_http_host_source?("custom-scheme://*.example.com:*/path")
|
134
|
+
# => false
|
135
|
+
```
|
136
|
+
|
137
|
+
## Contributing
|
138
|
+
|
139
|
+
Feel free to contribute at https://github.com/KirIgor/csp-parser.
|
140
|
+
|
141
|
+
## License
|
142
|
+
|
143
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
144
|
+
|
145
|
+
## Author
|
146
|
+
|
147
|
+
Created by Igor Kirianov.
|
148
|
+
|
149
|
+
## Reference
|
150
|
+
|
151
|
+
1. MDN Web Docs: https://developer.mozilla.org/ru/docs/Web/HTTP/CSP
|
152
|
+
2. Document: https://www.w3.org/TR/CSP3/
|
data/csp_parser.gemspec
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.required_ruby_version = ">= 2.5.0"
|
5
|
+
s.name = "csp_parser"
|
6
|
+
s.version = "0.1.1"
|
7
|
+
s.summary = "Content Security Policy Parser"
|
8
|
+
s.description = "Gem for parsing Content Security Policy."
|
9
|
+
s.authors = ["Igor Kirianov"]
|
10
|
+
s.email = "ig6966.ivanov@gmail.com"
|
11
|
+
s.homepage =
|
12
|
+
"https://rubygems.org/gems/csp_parser"
|
13
|
+
s.license = "MIT"
|
14
|
+
|
15
|
+
s.files = `git ls-files -z`.split("\x0").reject do |f|
|
16
|
+
f.match(%r{^(test|spec|features)/})
|
17
|
+
end
|
18
|
+
s.require_paths = ["lib"]
|
19
|
+
end
|
data/lib/csp.rb
ADDED
data/lib/csp_parser.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./csp"
|
4
|
+
require_relative "./serialized_policy"
|
5
|
+
require_relative "./directive_value/source/http_host"
|
6
|
+
|
7
|
+
class CSP::Parser
|
8
|
+
class << self
|
9
|
+
def parse(csp_policy_str)
|
10
|
+
CSP::SerializedPolicy.new(csp_policy_str)
|
11
|
+
end
|
12
|
+
|
13
|
+
def valid_http_host_source?(http_host_str)
|
14
|
+
!!CSP::DirectiveValue::Source::HttpHost.new(http_host_str)
|
15
|
+
rescue
|
16
|
+
false
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/directive.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./directive_value/serialized_source_list"
|
4
|
+
require_relative "./directive_value/token"
|
5
|
+
require_relative "./directive_value/sandbox"
|
6
|
+
require_relative "./directive_value/default"
|
7
|
+
require_relative "./grammar"
|
8
|
+
require_relative "./csp"
|
9
|
+
|
10
|
+
class CSP::Directive
|
11
|
+
ParseError = Class.new(StandardError)
|
12
|
+
|
13
|
+
DIRECTIVE_VALUES = {
|
14
|
+
"child-src" => CSP::DirectiveValue::SerializedSourceList,
|
15
|
+
"connect-src" => CSP::DirectiveValue::SerializedSourceList,
|
16
|
+
"default-src" => CSP::DirectiveValue::SerializedSourceList,
|
17
|
+
"font-src" => CSP::DirectiveValue::SerializedSourceList,
|
18
|
+
"frame-src" => CSP::DirectiveValue::SerializedSourceList,
|
19
|
+
"img-src" => CSP::DirectiveValue::SerializedSourceList,
|
20
|
+
"manifest-src" => CSP::DirectiveValue::SerializedSourceList,
|
21
|
+
"media-src" => CSP::DirectiveValue::SerializedSourceList,
|
22
|
+
"object-src" => CSP::DirectiveValue::SerializedSourceList,
|
23
|
+
"prefetch-src" => CSP::DirectiveValue::SerializedSourceList,
|
24
|
+
"script-src" => CSP::DirectiveValue::SerializedSourceList,
|
25
|
+
"script-src-elem" => CSP::DirectiveValue::SerializedSourceList,
|
26
|
+
"script-src-attrs" => CSP::DirectiveValue::SerializedSourceList,
|
27
|
+
"style-src" => CSP::DirectiveValue::SerializedSourceList,
|
28
|
+
"style-src-elem" => CSP::DirectiveValue::SerializedSourceList,
|
29
|
+
"style-src-attr" => CSP::DirectiveValue::SerializedSourceList,
|
30
|
+
"worker-src" => CSP::DirectiveValue::SerializedSourceList,
|
31
|
+
"base-uri" => CSP::DirectiveValue::SerializedSourceList,
|
32
|
+
"sandbox" => CSP::DirectiveValue::Sandbox,
|
33
|
+
"form-action" => CSP::DirectiveValue::SerializedSourceList,
|
34
|
+
"frame-ancestors" => CSP::DirectiveValue::SerializedSourceList,
|
35
|
+
"navigate-to" => CSP::DirectiveValue::SerializedSourceList,
|
36
|
+
"report-to" => CSP::DirectiveValue::Token,
|
37
|
+
}.freeze
|
38
|
+
|
39
|
+
def initialize(value_str)
|
40
|
+
@value_str = value_str
|
41
|
+
@match = value_str.match(/\A#{CSP::Grammar::SERIALIZED_DIRECTIVE}\z/o)
|
42
|
+
|
43
|
+
raise ParseError, @value_str if @match.nil?
|
44
|
+
|
45
|
+
@name = @match["name"]
|
46
|
+
value_class = DIRECTIVE_VALUES[@name] || CSP::DirectiveValue::Default
|
47
|
+
@value = value_class.new(@match["value"])
|
48
|
+
end
|
49
|
+
|
50
|
+
attr_reader :name, :value
|
51
|
+
|
52
|
+
def to_s
|
53
|
+
@value_str
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../csp"
|
4
|
+
|
5
|
+
class CSP::DirectiveValue::Base
|
6
|
+
def initialize(value_str)
|
7
|
+
@value_str = value_str
|
8
|
+
@match = @value_str.match(regexp)
|
9
|
+
|
10
|
+
raise CSP::DirectiveValue::ParseError, @value_str if @match.nil?
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
@value_str
|
15
|
+
end
|
16
|
+
|
17
|
+
# @!method regexp
|
18
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./directive_value"
|
4
|
+
require_relative "./base"
|
5
|
+
require_relative "../grammar"
|
6
|
+
require_relative "../csp"
|
7
|
+
|
8
|
+
class CSP::DirectiveValue::Default < CSP::DirectiveValue::Base
|
9
|
+
private
|
10
|
+
|
11
|
+
def regexp
|
12
|
+
/\A#{CSP::Grammar::DIRECTIVE_VALUE}\z/o
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./directive_value"
|
4
|
+
require_relative "./base"
|
5
|
+
require_relative "../grammar"
|
6
|
+
require_relative "../csp"
|
7
|
+
|
8
|
+
class CSP::DirectiveValue::Sandbox < CSP::DirectiveValue::Base
|
9
|
+
private
|
10
|
+
|
11
|
+
def regexp
|
12
|
+
/\A#{CSP::Grammar::SANDBOX}\z/o
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./directive_value"
|
4
|
+
require_relative "./base"
|
5
|
+
require_relative "../csp"
|
6
|
+
# rubocop:disable Lint/NonDeterministicRequireOrder, Style/StringConcatenation
|
7
|
+
Dir[File.dirname(__FILE__) + "/source/*.rb"].each { |file| require file }
|
8
|
+
# rubocop:enable Lint/NonDeterministicRequireOrder, Style/StringConcatenation
|
9
|
+
|
10
|
+
class CSP::DirectiveValue::SerializedSourceList < CSP::DirectiveValue::Base
|
11
|
+
SOURCE_LIST = [
|
12
|
+
CSP::DirectiveValue::Source::None,
|
13
|
+
CSP::DirectiveValue::Source::Keyword,
|
14
|
+
CSP::DirectiveValue::Source::Hash,
|
15
|
+
CSP::DirectiveValue::Source::Nonce,
|
16
|
+
CSP::DirectiveValue::Source::Scheme,
|
17
|
+
CSP::DirectiveValue::Source::HttpHost,
|
18
|
+
CSP::DirectiveValue::Source::Host,
|
19
|
+
].freeze
|
20
|
+
|
21
|
+
attr_reader :sources
|
22
|
+
|
23
|
+
def initialize(value_str)
|
24
|
+
super
|
25
|
+
|
26
|
+
@sources = @value_str.split.map do |source_str|
|
27
|
+
source = SOURCE_LIST.lazy.map do |s|
|
28
|
+
s.new(source_str)
|
29
|
+
rescue
|
30
|
+
nil
|
31
|
+
end.find(&:itself)
|
32
|
+
|
33
|
+
source
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def regexp
|
40
|
+
/\A#{CSP::Grammar::SERIALIZED_SOURCE_LIST}\z/o
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../csp"
|
4
|
+
|
5
|
+
class CSP::DirectiveValue::Source::Base
|
6
|
+
def initialize(value_str)
|
7
|
+
@value_str = value_str
|
8
|
+
@match = @value_str.match(regexp)
|
9
|
+
|
10
|
+
raise CSP::DirectiveValue::InvalidSource, @value_str if @match.nil?
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_s
|
14
|
+
@value_str
|
15
|
+
end
|
16
|
+
|
17
|
+
# @!method regexp
|
18
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../directive_value"
|
4
|
+
require_relative "./source"
|
5
|
+
require_relative "./base"
|
6
|
+
require_relative "../../grammar"
|
7
|
+
require_relative "../../csp"
|
8
|
+
|
9
|
+
class CSP::DirectiveValue::Source::Hash < CSP::DirectiveValue::Source::Base
|
10
|
+
def algorithm
|
11
|
+
@match[:algorithm]
|
12
|
+
end
|
13
|
+
|
14
|
+
def value
|
15
|
+
@match[:value]
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def regexp
|
21
|
+
/\A#{CSP::Grammar::HASH_SOURCE}\z/o
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../directive_value"
|
4
|
+
require_relative "./source"
|
5
|
+
require_relative "./base"
|
6
|
+
require_relative "../../grammar"
|
7
|
+
require_relative "../../csp"
|
8
|
+
|
9
|
+
class CSP::DirectiveValue::Source::Host < CSP::DirectiveValue::Source::Base
|
10
|
+
def scheme_part
|
11
|
+
@match[:scheme_part]
|
12
|
+
end
|
13
|
+
|
14
|
+
def host_part
|
15
|
+
@match[:host_part]
|
16
|
+
end
|
17
|
+
|
18
|
+
def port_part
|
19
|
+
@match[:port_part]
|
20
|
+
end
|
21
|
+
|
22
|
+
def path_part
|
23
|
+
@match[:path_part]
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def regexp
|
29
|
+
/\A#{CSP::Grammar::HOST_SOURCE}\z/o
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../directive_value"
|
4
|
+
require_relative "./source"
|
5
|
+
require_relative "../../grammar"
|
6
|
+
require_relative "./host"
|
7
|
+
require_relative "../../csp"
|
8
|
+
|
9
|
+
class CSP::DirectiveValue::Source::HttpHost < CSP::DirectiveValue::Source::Host
|
10
|
+
private
|
11
|
+
|
12
|
+
def regexp
|
13
|
+
/\A#{CSP::Grammar::HTTP_HOST_SOURCE}\z/o
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../directive_value"
|
4
|
+
require_relative "./source"
|
5
|
+
require_relative "./base"
|
6
|
+
require_relative "../../grammar"
|
7
|
+
require_relative "../../csp"
|
8
|
+
|
9
|
+
class CSP::DirectiveValue::Source::Keyword < CSP::DirectiveValue::Source::Base
|
10
|
+
private
|
11
|
+
|
12
|
+
def regexp
|
13
|
+
/\A#{CSP::Grammar::KEYWORD_SOURCE}\z/o
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../directive_value"
|
4
|
+
require_relative "./source"
|
5
|
+
require_relative "./base"
|
6
|
+
require_relative "../../grammar"
|
7
|
+
require_relative "../../csp"
|
8
|
+
|
9
|
+
class CSP::DirectiveValue::Source::Nonce < CSP::DirectiveValue::Source::Base
|
10
|
+
def value
|
11
|
+
@match[:value]
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def regexp
|
17
|
+
/\A#{CSP::Grammar::NONCE_SOURCE}\z/o
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../directive_value"
|
4
|
+
require_relative "./source"
|
5
|
+
require_relative "./base"
|
6
|
+
require_relative "../../grammar"
|
7
|
+
require_relative "../../csp"
|
8
|
+
|
9
|
+
class CSP::DirectiveValue::Source::None < CSP::DirectiveValue::Source::Base
|
10
|
+
private
|
11
|
+
|
12
|
+
def regexp
|
13
|
+
/\A#{CSP::Grammar::NONE_SOURCE}\z/o
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../directive_value"
|
4
|
+
require_relative "./source"
|
5
|
+
require_relative "./base"
|
6
|
+
require_relative "../../grammar"
|
7
|
+
require_relative "../../csp"
|
8
|
+
|
9
|
+
class CSP::DirectiveValue::Source::Scheme < CSP::DirectiveValue::Source::Base
|
10
|
+
private
|
11
|
+
|
12
|
+
def regexp
|
13
|
+
/\A#{CSP::Grammar::SCHEME_SOURCE}\z/o
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./directive_value"
|
4
|
+
require_relative "./base"
|
5
|
+
require_relative "../grammar"
|
6
|
+
require_relative "../csp"
|
7
|
+
|
8
|
+
class CSP::DirectiveValue::Token < CSP::DirectiveValue::Base
|
9
|
+
private
|
10
|
+
|
11
|
+
def regexp
|
12
|
+
/\A#{CSP::Grammar::TOKEN}\z/o
|
13
|
+
end
|
14
|
+
end
|
data/lib/grammar.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./csp"
|
4
|
+
|
5
|
+
class CSP::Grammar
|
6
|
+
ASCII_WHITESPACE = "(\x09|\x0a|\x0c|\x0d|\x20)"
|
7
|
+
OPTIONAL_ASCII_WHITESPACE = "#{ASCII_WHITESPACE}*"
|
8
|
+
REQUIRED_ASCII_WHITESPACE = "#{ASCII_WHITESPACE}+"
|
9
|
+
ALPHA = "[a-zA-Z]"
|
10
|
+
DIGIT = "[0-9]"
|
11
|
+
SCHEME_PART = "#{ALPHA}([a-zA-Z0-9+\\-.])*"
|
12
|
+
HTTP_SCHEME_PART = "https?"
|
13
|
+
SCHEME_SOURCE = "#{SCHEME_PART}:"
|
14
|
+
HOST_CHAR = '[a-zA-Z0-9\-]'
|
15
|
+
HOST_PART = "(\\*|(?:\\*\\.)?#{HOST_CHAR}+(\\.#{HOST_CHAR}+)*)"
|
16
|
+
PORT_PART = "(#{DIGIT}+|\\*)"
|
17
|
+
PATH_CHAR = '[a-zA-Z0-9\-._!&\'()*+=]'
|
18
|
+
PATH_PART = "/(#{PATH_CHAR}+(?:/#{PATH_CHAR}*)*)?"
|
19
|
+
HOST_SOURCE = "(?:(?<scheme_part>#{SCHEME_PART})://)?"\
|
20
|
+
"(?<host_part>#{HOST_PART})"\
|
21
|
+
"(?::(?<port_part>#{PORT_PART}))?"\
|
22
|
+
"(?<path_part>#{PATH_PART})?"
|
23
|
+
HTTP_HOST_SOURCE = "(?:(?<scheme_part>#{HTTP_SCHEME_PART})://)?"\
|
24
|
+
"(?<host_part>#{HOST_PART})"\
|
25
|
+
"(?::(?<port_part>#{PORT_PART}))?"\
|
26
|
+
"(?<path_part>#{PATH_PART})?"
|
27
|
+
KEYWORD_SOURCE = "('self'|'unsafe-inline'|'unsafe-eval'|'strict-dynamic'|"\
|
28
|
+
"'unsafe-hashes'|'report-sample'|'unsafe-allow-redirects')"
|
29
|
+
BASE64_VALUE = '[a-zA-Z0-9+/\-_]+={0,2}'
|
30
|
+
NONCE_SOURCE = "'nonce-(?<value>#{BASE64_VALUE})'"
|
31
|
+
HASH_ALGORITHM = "(sha256|sha384|sha512)"
|
32
|
+
HASH_SOURCE = "'(?<algorithm>#{HASH_ALGORITHM})-(?<value>#{BASE64_VALUE})'"
|
33
|
+
NONE_SOURCE = "'none'"
|
34
|
+
SOURCE_EXPRESSION = "(#{SCHEME_SOURCE}|#{HOST_SOURCE}|#{KEYWORD_SOURCE}"\
|
35
|
+
"|#{NONCE_SOURCE}|#{HASH_SOURCE}|#{NONE_SOURCE})"
|
36
|
+
SERIALIZED_SOURCE_LIST = "(#{SOURCE_EXPRESSION}"\
|
37
|
+
"(?:#{REQUIRED_ASCII_WHITESPACE}#{SOURCE_EXPRESSION})*)"
|
38
|
+
TOKEN_CHAR = '[!#$%&\'*+\-.^_`|~0-9a-zA-Z]'
|
39
|
+
TOKEN = "#{TOKEN_CHAR}+"
|
40
|
+
SANDBOX = "(|#{TOKEN}(?:#{REQUIRED_ASCII_WHITESPACE}#{TOKEN})*)"
|
41
|
+
ANCESTOR_SOURCE = "(#{SCHEME_SOURCE}|#{HOST_SOURCE}|'self')"
|
42
|
+
ANCESTOR_SOURCE_LIST = "(#{ANCESTOR_SOURCE}"\
|
43
|
+
"(?:#{REQUIRED_ASCII_WHITESPACE}#{ANCESTOR_SOURCE})*|'none')"
|
44
|
+
DIRECTIVE_NAME = '[a-zA-Z0-9\-]+'
|
45
|
+
DIRECTIVE_VALUE = "[ \x21-\x2B\\\x2D-\x3A\x3C-\x7E]*"
|
46
|
+
SERIALIZED_DIRECTIVE = "(?<name>#{DIRECTIVE_NAME})"\
|
47
|
+
"(?:#{REQUIRED_ASCII_WHITESPACE}(?<value>#{DIRECTIVE_VALUE}))?"
|
48
|
+
SERIALIZED_POLICY = "#{SERIALIZED_DIRECTIVE}(?:#{OPTIONAL_ASCII_WHITESPACE};"\
|
49
|
+
"(?:#{OPTIONAL_ASCII_WHITESPACE}#{SERIALIZED_DIRECTIVE})?)*"
|
50
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "./csp"
|
4
|
+
require_relative "./grammar"
|
5
|
+
require_relative "./directive"
|
6
|
+
|
7
|
+
class CSP::SerializedPolicy
|
8
|
+
ParseError = Class.new(StandardError)
|
9
|
+
|
10
|
+
def initialize(value_str)
|
11
|
+
@value_str = value_str
|
12
|
+
@match = @value_str.match(regexp)
|
13
|
+
|
14
|
+
raise ParseError, @value_str if @match.nil?
|
15
|
+
|
16
|
+
@directives = @value_str.split(";").map do |directive_str|
|
17
|
+
CSP::Directive.new(directive_str.strip)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
@value_str
|
23
|
+
end
|
24
|
+
|
25
|
+
attr_reader :directives
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def regexp
|
30
|
+
/\A#{CSP::Grammar::SERIALIZED_POLICY}\z/o
|
31
|
+
end
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: csp_parser
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Igor Kirianov
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-06-21 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: Gem for parsing Content Security Policy.
|
14
|
+
email: ig6966.ivanov@gmail.com
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- ".github/workflows/ruby.yml"
|
20
|
+
- ".gitignore"
|
21
|
+
- ".rubocop.yml"
|
22
|
+
- Gemfile
|
23
|
+
- Gemfile.lock
|
24
|
+
- LICENSE
|
25
|
+
- README.md
|
26
|
+
- csp_parser.gemspec
|
27
|
+
- lib/csp.rb
|
28
|
+
- lib/csp_parser.rb
|
29
|
+
- lib/directive.rb
|
30
|
+
- lib/directive_value/base.rb
|
31
|
+
- lib/directive_value/default.rb
|
32
|
+
- lib/directive_value/directive_value.rb
|
33
|
+
- lib/directive_value/sandbox.rb
|
34
|
+
- lib/directive_value/serialized_source_list.rb
|
35
|
+
- lib/directive_value/source/base.rb
|
36
|
+
- lib/directive_value/source/hash.rb
|
37
|
+
- lib/directive_value/source/host.rb
|
38
|
+
- lib/directive_value/source/http_host.rb
|
39
|
+
- lib/directive_value/source/keyword.rb
|
40
|
+
- lib/directive_value/source/nonce.rb
|
41
|
+
- lib/directive_value/source/none.rb
|
42
|
+
- lib/directive_value/source/scheme.rb
|
43
|
+
- lib/directive_value/source/source.rb
|
44
|
+
- lib/directive_value/token.rb
|
45
|
+
- lib/grammar.rb
|
46
|
+
- lib/serialized_policy.rb
|
47
|
+
homepage: https://rubygems.org/gems/csp_parser
|
48
|
+
licenses:
|
49
|
+
- MIT
|
50
|
+
metadata: {}
|
51
|
+
post_install_message:
|
52
|
+
rdoc_options: []
|
53
|
+
require_paths:
|
54
|
+
- lib
|
55
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
version: 2.5.0
|
60
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
version: '0'
|
65
|
+
requirements: []
|
66
|
+
rubygems_version: 3.0.3
|
67
|
+
signing_key:
|
68
|
+
specification_version: 4
|
69
|
+
summary: Content Security Policy Parser
|
70
|
+
test_files: []
|