csp_parser 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
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: []