From 173edd7c4f9dc0de15109038ead5dc06311ce8e9 Mon Sep 17 00:00:00 2001 From: Simon de Haan Date: Wed, 19 Aug 2026 16:50:26 +0200 Subject: [PATCH 1/2] [gax] Set max_body_size: :infinity on DecompressResponse for Tesla 1.21 Tesla 1.21 made :max_body_size mandatory on the compression middleware (a decompression-bomb CVE fix). Upstream elixir-google-api was archived before adopting it, so pin :infinity to preserve pre-1.21 behaviour and unblock the Tesla upgrade in turnhub/engage. Temporary until we migrate off this library. Co-Authored-By: Claude Opus 4.8 (1M context) --- clients/gax/lib/google_api/gax/connection.ex | 7 ++++++- clients/gax/mix.exs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/clients/gax/lib/google_api/gax/connection.ex b/clients/gax/lib/google_api/gax/connection.ex index 1b7e95edfe..a4129ec5f3 100644 --- a/clients/gax/lib/google_api/gax/connection.ex +++ b/clients/gax/lib/google_api/gax/connection.ex @@ -33,7 +33,12 @@ defmodule GoogleApi.Gax.Connection do ) ) - plug(Tesla.Middleware.DecompressResponse, []) + # turnhub patch: Tesla 1.21 made :max_body_size mandatory on the + # (de)compression middleware (decompression-bomb CVE fix). Upstream + # elixir-google-api was archived (2026-06) before adopting it, so pin + # :infinity here to preserve the pre-1.21 behaviour. Remove once we migrate + # off this library (turnhub/engage Req+Goth migration). + plug(Tesla.Middleware.DecompressResponse, max_body_size: :infinity) plug(Tesla.Middleware.EncodeJson, engine: Poison) diff --git a/clients/gax/mix.exs b/clients/gax/mix.exs index b654b6cc49..f4f750125e 100644 --- a/clients/gax/mix.exs +++ b/clients/gax/mix.exs @@ -27,7 +27,7 @@ defmodule GoogleApi.Gax.MixProject do defp deps() do [ {:tesla, "~> 1.2"}, - {:mime, "~> 1.0"}, + {:mime, "~> 1.0 or ~> 2.0"}, {:poison, ">= 3.0.0 and < 5.0.0"}, {:ex_doc, "~> 0.16", only: :dev}, {:dialyxir, "~> 0.5", only: [:dev], runtime: false} From 3e022d8adbe3b3e24ff01c7a73b04235d1db3c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Thallis?= Date: Thu, 20 Aug 2026 15:17:35 -0300 Subject: [PATCH 2/2] [gax] Stringify multipart field names for Tesla 1.21 Tesla 1.21 validates multipart field names with assert_quoted_string_safe!/2, which only accepts binaries. build_body/3 passed the :metadata atom and keyword-list body/file param keys straight through, so every multipart upload (e.g. GCS storage_objects_insert_simple) crashed with FunctionClauseError. The wire format is unchanged: atoms were already interpolated into the Content-Disposition name before. Co-Authored-By: Claude Fable 5 --- clients/gax/lib/google_api/gax/connection.ex | 29 ++++++++++++-------- clients/gax/test/gax/connection_test.exs | 6 ++++ 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/clients/gax/lib/google_api/gax/connection.ex b/clients/gax/lib/google_api/gax/connection.ex index a4129ec5f3..6fdce9405b 100644 --- a/clients/gax/lib/google_api/gax/connection.ex +++ b/clients/gax/lib/google_api/gax/connection.ex @@ -172,7 +172,7 @@ defmodule GoogleApi.Gax.Connection do defp build_body(output, [], file_params) do body = Enum.reduce(file_params, Tesla.Multipart.new(), fn {file_name, file_path}, b -> - Tesla.Multipart.add_file(b, file_path, name: file_name) + Tesla.Multipart.add_file(b, file_path, name: to_string(file_name)) end) Keyword.put(output, :body, body) @@ -183,22 +183,27 @@ defmodule GoogleApi.Gax.Connection do {meta, body_params} = extract_metadata(body_params) - body = case meta do - nil -> body - _ -> Tesla.Multipart.add_field( - body, - :metadata, - Poison.encode!(meta), - headers: [{:"Content-Type", "application/json"}] - ) - end + body = + case meta do + nil -> + body + + _ -> + Tesla.Multipart.add_field( + body, + "metadata", + Poison.encode!(meta), + headers: [{:"Content-Type", "application/json"}] + ) + end body = Enum.reduce(body_params, body, fn {body_name, data}, b -> {res, type} = try_encode_multipart_field(data, meta) + Tesla.Multipart.add_field( b, - body_name, + to_string(body_name), res, headers: [{:"Content-Type", type}] ) @@ -206,7 +211,7 @@ defmodule GoogleApi.Gax.Connection do body = Enum.reduce(file_params, body, fn {file_name, file_path}, b -> - Tesla.Multipart.add_file(b, file_path, name: file_name) + Tesla.Multipart.add_file(b, file_path, name: to_string(file_name)) end) Keyword.put(output, :body, body) diff --git a/clients/gax/test/gax/connection_test.exs b/clients/gax/test/gax/connection_test.exs index 1c2fef131f..796ef9b33f 100644 --- a/clients/gax/test/gax/connection_test.exs +++ b/clients/gax/test/gax/connection_test.exs @@ -84,6 +84,7 @@ defmodule Gax.ConnectionTest do test "builds a multipart upload request with iodata and content type" do metadata = %{contentType: "text/plain"} data = ["1", ["2"]] + request = Request.new() |> Request.add_param(:body, :metadata, metadata) @@ -94,13 +95,16 @@ defmodule Gax.ConnectionTest do [part1, part2] = body.parts assert "{\"contentType\":\"text/plain\"}" == part1.body assert [{:"Content-Type", "application/json"}] == part1.headers + assert [name: "metadata"] == part1.dispositions assert data == part2.body assert [{:"Content-Type", "text/plain"}] == part2.headers + assert [name: "data"] == part2.dispositions end test "builds a multipart upload request with iodata but no content type" do metadata = %{foo: "bar"} data = ["1", ["2"]] + request = Request.new() |> Request.add_param(:body, :metadata, metadata) @@ -118,6 +122,7 @@ defmodule Gax.ConnectionTest do test "builds a multipart upload request with a JSON decodable struct" do metadata = %{foo: "bar"} data = %{baz: "qux"} + request = Request.new() |> Request.add_param(:body, :metadata, metadata) @@ -135,6 +140,7 @@ defmodule Gax.ConnectionTest do test "builds a multipart upload request with a non-JSON struct" do metadata = %{foo: "bar"} data = %{baz: {}} + assert_raise(Poison.EncodeError, fn -> Request.new() |> Request.add_param(:body, :metadata, metadata)