Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Net::Support parameters for MAIL FROM and RCPT TO #3359

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
51 changes: 36 additions & 15 deletions lib/net/smtp.rb
Expand Up @@ -249,7 +249,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.
Expand Down Expand Up @@ -630,10 +629,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
#
Expand All @@ -656,7 +660,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

Expand All @@ -679,10 +686,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
#
Expand All @@ -709,7 +721,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

Expand Down Expand Up @@ -831,17 +846,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
Expand All @@ -856,8 +872,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
req = "#{command}:<#{addr}>#{params_list}"
end

# This method sends a message.
Expand Down
168 changes: 166 additions & 2 deletions test/net/smtp/test_smtp.rb
Expand Up @@ -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

Expand All @@ -41,6 +54,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:<foo@example.com>\r\n", sock.write_io.readline
assert_equal "RCPT TO:<bar@example.com>\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@example.com> 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:<foo@example.com>\r\n", sock.write_io.readline
assert_equal "RCPT TO:<bar@example.com>\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@example.com> 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
Expand All @@ -51,11 +160,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
Expand All @@ -66,6 +201,14 @@ def test_mailfrom
assert_equal "MAIL FROM:<foo@example.com>\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@example.com> FOO\r\n", sock.write_io.string
end

def test_rcptto
sock = FakeSocket.new
smtp = Net::SMTP.new 'localhost', 25
Expand All @@ -74,6 +217,27 @@ def test_rcptto
assert_equal "RCPT TO:<foo@example.com>\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@example.com> 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:<foo@example.com>", res

res = smtp.addr_req("MAIL FROM", "foo@example.com", [:FOO, "BAR"])
assert_equal "MAIL FROM:<foo@example.com> FOO BAR", res

res = smtp.addr_req("MAIL FROM", "foo@example.com", {FOO: nil, BAR: "1"})
assert_equal "MAIL FROM:<foo@example.com> FOO BAR=1", res
end

def test_auth_plain
sock = FakeSocket.new
smtp = Net::SMTP.new 'localhost', 25
Expand Down