Skip to content

Commit fc3b309

Browse files
committed
fix(http-client): resolve HTTParty encoding compatibility issue with multipart requests
Fixes #528 - Resolves 'incompatible character encodings: ASCII-8BIT and UTF-8' error when sending multipart requests - Normalizes all multipart payload strings to consistent ASCII-8BIT encoding for HTTParty compatibility - Maintains backward compatibility while preventing encoding conflicts during multipart body generation - Updates test expectations to reflect new encoding behavior
1 parent 248b129 commit fc3b309

File tree

3 files changed

+29
-8
lines changed

3 files changed

+29
-8
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* Added support for `single_level` query parameter in Folders API for Microsoft accounts
55
* Added support for `include_hidden_folders` query parameter in folders list endpoint for Microsoft accounts to control whether hidden folders are included in the response
66
* Replace Tempfile with StringIO for multipart uploads to eliminate filesystem dependencies and improve compatibility with read-only containers and AWS Lambda environments
7+
* Fixed HTTParty encoding compatibility issue with multipart requests (#528)
78

89
### 6.5.0 / 2025-06-13
910
* Replaced `rest-client` dependency with `httparty` for improved maintainability and security

lib/nylas/handler/http_client.rb

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -215,21 +215,27 @@ def prepare_multipart_payload(payload)
215215

216216
modified_payload = payload.dup
217217

218+
# First, normalize all string encodings to prevent HTTParty encoding conflicts
219+
normalize_multipart_encodings!(modified_payload)
220+
218221
# Handle binary content attachments (file0, file1, etc.) by converting them to enhanced StringIO
219222
# HTTParty expects file uploads to be objects with full file-like interface
220-
payload.each do |key, value|
223+
modified_payload.each do |key, value|
221224
next unless key.is_a?(String) && key.match?(/^file\d+$/) && value.is_a?(String)
222225

226+
# Get the original value to check for singleton methods
227+
original_value = payload[key]
228+
223229
# Create an enhanced StringIO object for HTTParty compatibility
224230
string_io = create_file_like_stringio(value)
225231

226232
# Preserve filename and content_type if they exist as singleton methods
227-
if value.respond_to?(:original_filename)
228-
string_io.define_singleton_method(:original_filename) { value.original_filename }
233+
if original_value.respond_to?(:original_filename)
234+
string_io.define_singleton_method(:original_filename) { original_value.original_filename }
229235
end
230236

231-
if value.respond_to?(:content_type)
232-
string_io.define_singleton_method(:content_type) { value.content_type }
237+
if original_value.respond_to?(:content_type)
238+
string_io.define_singleton_method(:content_type) { original_value.content_type }
233239
end
234240

235241
modified_payload[key] = string_io
@@ -239,8 +245,22 @@ def prepare_multipart_payload(payload)
239245
[modified_payload, []]
240246
end
241247

248+
# Normalize string encodings in multipart payload to prevent HTTParty encoding conflicts
249+
# This ensures all string fields use consistent ASCII-8BIT encoding for multipart compatibility
250+
def normalize_multipart_encodings!(payload)
251+
payload.each do |key, value|
252+
next unless value.is_a?(String)
253+
254+
# Force all string values to ASCII-8BIT encoding for multipart compatibility
255+
# HTTParty/multipart-post expects binary encoding for consistent concatenation
256+
payload[key] = value.dup.force_encoding(Encoding::ASCII_8BIT)
257+
end
258+
end
259+
242260
# Create a StringIO object that behaves more like a File for HTTParty compatibility
243261
def create_file_like_stringio(content)
262+
# Content is already normalized to ASCII-8BIT by normalize_multipart_encodings!
263+
# Create StringIO with the normalized binary content
244264
string_io = StringIO.new(content)
245265

246266
# Add methods that HTTParty/multipart-post might expect
@@ -250,7 +270,7 @@ def create_file_like_stringio(content)
250270
File.instance_methods.include?(method_name) || super(method_name, include_private)
251271
end
252272

253-
# Ensure binary mode for consistent behavior
273+
# Set binary mode for file-like behavior
254274
string_io.binmode if string_io.respond_to?(:binmode)
255275

256276
string_io

spec/nylas/handler/http_client_spec.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -654,12 +654,12 @@ class TestHttpClient
654654
end
655655

656656
describe "#prepare_multipart_payload" do
657-
it "leaves ASCII-only message payloads unchanged" do
657+
it "normalizes message payloads to ASCII-8BIT encoding for HTTParty compatibility" do
658658
payload = { "message" => "Hello World" }
659659
result, temp_files = http_client.send(:prepare_multipart_payload, payload)
660660

661661
expect(result["message"]).to eq("Hello World")
662-
expect(result["message"].encoding).to eq(Encoding::UTF_8)
662+
expect(result["message"].encoding).to eq(Encoding::ASCII_8BIT)
663663
expect(temp_files).to be_empty
664664
end
665665

0 commit comments

Comments
 (0)