csp_parser 0.1.1

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 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/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ .idea/
2
+ coverage/
3
+
4
+ .rspec
5
+ .DS_Store
data/.rubocop.yml ADDED
@@ -0,0 +1,6 @@
1
+ inherit_gem:
2
+ rubocop-config-umbrellio: lib/rubocop.yml
3
+
4
+ AllCops:
5
+ DisplayCopNames: true
6
+ TargetRubyVersion: 2.5
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/
@@ -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
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CSP
4
+ end
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,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../csp"
4
+
5
+ module CSP::DirectiveValue
6
+ ParseError = Class.new(StandardError)
7
+ InvalidSource = Class.new(StandardError)
8
+ 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,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../csp"
4
+
5
+ module CSP::DirectiveValue::Source
6
+ 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: []