From 138291369319be7a7abd77dca6c002c9e7fe7da9 Mon Sep 17 00:00:00 2001 From: Christian Schmidt Date: Sat, 28 Nov 2020 17:07:54 +0100 Subject: [PATCH] Support parameters for MAIL FROM and RCPT TO --- lib/net/smtp.rb | 51 +++++++---- test/net/smtp/test_smtp.rb | 168 ++++++++++++++++++++++++++++++++++++- 2 files changed, 202 insertions(+), 17 deletions(-) diff --git a/lib/net/smtp.rb b/lib/net/smtp.rb index a97c0c3..67e3956 100644 --- a/lib/net/smtp.rb +++ b/lib/net/smtp.rb @@ -255,7 +255,6 @@ def capable?(key) return nil unless @capabilities @capabilities[key] ? true : false end - private :capable? # true if server advertises AUTH PLAIN. # You cannot get valid value before opening SMTP session. @@ -669,10 +668,15 @@ def do_finish # binary message with this method. +msgstr+ should include both # the message headers and body. # - # +from_addr+ is a String representing the source mail address. + # +from_addr+ is the source mail address. # - # +to_addr+ is a String or Strings or Array of Strings, representing - # the destination mail address or addresses. + # +to_addr+ is an address or Array of addresses. + # + # An address is a String representing the source mail address, or an [addr, params] + # pair, where +addr+ is a String representing the source mail address and + # +params+ is an Array or Hash of parameters. + # + # Parameters may be a Hash or an Array of Strings. # # === Example # @@ -695,7 +699,10 @@ def do_finish # def send_message(msgstr, from_addr, *to_addrs) raise IOError, 'closed session' unless @socket - mailfrom from_addr + mailfrom(*Array(from_addr)) + if to_addrs.first.is_a?(Array) && to_addrs.count == 1 + to_addrs = to_addrs.first + end rcptto_list(to_addrs) {data msgstr} end @@ -718,10 +725,15 @@ def send_message(msgstr, from_addr, *to_addrs) # # === Parameters # - # +from_addr+ is a String representing the source mail address. + # +from_addr+ is the source mail address. + # + # +to_addr+ is an address or Array of addresses. # - # +to_addr+ is a String or Strings or Array of Strings, representing - # the destination mail address or addresses. + # An address is a String representing the source mail address, or an [addr, params] + # pair, where +addr+ is a String representing the source mail address and + # +params+ is an Array or Hash of parameters. + # + # Parameters may be a Hash or an Array of Strings. # # === Example # @@ -748,7 +760,10 @@ def send_message(msgstr, from_addr, *to_addrs) # def open_message_stream(from_addr, *to_addrs, &block) # :yield: stream raise IOError, 'closed session' unless @socket - mailfrom from_addr + mailfrom(*Array(from_addr)) + if to_addrs.first.is_a?(Array) && to_addrs.count == 1 + to_addrs = to_addrs.first + end rcptto_list(to_addrs) {data(&block)} end @@ -870,17 +885,18 @@ def ehlo(domain) getok("EHLO #{domain}") end - def mailfrom(from_addr) - getok("MAIL FROM:<#{from_addr}>") + def mailfrom(from_addr, params = []) + getok(addr_req("MAIL FROM", from_addr, params)) end def rcptto_list(to_addrs) raise ArgumentError, 'mail destination not given' if to_addrs.empty? ok_users = [] unknown_users = [] - to_addrs.flatten.each do |addr| + to_addrs.each do |addr| + addr, params = Array(addr) begin - rcptto addr + rcptto addr, params rescue SMTPAuthenticationError unknown_users << addr.dump else @@ -895,8 +911,13 @@ def rcptto_list(to_addrs) ret end - def rcptto(to_addr) - getok("RCPT TO:<#{to_addr}>") + def rcptto(to_addr, params = []) + getok(addr_req("RCPT TO", to_addr, params)) + end + + def addr_req(command, addr, params) + params_list = params.to_a.map {|param| " " + Array(param).compact.join("=") }.join + "#{command}:<#{addr}>#{params_list}" end # This method sends a message. diff --git a/test/net/smtp/test_smtp.rb b/test/net/smtp/test_smtp.rb index af30bb7..d3d5e04 100644 --- a/test/net/smtp/test_smtp.rb +++ b/test/net/smtp/test_smtp.rb @@ -24,7 +24,20 @@ def writeline line def readline line = @read_io.gets raise 'ran out of input' unless line - line.chop + line.chomp + end + + def io + @write_io + end + + def write_message msg + @write_io.write "#{msg.chomp}\r\n.\r\n" + end + + def write_message_by_block &block + block.call(@write_io) + @write_io.write ".\r\n" end end @@ -49,6 +62,102 @@ def test_critical '[Bug #9125]' end + def test_send_message + sock = FakeSocket.new [ + "220 OK", # MAIL FROM + "250 OK", # RCPT TO + "354 Send data", # DATA + "250 OK", + ].join "\r\n" + smtp = Net::SMTP.new 'localhost', 25 + smtp.instance_variable_set :@socket, sock + + smtp.send_message "Lorem ipsum", "foo@example.com", "bar@example.com" + + sock.write_io.rewind + assert_equal "MAIL FROM:\r\n", sock.write_io.readline + assert_equal "RCPT TO:\r\n", sock.write_io.readline + assert_equal "DATA\r\n", sock.write_io.readline + assert_equal "Lorem ipsum\r\n", sock.write_io.readline + assert_equal ".\r\n", sock.write_io.readline + assert sock.write_io.eof? + end + + def test_send_message_params + sock = FakeSocket.new [ + "220 OK", # MAIL FROM + "250 OK", # RCPT TO + "250 OK", # RCPT TO + "250 OK", # RCPT TO + "354 Send data", # DATA + "250 OK", + ].join "\r\n" + smtp = Net::SMTP.new 'localhost', 25 + smtp.instance_variable_set :@socket, sock + + smtp.send_message "Lorem ipsum", + ["foo@example.com", [:FOO]], + ["1@example.com", ["2@example.com", ["FOO"]], ["3@example.com", {BAR: 1}]] + + sock.write_io.rewind + assert_equal "MAIL FROM: FOO\r\n", sock.write_io.readline + assert_equal "RCPT TO:<1@example.com>\r\n", sock.write_io.readline + assert_equal "RCPT TO:<2@example.com> FOO\r\n", sock.write_io.readline + assert_equal "RCPT TO:<3@example.com> BAR=1\r\n", sock.write_io.readline + assert_equal "DATA\r\n", sock.write_io.readline + assert_equal "Lorem ipsum\r\n", sock.write_io.readline + assert_equal ".\r\n", sock.write_io.readline + assert sock.write_io.eof? + end + + def test_open_message_stream + sock = FakeSocket.new [ + "220 OK", # MAIL FROM + "250 OK", # RCPT TO + "354 Send data", # DATA + "250 OK", + ].join "\r\n" + smtp = Net::SMTP.new 'localhost', 25 + smtp.instance_variable_set :@socket, sock + + smtp.open_message_stream "foo@example.com", "bar@example.com" do |f| + f.puts "Lorem ipsum" + end + + sock.write_io.rewind + assert_equal "MAIL FROM:\r\n", sock.write_io.readline + assert_equal "RCPT TO:\r\n", sock.write_io.readline + assert_equal "DATA\r\n", sock.write_io.readline + assert_equal "Lorem ipsum\n", sock.write_io.readline + assert_equal ".\r\n", sock.write_io.readline + assert sock.write_io.eof? + end + + def test_open_message_stream_params + sock = FakeSocket.new [ + "220 OK", # MAIL FROM + "250 OK", # RCPT TO + "250 OK", # RCPT TO + "354 Send data", # DATA + "250 OK", + ].join "\r\n" + smtp = Net::SMTP.new 'localhost', 25 + smtp.instance_variable_set :@socket, sock + + smtp.open_message_stream ["foo@example.com", [:FOO]], ["1@example.com", ["2@example.com", ["BAR"]]] do |f| + f.puts "Lorem ipsum" + end + + sock.write_io.rewind + assert_equal "MAIL FROM: FOO\r\n", sock.write_io.readline + assert_equal "RCPT TO:<1@example.com>\r\n", sock.write_io.readline + assert_equal "RCPT TO:<2@example.com> BAR\r\n", sock.write_io.readline + assert_equal "DATA\r\n", sock.write_io.readline + assert_equal "Lorem ipsum\n", sock.write_io.readline + assert_equal ".\r\n", sock.write_io.readline + assert sock.write_io.eof? + end + def test_esmtp smtp = Net::SMTP.new 'localhost', 25 assert smtp.esmtp @@ -59,11 +168,37 @@ def test_esmtp assert_equal 'omg', smtp.esmtp? end + def test_helo + sock = FakeSocket.new + smtp = Net::SMTP.new 'localhost', 25 + smtp.instance_variable_set :@socket, sock + + assert smtp.helo("example.com") + assert_equal "HELO example.com\r\n", sock.write_io.string + end + + def test_ehlo + sock = FakeSocket.new [ + "220-smtp.example.com", + "250-STARTTLS", + "250-SIZE 100", + "250 XFOO 1 2 3", + ].join "\r\n" + smtp = Net::SMTP.new 'localhost', 25 + smtp.instance_variable_set :@socket, sock + res = smtp.ehlo("example.com") + assert res.success? + assert_equal ({"STARTTLS" => [], "SIZE" => ["100"], "XFOO" => ["1", "2", "3"]}), res.capabilities + assert_equal "EHLO example.com\r\n", sock.write_io.string + end + def test_rset + sock = FakeSocket.new smtp = Net::SMTP.new 'localhost', 25 - smtp.instance_variable_set :@socket, FakeSocket.new + smtp.instance_variable_set :@socket, sock assert smtp.rset + assert_equal "RSET\r\n", sock.write_io.string end def test_mailfrom @@ -74,6 +209,14 @@ def test_mailfrom assert_equal "MAIL FROM:\r\n", sock.write_io.string end + def test_mailfrom_params + sock = FakeSocket.new + smtp = Net::SMTP.new 'localhost', 25 + smtp.instance_variable_set :@socket, sock + assert smtp.mailfrom("foo@example.com", [:FOO]).success? + assert_equal "MAIL FROM: FOO\r\n", sock.write_io.string + end + def test_rcptto sock = FakeSocket.new smtp = Net::SMTP.new 'localhost', 25 @@ -82,6 +225,27 @@ def test_rcptto assert_equal "RCPT TO:\r\n", sock.write_io.string end + def test_rcptto_params + sock = FakeSocket.new + smtp = Net::SMTP.new 'localhost', 25 + smtp.instance_variable_set :@socket, sock + assert smtp.rcptto("foo@example.com", ["FOO"]).success? + assert_equal "RCPT TO: FOO\r\n", sock.write_io.string + end + + def test_addr_req + smtp = Net::SMTP.new 'localhost', 25 + + res = smtp.addr_req("MAIL FROM", "foo@example.com", []) + assert_equal "MAIL FROM:", res + + res = smtp.addr_req("MAIL FROM", "foo@example.com", [:FOO, "BAR"]) + assert_equal "MAIL FROM: FOO BAR", res + + res = smtp.addr_req("MAIL FROM", "foo@example.com", {FOO: nil, BAR: "1"}) + assert_equal "MAIL FROM: FOO BAR=1", res + end + def test_auth_plain sock = FakeSocket.new smtp = Net::SMTP.new 'localhost', 25