mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
79f86a67d0
only if the URI has path-rootless, not path-empty. [ruby-core:76055] [Bug #12498] patched by Chris Heisterkamp <cheister@squareup.com> git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@55563 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
294 lines
7.8 KiB
Ruby
294 lines
7.8 KiB
Ruby
# frozen_string_literal: false
|
|
# = uri/mailto.rb
|
|
#
|
|
# Author:: Akira Yamada <akira@ruby-lang.org>
|
|
# License:: You can redistribute it and/or modify it under the same term as Ruby.
|
|
# Revision:: $Id$
|
|
#
|
|
# See URI for general documentation
|
|
#
|
|
|
|
require 'uri/generic'
|
|
|
|
module URI
|
|
|
|
#
|
|
# RFC6068, The mailto URL scheme
|
|
#
|
|
class MailTo < Generic
|
|
include REGEXP
|
|
|
|
# A Default port of nil for URI::MailTo
|
|
DEFAULT_PORT = nil
|
|
|
|
# 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.
|
|
#
|
|
# "#mailbox" is as specified in RFC 822 [RFC822]. This means that it
|
|
# consists of zero or more comma-separated mail addresses, possibly
|
|
# including "phrase" and "comment" components. Note that all URL
|
|
# reserved characters in "to" must be encoded: in particular,
|
|
# parentheses, commas, and the percent sign ("%"), which commonly occur
|
|
# in the "mailbox" syntax.
|
|
#
|
|
# Within mailto URLs, the characters "?", "=", "&" are reserved.
|
|
|
|
# ; 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
|
|
# http://www.whatwg.org/specs/web-apps/current-work/multipage/states-of-the-type-attribute.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 [to, headers].
|
|
#
|
|
# 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
|
|
# "subject=subscribe&cc=address", or as an Array of Arrays like
|
|
# [['subject', 'subscribe'], ['cc', 'address']]
|
|
#
|
|
# Examples:
|
|
#
|
|
# require 'uri'
|
|
#
|
|
# m1 = URI::MailTo.build(['joe@example.com', 'subject=Ruby'])
|
|
# puts m1.to_s -> mailto:joe@example.com?subject=Ruby
|
|
#
|
|
# m2 = URI::MailTo.build(['john@example.com', [['Subject', 'Ruby'], ['Cc', 'jack@example.com']]])
|
|
# puts m2.to_s -> mailto:john@example.com?Subject=Ruby&Cc=jack@example.com
|
|
#
|
|
# m3 = URI::MailTo.build({:to => 'listman@example.com', :headers => [['subject', 'subscribe']]})
|
|
# puts m3.to_s -> mailto:listman@example.com?subject=subscribe
|
|
#
|
|
def self.build(args)
|
|
tmp = Util::make_components_hash(self, args)
|
|
|
|
case tmp[:to]
|
|
when Array
|
|
tmp[:opaque] = tmp[:to].join(',')
|
|
when String
|
|
tmp[:opaque] = tmp[:to].dup
|
|
else
|
|
tmp[:opaque] = ''
|
|
end
|
|
|
|
if tmp[:headers]
|
|
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)
|
|
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 = []
|
|
|
|
# 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
|
|
set_to(to)
|
|
set_headers(header)
|
|
end
|
|
end
|
|
|
|
# The primary e-mail address of the URL, as a String
|
|
attr_reader :to
|
|
|
|
# E-mail headers set by the URL, as an Array of Arrays
|
|
attr_reader :headers
|
|
|
|
# check the to +v+ component
|
|
def check_to(v)
|
|
return true unless v
|
|
return true if v.size == 0
|
|
|
|
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
|
|
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
|
|
|
|
# check the headers +v+ component against either
|
|
# * HEADER_REGEXP
|
|
def check_headers(v)
|
|
return true unless v
|
|
return true if v.size == 0
|
|
if HEADER_REGEXP !~ v
|
|
raise InvalidComponentError,
|
|
"bad component(expected opaque component): #{v}"
|
|
end
|
|
|
|
return true
|
|
end
|
|
private :check_headers
|
|
|
|
# private setter for headers +v+
|
|
def set_headers(v)
|
|
@headers = []
|
|
if v
|
|
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 +
|
|
if @fragment
|
|
'#' + @fragment
|
|
else
|
|
''
|
|
end
|
|
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 = parser.unescape(@to)
|
|
head = ''
|
|
body = ''
|
|
@headers.each do |x|
|
|
case x[0]
|
|
when 'body'
|
|
body = parser.unescape(x[1])
|
|
when 'to'
|
|
to << ', ' + parser.unescape(x[1])
|
|
else
|
|
head << parser.unescape(x[0]).capitalize + ': ' +
|
|
parser.unescape(x[1]) + "\n"
|
|
end
|
|
end
|
|
|
|
return "To: #{to}
|
|
#{head}
|
|
#{body}
|
|
"
|
|
end
|
|
alias to_rfc822text to_mailtext
|
|
end
|
|
|
|
@@schemes['MAILTO'] = MailTo
|
|
end
|