PageRenderTime 55ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/uri/mailto.rb

https://gitlab.com/leduc1984/ruby
Ruby | 286 lines | 151 code | 31 blank | 104 comment | 15 complexity | 60ad94d09d5b85b6755b9ac6bc6d9f8d MD5 | raw file
  1. # = uri/mailto.rb
  2. #
  3. # Author:: Akira Yamada <akira@ruby-lang.org>
  4. # License:: You can redistribute it and/or modify it under the same term as Ruby.
  5. # Revision:: $Id$
  6. #
  7. # See URI for general documentation
  8. #
  9. require 'uri/generic'
  10. module URI
  11. #
  12. # RFC6068, The mailto URL scheme
  13. #
  14. class MailTo < Generic
  15. include REGEXP
  16. # A Default port of nil for URI::MailTo
  17. DEFAULT_PORT = nil
  18. # An Array of the available components for URI::MailTo
  19. COMPONENT = [ :scheme, :to, :headers ].freeze
  20. # :stopdoc:
  21. # "hname" and "hvalue" are encodings of an RFC 822 header name and
  22. # value, respectively. As with "to", all URL reserved characters must
  23. # be encoded.
  24. #
  25. # "#mailbox" is as specified in RFC 822 [RFC822]. This means that it
  26. # consists of zero or more comma-separated mail addresses, possibly
  27. # including "phrase" and "comment" components. Note that all URL
  28. # reserved characters in "to" must be encoded: in particular,
  29. # parentheses, commas, and the percent sign ("%"), which commonly occur
  30. # in the "mailbox" syntax.
  31. #
  32. # Within mailto URLs, the characters "?", "=", "&" are reserved.
  33. # ; RFC 6068
  34. # hfields = "?" hfield *( "&" hfield )
  35. # hfield = hfname "=" hfvalue
  36. # hfname = *qchar
  37. # hfvalue = *qchar
  38. # qchar = unreserved / pct-encoded / some-delims
  39. # some-delims = "!" / "$" / "'" / "(" / ")" / "*"
  40. # / "+" / "," / ";" / ":" / "@"
  41. #
  42. # ; RFC3986
  43. # unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
  44. # pct-encoded = "%" HEXDIG HEXDIG
  45. HEADER_REGEXP = /\A(?<hfield>(?:%\h\h|[!$'-.0-;@-Z_a-z~])*=(?:%\h\h|[!$'-.0-;@-Z_a-z~])*)(?:&\g<hfield>)*\z/
  46. # practical regexp for email address
  47. # http://www.whatwg.org/specs/web-apps/current-work/multipage/states-of-the-type-attribute.html#valid-e-mail-address
  48. 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/
  49. # :startdoc:
  50. #
  51. # == Description
  52. #
  53. # Creates a new URI::MailTo object from components, with syntax checking.
  54. #
  55. # Components can be provided as an Array or Hash. If an Array is used,
  56. # the components must be supplied as [to, headers].
  57. #
  58. # If a Hash is used, the keys are the component names preceded by colons.
  59. #
  60. # The headers can be supplied as a pre-encoded string, such as
  61. # "subject=subscribe&cc=address", or as an Array of Arrays like
  62. # [['subject', 'subscribe'], ['cc', 'address']]
  63. #
  64. # Examples:
  65. #
  66. # require 'uri'
  67. #
  68. # m1 = URI::MailTo.build(['joe@example.com', 'subject=Ruby'])
  69. # puts m1.to_s -> mailto:joe@example.com?subject=Ruby
  70. #
  71. # m2 = URI::MailTo.build(['john@example.com', [['Subject', 'Ruby'], ['Cc', 'jack@example.com']]])
  72. # puts m2.to_s -> mailto:john@example.com?Subject=Ruby&Cc=jack@example.com
  73. #
  74. # m3 = URI::MailTo.build({:to => 'listman@example.com', :headers => [['subject', 'subscribe']]})
  75. # puts m3.to_s -> mailto:listman@example.com?subject=subscribe
  76. #
  77. def self.build(args)
  78. tmp = Util::make_components_hash(self, args)
  79. case tmp[:to]
  80. when Array
  81. tmp[:opaque] = tmp[:to].join(',')
  82. when String
  83. tmp[:opaque] = tmp[:to].dup
  84. else
  85. tmp[:opaque] = ''
  86. end
  87. if tmp[:headers]
  88. query =
  89. case tmp[:headers]
  90. when Array
  91. tmp[:headers].collect { |x|
  92. if x.kind_of?(Array)
  93. x[0] + '=' + x[1..-1].join
  94. else
  95. x.to_s
  96. end
  97. }.join('&')
  98. when Hash
  99. tmp[:headers].collect { |h,v|
  100. h + '=' + v
  101. }.join('&')
  102. else
  103. tmp[:headers].to_s
  104. end
  105. unless query.empty?
  106. tmp[:opaque] << '?' << query
  107. end
  108. end
  109. return super(tmp)
  110. end
  111. #
  112. # == Description
  113. #
  114. # Creates a new URI::MailTo object from generic URL components with
  115. # no syntax checking.
  116. #
  117. # This method is usually called from URI::parse, which checks
  118. # the validity of each component.
  119. #
  120. def initialize(*arg)
  121. super(*arg)
  122. @to = nil
  123. @headers = []
  124. to, header = @opaque.split('?', 2)
  125. # allow semicolon as a addr-spec separator
  126. # http://support.microsoft.com/kb/820868
  127. unless /\A(?:[^@,;]+@[^@,;]+(?:\z|[,;]))*\z/ =~ to
  128. raise InvalidComponentError,
  129. "unrecognised opaque part for mailtoURL: #{@opaque}"
  130. end
  131. if arg[10] # arg_check
  132. self.to = to
  133. self.headers = header
  134. else
  135. set_to(to)
  136. set_headers(header)
  137. end
  138. end
  139. # The primary e-mail address of the URL, as a String
  140. attr_reader :to
  141. # E-mail headers set by the URL, as an Array of Arrays
  142. attr_reader :headers
  143. # check the to +v+ component
  144. def check_to(v)
  145. return true unless v
  146. return true if v.size == 0
  147. v.split(/[,;]/).each do |addr|
  148. # check url safety as path-rootless
  149. if /\A(?:%\h\h|[!$&-.0-;=@-Z_a-z~])*\z/ !~ addr
  150. raise InvalidComponentError,
  151. "an address in 'to' is invalid as URI #{addr.dump}"
  152. end
  153. # check addr-spec
  154. # don't s/\+/ /g
  155. addr.gsub!(/%\h\h/, URI::TBLDECWWWCOMP_)
  156. if EMAIL_REGEXP !~ addr
  157. raise InvalidComponentError,
  158. "an address in 'to' is invalid as uri-escaped addr-spec #{addr.dump}"
  159. end
  160. end
  161. return true
  162. end
  163. private :check_to
  164. # private setter for to +v+
  165. def set_to(v)
  166. @to = v
  167. end
  168. protected :set_to
  169. # setter for to +v+
  170. def to=(v)
  171. check_to(v)
  172. set_to(v)
  173. v
  174. end
  175. # check the headers +v+ component against either
  176. # * HEADER_REGEXP
  177. def check_headers(v)
  178. return true unless v
  179. return true if v.size == 0
  180. if HEADER_REGEXP !~ v
  181. raise InvalidComponentError,
  182. "bad component(expected opaque component): #{v}"
  183. end
  184. return true
  185. end
  186. private :check_headers
  187. # private setter for headers +v+
  188. def set_headers(v)
  189. @headers = []
  190. if v
  191. v.split('&').each do |x|
  192. @headers << x.split(/=/, 2)
  193. end
  194. end
  195. end
  196. protected :set_headers
  197. # setter for headers +v+
  198. def headers=(v)
  199. check_headers(v)
  200. set_headers(v)
  201. v
  202. end
  203. # Constructs String from URI
  204. def to_s
  205. @scheme + ':' +
  206. if @to
  207. @to
  208. else
  209. ''
  210. end +
  211. if @headers.size > 0
  212. '?' + @headers.collect{|x| x.join('=')}.join('&')
  213. else
  214. ''
  215. end +
  216. if @fragment
  217. '#' + @fragment
  218. else
  219. ''
  220. end
  221. end
  222. # Returns the RFC822 e-mail text equivalent of the URL, as a String.
  223. #
  224. # Example:
  225. #
  226. # require 'uri'
  227. #
  228. # uri = URI.parse("mailto:ruby-list@ruby-lang.org?Subject=subscribe&cc=myaddr")
  229. # uri.to_mailtext
  230. # # => "To: ruby-list@ruby-lang.org\nSubject: subscribe\nCc: myaddr\n\n\n"
  231. #
  232. def to_mailtext
  233. to = parser.unescape(@to)
  234. head = ''
  235. body = ''
  236. @headers.each do |x|
  237. case x[0]
  238. when 'body'
  239. body = parser.unescape(x[1])
  240. when 'to'
  241. to << ', ' + parser.unescape(x[1])
  242. else
  243. head << parser.unescape(x[0]).capitalize + ': ' +
  244. parser.unescape(x[1]) + "\n"
  245. end
  246. end
  247. return "To: #{to}
  248. #{head}
  249. #{body}
  250. "
  251. end
  252. alias to_rfc822text to_mailtext
  253. end
  254. @@schemes['MAILTO'] = MailTo
  255. end