From 1bf3b140c667c71b54b161835e173720ff3deb90 Mon Sep 17 00:00:00 2001 From: SeongHoon Ryu <4997174+ryush00@users.noreply.github.com> Date: Sun, 8 Mar 2026 13:06:53 +0900 Subject: [PATCH] test: cover folded line-break reply-to parsing error --- lib/mailtrap/mail.rb | 2 + ...converts_the_message_and_sends_via_API.yml | 2 +- spec/mailtrap/mail_spec.rb | 61 ++++++++++++++++++- 3 files changed, 63 insertions(+), 2 deletions(-) diff --git a/lib/mailtrap/mail.rb b/lib/mailtrap/mail.rb index 20091ec..99e1bc1 100644 --- a/lib/mailtrap/mail.rb +++ b/lib/mailtrap/mail.rb @@ -17,6 +17,7 @@ module Mail # rubocop:disable Metrics/ModuleLength category customvariables contenttype + replyto ].freeze private_constant :SPECIAL_HEADERS @@ -195,6 +196,7 @@ def from_message(message) to: prepare_addresses(message['to']), cc: prepare_addresses(message['cc']), bcc: prepare_addresses(message['bcc']), + reply_to: prepare_addresses(message['reply-to']).first, subject: message.subject, text: prepare_text_part(message), html: prepare_html_part(message), diff --git a/spec/fixtures/vcr_cassettes/Mailtrap_ActionMailer_DeliveryMethod/_deliver_/converts_the_message_and_sends_via_API.yml b/spec/fixtures/vcr_cassettes/Mailtrap_ActionMailer_DeliveryMethod/_deliver_/converts_the_message_and_sends_via_API.yml index 002dcfe..e391e0c 100644 --- a/spec/fixtures/vcr_cassettes/Mailtrap_ActionMailer_DeliveryMethod/_deliver_/converts_the_message_and_sends_via_API.yml +++ b/spec/fixtures/vcr_cassettes/Mailtrap_ActionMailer_DeliveryMethod/_deliver_/converts_the_message_and_sends_via_API.yml @@ -5,7 +5,7 @@ http_interactions: uri: https://send.api.mailtrap.io/api/send body: encoding: UTF-8 - string: '{"from":{"email":"mailtrap@mailtrap.io","name":"Mailtrap Test"},"to":[{"email":"to_1@railsware.com","name":"To 1"},{"email":"to_2@railsware.com"}],"cc":[{"email":"cc_1@railsware.com"},{"email":"cc_2@railsware.com","name":"Cc 2"}],"bcc":[{"email":"bcc_1@railsware.com"},{"email":"bcc_2@railsware.com"}],"subject":"You are awesome!","text":"Some text","html":"
HTML part
","attachments":[{"content":"VGhpcyBpcyBhIHRleHQgZmlsZQo=","type":"text/plain","filename":"file.txt","disposition":"attachment","content_id":"txt_content_id@test.mail"},{"content":"iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABiElEQVRIid3VSW5UMRCA4U8d0gIx7AjhAghBTgFICAkxRFHukDDDIRiuwI7xPNAMISQRV4CABFmkWbie2nrt9muaFZTkxavhr7Jfucz/Ln2s4hU28B0/sBO6VczPCl/GNoYd6wsuZ3ELWKuBe3iSAQa4g7M4jEM4jRt4g5+4lMEHEbc+KUED/xVOvY5iThXgg/gek+UMfq4CbstU8L7RmU/c3qxwUkc0TnN/CT9Ycn4djrenhB/H24j5iMXQX8TTUsCncD6T6daUtzyp8hNSV30uJfgWAUfje70AqMFF7FC6kGOy20rQPoKTBd1ii3EsbF9LCUpH1K62q1uWwr5RSvAyjHdb+rzqSZU38iB8npWMTZu+M96mzU5qfT6H98FYKTn0sRUONwv2hQqcNK+G2FSZsNeNRsX5CqwtF7CHfVzpcn6cJbmlfqsPSJXvRczDaarpZUmaf3JP6pAjsZZw3+jM9/FIffKOyTXpRnY9OJu4+ifgXOaljnghtedurA94HraZn8x/Q34DYaON8Fk9Z1IAAAAASUVORK5CYII=","type":"image/png","filename":"file.png","disposition":"inline","content_id":"png_content_id@test.mail"}],"headers":{"Reply-To":"reply-to@railsware.com","X-Special-Domain-Specific-Header":"SecretValue","One-more-custom-header":"CustomValue"},"category":"Module Test"}' + string: '{"from":{"email":"mailtrap@mailtrap.io","name":"Mailtrap Test"},"to":[{"email":"to_1@railsware.com","name":"To 1"},{"email":"to_2@railsware.com"}],"reply_to":{"email":"reply-to@railsware.com"},"cc":[{"email":"cc_1@railsware.com"},{"email":"cc_2@railsware.com","name":"Cc 2"}],"bcc":[{"email":"bcc_1@railsware.com"},{"email":"bcc_2@railsware.com"}],"subject":"You are awesome!","text":"Some text","html":"
HTML part
","attachments":[{"content":"VGhpcyBpcyBhIHRleHQgZmlsZQo=","type":"text/plain","filename":"file.txt","disposition":"attachment","content_id":"txt_content_id@test.mail"},{"content":"iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABmJLR0QA/wD/AP+gvaeTAAABiElEQVRIid3VSW5UMRCA4U8d0gIx7AjhAghBTgFICAkxRFHukDDDIRiuwI7xPNAMISQRV4CABFmkWbie2nrt9muaFZTkxavhr7Jfucz/Ln2s4hU28B0/sBO6VczPCl/GNoYd6wsuZ3ELWKuBe3iSAQa4g7M4jEM4jRt4g5+4lMEHEbc+KUED/xVOvY5iThXgg/gek+UMfq4CbstU8L7RmU/c3qxwUkc0TnN/CT9Ycn4djrenhB/H24j5iMXQX8TTUsCncD6T6daUtzyp8hNSV30uJfgWAUfje70AqMFF7FC6kGOy20rQPoKTBd1ii3EsbF9LCUpH1K62q1uWwr5RSvAyjHdb+rzqSZU38iB8npWMTZu+M96mzU5qfT6H98FYKTn0sRUONwv2hQqcNK+G2FSZsNeNRsX5CqwtF7CHfVzpcn6cJbmlfqsPSJXvRczDaarpZUmaf3JP6pAjsZZw3+jM9/FIffKOyTXpRnY9OJu4+ifgXOaljnghtedurA94HraZn8x/Q34DYaON8Fk9Z1IAAAAASUVORK5CYII=","type":"image/png","filename":"file.png","disposition":"inline","content_id":"png_content_id@test.mail"}],"headers":{"X-Special-Domain-Specific-Header":"SecretValue","One-more-custom-header":"CustomValue"},"category":"Module Test"}' headers: Accept-Encoding: - gzip;q=1.0,deflate;q=0.6,identity;q=0.3 diff --git a/spec/mailtrap/mail_spec.rb b/spec/mailtrap/mail_spec.rb index 80c7e56..2e84548 100644 --- a/spec/mailtrap/mail_spec.rb +++ b/spec/mailtrap/mail_spec.rb @@ -40,7 +40,6 @@ let(:expected_headers) do { 'X-Special-Domain-Specific-Header' => 'SecretValue', - 'Reply-To' => 'Reply To ', 'One-more-custom-header' => 'CustomValue' } end @@ -53,6 +52,35 @@ it { is_expected.to eq(expected_headers) } end + + context 'when reply-to is added in varying formats' do + [ + ['"메일트랩" ', { email: 'support@mailtrap.io', name: '메일트랩' }], + ['"Mailtrap Team" ', { email: 'support@mailtrap.io', name: 'Mailtrap Team' }], + ['support@mailtrap.io', { email: 'support@mailtrap.io' }] + ].each do |reply_to, expected_reply_to| + it "maps '#{reply_to}' to structured field and excludes header copy" do + message.reply_to = reply_to + + expect(mail.reply_to).to eq(expected_reply_to) + expect(headers).not_to have_key('Reply-To') + end + end + end + + context 'when custom header and reply-to variants are present' do + before do + message.header['Reply-To'] = 'Reply To ' + message.header['REPLY-TO'] = 'Upper Case ' + message.header['reply-to'] = 'Lower Case ' + message.header['X-Special-Domain-Specific-Header'] = 'SecretValue' + end + + it 'keeps only custom headers and strips all reply-to header variants' do + expect(mail.reply_to).to eq({ email: 'lower-reply-to@railsware.com', name: 'Lower Case' }) + expect(headers).to eq('X-Special-Domain-Specific-Header' => 'SecretValue') + end + end end describe '#attachment' do @@ -167,5 +195,36 @@ end end end + + context "when 'reply-to' is invalid" do + let(:invalid_reply_to) { 'invalid email@example.com' } + + before do + message.header['Reply-To'] = invalid_reply_to + end + + it 'raises an error' do + expect { mail }.to raise_error( + Mailtrap::Error, + "failed to parse 'Reply-To': 'invalid email@example.com'" + ) + end + + context 'when address contains a folded line break' do + let(:invalid_reply_to) do + encoded_name = ['메일트랩 팀'.encode('UTF-8')].pack('m0') + folded_domain = %w[exa mple.com].join("\r\n ") + + "=?UTF-8?B?#{encoded_name}?= " + end + + it 'raises an error' do + expect { mail }.to raise_error( + Mailtrap::Error, + /failed to parse 'Reply-To': '.*no-reply@exa/m + ) + end + end + end end end