hearth 1.0.0.pre2 → 1.0.0.pre3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/VERSION +1 -1
- data/lib/hearth/anonymous_auth_resolver.rb +11 -0
- data/lib/hearth/auth_schemes/anonymous.rb +3 -3
- data/lib/hearth/auth_schemes.rb +3 -3
- data/lib/hearth/client.rb +66 -0
- data/lib/hearth/client_stubs.rb +1 -3
- data/lib/hearth/config/resolver.rb +6 -5
- data/lib/hearth/context.rb +1 -0
- data/lib/hearth/dns/host_address.rb +20 -16
- data/lib/hearth/endpoint_rules.rb +154 -0
- data/lib/hearth/http/client.rb +5 -7
- data/lib/hearth/http/error_inspector.rb +2 -2
- data/lib/hearth/http/field.rb +4 -19
- data/lib/hearth/http/header_list_builder.rb +42 -0
- data/lib/hearth/http/header_list_parser.rb +92 -0
- data/lib/hearth/http/middleware/content_length.rb +3 -3
- data/lib/hearth/http/middleware/content_md5.rb +0 -1
- data/lib/hearth/http/middleware/request_compression.rb +7 -10
- data/lib/hearth/http.rb +2 -0
- data/lib/hearth/{identity_resolver.rb → identity_provider.rb} +1 -1
- data/lib/hearth/interceptor_context.rb +8 -4
- data/lib/hearth/interceptors.rb +2 -1
- data/lib/hearth/json.rb +4 -4
- data/lib/hearth/middleware/auth.rb +9 -6
- data/lib/hearth/middleware/build.rb +0 -1
- data/lib/hearth/middleware/endpoint.rb +79 -0
- data/lib/hearth/middleware/host_prefix.rb +1 -2
- data/lib/hearth/middleware/initialize.rb +0 -1
- data/lib/hearth/middleware/parse.rb +0 -1
- data/lib/hearth/middleware/retry.rb +9 -2
- data/lib/hearth/middleware/send.rb +0 -1
- data/lib/hearth/middleware.rb +1 -0
- data/lib/hearth/middleware_stack.rb +1 -1
- data/lib/hearth/number_helper.rb +1 -1
- data/lib/hearth/query/param.rb +7 -3
- data/lib/hearth/query/param_matcher.rb +5 -6
- data/lib/hearth/{refreshing_identity_resolver.rb → refreshing_identity_provider.rb} +2 -2
- data/lib/hearth/request.rb +2 -2
- data/lib/hearth/response.rb +5 -2
- data/lib/hearth/retry/adaptive.rb +2 -2
- data/lib/hearth/retry/client_rate_limiter.rb +8 -6
- data/lib/hearth/retry/exponential_backoff.rb +1 -1
- data/lib/hearth/retry/standard.rb +2 -2
- data/lib/hearth/retry.rb +16 -3
- data/lib/hearth/structure.rb +7 -3
- data/lib/hearth/stubs.rb +12 -4
- data/lib/hearth/time_helper.rb +1 -2
- data/lib/hearth/validator.rb +37 -21
- data/lib/hearth/waiters/poller.rb +4 -2
- data/lib/hearth/waiters/waiter.rb +6 -5
- data/lib/hearth/xml/node.rb +0 -1
- data/lib/hearth/xml/node_matcher.rb +0 -1
- data/lib/hearth.rb +8 -4
- data/sig/lib/hearth/aliases.rbs +5 -3
- data/sig/lib/hearth/anonymous_auth_resolver.rbs +5 -0
- data/sig/lib/hearth/auth_schemes.rbs +1 -1
- data/sig/lib/hearth/client.rbs +9 -0
- data/sig/lib/hearth/configuration.rbs +2 -2
- data/sig/lib/hearth/dns/host_address.rbs +1 -3
- data/sig/lib/hearth/dns/host_resolver.rbs +3 -3
- data/sig/lib/hearth/endpoint_rules.rbs +17 -0
- data/sig/lib/hearth/http/field.rbs +1 -1
- data/sig/lib/hearth/http/fields.rbs +1 -1
- data/sig/lib/hearth/http/header_list_builder.rbs +15 -0
- data/sig/lib/hearth/http/header_list_parser.rbs +19 -0
- data/sig/lib/hearth/http/networking_error.rbs +6 -0
- data/sig/lib/hearth/http/response.rbs +1 -1
- data/sig/lib/hearth/identities.rbs +1 -1
- data/sig/lib/hearth/{identity_resolver.rbs → identity_provider.rbs} +1 -1
- data/sig/lib/hearth/interceptor_context.rbs +4 -2
- data/sig/lib/hearth/interfaces.rbs +52 -30
- data/sig/lib/hearth/json/parse_error.rbs +9 -0
- data/sig/lib/hearth/networking_error.rbs +7 -0
- data/sig/lib/hearth/output.rbs +4 -4
- data/sig/lib/hearth/plugin_list.rbs +5 -7
- data/sig/lib/hearth/query/param.rbs +2 -2
- data/sig/lib/hearth/refreshing_identity_provider.rbs +10 -0
- data/sig/lib/hearth/request.rbs +2 -2
- data/sig/lib/hearth/response.rbs +2 -2
- data/sig/lib/hearth/retry/exponential_backoff.rbs +1 -1
- data/sig/lib/hearth/retry.rbs +1 -1
- data/sig/lib/hearth/structure.rbs +1 -2
- data/sig/lib/hearth/stubs.rbs +9 -0
- data/sig/lib/hearth/union.rbs +1 -1
- data/sig/lib/hearth/xml/parse_error.rbs +9 -0
- metadata +26 -10
- data/lib/hearth/retry/strategy.rb +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5a6aa5be0acf82ba6ba9b2d399c702fd0da402764dd4769cc3d72a632074dbb4
|
4
|
+
data.tar.gz: f7b14b1778860d004cb9fcea31c1fb0d230760c330447136414342f37cbd3a52
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: dd6f92a2e98ab0942ddd68a6edb86cd4e274389c4d1595ec2ff360a6c6a7a9887a5534a58271009eef0697018bf8327b5c144cc47a541e102d57a0f3fe198be2
|
7
|
+
data.tar.gz: 7ae01e2405d9d7301ea2b9f6b8805fcd438de16d498b9e394f7e0ba5452a3acc6e681b5a32e7371b3cbe9fced1984ded25e4b16bb3512d1bf282f19079490614
|
data/CHANGELOG.md
CHANGED
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.0.0.
|
1
|
+
1.0.0.pre3
|
@@ -0,0 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
# Always returns the Anonymous/noAuth auth scheme.
|
5
|
+
# Can be used to effectively disable/skip auth.
|
6
|
+
class AnonymousAuthResolver
|
7
|
+
def resolve(_params)
|
8
|
+
[Hearth::AuthOption.new(scheme_id: 'smithy.api#noAuth')]
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -12,9 +12,9 @@ module Hearth
|
|
12
12
|
)
|
13
13
|
end
|
14
14
|
|
15
|
-
# @return [
|
16
|
-
def
|
17
|
-
Hearth::
|
15
|
+
# @return [IdentityProvider, nil]
|
16
|
+
def identity_provider(_identity_providers = {})
|
17
|
+
Hearth::IdentityProvider.new(proc { Identities::Anonymous.new })
|
18
18
|
end
|
19
19
|
end
|
20
20
|
end
|
data/lib/hearth/auth_schemes.rb
CHANGED
@@ -14,9 +14,9 @@ module Hearth
|
|
14
14
|
# @return [String]
|
15
15
|
attr_reader :scheme_id
|
16
16
|
|
17
|
-
# @return [
|
18
|
-
def
|
19
|
-
|
17
|
+
# @return [IdentityProvider, nil]
|
18
|
+
def identity_provider(identity_provider = {})
|
19
|
+
identity_provider[@identity_type]
|
20
20
|
end
|
21
21
|
|
22
22
|
# @return [Signers::Base]
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'client_stubs'
|
4
|
+
|
5
|
+
module Hearth
|
6
|
+
# Base Client class for all generated SDK clients.
|
7
|
+
class Client
|
8
|
+
include ClientStubs
|
9
|
+
|
10
|
+
# Plugins applied to all instances of this client.
|
11
|
+
# @return [Hearth::PluginList]
|
12
|
+
def self.plugins
|
13
|
+
@plugins ||= PluginList.new
|
14
|
+
end
|
15
|
+
|
16
|
+
# @param [Hash] options
|
17
|
+
# Options used to construct an instance of {Config}
|
18
|
+
# @param [Class] config_class
|
19
|
+
# The configuration class to use.
|
20
|
+
def initialize(options, config_class)
|
21
|
+
@config = initialize_config(options, config_class)
|
22
|
+
end
|
23
|
+
|
24
|
+
# @return [Configuration]
|
25
|
+
attr_reader :config
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def initialize_config(options, config_class)
|
30
|
+
client_interceptors = options.delete(:interceptors) || []
|
31
|
+
config = config_class.new(**options)
|
32
|
+
config.validate!
|
33
|
+
self.class.plugins.each { |p| p.call(config) }
|
34
|
+
config.plugins.each { |p| p.call(config) }
|
35
|
+
config.interceptors.concat(client_interceptors)
|
36
|
+
config.validate!
|
37
|
+
config.freeze
|
38
|
+
end
|
39
|
+
|
40
|
+
def operation_config(options)
|
41
|
+
return @config if options.empty?
|
42
|
+
|
43
|
+
if options.include?(:stub_responses) || options.include?(:stubs)
|
44
|
+
msg = 'Overriding stubs or stub_responses on ' \
|
45
|
+
'operations is not allowed'
|
46
|
+
raise ArgumentError, msg
|
47
|
+
end
|
48
|
+
|
49
|
+
operation_plugins = options.delete(:plugins)
|
50
|
+
operation_interceptors = options.delete(:interceptors) || []
|
51
|
+
config = @config.merge(options)
|
52
|
+
config.validate!
|
53
|
+
operation_plugins&.each { |p| p.call(config) }
|
54
|
+
config.interceptors.concat(operation_interceptors)
|
55
|
+
config.validate!
|
56
|
+
config.freeze
|
57
|
+
end
|
58
|
+
|
59
|
+
def output_stream(options = {}, &block)
|
60
|
+
return options.delete(:output_stream) if options[:output_stream]
|
61
|
+
return Hearth::BlockIO.new(block) if block
|
62
|
+
|
63
|
+
::StringIO.new
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/hearth/client_stubs.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative 'stubs'
|
4
|
-
|
5
3
|
module Hearth
|
6
4
|
# This module provides the ability to specify the data and/or errors to
|
7
5
|
# return when a client is using stubbed responses.
|
@@ -119,7 +117,7 @@ module Hearth
|
|
119
117
|
# `:stub_responses => true`.
|
120
118
|
def stub_responses(operation_name, *stubs)
|
121
119
|
if @config.stub_responses
|
122
|
-
@stubs.
|
120
|
+
@config.stubs.set_stubs(operation_name, stubs.flatten)
|
123
121
|
else
|
124
122
|
msg = 'Stubbing is not enabled. Enable stubbing in Config ' \
|
125
123
|
'with `stub_responses: true`'
|
@@ -38,11 +38,12 @@ module Hearth
|
|
38
38
|
|
39
39
|
def resolve_default(key)
|
40
40
|
@defaults[key]&.each do |default|
|
41
|
-
value =
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
41
|
+
value =
|
42
|
+
if default.respond_to?(:call)
|
43
|
+
default.call(self)
|
44
|
+
else
|
45
|
+
default
|
46
|
+
end
|
46
47
|
return value unless value.nil?
|
47
48
|
end
|
48
49
|
nil
|
data/lib/hearth/context.rb
CHANGED
@@ -3,21 +3,25 @@
|
|
3
3
|
module Hearth
|
4
4
|
module DNS
|
5
5
|
# Address results from a DNS lookup in {HostResolver}.
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
6
|
+
# @!method initialize(*args)
|
7
|
+
# @option args [Symbol] :address_type The type of address. For example,
|
8
|
+
# :A or :AAAA.
|
9
|
+
# @option args [String] :address The IP address.
|
10
|
+
# @option args [String] :hostname The hostname that was resolved.
|
11
|
+
# @!attribute address_type
|
12
|
+
# The type of address. For example, :A or :AAAA.
|
13
|
+
# @return [Symbol]
|
14
|
+
# @!attribute address
|
15
|
+
# The IP address.
|
16
|
+
# @return [String]
|
17
|
+
# @!attribute hostname
|
18
|
+
# The hostname that was resolved.
|
19
|
+
# @return [String]
|
20
|
+
HostAddress = Struct.new(
|
21
|
+
:address_type,
|
22
|
+
:address,
|
23
|
+
:hostname,
|
24
|
+
keyword_init: true
|
25
|
+
)
|
22
26
|
end
|
23
27
|
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cgi'
|
4
|
+
require 'ipaddr'
|
5
|
+
require 'uri'
|
6
|
+
|
7
|
+
module Hearth
|
8
|
+
# Functions in the Smithy rules engine are named routines that
|
9
|
+
# operate on a finite set of specified inputs, returning an output.
|
10
|
+
# The rules engine has a set of included functions that can be
|
11
|
+
# invoked without additional dependencies, called the standard library.
|
12
|
+
module EndpointRules
|
13
|
+
# An Authentication Scheme supported by an Endpoint
|
14
|
+
# @!attribute scheme_id
|
15
|
+
# The identifier of the authentication scheme.
|
16
|
+
# @return [String]
|
17
|
+
# @!attribute properties
|
18
|
+
# Additional properties of the authentication scheme.
|
19
|
+
# @return [Hash]
|
20
|
+
AuthScheme = Struct.new(
|
21
|
+
:scheme_id,
|
22
|
+
:properties,
|
23
|
+
keyword_init: true
|
24
|
+
) do
|
25
|
+
# @option args [String] :scheme_id
|
26
|
+
# @option args [Hash] :properties ({})
|
27
|
+
def initialize(*args)
|
28
|
+
super
|
29
|
+
self.properties ||= {}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# An Endpoint resolved by an EndpointProvider
|
34
|
+
# @!attribute uri
|
35
|
+
# The URI of the endpoint.
|
36
|
+
# @return [String]
|
37
|
+
# @!attribute auth_schemes
|
38
|
+
# The authentication schemes supported by the endpoint.
|
39
|
+
# @return [Array<AuthScheme>]
|
40
|
+
# @!attribute headers
|
41
|
+
# The headers to include in requests to the endpoint.
|
42
|
+
# @return [Hash]
|
43
|
+
Endpoint = Struct.new(
|
44
|
+
:uri,
|
45
|
+
:auth_schemes,
|
46
|
+
:headers,
|
47
|
+
keyword_init: true
|
48
|
+
) do
|
49
|
+
# @option args [String] :uri
|
50
|
+
# @option args [Array<AuthScheme>] :auth_schemes ([])
|
51
|
+
# @option args [Hash] :headers ({})
|
52
|
+
def initialize(*args)
|
53
|
+
super
|
54
|
+
self.auth_schemes ||= []
|
55
|
+
self.headers ||= {}
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Evaluates whether the input string is a compliant RFC 1123 host segment.
|
60
|
+
# When allowSubDomains is true, evaluates whether the input string is
|
61
|
+
# composed of values that are each compliant RFC 1123 host segments
|
62
|
+
# joined by dot (.) characters.
|
63
|
+
# @api private
|
64
|
+
# rubocop:disable Style/OptionalBooleanParameter
|
65
|
+
def self.valid_host_label?(value, allow_sub_domains = false)
|
66
|
+
return false if value.empty?
|
67
|
+
|
68
|
+
if allow_sub_domains
|
69
|
+
labels = value.split('.', -1)
|
70
|
+
return labels.all? { |l| valid_host_label?(l, false) }
|
71
|
+
end
|
72
|
+
|
73
|
+
!!(value =~ /\A(?!-)[a-zA-Z0-9-]{1,63}(?<!-)\z/)
|
74
|
+
end
|
75
|
+
# rubocop:enable Style/OptionalBooleanParameter
|
76
|
+
|
77
|
+
# Computes a URL structure given an input string.
|
78
|
+
# @api private
|
79
|
+
def self.parse_url(value)
|
80
|
+
URL.new(value).as_json
|
81
|
+
rescue ArgumentError, URI::InvalidURIError
|
82
|
+
nil
|
83
|
+
end
|
84
|
+
|
85
|
+
# Computes a portion of a given string based on
|
86
|
+
# the provided start and end indices.
|
87
|
+
# @api private
|
88
|
+
def self.substring(input, start, stop, reverse)
|
89
|
+
return nil if start >= stop || input.size < stop
|
90
|
+
|
91
|
+
return nil if input.chars.any? { |c| c.ord > 127 }
|
92
|
+
|
93
|
+
return input[start...stop] unless reverse
|
94
|
+
|
95
|
+
r_start = input.size - stop
|
96
|
+
r_stop = input.size - start
|
97
|
+
input[r_start...r_stop]
|
98
|
+
end
|
99
|
+
|
100
|
+
# Performs RFC 3986#section-2.1 defined percent-encoding on the input value.
|
101
|
+
# @api private
|
102
|
+
def self.uri_encode(value)
|
103
|
+
CGI.escape(value.encode('UTF-8')).gsub('+', '%20').gsub('%7E', '~')
|
104
|
+
end
|
105
|
+
|
106
|
+
# @api private
|
107
|
+
class URL
|
108
|
+
def initialize(url)
|
109
|
+
uri = URI(url)
|
110
|
+
@scheme = uri.scheme
|
111
|
+
# only support http and https schemes
|
112
|
+
raise ArgumentError unless %w[https http].include?(@scheme)
|
113
|
+
|
114
|
+
# do not support query
|
115
|
+
raise ArgumentError if uri.query
|
116
|
+
|
117
|
+
@authority = _authority(url, uri)
|
118
|
+
@path = uri.path
|
119
|
+
@normalized_path = uri.path + (uri.path[-1] == '/' ? '' : '/')
|
120
|
+
@is_ip = _is_ip(uri.host)
|
121
|
+
end
|
122
|
+
|
123
|
+
attr_reader :scheme, :authority, :path, :normalized_path, :is_ip
|
124
|
+
|
125
|
+
def as_json(_options = {})
|
126
|
+
{
|
127
|
+
'scheme' => scheme,
|
128
|
+
'authority' => authority,
|
129
|
+
'path' => path,
|
130
|
+
'normalizedPath' => normalized_path,
|
131
|
+
'isIp' => is_ip
|
132
|
+
}
|
133
|
+
end
|
134
|
+
|
135
|
+
private
|
136
|
+
|
137
|
+
def _authority(url, uri)
|
138
|
+
# don't include port if it's default and not parsed originally
|
139
|
+
if uri.default_port == uri.port && !url.include?(":#{uri.port}")
|
140
|
+
uri.host
|
141
|
+
else
|
142
|
+
"#{uri.host}:#{uri.port}"
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
def _is_ip(authority)
|
147
|
+
IPAddr.new(authority)
|
148
|
+
true
|
149
|
+
rescue IPAddr::InvalidAddressError
|
150
|
+
false
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
data/lib/hearth/http/client.rb
CHANGED
@@ -11,7 +11,7 @@ module Hearth
|
|
11
11
|
class Client
|
12
12
|
# @api private
|
13
13
|
OPTIONS = {
|
14
|
-
logger:
|
14
|
+
logger: nil,
|
15
15
|
debug_output: nil,
|
16
16
|
proxy: nil,
|
17
17
|
open_timeout: 15,
|
@@ -31,12 +31,11 @@ module Hearth
|
|
31
31
|
#
|
32
32
|
# @param [Hash] options The options for this HTTP Client
|
33
33
|
#
|
34
|
-
# @option options [Logger] :logger (
|
35
|
-
#
|
36
|
-
# is enabled.
|
34
|
+
# @option options [Logger] :logger (nil) A logger used to log Net::HTTP
|
35
|
+
# requests and responses when `:debug_output` is enabled.
|
37
36
|
#
|
38
37
|
# @option options [Boolean] :debug_output (false) When `true`,
|
39
|
-
# sets an output stream to the configured Logger for debugging.
|
38
|
+
# sets an output stream to the configured Logger (if any) for debugging.
|
40
39
|
#
|
41
40
|
# @option options [String, URI] :proxy A proxy to send
|
42
41
|
# requests through. Formatted like 'http://proxy.com:123'.
|
@@ -131,11 +130,10 @@ module Hearth
|
|
131
130
|
end
|
132
131
|
|
133
132
|
# Starts and returns a new HTTP connection.
|
134
|
-
# @param [URI] endpoint
|
135
133
|
# @return [Net::HTTP]
|
136
134
|
def new_connection(endpoint, logger)
|
137
135
|
http = create_http(endpoint)
|
138
|
-
http.set_debug_output(logger ||
|
136
|
+
http.set_debug_output(@logger || logger) if @debug_output
|
139
137
|
configure_timeouts(http)
|
140
138
|
|
141
139
|
if endpoint.scheme == 'https'
|
@@ -5,7 +5,6 @@ require 'time'
|
|
5
5
|
module Hearth
|
6
6
|
module HTTP
|
7
7
|
# An HTTP error inspector, using hints from status code and headers.
|
8
|
-
# @api private
|
9
8
|
class ErrorInspector
|
10
9
|
def initialize(error, http_response)
|
11
10
|
@error = error
|
@@ -17,6 +16,7 @@ module Hearth
|
|
17
16
|
throttling? ||
|
18
17
|
transient? ||
|
19
18
|
server?) &&
|
19
|
+
# IO does not respond to #truncate and is not rewindable
|
20
20
|
@http_response.body.respond_to?(:truncate)
|
21
21
|
end
|
22
22
|
|
@@ -77,7 +77,7 @@ module Hearth
|
|
77
77
|
rescue ArgumentError # empty string, somehow
|
78
78
|
nil
|
79
79
|
end
|
80
|
-
rescue TypeError # header is not
|
80
|
+
rescue TypeError # header is not present
|
81
81
|
nil
|
82
82
|
end
|
83
83
|
end
|
data/lib/hearth/http/field.rb
CHANGED
@@ -5,9 +5,8 @@ module Hearth
|
|
5
5
|
# Represents an HTTP field.
|
6
6
|
class Field
|
7
7
|
# @param [String] name The name of the field.
|
8
|
-
# @param [
|
9
|
-
# object that responds to `#to_s
|
10
|
-
# `#to_s`.
|
8
|
+
# @param [#to_s] value (nil) The value for the field. It can be any
|
9
|
+
# object that responds to `#to_s`.
|
11
10
|
# @param [Symbol] kind The kind of field, either :header or :trailer.
|
12
11
|
def initialize(name, value = nil, kind: :header)
|
13
12
|
if name.nil? || name.empty?
|
@@ -25,17 +24,10 @@ module Hearth
|
|
25
24
|
# @return [Symbol]
|
26
25
|
attr_reader :kind
|
27
26
|
|
28
|
-
# Returns
|
27
|
+
# Returns a string representation of the field.
|
29
28
|
# @return [String]
|
30
29
|
def value(encoding = nil)
|
31
|
-
value
|
32
|
-
if @value.is_a?(Array)
|
33
|
-
@value.compact.map { |v| escape_value(v.to_s) }.join(', ')
|
34
|
-
else
|
35
|
-
@value.to_s
|
36
|
-
end
|
37
|
-
value = value.encode(encoding) if encoding
|
38
|
-
value
|
30
|
+
encoding ? @value.to_s.encode(encoding) : @value.to_s
|
39
31
|
end
|
40
32
|
|
41
33
|
# @return [Boolean]
|
@@ -52,13 +44,6 @@ module Hearth
|
|
52
44
|
def to_h
|
53
45
|
{ @name => value }
|
54
46
|
end
|
55
|
-
|
56
|
-
private
|
57
|
-
|
58
|
-
def escape_value(str)
|
59
|
-
s = str
|
60
|
-
s.include?('"') || s.include?(',') ? "\"#{s.gsub('"', '\"')}\"" : s
|
61
|
-
end
|
62
47
|
end
|
63
48
|
end
|
64
49
|
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Hearth
|
4
|
+
module HTTP
|
5
|
+
# @api private
|
6
|
+
module HeaderListBuilder
|
7
|
+
class << self
|
8
|
+
def build_list(value)
|
9
|
+
value.compact.join(', ')
|
10
|
+
end
|
11
|
+
|
12
|
+
# builds a string from a list of possibly quoted values
|
13
|
+
# ensures that quoted values are escaped
|
14
|
+
def build_string_list(value)
|
15
|
+
value.compact.map { |s| escape_value(s) }.join(', ')
|
16
|
+
end
|
17
|
+
|
18
|
+
def build_http_date_list(value)
|
19
|
+
value.compact.map { |t| Hearth::TimeHelper.to_http_date(t) }
|
20
|
+
.join(', ')
|
21
|
+
end
|
22
|
+
|
23
|
+
def build_date_time_list(value)
|
24
|
+
value.compact.map { |t| Hearth::TimeHelper.to_date_time(t) }
|
25
|
+
.join(', ')
|
26
|
+
end
|
27
|
+
|
28
|
+
def build_epoch_seconds_list(value)
|
29
|
+
value.compact.map { |t| Hearth::TimeHelper.to_epoch_seconds(t) }
|
30
|
+
.join(', ')
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def escape_value(str)
|
36
|
+
s = str
|
37
|
+
s.include?('"') || s.include?(',') ? "\"#{s.gsub('"', '\"')}\"" : s
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'strscan'
|
4
|
+
|
5
|
+
module Hearth
|
6
|
+
module HTTP
|
7
|
+
# @api private
|
8
|
+
module HeaderListParser
|
9
|
+
class << self
|
10
|
+
def parse_boolean_list(value)
|
11
|
+
value.split(', ').map { |s| s == 'true' }
|
12
|
+
end
|
13
|
+
|
14
|
+
def parse_integer_list(value)
|
15
|
+
value.split(', ').map(&:to_i)
|
16
|
+
end
|
17
|
+
|
18
|
+
def parse_float_list(value)
|
19
|
+
value.split(', ').map(&:to_f)
|
20
|
+
end
|
21
|
+
|
22
|
+
# parse a list of possibly quoted and escaped string values
|
23
|
+
# Follows:
|
24
|
+
# # [RFC-7230's specification of header values](https://datatracker.ietf.org/doc/html/rfc7230#section-3.2.6).
|
25
|
+
def parse_string_list(value)
|
26
|
+
buffer = StringScanner.new(value)
|
27
|
+
parsed = []
|
28
|
+
|
29
|
+
parsed << read_value(buffer) until buffer.eos?
|
30
|
+
|
31
|
+
parsed
|
32
|
+
end
|
33
|
+
|
34
|
+
# rfc822/http-date has a comma after day but is NOT escaped.
|
35
|
+
# # eg: Mon, 16 Dec 2019 23:48:18 GMT, Mon, 16 Dec 2019 23:48:18 GMT
|
36
|
+
def parse_http_date_list(value)
|
37
|
+
value.split(',').each_slice(2).map { |v| Time.parse(v[0] + v[1]) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def parse_date_time_list(value)
|
41
|
+
value.split(',').map { |v| Time.parse(v) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def parse_epoch_seconds_list(value)
|
45
|
+
value.split(',').map { |v| Time.at(v.to_i) }
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def read_value(buffer)
|
51
|
+
until buffer.eos?
|
52
|
+
case buffer.peek(1)
|
53
|
+
when ' ', "\t"
|
54
|
+
# drop leading whitespace
|
55
|
+
buffer.getch
|
56
|
+
next
|
57
|
+
when '"'
|
58
|
+
buffer.getch # drop the quote and advance
|
59
|
+
return read_quoted_value(buffer)
|
60
|
+
else
|
61
|
+
return read_unquoted_value(buffer)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
# buffer is only whitespace
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def read_unquoted_value(buffer)
|
69
|
+
# there cannot be any escaped values
|
70
|
+
value = buffer.scan_until(/,|$/)
|
71
|
+
# drop the comma if we matched it
|
72
|
+
buffer.matched == ',' ? value.chop : value
|
73
|
+
end
|
74
|
+
|
75
|
+
def read_quoted_value(buffer)
|
76
|
+
# scan until we have an unescaped double quote
|
77
|
+
value = buffer.scan_until(/[^\\]"/)
|
78
|
+
unless value
|
79
|
+
raise ArgumentError,
|
80
|
+
'Invalid String list: No closing quote found'
|
81
|
+
end
|
82
|
+
|
83
|
+
# drop any remaining whitespace/commas
|
84
|
+
buffer.scan_until(/[\s,]*/)
|
85
|
+
# the last character will always be the closing quote.
|
86
|
+
# Add a starting quote and then unescape (undump)
|
87
|
+
"\"#{value}".undump
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -4,7 +4,6 @@ module Hearth
|
|
4
4
|
module HTTP
|
5
5
|
module Middleware
|
6
6
|
# A middleware that sets Content-Length for any body that has a size.
|
7
|
-
# @api private
|
8
7
|
class ContentLength
|
9
8
|
include Hearth::Middleware::Logging
|
10
9
|
|
@@ -17,9 +16,10 @@ module Hearth
|
|
17
16
|
# @return [Output]
|
18
17
|
def call(input, context)
|
19
18
|
request = context.request
|
19
|
+
body = request.body
|
20
20
|
if !request.headers.key?('Content-Length') &&
|
21
|
-
|
22
|
-
length =
|
21
|
+
(body.respond_to?(:size) && body.size.positive?)
|
22
|
+
length = body.size
|
23
23
|
request.headers['Content-Length'] = length
|
24
24
|
log_debug(context, "Set Content-Length to #{length}")
|
25
25
|
end
|