1+ # frozen_string_literal: true
2+
3+ # Released under the MIT License.
4+ # Copyright, 2025, by Samuel Williams.
5+
6+ require_relative "split"
7+ require_relative "quoted_string"
8+ require_relative "../error"
9+
10+ module Protocol
11+ module HTTP
12+ module Header
13+ # The `te` header indicates the transfer encodings the client is willing to accept.
14+ #
15+ # The `te` header allows a client to indicate which transfer encodings it can handle, and in what order of preference using quality factors.
16+ class TE < Split
17+ ParseError = Class . new ( Error )
18+
19+ # Transfer encoding token pattern
20+ TOKEN = /[!#$%&'*+\- .0-9A-Z^_`a-z|~]+/
21+
22+ # Quality value pattern (0.0 to 1.0)
23+ QVALUE = /0(\. [0-9]{0,3})?|1(\. [0]{0,3})?/
24+
25+ # Pattern for parsing transfer encoding with optional quality factor
26+ TRANSFER_CODING = /\A (?<name>#{ TOKEN } )(\s *;\s *q=(?<q>#{ QVALUE } ))?\z /
27+
28+ # The `chunked` transfer encoding
29+ CHUNKED = "chunked"
30+
31+ # The `gzip` transfer encoding
32+ GZIP = "gzip"
33+
34+ # The `deflate` transfer encoding
35+ DEFLATE = "deflate"
36+
37+ # The `compress` transfer encoding
38+ COMPRESS = "compress"
39+
40+ # The `identity` transfer encoding
41+ IDENTITY = "identity"
42+
43+ # The `trailers` pseudo-encoding indicates willingness to accept trailer fields
44+ TRAILERS = "trailers"
45+
46+ # A single transfer coding entry with optional quality factor
47+ TransferCoding = Struct . new ( :name , :q ) do
48+ def quality_factor
49+ ( q || 1.0 ) . to_f
50+ end
51+
52+ def <=> other
53+ other . quality_factor <=> self . quality_factor
54+ end
55+
56+ def to_s
57+ if q && q != 1.0
58+ "#{ name } ;q=#{ q } "
59+ else
60+ name . to_s
61+ end
62+ end
63+ end
64+
65+ # Initializes the TE header with the given value. The value is split into distinct entries and converted to lowercase for normalization.
66+ #
67+ # @parameter value [String | Nil] the raw header value containing transfer encodings separated by commas.
68+ def initialize ( value = nil )
69+ super ( value &.downcase )
70+ end
71+
72+ # Adds one or more comma-separated values to the TE header. The values are converted to lowercase for normalization.
73+ #
74+ # @parameter value [String] the value or values to add, separated by commas.
75+ def << value
76+ super ( value . downcase )
77+ end
78+
79+ # Parse the `te` header value into a list of transfer codings with quality factors.
80+ #
81+ # @returns [Array(TransferCoding)] the list of transfer codings and their associated quality factors.
82+ def transfer_codings
83+ self . map do |value |
84+ if match = value . match ( TRANSFER_CODING )
85+ TransferCoding . new ( match [ :name ] , match [ :q ] )
86+ else
87+ raise ParseError . new ( "Could not parse transfer coding: #{ value . inspect } " )
88+ end
89+ end
90+ end
91+
92+ # @returns [Boolean] whether the `chunked` encoding is accepted.
93+ def chunked?
94+ self . any? { |value | value . start_with? ( CHUNKED ) }
95+ end
96+
97+ # @returns [Boolean] whether the `gzip` encoding is accepted.
98+ def gzip?
99+ self . any? { |value | value . start_with? ( GZIP ) }
100+ end
101+
102+ # @returns [Boolean] whether the `deflate` encoding is accepted.
103+ def deflate?
104+ self . any? { |value | value . start_with? ( DEFLATE ) }
105+ end
106+
107+ # @returns [Boolean] whether the `compress` encoding is accepted.
108+ def compress?
109+ self . any? { |value | value . start_with? ( COMPRESS ) }
110+ end
111+
112+ # @returns [Boolean] whether the `identity` encoding is accepted.
113+ def identity?
114+ self . any? { |value | value . start_with? ( IDENTITY ) }
115+ end
116+
117+ # @returns [Boolean] whether trailers are accepted.
118+ def trailers?
119+ self . any? { |value | value . start_with? ( TRAILERS ) }
120+ end
121+
122+ def self . trailer_forbidden?
123+ true
124+ end
125+ end
126+ end
127+ end
128+ end
0 commit comments