gapic-common 1.4.0 → 1.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6dbc3fc8f2b53d01d228f377851d21d941cedd307a9528e7ba732c5e8d2565e2
4
- data.tar.gz: d6ded505cf91783149b49373f2efb2ef105860705a22f2940c27f6b27d83be39
3
+ metadata.gz: f9cc2de9bcbe93a62a1706acc73f6d37064bbde7a81a5c9fd434570ed8c6e22f
4
+ data.tar.gz: 3be68b0522ce0e3465bfb555820c7c73882f2e1bf36ac009fd4bb4f31c0bb3bd
5
5
  SHA512:
6
- metadata.gz: e7533c44cae4643cec30b87e96fc61750ca2ba2a3458c711788d51987436b25ca3248f6c8d011cf06a00992eec4458e3403ac0b64850a43d153368cd3a6c0203
7
- data.tar.gz: cd51bd91e418f8d5c71c3f6e484b29f4c420121b14baca686309f074928162f97c0259555bd32d28cb6e747a397c99c0e3d5aeb6cd99085d309ed61482af7cfa
6
+ metadata.gz: f958c2009dead1a09f0f20a9dda26f65d1698399657462b47b501297aff0e6af7496b4b0314237485bbb4f9a61d3866b9ee8e61ac26469c0ee69184c53cddd5b
7
+ data.tar.gz: e8fd4368fab545fbc33636d5999e73bd9f8b664006f5967929cd8e09b25cfafb22e34a17d58ce1481b94474d952d2c7add11ba7644ba77ffab23b98c2b83728a
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Release History
2
2
 
3
+ ### 1.5.0 (2026-09-29)
4
+
5
+ #### Features
6
+
7
+ * Resumable Media Upload functionality implementation ([#72](https://github.com/googleapis/ruby-core-libraries/issues/72))
8
+ #### Documentation
9
+
10
+ * remove stale preview notice and disclaimer from READMEs ([#77](https://github.com/googleapis/ruby-core-libraries/issues/77))
11
+
12
+ ### 1.4.1 (2026-09-28)
13
+
14
+ #### Bug Fixes
15
+
16
+ * validate path parameters and prevent traversal/injection in REST transcoder ([#67](https://github.com/googleapis/ruby-core-libraries/issues/67))
17
+
3
18
  ### 1.4.0 (2026-09-23)
4
19
 
5
20
  #### Features
data/README.md CHANGED
@@ -61,9 +61,6 @@ See the {file:CONTRIBUTING.md CONTRIBUTING} documentation for more information o
61
61
 
62
62
  ## Versioning
63
63
 
64
- This library is currently a **preview** with no guarantees of stability or support. Please get
65
- involved and let us know if you find it useful and we'll work towards a stable version.
64
+ This library follows [Semantic Versioning](http://semver.org/).
66
65
 
67
- ## Disclaimer
68
-
69
- This is not an official Google product.
66
+ This library is considered to be stable and will not have backwards-incompatible changes introduced in subsequent minor releases.
@@ -19,6 +19,12 @@ module Gapic
19
19
  ##
20
20
  # Gapic Common retry policy base class.
21
21
  #
22
+ # A policy distinguishes "set to this value" from "never set". Every setting is stored as `nil`
23
+ # until someone supplies it, and the reader for each substitutes the corresponding `DEFAULT_`
24
+ # constant on the way out, so a reader can never say which of the two happened. That distinction is
25
+ # what lets {#apply_defaults} fill in gaps without overwriting a caller's choices, and {#overrides}
26
+ # report what a caller actually asked for.
27
+ #
22
28
  class RetryPolicy
23
29
  # @return [Numeric] Default initial delay in seconds.
24
30
  DEFAULT_INITIAL_DELAY = 1
@@ -111,6 +117,34 @@ module Gapic
111
117
  @retry_predicate
112
118
  end
113
119
 
120
+ ##
121
+ # @private
122
+ # The settings this policy actually carries, as keyword arguments for {RetryPolicy.initialize}.
123
+ #
124
+ # A key is present only if that setting was explicitly supplied; a key that was never set is
125
+ # absent rather than `nil`. This is what the readers cannot tell you — {#max_delay} returns
126
+ # {DEFAULT_MAX_DELAY} whether the caller chose that number or said nothing — so it is the only
127
+ # safe way to carry one policy's settings onto another without dragging defaults along, and
128
+ # without mistaking a deliberate choice that happens to equal a default for silence.
129
+ #
130
+ # An empty `retry_codes` list counts as unset. It retries nothing, which is exactly what an
131
+ # unset list does, so there is nothing for it to carry.
132
+ #
133
+ # @return [Hash{Symbol=>Object}] Explicitly set settings only
134
+ def overrides
135
+ # Assigns nil, and so omits the key, when the list is absent or empty.
136
+ retry_codes = @retry_codes unless @retry_codes.nil? || @retry_codes.empty?
137
+ {
138
+ initial_delay: @initial_delay,
139
+ max_delay: @max_delay,
140
+ multiplier: @multiplier,
141
+ retry_codes: retry_codes,
142
+ timeout: @timeout,
143
+ jitter: @jitter,
144
+ retry_predicate: @retry_predicate
145
+ }.compact
146
+ end
147
+
114
148
  ##
115
149
  # Returns a duplicate in a non-executing state, i.e. with the deadline
116
150
  # and current delay reset.
@@ -14,6 +14,6 @@
14
14
 
15
15
  module Gapic
16
16
  module Common
17
- VERSION = "1.4.0".freeze
17
+ VERSION = "1.5.0".freeze
18
18
  end
19
19
  end
@@ -67,6 +67,10 @@ module Gapic
67
67
  log(Logger::DEBUG, &)
68
68
  end
69
69
 
70
+ def warn(&)
71
+ log(Logger::WARN, &)
72
+ end
73
+
70
74
  ##
71
75
  # @private
72
76
  # Builder for a log entry, passed to {StubLogger#log}.
@@ -287,17 +287,27 @@ module Gapic
287
287
  entry.set "requestId", request_id
288
288
  entry.message = "Sending request to #{entry.service}.#{method_name} (try #{try_number})"
289
289
  end
290
- body = body.to_s
290
+ body_str = body.to_s
291
291
  metadata = metadata.to_h rescue {}
292
- return if body.empty? && metadata.empty?
292
+ return if body_str.empty? && metadata.empty?
293
293
  stub_logger.debug do |entry|
294
294
  entry.set "requestId", request_id
295
- entry.set "request", body
295
+ entry.set "request", abridge_request_body(body_str)
296
296
  entry.set "headers", metadata
297
297
  entry.message = "(request payload as JSON)"
298
298
  end
299
299
  end
300
300
 
301
+ def abridge_request_body body_str
302
+ utf8_body = body_str.dup.force_encoding Encoding::UTF_8
303
+ if body_str.bytesize > 1024 || !utf8_body.valid_encoding?
304
+ prefix_hex = body_str.byteslice(0, 32).unpack1 "H*"
305
+ "<#{body_str.bytesize} bytes, first 32: #{prefix_hex}>"
306
+ else
307
+ utf8_body
308
+ end
309
+ end
310
+
301
311
  def log_response method_name, request_id, try_number, response, is_server_streaming
302
312
  return unless stub_logger&.enabled?
303
313
  stub_logger.info do |entry|
@@ -22,6 +22,9 @@ module Gapic
22
22
  module Rest
23
23
  # Gapic REST exception class
24
24
  class Error < ::Gapic::Common::Error
25
+ # @private
26
+ REST_ERROR_PREFIX = "An error has occurred when making a REST request".freeze
27
+
25
28
  # @return [Integer, nil] the http status code for the error
26
29
  attr_reader :status_code
27
30
  # @return [Object, nil] the text representation of status as parsed from the response body
@@ -79,7 +82,7 @@ module Gapic
79
82
 
80
83
  if err.response_body
81
84
  msg, code, status, details = try_parse_from_body err.response_body
82
- message = "An error has occurred when making a REST request: #{msg}" unless msg.nil?
85
+ message = "#{REST_ERROR_PREFIX}: #{msg}" unless msg.nil?
83
86
  status_code = code unless code.nil?
84
87
  end
85
88
 
@@ -111,12 +111,14 @@ module Gapic
111
111
  # @return [Hash{String, String}]
112
112
  # Name to value hash of the variables for the uri template expansion.
113
113
  # The values are percent-escaped with slashes potentially preserved.
114
+ # @raise [Gapic::Common::Error] If any parameter value fails path traversal or injection validation.
114
115
  def bind_uri_values! http_binding, request_hash
115
116
  http_binding.field_bindings.to_h do |field_binding|
116
117
  field_path_camel = field_binding.field_path.split(".").map { |part| camel_name_for part }.join(".")
117
118
  field_value = extract_scalar_value! request_hash, field_path_camel, field_binding.regex
118
119
 
119
120
  if field_value
121
+ validate_field_binding! field_binding, field_value
120
122
  field_value = field_value.split("/").map { |segment| percent_escape segment }.join("/")
121
123
  end
122
124
 
@@ -124,6 +126,43 @@ module Gapic
124
126
  end
125
127
  end
126
128
 
129
+ # Validates a user-supplied parameter value bound to a standard (*) or path (**) URI template variable
130
+ # to prevent directory traversal exploits.
131
+ #
132
+ # @param field_binding [HttpBinding::FieldBinding] The field binding template metadata.
133
+ # @param field_value [String] The parameter value to validate.
134
+ # @raise [Gapic::Common::Error] If validation fails.
135
+ def validate_field_binding! field_binding, field_value
136
+ validate_path_binding! field_binding, field_value
137
+ end
138
+
139
+ # Validates standard (*) and path (**) parameters by ensuring that no segment in the parameter
140
+ # value is a directory traversal segment (. or ..).
141
+ #
142
+ # Validation Mechanism:
143
+ # 1. Splits the parameter value by slash (`/`) using `-1` limit to preserve all segments.
144
+ # 2. Checks each segment. If any segment matches `.` or `..`, it immediately raises
145
+ # a `Gapic::Common::Error`, aborting the request.
146
+ # 3. Empty segments (e.g. duplicate slashes `//` or trailing slashes `/`) are allowed
147
+ # by this validator and passed to the server, which handles normalization or returns 400.
148
+ #
149
+ # @param field_binding [HttpBinding::FieldBinding] The field binding template metadata.
150
+ # @param field_value [String] The parameter value to validate.
151
+ # @raise [Gapic::Common::Error] If validation fails.
152
+ def validate_path_binding! field_binding, field_value
153
+ segments = field_value.split("/", -1)
154
+ segments.each do |segment|
155
+ next unless [".", ".."].include? segment
156
+ if field_binding.preserve_slashes
157
+ raise ::Gapic::Common::Error,
158
+ "Value for #{field_binding.field_path} must not contain segments that are exactly '#{segment}'."
159
+ else
160
+ raise ::Gapic::Common::Error,
161
+ "Invalid value for #{field_binding.field_path} '#{segment}'."
162
+ end
163
+ end
164
+ end
165
+
127
166
  # Percent-escapes a string.
128
167
  # @param str [String] String to escape.
129
168
  # @return [String] Escaped string.
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright 2026 Google LLC
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # https://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+
17
+ require "gapic/rest/resumable_upload/data_types"
18
+ require "gapic/rest/resumable_upload/rules"
19
+
20
+ module Gapic
21
+ module Rest
22
+ module ResumableUpload
23
+ ##
24
+ # @private
25
+ # State machine container holding the immutable State snapshot.
26
+ # Contains zero protocol branching logic and zero side-effects.
27
+ #
28
+ # The middle tier of the three-tier design: `Driver` executes side effects, {Rules} decides transitions,
29
+ # and Core holds the {State} between the two. See {Rules} for the protocol narrative and state graph, and
30
+ # `design/resumable_upload/implementation-guide.md` section 1 for the tier boundaries.
31
+ #
32
+ class Core
33
+ # @private
34
+ # @return [State] Current immutable state snapshot
35
+ attr_reader :state
36
+
37
+ # @private
38
+ # @return [Decision, nil] Decision emitted during the last dispatch
39
+ attr_reader :last_decision
40
+
41
+ ##
42
+ # @private
43
+ # Initializes a Core state machine container.
44
+ #
45
+ # @param config [StartUploadConfig, ResumeUploadConfig] Upload session configuration
46
+ #
47
+ def initialize config
48
+ @config = config
49
+ @last_decision = nil
50
+ @state = State.new(
51
+ status: :initializing,
52
+ upload_url: nil,
53
+ offset: 0,
54
+ chunk_size: config.chunk_size || Rules::DEFAULT_CHUNK_SIZE,
55
+ chunk_granularity: nil,
56
+ in_flight_length: 0,
57
+ last_error: nil
58
+ )
59
+ end
60
+
61
+ ##
62
+ # @private
63
+ # Dispatches event to Rules and updates internal state snapshot.
64
+ #
65
+ # @param event [Object] Input event
66
+ # @return [Array<Object>] Driver instructions
67
+ #
68
+ def dispatch event
69
+ decision = Rules.decide @state, event, @config
70
+ @state = decision.next_state
71
+ @last_decision = decision
72
+ decision.instructions
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end