File: te.rb

package info (click to toggle)
ruby-protocol-http 0.55.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 840 kB
  • sloc: ruby: 6,904; makefile: 4
file content (131 lines) | stat: -rw-r--r-- 4,066 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
# frozen_string_literal: true

# Released under the MIT License.
# Copyright, 2025, 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
				
				# Initializes the TE header with the given value. The value is split into distinct entries and converted to lowercase for normalization.
				#
				# @parameter value [String | Nil] the raw header value containing transfer encodings separated by commas.
				def initialize(value = nil)
					super(value&.downcase)
				end
				
				# Adds one or more comma-separated values to the TE header. The values are converted to lowercase for normalization.
				#
				# @parameter value [String] the value or values to add, 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