module Sendmail
many ways to designate mail addresses.
-
array: you can put multiple addresses (string) into array.
1b array of array:
[["hoge@example.com","Hoge Taro"]]
-
simple string: “mailaddress@example.com” works just fine.
-
encoded string:
Sendmail.address
(“hoge@example.com”, “Hoge Taro”) -
hash: { “Hoge Taro” => “hoge@example.com” }
Attributes
force_dkim[RW]
lastmail[R]
mock[RW]
override_server[RW]
Public Class Methods
_send(text, envelope_from, to, host = 'localhost')
click to toggle source
# File lib/egalite/sendmail.rb, line 218 def _send(text, envelope_from, to, host = 'localhost') if @mock @lastmail = [text, envelope_from, to, host] else Net::SMTP.start(host) { |smtp| smtp.send_message(text, envelope_from, to) } end end
address(addrspec, name = nil, header='Reply-to')
click to toggle source
# File lib/egalite/sendmail.rb, line 129 def address(addrspec, name = nil, header='Reply-to') # mailbox in RFC5322 section 3.4. not 'address' as in RFC. raise 'invalid mail address.' unless parse_addrspec(addrspec) if name and name.size > 0 QualifiedMailbox.new(folding(header, "#{encode_phrase(header, name)} <#{addrspec}>")) else addrspec end end
atext()
click to toggle source
# File lib/egalite/sendmail.rb, line 105 def atext; '[0-9a-zA-Z!#$%&\'*+\-/=?\^_`{|}~]'; end
atext_loose()
click to toggle source
# File lib/egalite/sendmail.rb, line 106 def atext_loose; '[0-9a-zA-Z!#$%&\'*+\-/=?\^_`{|}~.]'; end
check_domain(s)
click to toggle source
# File lib/egalite/sendmail.rb, line 108 def check_domain(s) s =~ /\A#{atext}+?(\.#{atext}+?)+\Z/ end
check_local_loose(s)
click to toggle source
# File lib/egalite/sendmail.rb, line 111 def check_local_loose(s) s =~ /\A#{atext_loose}+\Z/ end
encode_phrase(header, s)
click to toggle source
# File lib/egalite/sendmail.rb, line 91 def encode_phrase(header, s) if multibyte?(s) multibyte_folding(header, s) else folding(header, quote_string(s)) end end
encode_unstructured(header, s)
click to toggle source
# File lib/egalite/sendmail.rb, line 98 def encode_unstructured(header, s) if multibyte?(s) multibyte_folding(header, s) else folding(header, vchar(wsp(s))) end end
folding(h, s)
click to toggle source
# File lib/egalite/sendmail.rb, line 40 def folding(h, s) # folding white space. see RFC5322, section 2.3.3 and 3.2.2. len = 78 - h.size - ": ".size len2nd = 78 - " ".size lines = [] line = "" s.strip.split(/\s+/).each { |c| # each word (according to gmail's behavior) if (line+c).size > len len = len2nd lines << line.strip line = c + " " else line << c + " " end } lines << line.strip if line.size > 0 lines.join("\n ") end
mailboxlist(value, header = 'Reply-to')
click to toggle source
# File lib/egalite/sendmail.rb, line 138 def mailboxlist(value, header = 'Reply-to') case value when QualifiedMailbox value when String parse_addrspec(value) ? value : nil when Hash folding(header, value.map { |name, address| address(address,name) }.join(', ')) when Array folding(header, value.map { |v| v.is_a?(Array) ? address(v[0],v[1]) : mailboxlist(v,header) }.join(', ')) else nil end end
message(body, params)
click to toggle source
# File lib/egalite/sendmail.rb, line 156 def message(body, params) headers = {} raise "From must be exist." unless params[:from] raise "The number of sender must be zero or one." if params[:sender].is_a?(Array) and params[:sender].size > 1 raise "When the number of 'from' is more than one, sender must be exist" if params[:from].is_a?(Array) and params[:from].size > 1 and not params[:sender] %w[From To Sender Reply-To Cc].each { |s| v = params[s.gsub(/-/,'_').downcase.to_sym] headers[s] = mailboxlist(v,s) if v and v.size >= 1 } headers["Subject"] = encode_unstructured("Subject",params[:subject].to_s) if params[:subject] headers["MIME-Version"] = "1.0" date = params[:date] || Time.now headers["Date"] = date.is_a?(Time) ? date.rfc822 : date headers["Content-Type"] = params[:content_type] headers["Content-Type"] ||= "text/plain; charset=UTF-8" if params[:content_type] =~ /multipart/ body = body elsif multibyte?(body) headers["Content-Transfer-Encoding"] = "base64" body = [body].pack('m') else headers["Content-Transfer-Encoding"] = "7bit" end params[:headers].each_pair { |k,v| headers[k] = v unless headers.key?(k)} if params.key?(:headers) text = [headers.map{|k,v| "#{k}: #{v}"}.join("\n"),body].join("\n\n") end
mime_combine(parts, type = "mixed")
click to toggle source
create MIME multipart
# File lib/egalite/sendmail.rb, line 300 def mime_combine(parts, type = "mixed") boundary = OpenSSL::Random.random_bytes(10).unpack('h*')[0] content_type = "multipart/#{type}; boundary=\"#{boundary}\"" body = "" parts.each { |part| body << "--" + boundary body << "\n" body << part } body << "--" + boundary body << "--\n" [body, content_type] end
mime_header(header,s)
click to toggle source
# File lib/egalite/sendmail.rb, line 314 def mime_header(header,s) if multibyte?(s) header + '"' + multibyte_folding(header, s) + '"' else header + folding(header, quote_string(s)) end end
mime_part(body, content_type = nil, filename = nil)
click to toggle source
# File lib/egalite/sendmail.rb, line 321 def mime_part(body, content_type = nil, filename = nil) content_type = "text/plain; charset=UTF-8" unless content_type part = "" if filename part << "Content-Type: #{content_type};\n" part << mime_header(" name=", filename) part << "\n" part << "Content-Disposition: attachment;\n" part << mime_header(" filename=", filename) part << "\n" part << "Content-Transfer-Encoding: base64\n" else part << "Content-Type: #{content_type}\n" part << "Content-Transfer-Encoding: base64\n" end part << "\n" part << [body].pack('m') end
multibyte?(s)
click to toggle source
# File lib/egalite/sendmail.rb, line 88 def multibyte?(s) s.each_byte.any? { |c| c > 0x7f } end
multibyte_folding(h, s, encoding = 'UTF-8')
click to toggle source
# File lib/egalite/sendmail.rb, line 57 def multibyte_folding(h, s, encoding = 'UTF-8') # RFC2047 bracketsize = "=?#{encoding}?B??=".size len = 76 - h.size - ": ".size - bracketsize len2nd = 76 - bracketsize lines = [] line = "" s = s.gsub(/\s+/, ' ').strip s.split(//).each { |c| # each character (including multi-byte ones) teststr = line+c teststr = NKF.nkf('-Wj',teststr) if encoding =~ /iso-2022-jp/i if [teststr].pack('m').chomp.size > len len = len2nd lines << line line = c else line << c end } lines << line if line.size > 0 lines = lines.map { |s| "=?#{encoding}?B?#{[s].pack('m').gsub(/\n/,'')}?=" } lines.join("\n ") end
parse_addrspec(addrspec)
click to toggle source
# File lib/egalite/sendmail.rb, line 114 def parse_addrspec(addrspec) # no support for CFWS, FWS, and domain-literal. if addrspec[0,1] == '"' # quoted-string addrspec =~ /\A(\".*?[^\\]\")\@(.+)\Z/ (local, domain) = [$1, $2] return nil if local =~ /[\x00-\x1f\x7f]/ return nil unless check_domain(domain) [local, domain] else (local, domain) = addrspec.split(/@/,2) return nil unless check_local_loose(local) return nil unless check_domain(domain) [local, domain] end end
quote_string(s)
click to toggle source
# File lib/egalite/sendmail.rb, line 85 def quote_string(s) '"' + vchar(wsp(s)).gsub(/\\/,"\\\\\\").gsub(/\"/,'\\\"') + '"' end
read_private_key(pem_filename)
click to toggle source
# File lib/egalite/sendmail.rb, line 227 def read_private_key(pem_filename) OpenSSL::PKey::RSA.new(open(pem_filename).read) end
send(body, params, host = 'localhost')
click to toggle source
# File lib/egalite/sendmail.rb, line 245 def send(body, params, host = 'localhost') send_inner_2(body, params, host, @force_dkim, {}) end
send_inner_2(body, params, host, dkim, dkim_params)
click to toggle source
# File lib/egalite/sendmail.rb, line 230 def send_inner_2(body, params, host, dkim, dkim_params) text = message(body, params) if dkim text = Dkim.sign(text,dkim_params) end envelope_from = _extract_addrspec(params[:envelope_from] || params[:sender] || params[:from]) envelope_from = envelope_from[0] if envelope_from.is_a?(Array) host = @override_server if host == 'localhost' and @override_server _send( text, envelope_from, to_addresses(params), host ) end
send_multipart(parts, params, host = 'localhost')
click to toggle source
# File lib/egalite/sendmail.rb, line 251 def send_multipart(parts, params, host = 'localhost') (body, content_type) = mime_combine(parts) params[:content_type] = content_type send_inner_2(body, params, host, @force_dkim, {}) end
send_with_dkim(body, params, host = 'localhost', dkim_params = {})
click to toggle source
# File lib/egalite/sendmail.rb, line 248 def send_with_dkim(body, params, host = 'localhost', dkim_params = {}) send_inner_2(body, params, host, true, dkim_params) end
send_with_template(filename, params, host = 'localhost')
click to toggle source
# File lib/egalite/sendmail.rb, line 271 def send_with_template(filename, params, host = 'localhost') File.open("mail/"+ filename ,"r") { |f| text = f.read tengine = Egalite::HTMLTemplate.new tengine.default_escape = false text = tengine.handleTemplate(text,params) send(text, params, host) } end
send_with_uploaded_files(body, files, params, host = 'localhost')
click to toggle source
# File lib/egalite/sendmail.rb, line 256 def send_with_uploaded_files(body, files, params, host = 'localhost') # files should be Rack uploaded files. sum_size = 0 files = [files].flatten parts = [mime_part(body)] parts += files.map { |f| File.open(f[:tempfile].path, "r") {|tf| binary = tf.read sum_size += binary.size raise AttachmentsTooLarge if sum_size >= 25 * 1000 * 1000 mime_part(binary, f[:type], f[:filename]) } } send_multipart(parts, params, host) end
to_addresses(params)
click to toggle source
# File lib/egalite/sendmail.rb, line 212 def to_addresses(params) addresses = [:to, :cc, :bcc].map { |s| _extract_addrspec(params[s]) } addresses.flatten.compact.uniq end
vchar(s)
click to toggle source
# File lib/egalite/sendmail.rb, line 79 def vchar(s) s.gsub(/[\x00-\x1f\x7f]/,'') end
verify_address(email)
click to toggle source
check validity of email address with DNS lookup.
# File lib/egalite/sendmail.rb, line 284 def verify_address(email) (local,domain) = parse_addrspec(email) return false unless domain mx = Resolv::DNS.new.getresource(domain, Resolv::DNS::Resource::IN::MX) rescue nil return true if mx a = Resolv::DNS.new.getresource(domain, Resolv::DNS::Resource::IN::A) rescue nil return true if a aaaa = Resolv::DNS.new.getresource(domain, Resolv::DNS::Resource::IN::AAAA) rescue nil return true if aaaa false end
wsp(s)
click to toggle source
# File lib/egalite/sendmail.rb, line 82 def wsp(s) s.gsub(/\s+/,' ') end
Private Class Methods
_extract_addrspec(value)
click to toggle source
# File lib/egalite/sendmail.rb, line 189 def _extract_addrspec(value) case value when QualifiedMailbox value =~ /<(#{atext_loose}+?@#{atext_loose}+?)>\Z/ $1 when String parse_addrspec(value) ? value : nil when Hash value.values.map { |s| parse_addrspec(s) ? s : nil } when Array value.map { |v| if v.is_a?(Array) parse_addrspec(v[0]) ? v[0] : nil else _extract_addrspec(v) end } else nil end end