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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3d4bcbb3c1002def0f437335a19871b535ce7764369d43fed1c4a1548367988f
4
- data.tar.gz: 03dea17ed3566ea8cce9ea8511f741c92487113b6ba0a4cdb899ab1835f07009
3
+ metadata.gz: b397e499d587d20070ad785372aa81dc6574f583de2663cb105598ba229f3b52
4
+ data.tar.gz: cf8de787ddaa0f5cf2c95efa89f17e8db64d0368262bdd691b29c029fc0587fe
5
5
  SHA512:
6
- metadata.gz: 35c43a782a9e1ba431f972d18ec2c248a6141e96f79c082d09a2a48f973aee449d684011e4b70e3f95b4c488ff71fb1537c8da716e0186669e9ca974ff267792
7
- data.tar.gz: 1b82e5bdea29824445c2097f21009f57a0f71cebbd80799386aed785e2247de8a4c87bd65136e198fea1150485c6909264f21cfed3970a2365c47c61ca7b7ce7
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
  [![Tests](https://github.com/philiprehberger/rb-header-kit/actions/workflows/ci.yml/badge.svg)](https://github.com/philiprehberger/rb-header-kit/actions/workflows/ci.yml)
4
4
  [![Gem Version](https://badge.fury.io/rb/philiprehberger-header_kit.svg)](https://rubygems.org/gems/philiprehberger-header_kit)
5
- [![GitHub release](https://img.shields.io/github/v/release/philiprehberger/rb-header-kit)](https://github.com/philiprehberger/rb-header-kit/releases)
6
5
  [![Last updated](https://img.shields.io/github/last-commit/philiprehberger/rb-header-kit)](https://github.com/philiprehberger/rb-header-kit/commits/main)
7
- [![License](https://img.shields.io/github/license/philiprehberger/rb-header-kit)](LICENSE)
8
- [![Bug Reports](https://img.shields.io/github/issues/philiprehberger/rb-header-kit/bug)](https://github.com/philiprehberger/rb-header-kit/issues?q=is%3Aissue+is%3Aopen+label%3Abug)
9
- [![Feature Requests](https://img.shields.io/github/issues/philiprehberger/rb-header-kit/enhancement)](https://github.com/philiprehberger/rb-header-kit/issues?q=is%3Aissue+is%3Aopen+label%3Aenhancement)
10
- [![Sponsor](https://img.shields.io/badge/sponsor-GitHub%20Sponsors-ec6cb9)](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 package useful, consider giving it a star on GitHub — it helps motivate continued maintenance and development.
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
- [![LinkedIn](https://img.shields.io/badge/Philip%20Rehberger-LinkedIn-0A66C2?logo=linkedin)](https://www.linkedin.com/in/philiprehberger)
172
- [![More packages](https://img.shields.io/badge/more-open%20source%20packages-blue)](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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Philiprehberger
4
4
  module HeaderKit
5
- VERSION = '0.2.0'
5
+ VERSION = '0.3.1'
6
6
  end
7
7
  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.2.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-29 00:00:00.000000000 Z
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 Link HTTP headers. Includes content negotiation
15
- for selecting the best response type or language.
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://github.com/philiprehberger/rb-header-kit
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://github.com/philiprehberger/rb-header-kit
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