philiprehberger-header_kit 0.2.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +68 -8
- data/lib/philiprehberger/header_kit/cors.rb +29 -0
- data/lib/philiprehberger/header_kit/forwarded.rb +47 -0
- data/lib/philiprehberger/header_kit/security.rb +27 -0
- data/lib/philiprehberger/header_kit/version.rb +1 -1
- data/lib/philiprehberger/header_kit.rb +43 -0
- metadata +9 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b397e499d587d20070ad785372aa81dc6574f583de2663cb105598ba229f3b52
|
|
4
|
+
data.tar.gz: cf8de787ddaa0f5cf2c95efa89f17e8db64d0368262bdd691b29c029fc0587fe
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 600588c8096d75b8b8559a2aa0d9477025a7e02706e3906f7c57297999f303083078f8dd7dfe75c70a56a2ddb6e424d88c69e94b11cfee8a9038eb1c1e7bc275
|
|
7
|
+
data.tar.gz: 9984feba85757ee07365321359798fe91a87c0f44902c65c81f52bb1885683c0971ea78590eee5db427e952afe72bbc1dceee4eba39478ca90e24e04622bcb97
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.3.1] - 2026-03-31
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- Standardize README badges, support section, and license format
|
|
14
|
+
|
|
15
|
+
## [0.3.0] - 2026-03-31
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- `parse_cors` and `build_cors` for CORS header handling
|
|
20
|
+
- `security_headers` for generating recommended security headers
|
|
21
|
+
- `parse_forwarded` for RFC 7239 Forwarded header parsing
|
|
22
|
+
- `parse_via` for Via header parsing
|
|
23
|
+
|
|
10
24
|
## [0.2.0] - 2026-03-28
|
|
11
25
|
|
|
12
26
|
### Added
|
data/README.md
CHANGED
|
@@ -2,12 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://github.com/philiprehberger/rb-header-kit/actions/workflows/ci.yml)
|
|
4
4
|
[](https://rubygems.org/gems/philiprehberger-header_kit)
|
|
5
|
-
[](https://github.com/philiprehberger/rb-header-kit/releases)
|
|
6
5
|
[](https://github.com/philiprehberger/rb-header-kit/commits/main)
|
|
7
|
-
[](LICENSE)
|
|
8
|
-
[](https://github.com/philiprehberger/rb-header-kit/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
|
|
9
|
-
[](https://github.com/philiprehberger/rb-header-kit/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement)
|
|
10
|
-
[](https://github.com/sponsors/philiprehberger)
|
|
11
6
|
|
|
12
7
|
HTTP header parsing, construction, and content negotiation
|
|
13
8
|
|
|
@@ -137,6 +132,55 @@ Philiprehberger::HeaderKit.negotiate("text/html;q=0.9, application/json", ["text
|
|
|
137
132
|
# => "application/json"
|
|
138
133
|
```
|
|
139
134
|
|
|
135
|
+
### Parse CORS
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
headers = {
|
|
139
|
+
'Origin' => 'https://example.com',
|
|
140
|
+
'Access-Control-Request-Method' => 'POST',
|
|
141
|
+
'Access-Control-Request-Headers' => 'Content-Type, Authorization'
|
|
142
|
+
}
|
|
143
|
+
Philiprehberger::HeaderKit.parse_cors(headers)
|
|
144
|
+
# => {origin: "https://example.com", method: "POST", headers: ["Content-Type", "Authorization"]}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Build CORS
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
Philiprehberger::HeaderKit.build_cors(
|
|
151
|
+
origin: "https://example.com",
|
|
152
|
+
methods: ["GET", "POST"],
|
|
153
|
+
headers: ["Content-Type"],
|
|
154
|
+
max_age: 3600,
|
|
155
|
+
credentials: true
|
|
156
|
+
)
|
|
157
|
+
# => {"Access-Control-Allow-Origin" => "https://example.com", ...}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Security Headers
|
|
161
|
+
|
|
162
|
+
```ruby
|
|
163
|
+
Philiprehberger::HeaderKit.security_headers
|
|
164
|
+
# => {"X-Content-Type-Options" => "nosniff", "X-Frame-Options" => "DENY", ...}
|
|
165
|
+
|
|
166
|
+
Philiprehberger::HeaderKit.security_headers(hsts: "max-age=31536000", csp: "default-src 'self'")
|
|
167
|
+
# => includes Strict-Transport-Security and Content-Security-Policy
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Parse Forwarded
|
|
171
|
+
|
|
172
|
+
```ruby
|
|
173
|
+
Philiprehberger::HeaderKit.parse_forwarded('for=192.0.2.60;proto=http;by=203.0.113.43')
|
|
174
|
+
# => [{for: "192.0.2.60", proto: "http", by: "203.0.113.43"}]
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Parse Via
|
|
178
|
+
|
|
179
|
+
```ruby
|
|
180
|
+
Philiprehberger::HeaderKit.parse_via('1.1 vegur, 1.0 fred')
|
|
181
|
+
# => [{protocol: "1.1", host: "vegur"}, {protocol: "1.0", host: "fred"}]
|
|
182
|
+
```
|
|
183
|
+
|
|
140
184
|
## API
|
|
141
185
|
|
|
142
186
|
| Method | Description |
|
|
@@ -155,6 +199,11 @@ Philiprehberger::HeaderKit.negotiate("text/html;q=0.9, application/json", ["text
|
|
|
155
199
|
| `HeaderKit.parse_link(header)` | Parse Link header into entry array |
|
|
156
200
|
| `HeaderKit.build_link(links)` | Build Link header from array of hashes |
|
|
157
201
|
| `HeaderKit.negotiate(accept_header, available)` | Content negotiation, returns best match or nil |
|
|
202
|
+
| `HeaderKit.parse_cors(headers)` | Parse CORS-related request headers |
|
|
203
|
+
| `HeaderKit.build_cors(**options)` | Build CORS response headers |
|
|
204
|
+
| `HeaderKit.security_headers(**options)` | Generate recommended security headers |
|
|
205
|
+
| `HeaderKit.parse_forwarded(header)` | Parse RFC 7239 Forwarded header |
|
|
206
|
+
| `HeaderKit.parse_via(header)` | Parse Via header into structured entries |
|
|
158
207
|
|
|
159
208
|
## Development
|
|
160
209
|
|
|
@@ -166,10 +215,21 @@ bundle exec rubocop
|
|
|
166
215
|
|
|
167
216
|
## Support
|
|
168
217
|
|
|
169
|
-
If you find this
|
|
218
|
+
If you find this project useful:
|
|
219
|
+
|
|
220
|
+
⭐ [Star the repo](https://github.com/philiprehberger/rb-header-kit)
|
|
221
|
+
|
|
222
|
+
🐛 [Report issues](https://github.com/philiprehberger/rb-header-kit/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
|
|
223
|
+
|
|
224
|
+
💡 [Suggest features](https://github.com/philiprehberger/rb-header-kit/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement)
|
|
225
|
+
|
|
226
|
+
❤️ [Sponsor development](https://github.com/sponsors/philiprehberger)
|
|
227
|
+
|
|
228
|
+
🌐 [All Open Source Projects](https://philiprehberger.com/open-source-packages)
|
|
229
|
+
|
|
230
|
+
💻 [GitHub Profile](https://github.com/philiprehberger)
|
|
170
231
|
|
|
171
|
-
[
|
|
172
|
-
[](https://philiprehberger.com/open-source-packages)
|
|
232
|
+
🔗 [LinkedIn Profile](https://www.linkedin.com/in/philiprehberger)
|
|
173
233
|
|
|
174
234
|
## License
|
|
175
235
|
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Philiprehberger
|
|
4
|
+
module HeaderKit
|
|
5
|
+
module Cors
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def parse(headers)
|
|
9
|
+
result = {}
|
|
10
|
+
result[:origin] = headers['Origin'] || headers['origin']
|
|
11
|
+
result[:method] = headers['Access-Control-Request-Method'] || headers['access-control-request-method']
|
|
12
|
+
request_headers = headers['Access-Control-Request-Headers'] || headers['access-control-request-headers']
|
|
13
|
+
result[:headers] = request_headers&.split(',')&.map(&:strip) || []
|
|
14
|
+
result
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def build(origin:, methods: ['GET'], headers: [], max_age: nil, credentials: false, expose: [])
|
|
18
|
+
result = {}
|
|
19
|
+
result['Access-Control-Allow-Origin'] = origin
|
|
20
|
+
result['Access-Control-Allow-Methods'] = Array(methods).join(', ')
|
|
21
|
+
result['Access-Control-Allow-Headers'] = Array(headers).join(', ') unless headers.empty?
|
|
22
|
+
result['Access-Control-Max-Age'] = max_age.to_s if max_age
|
|
23
|
+
result['Access-Control-Allow-Credentials'] = 'true' if credentials
|
|
24
|
+
result['Access-Control-Expose-Headers'] = Array(expose).join(', ') unless expose.empty?
|
|
25
|
+
result
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Philiprehberger
|
|
4
|
+
module HeaderKit
|
|
5
|
+
module Forwarded
|
|
6
|
+
module_function
|
|
7
|
+
|
|
8
|
+
def parse(header)
|
|
9
|
+
return [] if header.nil? || header.empty?
|
|
10
|
+
|
|
11
|
+
header.split(',').map do |part|
|
|
12
|
+
entry = {}
|
|
13
|
+
part.strip.split(';').each do |pair|
|
|
14
|
+
key, value = pair.strip.split('=', 2)
|
|
15
|
+
next unless key && value
|
|
16
|
+
|
|
17
|
+
entry[key.strip.downcase.to_sym] = unquote(value.strip)
|
|
18
|
+
end
|
|
19
|
+
entry
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def parse_via(header)
|
|
24
|
+
return [] if header.nil? || header.empty?
|
|
25
|
+
|
|
26
|
+
header.split(',').map do |entry|
|
|
27
|
+
parts = entry.strip.split(/\s+/, 3)
|
|
28
|
+
result = {}
|
|
29
|
+
if parts.length >= 2
|
|
30
|
+
result[:protocol] = parts[0]
|
|
31
|
+
result[:host] = parts[1]
|
|
32
|
+
result[:comment] = parts[2] if parts[2]
|
|
33
|
+
else
|
|
34
|
+
result[:host] = parts[0]
|
|
35
|
+
end
|
|
36
|
+
result
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def unquote(value)
|
|
41
|
+
value.start_with?('"') && value.end_with?('"') ? value[1..-2] : value
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private_class_method :unquote
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Philiprehberger
|
|
4
|
+
module HeaderKit
|
|
5
|
+
module Security
|
|
6
|
+
DEFAULTS = {
|
|
7
|
+
content_type_options: 'nosniff',
|
|
8
|
+
frame_options: 'DENY',
|
|
9
|
+
xss_protection: '0',
|
|
10
|
+
referrer_policy: 'strict-origin-when-cross-origin'
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
module_function
|
|
14
|
+
|
|
15
|
+
def headers(hsts: nil, csp: nil, frame: nil, content_type_options: nil, referrer_policy: nil)
|
|
16
|
+
result = {}
|
|
17
|
+
result['X-Content-Type-Options'] = content_type_options || DEFAULTS[:content_type_options]
|
|
18
|
+
result['X-Frame-Options'] = frame || DEFAULTS[:frame_options]
|
|
19
|
+
result['X-XSS-Protection'] = DEFAULTS[:xss_protection]
|
|
20
|
+
result['Referrer-Policy'] = referrer_policy || DEFAULTS[:referrer_policy]
|
|
21
|
+
result['Strict-Transport-Security'] = hsts if hsts
|
|
22
|
+
result['Content-Security-Policy'] = csp if csp
|
|
23
|
+
result
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -11,6 +11,9 @@ require_relative 'header_kit/content_type'
|
|
|
11
11
|
require_relative 'header_kit/cookie'
|
|
12
12
|
require_relative 'header_kit/link'
|
|
13
13
|
require_relative 'header_kit/negotiation'
|
|
14
|
+
require_relative 'header_kit/cors'
|
|
15
|
+
require_relative 'header_kit/security'
|
|
16
|
+
require_relative 'header_kit/forwarded'
|
|
14
17
|
|
|
15
18
|
module Philiprehberger
|
|
16
19
|
module HeaderKit
|
|
@@ -131,5 +134,45 @@ module Philiprehberger
|
|
|
131
134
|
def self.negotiate(accept_header, available)
|
|
132
135
|
Negotiation.negotiate(accept_header, available)
|
|
133
136
|
end
|
|
137
|
+
|
|
138
|
+
# Parse CORS-related headers from a request.
|
|
139
|
+
#
|
|
140
|
+
# @param headers [Hash] request headers hash
|
|
141
|
+
# @return [Hash] with :origin, :method, :headers keys
|
|
142
|
+
def self.parse_cors(headers)
|
|
143
|
+
Cors.parse(headers)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
# Build CORS response headers.
|
|
147
|
+
#
|
|
148
|
+
# @param options [Hash] CORS options (origin:, methods:, headers:, max_age:, credentials:, expose:)
|
|
149
|
+
# @return [Hash{String => String}] response headers
|
|
150
|
+
def self.build_cors(**options)
|
|
151
|
+
Cors.build(**options)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Generate recommended security response headers.
|
|
155
|
+
#
|
|
156
|
+
# @param options [Hash] overrides (hsts:, csp:, frame:, content_type_options:, referrer_policy:)
|
|
157
|
+
# @return [Hash{String => String}] security headers
|
|
158
|
+
def self.security_headers(**options)
|
|
159
|
+
Security.headers(**options)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Parse an RFC 7239 Forwarded header.
|
|
163
|
+
#
|
|
164
|
+
# @param header [String] the Forwarded header value
|
|
165
|
+
# @return [Array<Hash>] parsed entries with symbol keys
|
|
166
|
+
def self.parse_forwarded(header)
|
|
167
|
+
Forwarded.parse(header)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Parse a Via header into structured entries.
|
|
171
|
+
#
|
|
172
|
+
# @param header [String] the Via header value
|
|
173
|
+
# @return [Array<Hash>] entries with :protocol, :host, :comment keys
|
|
174
|
+
def self.parse_via(header)
|
|
175
|
+
Forwarded.parse_via(header)
|
|
176
|
+
end
|
|
134
177
|
end
|
|
135
178
|
end
|
metadata
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: philiprehberger-header_kit
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.3.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Philip Rehberger
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
11
|
+
date: 2026-03-31 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: Parse and build Accept, Accept-Language, Accept-Encoding, Authorization,
|
|
14
|
-
Cache-Control, Content-Type, Cookie, and
|
|
15
|
-
|
|
14
|
+
Cache-Control, Content-Type, Cookie, Link, CORS, Forwarded, and Via HTTP headers.
|
|
15
|
+
Includes content negotiation and security header generation.
|
|
16
16
|
email:
|
|
17
17
|
- me@philiprehberger.com
|
|
18
18
|
executables: []
|
|
@@ -31,14 +31,17 @@ files:
|
|
|
31
31
|
- lib/philiprehberger/header_kit/cache_control.rb
|
|
32
32
|
- lib/philiprehberger/header_kit/content_type.rb
|
|
33
33
|
- lib/philiprehberger/header_kit/cookie.rb
|
|
34
|
+
- lib/philiprehberger/header_kit/cors.rb
|
|
35
|
+
- lib/philiprehberger/header_kit/forwarded.rb
|
|
34
36
|
- lib/philiprehberger/header_kit/link.rb
|
|
35
37
|
- lib/philiprehberger/header_kit/negotiation.rb
|
|
38
|
+
- lib/philiprehberger/header_kit/security.rb
|
|
36
39
|
- lib/philiprehberger/header_kit/version.rb
|
|
37
|
-
homepage: https://
|
|
40
|
+
homepage: https://philiprehberger.com/open-source-packages/ruby/philiprehberger-header_kit
|
|
38
41
|
licenses:
|
|
39
42
|
- MIT
|
|
40
43
|
metadata:
|
|
41
|
-
homepage_uri: https://
|
|
44
|
+
homepage_uri: https://philiprehberger.com/open-source-packages/ruby/philiprehberger-header_kit
|
|
42
45
|
source_code_uri: https://github.com/philiprehberger/rb-header-kit
|
|
43
46
|
changelog_uri: https://github.com/philiprehberger/rb-header-kit/blob/main/CHANGELOG.md
|
|
44
47
|
bug_tracker_uri: https://github.com/philiprehberger/rb-header-kit/issues
|