summaryrefslogtreecommitdiff
path: root/lib/uri/mailto.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/uri/mailto.rb')
-rw-r--r--lib/uri/mailto.rb347
1 files changed, 188 insertions, 159 deletions
diff --git a/lib/uri/mailto.rb b/lib/uri/mailto.rb
index f01673fd63..cb8024f301 100644
--- a/lib/uri/mailto.rb
+++ b/lib/uri/mailto.rb
@@ -1,35 +1,29 @@
+# frozen_string_literal: false
+# = uri/mailto.rb
#
-# $Id$
+# Author:: Akira Yamada <akira@ruby-lang.org>
+# License:: You can redistribute it and/or modify it under the same term as Ruby.
#
-# Copyright (c) 2001 akira yamada <akira@ruby-lang.org>
-# You can redistribute it and/or modify it under the same term as Ruby.
+# See URI for general documentation
#
-require 'uri/generic'
+require_relative 'generic'
module URI
-=begin
-
-== URI::MailTo
-
-=== Super Class
-
-((<URI::Generic>))
-
-=end
-
- # RFC2368, The mailto URL scheme
+ #
+ # RFC6068, the mailto URL scheme.
+ #
class MailTo < Generic
- include REGEXP
+ include RFC2396_REGEXP
+ # A Default port of nil for URI::MailTo.
DEFAULT_PORT = nil
- COMPONENT = [
- :scheme,
- :to, :headers
- ].freeze
+ # An Array of the available components for URI::MailTo.
+ COMPONENT = [ :scheme, :to, :headers ].freeze
+ # :stopdoc:
# "hname" and "hvalue" are encodings of an RFC 822 header name and
# value, respectively. As with "to", all URL reserved characters must
# be encoded.
@@ -43,222 +37,257 @@ module URI
#
# Within mailto URLs, the characters "?", "=", "&" are reserved.
- # hname = *urlc
- # hvalue = *urlc
- # header = hname "=" hvalue
- HEADER_PATTERN = "(?:[^?=&]*=[^?=&]*)".freeze
- HEADER_REGEXP = Regexp.new(HEADER_PATTERN, 'N').freeze
- # headers = "?" header *( "&" header )
- # to = #mailbox
- # mailtoURL = "mailto:" [ to ] [ headers ]
- MAILBOX_PATTERN = "(?:#{PATTERN::ESCAPED}|[^(),%?=&])".freeze
- MAILTO_REGEXP = Regexp.new("
- \\A
- (#{MAILBOX_PATTERN}*?) (?# 1: to)
- (?:
- \\?
- (#{HEADER_PATTERN}(?:\\&#{HEADER_PATTERN})*) (?# 2: headers)
- )?
- \\z
- ", Regexp::EXTENDED, 'N').freeze
-
-=begin
-
-=== Class Methods
-
---- URI::MailTo::build
- Create a new URI::MailTo object from components of URI::MailTo
- with check. It is to and headers. It provided by an Array of a
- Hash. You can provide headers as an String like
- "subject=subscribe&cc=addr" or an Array like [["subject",
- "subscribe"], ["cc", "addr"]]
-
---- URI::MailTo::new
- Create a new URI::MailTo object from ``generic'' components with
- no check. Because, this method is usually called from URI::parse
- and the method checks validity of each components.
-
-=end
+ # ; RFC 6068
+ # hfields = "?" hfield *( "&" hfield )
+ # hfield = hfname "=" hfvalue
+ # hfname = *qchar
+ # hfvalue = *qchar
+ # qchar = unreserved / pct-encoded / some-delims
+ # some-delims = "!" / "$" / "'" / "(" / ")" / "*"
+ # / "+" / "," / ";" / ":" / "@"
+ #
+ # ; RFC3986
+ # unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
+ # pct-encoded = "%" HEXDIG HEXDIG
+ HEADER_REGEXP = /\A(?<hfield>(?:%\h\h|[!$'-.0-;@-Z_a-z~])*=(?:%\h\h|[!$'-.0-;@-Z_a-z~])*)(?:&\g<hfield>)*\z/
+ # practical regexp for email address
+ # https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
+ EMAIL_REGEXP = /\A[a-zA-Z0-9.!\#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*\z/
+ # :startdoc:
+ #
+ # == Description
+ #
+ # Creates a new URI::MailTo object from components, with syntax checking.
+ #
+ # Components can be provided as an Array or Hash. If an Array is used,
+ # the components must be supplied as <code>[to, headers]</code>.
+ #
+ # If a Hash is used, the keys are the component names preceded by colons.
+ #
+ # The headers can be supplied as a pre-encoded string, such as
+ # <code>"subject=subscribe&cc=address"</code>, or as an Array of Arrays
+ # like <code>[['subject', 'subscribe'], ['cc', 'address']]</code>.
+ #
+ # Examples:
+ #
+ # require 'uri'
+ #
+ # m1 = URI::MailTo.build(['joe@example.com', 'subject=Ruby'])
+ # m1.to_s # => "mailto:joe@example.com?subject=Ruby"
+ #
+ # m2 = URI::MailTo.build(['john@example.com', [['Subject', 'Ruby'], ['Cc', 'jack@example.com']]])
+ # m2.to_s # => "mailto:john@example.com?Subject=Ruby&Cc=jack@example.com"
+ #
+ # m3 = URI::MailTo.build({:to => 'listman@example.com', :headers => [['subject', 'subscribe']]})
+ # m3.to_s # => "mailto:listman@example.com?subject=subscribe"
+ #
def self.build(args)
- tmp = Util::make_components_hash(self, args)
+ tmp = Util.make_components_hash(self, args)
- if tmp[:to]
- tmp[:opaque] = tmp[:to]
+ case tmp[:to]
+ when Array
+ tmp[:opaque] = tmp[:to].join(',')
+ when String
+ tmp[:opaque] = tmp[:to].dup
else
- tmp[:opaque] = ''
+ tmp[:opaque] = ''
end
if tmp[:headers]
- tmp[:opaque] << '?'
-
- if tmp[:headers].kind_of?(Array)
- tmp[:opaque] << tmp[:headers].collect { |x|
- if x.kind_of?(Array)
- x[0] + '=' + x[1..-1].to_s
- else
- x.to_s
- end
- }.join('&')
-
- elsif tmp[:headers].kind_of?(Hash)
- tmp[:opaque] << tmp[:headers].collect { |h,v|
- h + '=' + v
- }.join('&')
-
- else
- tmp[:opaque] << tmp[:headers].to_s
- end
+ query =
+ case tmp[:headers]
+ when Array
+ tmp[:headers].collect { |x|
+ if x.kind_of?(Array)
+ x[0] + '=' + x[1..-1].join
+ else
+ x.to_s
+ end
+ }.join('&')
+ when Hash
+ tmp[:headers].collect { |h,v|
+ h + '=' + v
+ }.join('&')
+ else
+ tmp[:headers].to_s
+ end
+ unless query.empty?
+ tmp[:opaque] << '?' << query
+ end
end
- return super(tmp)
+ super(tmp)
end
+ #
+ # == Description
+ #
+ # Creates a new URI::MailTo object from generic URL components with
+ # no syntax checking.
+ #
+ # This method is usually called from URI::parse, which checks
+ # the validity of each component.
+ #
def initialize(*arg)
super(*arg)
@to = nil
@headers = []
- if MAILTO_REGEXP =~ @opaque
- if arg[-1]
- self.to = $1
- self.headers = $2
- else
- set_to($1)
- set_headers($2)
- end
+ # The RFC3986 parser does not normally populate opaque
+ @opaque = "?#{@query}" if @query && !@opaque
+
+ unless @opaque
+ raise InvalidComponentError,
+ "missing opaque part for mailto URL"
+ end
+ to, header = @opaque.split('?', 2)
+ # allow semicolon as a addr-spec separator
+ # http://support.microsoft.com/kb/820868
+ unless /\A(?:[^@,;]+@[^@,;]+(?:\z|[,;]))*\z/ =~ to
+ raise InvalidComponentError,
+ "unrecognised opaque part for mailtoURL: #{@opaque}"
+ end
+ if arg[10] # arg_check
+ self.to = to
+ self.headers = header
else
- raise InvalidComponentError,
- "unrecognised opaque part for mailtoURL: #{@opaque}"
+ set_to(to)
+ set_headers(header)
end
end
- attr_reader :to
- attr_reader :headers
-
-=begin
-
-=== Instance Methods
-
---- URI::MailTo#to
-
---- URI::MailTo#to=(v)
-=end
+ # The primary e-mail address of the URL, as a String.
+ attr_reader :to
- #
- # methods for to
- #
+ # E-mail headers set by the URL, as an Array of Arrays.
+ attr_reader :headers
+ # Checks the to +v+ component.
def check_to(v)
return true unless v
return true if v.size == 0
- if OPAQUE !~ v || /\A#{MAILBOX_PATTERN}*\z/o !~ v
- raise InvalidComponentError,
- "bad component(expected opaque component): #{v}"
+ v.split(/[,;]/).each do |addr|
+ # check url safety as path-rootless
+ if /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*\z/ !~ addr
+ raise InvalidComponentError,
+ "an address in 'to' is invalid as URI #{addr.dump}"
+ end
+
+ # check addr-spec
+ # don't s/\+/ /g
+ addr.gsub!(/%\h\h/, URI::TBLDECWWWCOMP_)
+ if EMAIL_REGEXP !~ addr
+ raise InvalidComponentError,
+ "an address in 'to' is invalid as uri-escaped addr-spec #{addr.dump}"
+ end
end
- return true
+ true
end
private :check_to
+ # Private setter for to +v+.
def set_to(v)
@to = v
end
protected :set_to
+ # Setter for to +v+.
def to=(v)
check_to(v)
set_to(v)
v
end
-=begin
-
---- URI::MailTo#headers
-
---- URI::MailTo#headers=(v)
-
-=end
-
- #
- # methods for headers
- #
-
+ # Checks the headers +v+ component against either
+ # * HEADER_REGEXP
def check_headers(v)
return true unless v
return true if v.size == 0
-
- if OPAQUE !~ v ||
- /\A(#{HEADER_PATTERN}(?:\&#{HEADER_PATTERN})*)\z/o !~ v
- raise InvalidComponentError,
- "bad component(expected opaque component): #{v}"
+ if HEADER_REGEXP !~ v
+ raise InvalidComponentError,
+ "bad component(expected opaque component): #{v}"
end
- return true
+ true
end
private :check_headers
+ # Private setter for headers +v+.
def set_headers(v)
@headers = []
if v
- v.scan(HEADER_REGEXP) do |x|
- @headers << x.split(/=/o, 2)
- end
+ v.split('&').each do |x|
+ @headers << x.split(/=/, 2)
+ end
end
end
protected :set_headers
+ # Setter for headers +v+.
def headers=(v)
check_headers(v)
set_headers(v)
v
end
+ # Constructs String from URI.
def to_s
- @scheme + ':' +
- if @to
- @to
- else
- ''
- end +
- if @headers.size > 0
- '?' + @headers.collect{|x| x.join('=')}.join('&')
- else
- ''
- end
+ @scheme + ':' +
+ if @to
+ @to
+ else
+ ''
+ end +
+ if @headers.size > 0
+ '?' + @headers.collect{|x| x.join('=')}.join('&')
+ else
+ ''
+ end +
+ if @fragment
+ '#' + @fragment
+ else
+ ''
+ end
end
-=begin
-
---- URI::MailTo#to_mailtext
-
-=end
+ # Returns the RFC822 e-mail text equivalent of the URL, as a String.
+ #
+ # Example:
+ #
+ # require 'uri'
+ #
+ # uri = URI.parse("mailto:ruby-list@ruby-lang.org?Subject=subscribe&cc=myaddr")
+ # uri.to_mailtext
+ # # => "To: ruby-list@ruby-lang.org\nSubject: subscribe\nCc: myaddr\n\n\n"
+ #
def to_mailtext
- to = URI::unescape(@to)
+ to = URI.decode_www_form_component(@to)
head = ''
body = ''
@headers.each do |x|
- case x[0]
- when 'body'
- body = URI::unescape(x[1])
- when 'to'
- to << ', ' + URI::unescape(x[1])
- else
- head << URI::unescape(x[0]).capitalize + ': ' +
- URI::unescape(x[1]) + "\n"
- end
+ case x[0]
+ when 'body'
+ body = URI.decode_www_form_component(x[1])
+ when 'to'
+ to << ', ' + URI.decode_www_form_component(x[1])
+ else
+ head << URI.decode_www_form_component(x[0]).capitalize + ': ' +
+ URI.decode_www_form_component(x[1]) + "\n"
+ end
end
- return "To: #{to}
+ "To: #{to}
#{head}
#{body}
"
end
alias to_rfc822text to_mailtext
- end # MailTo
+ end
- @@schemes['MAILTO'] = MailTo
-end # URI
+ register_scheme 'MAILTO', MailTo
+end