File: te.rb

package info (click to toggle)
ruby-protocol-http 0.59.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 864 kB
  • sloc: ruby: 7,612; makefile: 4
file content (146 lines) | stat: -rw-r--r-- 4,384 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2025-2026, by Samuel Williams.

require_relative "split"
require_relative "../quoted_string"
require_relative "../error"

module Protocol
	module HTTP
		module Header
			# The `te` header indicates the transfer encodings the client is willing to accept. AKA `accept-transfer-encoding`. How we ended up with `te` instead of `accept-transfer-encoding` is a mystery lost to time.
			#
			# The `te` header allows a client to indicate which transfer encodings it can handle, and in what order of preference using quality factors.
			class TE < Split
				ParseError = Class.new(Error)
				
				# Transfer encoding token pattern
				TOKEN = /[!#$%&'*+\-.0-9A-Z^_`a-z|~]+/
				
				# Quality value pattern (0.0 to 1.0)
				QVALUE = /0(\.[0-9]{0,3})?|1(\.[0]{0,3})?/
				
				# Pattern for parsing transfer encoding with optional quality factor
				TRANSFER_CODING = /\A(?<name>#{TOKEN})(\s*;\s*q=(?<q>#{QVALUE}))?\z/
				
				# The `chunked` transfer encoding
				CHUNKED = "chunked"
				
				# The `gzip` transfer encoding
				GZIP = "gzip"
				
				# The `deflate` transfer encoding  
				DEFLATE = "deflate"
				
				# The `compress` transfer encoding
				COMPRESS = "compress"
				
				# The `identity` transfer encoding
				IDENTITY = "identity"
				
				# The `trailers` pseudo-encoding indicates willingness to accept trailer fields
				TRAILERS = "trailers"
				
				# A single transfer coding entry with optional quality factor
				TransferCoding = Struct.new(:name, :q) do
					def quality_factor
						(q || 1.0).to_f
					end
					
					def <=> other
						other.quality_factor <=> self.quality_factor
					end
					
					def to_s
						if q && q != 1.0
							"#{name};q=#{q}"
						else
							name.to_s
						end
					end
				end
				
				# Parses a raw header value.
				#
				# @parameter value [String] a raw header value containing comma-separated encodings.
				# @returns [TE] a new instance with normalized (lowercase) encodings.
				def self.parse(value)
					self.new(value.downcase.split(COMMA))
				end
				
				# Coerces a value into a parsed header object.
				#
				# @parameter value [String | Array] the value to coerce.
				# @returns [TE] a parsed header object with normalized values.
				def self.coerce(value)
					case value
					when Array
						self.new(value.map(&:downcase))
					else
						self.parse(value.to_s)
					end
				end
				
				# Adds one or more comma-separated values to the TE header. The values are converted to lowercase for normalization.
				#
				# @parameter value [String] a raw header value containing one or more values separated by commas.
				def << value
					super(value.downcase)
				end
				
				# Parse the `te` header value into a list of transfer codings with quality factors.
				#
				# @returns [Array(TransferCoding)] the list of transfer codings and their associated quality factors.
				def transfer_codings
					self.map do |value|
						if match = value.match(TRANSFER_CODING)
							TransferCoding.new(match[:name], match[:q])
						else
							raise ParseError.new("Could not parse transfer coding: #{value.inspect}")
						end
					end
				end
				
				# @returns [Boolean] whether the `chunked` encoding is accepted.
				def chunked?
					self.any?{|value| value.start_with?(CHUNKED)}
				end
				
				# @returns [Boolean] whether the `gzip` encoding is accepted.
				def gzip?
					self.any?{|value| value.start_with?(GZIP)}
				end
				
				# @returns [Boolean] whether the `deflate` encoding is accepted.
				def deflate?
					self.any?{|value| value.start_with?(DEFLATE)}
				end
				
				# @returns [Boolean] whether the `compress` encoding is accepted.
				def compress?
					self.any?{|value| value.start_with?(COMPRESS)}
				end
				
				# @returns [Boolean] whether the `identity` encoding is accepted.
				def identity?
					self.any?{|value| value.start_with?(IDENTITY)}
				end
				
				# @returns [Boolean] whether trailers are accepted.
				def trailers?
					self.any?{|value| value.start_with?(TRAILERS)}
				end
				
				# Whether this header is acceptable in HTTP trailers.
				# TE headers negotiate transfer encodings and must not appear in trailers.
				# @returns [Boolean] `false`, as TE headers are hop-by-hop and control message framing.
				def self.trailer?
					false
				end
			end
		end
	end
end