Skip to content

Commit f595986

Browse files
committed
Move URL and Reference to Protocol::URL gem.
1 parent e796678 commit f595986

File tree

18 files changed

+208
-818
lines changed

18 files changed

+208
-818
lines changed

lib/protocol/http/cookie.rb

Lines changed: 38 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,18 +4,37 @@
44
# Copyright, 2019-2025, by Samuel Williams.
55
# Copyright, 2022, by Herrick Fang.
66

7-
require_relative "url"
7+
require_relative "quoted_string"
88

99
module Protocol
1010
module HTTP
1111
# Represents an individual cookie key-value pair.
1212
class Cookie
13+
# Valid cookie name characters according to RFC 6265.
14+
# cookie-name = token (RFC 2616 defines token)
15+
VALID_COOKIE_KEY = /\A#{TOKEN}\z/.freeze
16+
17+
# Valid cookie value characters according to RFC 6265.
18+
# cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
19+
# cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
20+
# Excludes control chars, whitespace, DQUOTE, comma, semicolon, and backslash
21+
VALID_COOKIE_VALUE = /\A[\x21\x23-\x2B\x2D-\x3A\x3C-\x5B\x5D-\x7E]*\z/.freeze
22+
1323
# Initialize the cookie with the given name, value, and directives.
1424
#
15-
# @parameter name [String] The name of the cookiel, e.g. "session_id".
25+
# @parameter name [String] The name of the cookie, e.g. "session_id".
1626
# @parameter value [String] The value of the cookie, e.g. "1234".
1727
# @parameter directives [Hash] The directives of the cookie, e.g. `{"path" => "/"}`.
18-
def initialize(name, value, directives)
28+
# @raises [ArgumentError] If the name or value contains invalid characters.
29+
def initialize(name, value, directives = nil)
30+
unless VALID_COOKIE_KEY.match?(name)
31+
raise ArgumentError, "Invalid cookie name: #{name.inspect}"
32+
end
33+
34+
if value && !VALID_COOKIE_VALUE.match?(value)
35+
raise ArgumentError, "Invalid cookie value: #{value.inspect}"
36+
end
37+
1938
@name = name
2039
@value = value
2140
@directives = directives
@@ -30,41 +49,37 @@ def initialize(name, value, directives)
3049
# @attribute [Hash] The directives of the cookie.
3150
attr :directives
3251

33-
# Encode the name of the cookie.
34-
def encoded_name
35-
URL.escape(@name)
36-
end
37-
38-
# Encode the value of the cookie.
39-
def encoded_value
40-
URL.escape(@value)
52+
# Encode a string for use in a cookie, escaping characters that are not allowed.
53+
#
54+
# @parameter string [String] The string to encode.
55+
# @returns [String] The encoded string.
56+
def self.encode(string)
57+
string.b.gsub(/([^!#$%&'*+\-\.\/0-9A-Z\^_`a-z|~]+)/) do |match|
58+
"%" + match.unpack("H2" * match.bytesize).join("%").upcase
59+
end
4160
end
4261

4362
# Convert the cookie to a string.
4463
#
4564
# @returns [String] The string representation of the cookie.
4665
def to_s
47-
buffer = String.new.b
66+
buffer = String.new
4867

49-
buffer << encoded_name << "=" << encoded_value
68+
buffer << @name << "=" << @value
5069

5170
if @directives
52-
@directives.collect do |key, value|
71+
@directives.each do |key, value|
5372
buffer << ";"
73+
buffer << key
5474

55-
case value
56-
when String
57-
buffer << key << "=" << value
58-
when TrueClass
59-
buffer << key
75+
if value != true
76+
buffer << "=" << value.to_s
6077
end
6178
end
6279
end
6380

6481
return buffer
65-
end
66-
67-
# Parse a string into a cookie.
82+
end # Parse a string into a cookie.
6883
#
6984
# @parameter string [String] The string to parse.
7085
# @returns [Cookie] The parsed cookie.
@@ -74,11 +89,7 @@ def self.parse(string)
7489
key, value = head.split("=", 2)
7590
directives = self.parse_directives(directives)
7691

77-
self.new(
78-
URL.unescape(key),
79-
URL.unescape(value),
80-
directives,
81-
)
92+
self.new(key, value, directives)
8293
end
8394

8495
# Parse a list of strings into a hash of directives.

lib/protocol/http/header/accept.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
# Copyright, 2025, by William T. Nelson.
66

77
require_relative "split"
8-
require_relative "quoted_string"
8+
require_relative "../quoted_string"
99
require_relative "../error"
1010

1111
module Protocol

lib/protocol/http/header/accept_charset.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# Copyright, 2025, by Samuel Williams.
55

66
require_relative "split"
7-
require_relative "quoted_string"
7+
require_relative "../quoted_string"
88
require_relative "../error"
99

1010
module Protocol

lib/protocol/http/header/accept_encoding.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# Copyright, 2025, by Samuel Williams.
55

66
require_relative "split"
7-
require_relative "quoted_string"
7+
require_relative "../quoted_string"
88
require_relative "../error"
99

1010
module Protocol

lib/protocol/http/header/accept_language.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# Copyright, 2025, by Samuel Williams.
55

66
require_relative "split"
7-
require_relative "quoted_string"
7+
require_relative "../quoted_string"
88
require_relative "../error"
99

1010
module Protocol

lib/protocol/http/header/digest.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# Copyright, 2025, by Samuel Williams.
55

66
require_relative "split"
7-
require_relative "quoted_string"
7+
require_relative "../quoted_string"
88
require_relative "../error"
99

1010
module Protocol

lib/protocol/http/header/quoted_string.rb

Lines changed: 0 additions & 49 deletions
This file was deleted.

lib/protocol/http/header/server_timing.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# Copyright, 2025, by Samuel Williams.
55

66
require_relative "split"
7-
require_relative "quoted_string"
7+
require_relative "../quoted_string"
88
require_relative "../error"
99

1010
module Protocol

lib/protocol/http/header/te.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
# Copyright, 2025, by Samuel Williams.
55

66
require_relative "split"
7-
require_relative "quoted_string"
7+
require_relative "../quoted_string"
88
require_relative "../error"
99

1010
module Protocol

lib/protocol/http/quoted_string.rb

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# frozen_string_literal: true
2+
3+
# Released under the MIT License.
4+
# Copyright, 2025, by Samuel Williams.
5+
6+
module Protocol
7+
module HTTP
8+
# According to https://tools.ietf.org/html/rfc7231#appendix-C
9+
TOKEN = /[!#$%&'*+\-.^_`|~0-9A-Z]+/i
10+
11+
QUOTED_STRING = /"(?:.(?!(?<!\\)"))*.?"/
12+
13+
# https://tools.ietf.org/html/rfc7231#section-5.3.1
14+
QVALUE = /0(\.[0-9]{0,3})?|1(\.[0]{0,3})?/
15+
16+
# Handling of HTTP quoted strings.
17+
module QuotedString
18+
# Unquote a "quoted-string" value according to <https://tools.ietf.org/html/rfc7230#section-3.2.6>. It should already match the QUOTED_STRING pattern above by the parser.
19+
def self.unquote(value, normalize_whitespace = true)
20+
value = value[1...-1]
21+
22+
value.gsub!(/\\(.)/, '\1')
23+
24+
if normalize_whitespace
25+
# LWS = [CRLF] 1*( SP | HT )
26+
value.gsub!(/[\r\n]+\s+/, " ")
27+
end
28+
29+
return value
30+
end
31+
32+
QUOTES_REQUIRED = /[()<>@,;:\\"\/\[\]?={} \t]/
33+
34+
# Quote a string for HTTP header values if required.
35+
#
36+
# @raises [ArgumentError] if the value contains invalid characters like control characters or newlines.
37+
def self.quote(value, force = false)
38+
# Check if quoting is required:
39+
if value =~ QUOTES_REQUIRED or force
40+
"\"#{value.gsub(/["\\]/, '\\\\\0')}\""
41+
else
42+
value
43+
end
44+
end
45+
end
46+
end
47+
end

0 commit comments

Comments
 (0)