philiprehberger-header_kit 0.1.0 → 0.2.0
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 +18 -0
- data/README.md +74 -4
- data/lib/philiprehberger/header_kit/accept_builder.rb +40 -0
- data/lib/philiprehberger/header_kit/accept_encoding.rb +56 -0
- data/lib/philiprehberger/header_kit/accept_language.rb +97 -0
- data/lib/philiprehberger/header_kit/authorization.rb +53 -0
- data/lib/philiprehberger/header_kit/cookie.rb +56 -0
- data/lib/philiprehberger/header_kit/version.rb +1 -1
- data/lib/philiprehberger/header_kit.rb +64 -0
- metadata +10 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3d4bcbb3c1002def0f437335a19871b535ce7764369d43fed1c4a1548367988f
|
|
4
|
+
data.tar.gz: 03dea17ed3566ea8cce9ea8511f741c92487113b6ba0a4cdb899ab1835f07009
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 35c43a782a9e1ba431f972d18ec2c248a6141e96f79c082d09a2a48f973aee449d684011e4b70e3f95b4c488ff71fb1537c8da716e0186669e9ca974ff267792
|
|
7
|
+
data.tar.gz: 1b82e5bdea29824445c2097f21009f57a0f71cebbd80799386aed785e2247de8a4c87bd65136e198fea1150485c6909264f21cfed3970a2365c47c61ca7b7ce7
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.2.0] - 2026-03-28
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- Parse Accept-Language headers with quality values
|
|
15
|
+
- Negotiate best language match from Accept-Language against available languages
|
|
16
|
+
- Parse Accept-Encoding headers with quality values
|
|
17
|
+
- Parse Authorization headers (Bearer, Basic, Digest schemes)
|
|
18
|
+
- Parse Cookie headers into name-value hashes
|
|
19
|
+
- Build Set-Cookie header strings with all standard attributes
|
|
20
|
+
- Build Accept header strings from structured type arrays
|
|
21
|
+
|
|
22
|
+
## [0.1.1] - 2026-03-26
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- Fix README compliance (sponsor badge format, license link)
|
|
27
|
+
|
|
10
28
|
## [0.1.0] - 2026-03-26
|
|
11
29
|
|
|
12
30
|
### Added
|
data/README.md
CHANGED
|
@@ -2,8 +2,12 @@
|
|
|
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
|
+
[](https://github.com/philiprehberger/rb-header-kit/commits/main)
|
|
5
7
|
[](LICENSE)
|
|
6
|
-
[](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)
|
|
7
11
|
|
|
8
12
|
HTTP header parsing, construction, and content negotiation
|
|
9
13
|
|
|
@@ -39,6 +43,44 @@ Philiprehberger::HeaderKit.parse_accept("text/html;q=0.9, application/json")
|
|
|
39
43
|
# {type: "text/html", quality: 0.9, params: {}}]
|
|
40
44
|
```
|
|
41
45
|
|
|
46
|
+
### Build Accept
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
Philiprehberger::HeaderKit.build_accept([{type: "text/html"}, {type: "application/json", quality: 0.9}])
|
|
50
|
+
# => "text/html, application/json;q=0.9"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Parse Accept-Language
|
|
54
|
+
|
|
55
|
+
```ruby
|
|
56
|
+
Philiprehberger::HeaderKit.parse_accept_language("en-US,en;q=0.9,fr;q=0.8")
|
|
57
|
+
# => [{language: "en-US", quality: 1.0}, {language: "en", quality: 0.9}, {language: "fr", quality: 0.8}]
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Negotiate Language
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
Philiprehberger::HeaderKit.negotiate_language("en-US,fr;q=0.9", ["fr", "en"])
|
|
64
|
+
# => "en"
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Parse Accept-Encoding
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
Philiprehberger::HeaderKit.parse_accept_encoding("gzip, deflate;q=0.5, br;q=0.8")
|
|
71
|
+
# => [{encoding: "gzip", quality: 1.0}, {encoding: "br", quality: 0.8}, {encoding: "deflate", quality: 0.5}]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Parse Authorization
|
|
75
|
+
|
|
76
|
+
```ruby
|
|
77
|
+
Philiprehberger::HeaderKit.parse_authorization("Bearer eyJhbGciOiJIUzI1NiJ9")
|
|
78
|
+
# => {scheme: "Bearer", credentials: "eyJhbGciOiJIUzI1NiJ9"}
|
|
79
|
+
|
|
80
|
+
Philiprehberger::HeaderKit.parse_authorization('Digest username="admin", realm="example"')
|
|
81
|
+
# => {scheme: "Digest", params: {"username" => "admin", "realm" => "example"}}
|
|
82
|
+
```
|
|
83
|
+
|
|
42
84
|
### Parse Cache-Control
|
|
43
85
|
|
|
44
86
|
```ruby
|
|
@@ -60,6 +102,20 @@ Philiprehberger::HeaderKit.parse_content_type("text/html; charset=utf-8")
|
|
|
60
102
|
# => {media_type: "text/html", charset: "utf-8", boundary: nil}
|
|
61
103
|
```
|
|
62
104
|
|
|
105
|
+
### Parse Cookie
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
Philiprehberger::HeaderKit.parse_cookie("session=abc123; user=john")
|
|
109
|
+
# => {"session" => "abc123", "user" => "john"}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Build Set-Cookie
|
|
113
|
+
|
|
114
|
+
```ruby
|
|
115
|
+
Philiprehberger::HeaderKit.build_set_cookie("session", "abc123", secure: true, httponly: true, path: "/")
|
|
116
|
+
# => "session=abc123; Path=/; Secure; HttpOnly"
|
|
117
|
+
```
|
|
118
|
+
|
|
63
119
|
### Parse Link
|
|
64
120
|
|
|
65
121
|
```ruby
|
|
@@ -86,9 +142,16 @@ Philiprehberger::HeaderKit.negotiate("text/html;q=0.9, application/json", ["text
|
|
|
86
142
|
| Method | Description |
|
|
87
143
|
|--------|-------------|
|
|
88
144
|
| `HeaderKit.parse_accept(header)` | Parse Accept header into sorted entries |
|
|
145
|
+
| `HeaderKit.build_accept(types)` | Build Accept header string from type array |
|
|
146
|
+
| `HeaderKit.parse_accept_language(header)` | Parse Accept-Language into sorted entries |
|
|
147
|
+
| `HeaderKit.negotiate_language(header, available)` | Language negotiation, returns best match or nil |
|
|
148
|
+
| `HeaderKit.parse_accept_encoding(header)` | Parse Accept-Encoding into sorted entries |
|
|
149
|
+
| `HeaderKit.parse_authorization(header)` | Parse Authorization header (Bearer, Basic, Digest) |
|
|
89
150
|
| `HeaderKit.parse_cache_control(header)` | Parse Cache-Control into directive hash |
|
|
90
151
|
| `HeaderKit.build_cache_control(directives)` | Build Cache-Control string from hash |
|
|
91
152
|
| `HeaderKit.parse_content_type(header)` | Parse Content-Type into components |
|
|
153
|
+
| `HeaderKit.parse_cookie(header)` | Parse Cookie header into name-value hash |
|
|
154
|
+
| `HeaderKit.build_set_cookie(name, value, **opts)` | Build Set-Cookie header string |
|
|
92
155
|
| `HeaderKit.parse_link(header)` | Parse Link header into entry array |
|
|
93
156
|
| `HeaderKit.build_link(links)` | Build Link header from array of hashes |
|
|
94
157
|
| `HeaderKit.negotiate(accept_header, available)` | Content negotiation, returns best match or nil |
|
|
@@ -97,10 +160,17 @@ Philiprehberger::HeaderKit.negotiate("text/html;q=0.9, application/json", ["text
|
|
|
97
160
|
|
|
98
161
|
```bash
|
|
99
162
|
bundle install
|
|
100
|
-
bundle exec rspec
|
|
101
|
-
bundle exec rubocop
|
|
163
|
+
bundle exec rspec
|
|
164
|
+
bundle exec rubocop
|
|
102
165
|
```
|
|
103
166
|
|
|
167
|
+
## Support
|
|
168
|
+
|
|
169
|
+
If you find this package useful, consider giving it a star on GitHub — it helps motivate continued maintenance and development.
|
|
170
|
+
|
|
171
|
+
[](https://www.linkedin.com/in/philiprehberger)
|
|
172
|
+
[](https://philiprehberger.com/open-source-packages)
|
|
173
|
+
|
|
104
174
|
## License
|
|
105
175
|
|
|
106
|
-
MIT
|
|
176
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Philiprehberger
|
|
4
|
+
module HeaderKit
|
|
5
|
+
# Builds Accept header strings from structured type entries.
|
|
6
|
+
module AcceptBuilder
|
|
7
|
+
# Build an Accept header string from an array of type hashes.
|
|
8
|
+
#
|
|
9
|
+
# @param types [Array<Hash>] each with :type and optional :quality
|
|
10
|
+
# @return [String] formatted Accept header value
|
|
11
|
+
def self.build(types)
|
|
12
|
+
return '' if types.nil? || types.empty?
|
|
13
|
+
|
|
14
|
+
parts = types.map do |entry|
|
|
15
|
+
type = entry[:type]
|
|
16
|
+
quality = entry[:quality]
|
|
17
|
+
|
|
18
|
+
if quality && quality < 1.0
|
|
19
|
+
"#{type};q=#{format_quality(quality)}"
|
|
20
|
+
else
|
|
21
|
+
type
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
parts.join(', ')
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Format a quality value, removing trailing zeros.
|
|
29
|
+
#
|
|
30
|
+
# @param quality [Float] the quality value
|
|
31
|
+
# @return [String] formatted quality string
|
|
32
|
+
def self.format_quality(quality)
|
|
33
|
+
formatted = format('%.3f', quality)
|
|
34
|
+
formatted.sub(/0+\z/, '').sub(/\.\z/, '.0')
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private_class_method :format_quality
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Philiprehberger
|
|
4
|
+
module HeaderKit
|
|
5
|
+
# Parses Accept-Encoding headers into structured encoding entries with quality values.
|
|
6
|
+
module AcceptEncoding
|
|
7
|
+
QUALITY_PATTERN = /\Aq\z/i
|
|
8
|
+
|
|
9
|
+
# Parse an Accept-Encoding header string.
|
|
10
|
+
#
|
|
11
|
+
# @param header [String] the Accept-Encoding header value
|
|
12
|
+
# @return [Array<Hash>] sorted by quality descending, each with :encoding, :quality
|
|
13
|
+
def self.parse(header)
|
|
14
|
+
return [] if header.nil? || header.strip.empty?
|
|
15
|
+
|
|
16
|
+
entries = header.split(',').map { |entry| parse_entry(entry.strip) }
|
|
17
|
+
entries.compact.sort_by { |e| [-e[:quality], entries.index(e)] }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Parse a single encoding entry.
|
|
21
|
+
#
|
|
22
|
+
# @param entry [String] a single encoding with optional quality
|
|
23
|
+
# @return [Hash, nil] parsed entry or nil if invalid
|
|
24
|
+
def self.parse_entry(entry)
|
|
25
|
+
return nil if entry.empty?
|
|
26
|
+
|
|
27
|
+
parts = entry.split(';').map(&:strip)
|
|
28
|
+
encoding = parts.shift
|
|
29
|
+
return nil if encoding.nil? || encoding.empty?
|
|
30
|
+
|
|
31
|
+
quality = 1.0
|
|
32
|
+
|
|
33
|
+
parts.each do |param|
|
|
34
|
+
key, value = param.split('=', 2).map(&:strip)
|
|
35
|
+
next if key.nil? || key.empty?
|
|
36
|
+
|
|
37
|
+
quality = parse_quality(value) if QUALITY_PATTERN.match?(key)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
{ encoding: encoding.downcase, quality: quality }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Parse a quality value, clamping to [0.0, 1.0].
|
|
44
|
+
#
|
|
45
|
+
# @param value [String, nil] the quality string
|
|
46
|
+
# @return [Float] parsed quality value
|
|
47
|
+
def self.parse_quality(value)
|
|
48
|
+
return 1.0 if value.nil? || value.empty?
|
|
49
|
+
|
|
50
|
+
value.to_f.clamp(0.0, 1.0)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private_class_method :parse_entry, :parse_quality
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Philiprehberger
|
|
4
|
+
module HeaderKit
|
|
5
|
+
# Parses Accept-Language headers into structured language entries with quality values.
|
|
6
|
+
module AcceptLanguage
|
|
7
|
+
QUALITY_PATTERN = /\Aq\z/i
|
|
8
|
+
|
|
9
|
+
# Parse an Accept-Language header string.
|
|
10
|
+
#
|
|
11
|
+
# @param header [String] the Accept-Language header value
|
|
12
|
+
# @return [Array<Hash>] sorted by quality descending, each with :language, :quality
|
|
13
|
+
def self.parse(header)
|
|
14
|
+
return [] if header.nil? || header.strip.empty?
|
|
15
|
+
|
|
16
|
+
entries = header.split(',').map { |entry| parse_entry(entry.strip) }
|
|
17
|
+
entries.compact.sort_by { |e| [-e[:quality], entries.index(e)] }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Negotiate the best language match from available languages.
|
|
21
|
+
#
|
|
22
|
+
# @param header [String] the Accept-Language header value
|
|
23
|
+
# @param available [Array<String>] list of available language tags
|
|
24
|
+
# @return [String, nil] the best matching language, or nil if no match
|
|
25
|
+
def self.negotiate(header, available)
|
|
26
|
+
return nil if available.nil? || available.empty?
|
|
27
|
+
return available.first if header.nil? || header.strip.empty?
|
|
28
|
+
|
|
29
|
+
parsed = parse(header)
|
|
30
|
+
return nil if parsed.empty?
|
|
31
|
+
|
|
32
|
+
parsed.each do |entry|
|
|
33
|
+
next if entry[:quality] <= 0.0
|
|
34
|
+
|
|
35
|
+
match = find_match(entry[:language], available)
|
|
36
|
+
return match if match
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
nil
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Parse a single language entry.
|
|
43
|
+
#
|
|
44
|
+
# @param entry [String] a single language tag with optional quality
|
|
45
|
+
# @return [Hash, nil] parsed entry or nil if invalid
|
|
46
|
+
def self.parse_entry(entry)
|
|
47
|
+
return nil if entry.empty?
|
|
48
|
+
|
|
49
|
+
parts = entry.split(';').map(&:strip)
|
|
50
|
+
language = parts.shift
|
|
51
|
+
return nil if language.nil? || language.empty?
|
|
52
|
+
|
|
53
|
+
quality = 1.0
|
|
54
|
+
|
|
55
|
+
parts.each do |param|
|
|
56
|
+
key, value = param.split('=', 2).map(&:strip)
|
|
57
|
+
next if key.nil? || key.empty?
|
|
58
|
+
|
|
59
|
+
quality = parse_quality(value) if QUALITY_PATTERN.match?(key)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
{ language: language, quality: quality }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Parse a quality value, clamping to [0.0, 1.0].
|
|
66
|
+
#
|
|
67
|
+
# @param value [String, nil] the quality string
|
|
68
|
+
# @return [Float] parsed quality value
|
|
69
|
+
def self.parse_quality(value)
|
|
70
|
+
return 1.0 if value.nil? || value.empty?
|
|
71
|
+
|
|
72
|
+
value.to_f.clamp(0.0, 1.0)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Find the best match for a language tag among available languages.
|
|
76
|
+
#
|
|
77
|
+
# @param tag [String] the requested language tag
|
|
78
|
+
# @param available [Array<String>] available language tags
|
|
79
|
+
# @return [String, nil] matching language or nil
|
|
80
|
+
def self.find_match(tag, available)
|
|
81
|
+
return available.first if tag == '*'
|
|
82
|
+
|
|
83
|
+
downcased = tag.downcase
|
|
84
|
+
exact = available.find { |a| a.downcase == downcased }
|
|
85
|
+
return exact if exact
|
|
86
|
+
|
|
87
|
+
prefix = available.find { |a| a.downcase.start_with?("#{downcased}-") }
|
|
88
|
+
return prefix if prefix
|
|
89
|
+
|
|
90
|
+
base = downcased.split('-').first
|
|
91
|
+
available.find { |a| a.downcase.split('-').first == base }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
private_class_method :parse_entry, :parse_quality, :find_match
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Philiprehberger
|
|
4
|
+
module HeaderKit
|
|
5
|
+
# Parses Authorization headers for Bearer, Basic, and Digest schemes.
|
|
6
|
+
module Authorization
|
|
7
|
+
DIGEST_PARAM_PATTERN = %r{(\w+)=(?:"([^"]*)"|([\w+/=]+))}
|
|
8
|
+
|
|
9
|
+
# Parse an Authorization header string.
|
|
10
|
+
#
|
|
11
|
+
# @param header [String] the Authorization header value
|
|
12
|
+
# @return [Hash] with :scheme and :credentials (Bearer/Basic) or :params (Digest)
|
|
13
|
+
def self.parse(header)
|
|
14
|
+
return { scheme: nil, credentials: nil } if header.nil? || header.strip.empty?
|
|
15
|
+
|
|
16
|
+
stripped = header.strip
|
|
17
|
+
space_index = stripped.index(' ')
|
|
18
|
+
|
|
19
|
+
unless space_index
|
|
20
|
+
return { scheme: stripped, credentials: nil }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
scheme = stripped[0...space_index]
|
|
24
|
+
rest = stripped[(space_index + 1)..].strip
|
|
25
|
+
|
|
26
|
+
case scheme.downcase
|
|
27
|
+
when 'bearer', 'basic'
|
|
28
|
+
{ scheme: scheme, credentials: rest }
|
|
29
|
+
when 'digest'
|
|
30
|
+
{ scheme: scheme, params: parse_digest_params(rest) }
|
|
31
|
+
else
|
|
32
|
+
{ scheme: scheme, credentials: rest }
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Parse Digest authorization parameters.
|
|
37
|
+
#
|
|
38
|
+
# @param params_str [String] the parameter string after "Digest "
|
|
39
|
+
# @return [Hash{String => String}] parsed key-value parameters
|
|
40
|
+
def self.parse_digest_params(params_str)
|
|
41
|
+
params = {}
|
|
42
|
+
|
|
43
|
+
params_str.scan(DIGEST_PARAM_PATTERN).each do |key, quoted_value, unquoted_value|
|
|
44
|
+
params[key] = quoted_value || unquoted_value
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
params
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
private_class_method :parse_digest_params
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Philiprehberger
|
|
4
|
+
module HeaderKit
|
|
5
|
+
# Parses Cookie headers and builds Set-Cookie header strings.
|
|
6
|
+
module Cookie
|
|
7
|
+
# Parse a Cookie header string into a name-value hash.
|
|
8
|
+
#
|
|
9
|
+
# @param header [String] the Cookie header value
|
|
10
|
+
# @return [Hash{String => String}] cookie name-value pairs
|
|
11
|
+
def self.parse(header)
|
|
12
|
+
return {} if header.nil? || header.strip.empty?
|
|
13
|
+
|
|
14
|
+
cookies = {}
|
|
15
|
+
|
|
16
|
+
header.split(';').each do |pair|
|
|
17
|
+
pair = pair.strip
|
|
18
|
+
next if pair.empty?
|
|
19
|
+
|
|
20
|
+
name, value = pair.split('=', 2)
|
|
21
|
+
next if name.nil? || name.strip.empty?
|
|
22
|
+
|
|
23
|
+
cookies[name.strip] = (value || '').strip
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
cookies
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Build a Set-Cookie header string.
|
|
30
|
+
#
|
|
31
|
+
# @param name [String] cookie name
|
|
32
|
+
# @param value [String] cookie value
|
|
33
|
+
# @param expires [String, nil] Expires attribute value
|
|
34
|
+
# @param max_age [Integer, nil] Max-Age attribute value
|
|
35
|
+
# @param secure [Boolean] include Secure flag
|
|
36
|
+
# @param httponly [Boolean] include HttpOnly flag
|
|
37
|
+
# @param samesite [String, nil] SameSite attribute (Strict, Lax, None)
|
|
38
|
+
# @param path [String, nil] Path attribute
|
|
39
|
+
# @param domain [String, nil] Domain attribute
|
|
40
|
+
# @return [String] formatted Set-Cookie header value
|
|
41
|
+
def self.build_set_cookie(name, value, expires: nil, max_age: nil, secure: false, # rubocop:disable Metrics/ParameterLists
|
|
42
|
+
httponly: false, samesite: nil, path: nil, domain: nil)
|
|
43
|
+
parts = ["#{name}=#{value}"]
|
|
44
|
+
parts << "Expires=#{expires}" if expires
|
|
45
|
+
parts << "Max-Age=#{max_age}" if max_age
|
|
46
|
+
parts << "Domain=#{domain}" if domain
|
|
47
|
+
parts << "Path=#{path}" if path
|
|
48
|
+
parts << 'Secure' if secure
|
|
49
|
+
parts << 'HttpOnly' if httponly
|
|
50
|
+
parts << "SameSite=#{samesite}" if samesite
|
|
51
|
+
|
|
52
|
+
parts.join('; ')
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -2,8 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'header_kit/version'
|
|
4
4
|
require_relative 'header_kit/accept'
|
|
5
|
+
require_relative 'header_kit/accept_builder'
|
|
6
|
+
require_relative 'header_kit/accept_encoding'
|
|
7
|
+
require_relative 'header_kit/accept_language'
|
|
8
|
+
require_relative 'header_kit/authorization'
|
|
5
9
|
require_relative 'header_kit/cache_control'
|
|
6
10
|
require_relative 'header_kit/content_type'
|
|
11
|
+
require_relative 'header_kit/cookie'
|
|
7
12
|
require_relative 'header_kit/link'
|
|
8
13
|
require_relative 'header_kit/negotiation'
|
|
9
14
|
|
|
@@ -19,6 +24,47 @@ module Philiprehberger
|
|
|
19
24
|
Accept.parse(header)
|
|
20
25
|
end
|
|
21
26
|
|
|
27
|
+
# Build an Accept header string from an array of type hashes.
|
|
28
|
+
#
|
|
29
|
+
# @param types [Array<Hash>] each with :type and optional :quality
|
|
30
|
+
# @return [String] formatted Accept header value
|
|
31
|
+
def self.build_accept(types)
|
|
32
|
+
AcceptBuilder.build(types)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Parse an Accept-Encoding header into structured entries sorted by quality.
|
|
36
|
+
#
|
|
37
|
+
# @param header [String] the Accept-Encoding header value
|
|
38
|
+
# @return [Array<Hash>] entries with :encoding, :quality keys
|
|
39
|
+
def self.parse_accept_encoding(header)
|
|
40
|
+
AcceptEncoding.parse(header)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Parse an Accept-Language header into structured entries sorted by quality.
|
|
44
|
+
#
|
|
45
|
+
# @param header [String] the Accept-Language header value
|
|
46
|
+
# @return [Array<Hash>] entries with :language, :quality keys
|
|
47
|
+
def self.parse_accept_language(header)
|
|
48
|
+
AcceptLanguage.parse(header)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Negotiate the best language from an Accept-Language header.
|
|
52
|
+
#
|
|
53
|
+
# @param header [String] the Accept-Language header value
|
|
54
|
+
# @param available [Array<String>] list of available language tags
|
|
55
|
+
# @return [String, nil] the best matching language, or nil if no match
|
|
56
|
+
def self.negotiate_language(header, available)
|
|
57
|
+
AcceptLanguage.negotiate(header, available)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Parse an Authorization header into its components.
|
|
61
|
+
#
|
|
62
|
+
# @param header [String] the Authorization header value
|
|
63
|
+
# @return [Hash] with :scheme and :credentials or :params
|
|
64
|
+
def self.parse_authorization(header)
|
|
65
|
+
Authorization.parse(header)
|
|
66
|
+
end
|
|
67
|
+
|
|
22
68
|
# Parse a Cache-Control header into a directive hash.
|
|
23
69
|
#
|
|
24
70
|
# @param header [String] the Cache-Control header value
|
|
@@ -43,6 +89,24 @@ module Philiprehberger
|
|
|
43
89
|
ContentType.parse(header)
|
|
44
90
|
end
|
|
45
91
|
|
|
92
|
+
# Parse a Cookie header into a name-value hash.
|
|
93
|
+
#
|
|
94
|
+
# @param header [String] the Cookie header value
|
|
95
|
+
# @return [Hash{String => String}] cookie name-value pairs
|
|
96
|
+
def self.parse_cookie(header)
|
|
97
|
+
Cookie.parse(header)
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Build a Set-Cookie header string.
|
|
101
|
+
#
|
|
102
|
+
# @param name [String] cookie name
|
|
103
|
+
# @param value [String] cookie value
|
|
104
|
+
# @param options [Hash] optional attributes (expires:, max_age:, secure:, httponly:, samesite:, path:, domain:)
|
|
105
|
+
# @return [String] formatted Set-Cookie header value
|
|
106
|
+
def self.build_set_cookie(name, value, **options)
|
|
107
|
+
Cookie.build_set_cookie(name, value, **options)
|
|
108
|
+
end
|
|
109
|
+
|
|
46
110
|
# Parse a Link header into an array of link entries.
|
|
47
111
|
#
|
|
48
112
|
# @param header [String] the Link header value
|
metadata
CHANGED
|
@@ -1,17 +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.2.0
|
|
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-29 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
|
-
description: Parse and build Accept,
|
|
14
|
-
|
|
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.
|
|
15
16
|
email:
|
|
16
17
|
- me@philiprehberger.com
|
|
17
18
|
executables: []
|
|
@@ -23,8 +24,13 @@ files:
|
|
|
23
24
|
- README.md
|
|
24
25
|
- lib/philiprehberger/header_kit.rb
|
|
25
26
|
- lib/philiprehberger/header_kit/accept.rb
|
|
27
|
+
- lib/philiprehberger/header_kit/accept_builder.rb
|
|
28
|
+
- lib/philiprehberger/header_kit/accept_encoding.rb
|
|
29
|
+
- lib/philiprehberger/header_kit/accept_language.rb
|
|
30
|
+
- lib/philiprehberger/header_kit/authorization.rb
|
|
26
31
|
- lib/philiprehberger/header_kit/cache_control.rb
|
|
27
32
|
- lib/philiprehberger/header_kit/content_type.rb
|
|
33
|
+
- lib/philiprehberger/header_kit/cookie.rb
|
|
28
34
|
- lib/philiprehberger/header_kit/link.rb
|
|
29
35
|
- lib/philiprehberger/header_kit/negotiation.rb
|
|
30
36
|
- lib/philiprehberger/header_kit/version.rb
|