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: mailto.rb 34360 2012-01-23 08:12:52Z naruse $ 6# 7# See URI for general documentation 8# 9 10require 'uri/generic' 11 12module URI 13 14 # 15 # RFC2368, The mailto URL scheme 16 # 17 class MailTo < Generic 18 include REGEXP 19 20 # A Default port of nil for URI::MailTo 21 DEFAULT_PORT = nil 22 23 # An Array of the available components for URI::MailTo 24 COMPONENT = [ :scheme, :to, :headers ].freeze 25 26 # :stopdoc: 27 # "hname" and "hvalue" are encodings of an RFC 822 header name and 28 # value, respectively. As with "to", all URL reserved characters must 29 # be encoded. 30 # 31 # "#mailbox" is as specified in RFC 822 [RFC822]. This means that it 32 # consists of zero or more comma-separated mail addresses, possibly 33 # including "phrase" and "comment" components. Note that all URL 34 # reserved characters in "to" must be encoded: in particular, 35 # parentheses, commas, and the percent sign ("%"), which commonly occur 36 # in the "mailbox" syntax. 37 # 38 # Within mailto URLs, the characters "?", "=", "&" are reserved. 39 40 # hname = *urlc 41 # hvalue = *urlc 42 # header = hname "=" hvalue 43 HEADER_PATTERN = "(?:[^?=&]*=[^?=&]*)".freeze 44 HEADER_REGEXP = Regexp.new(HEADER_PATTERN).freeze 45 # headers = "?" header *( "&" header ) 46 # to = #mailbox 47 # mailtoURL = "mailto:" [ to ] [ headers ] 48 MAILBOX_PATTERN = "(?:#{PATTERN::ESCAPED}|[^(),%?=&])".freeze 49 MAILTO_REGEXP = Regexp.new(" # :nodoc: 50 \\A 51 (#{MAILBOX_PATTERN}*?) (?# 1: to) 52 (?: 53 \\? 54 (#{HEADER_PATTERN}(?:\\&#{HEADER_PATTERN})*) (?# 2: headers) 55 )? 56 (?: 57 \\# 58 (#{PATTERN::FRAGMENT}) (?# 3: fragment) 59 )? 60 \\z 61 ", Regexp::EXTENDED).freeze 62 # :startdoc: 63 64 # 65 # == Description 66 # 67 # Creates a new URI::MailTo object from components, with syntax checking. 68 # 69 # Components can be provided as an Array or Hash. If an Array is used, 70 # the components must be supplied as [to, headers]. 71 # 72 # If a Hash is used, the keys are the component names preceded by colons. 73 # 74 # The headers can be supplied as a pre-encoded string, such as 75 # "subject=subscribe&cc=address", or as an Array of Arrays like 76 # [['subject', 'subscribe'], ['cc', 'address']] 77 # 78 # Examples: 79 # 80 # require 'uri' 81 # 82 # m1 = URI::MailTo.build(['joe@example.com', 'subject=Ruby']) 83 # puts m1.to_s -> mailto:joe@example.com?subject=Ruby 84 # 85 # m2 = URI::MailTo.build(['john@example.com', [['Subject', 'Ruby'], ['Cc', 'jack@example.com']]]) 86 # puts m2.to_s -> mailto:john@example.com?Subject=Ruby&Cc=jack@example.com 87 # 88 # m3 = URI::MailTo.build({:to => 'listman@example.com', :headers => [['subject', 'subscribe']]}) 89 # puts m3.to_s -> mailto:listman@example.com?subject=subscribe 90 # 91 def self.build(args) 92 tmp = Util::make_components_hash(self, args) 93 94 if tmp[:to] 95 tmp[:opaque] = tmp[:to] 96 else 97 tmp[:opaque] = '' 98 end 99 100 if tmp[:headers] 101 tmp[:opaque] << '?' 102 103 if tmp[:headers].kind_of?(Array) 104 tmp[:opaque] << tmp[:headers].collect { |x| 105 if x.kind_of?(Array) 106 x[0] + '=' + x[1..-1].join 107 else 108 x.to_s 109 end 110 }.join('&') 111 112 elsif tmp[:headers].kind_of?(Hash) 113 tmp[:opaque] << tmp[:headers].collect { |h,v| 114 h + '=' + v 115 }.join('&') 116 117 else 118 tmp[:opaque] << tmp[:headers].to_s 119 end 120 end 121 122 return super(tmp) 123 end 124 125 # 126 # == Description 127 # 128 # Creates a new URI::MailTo object from generic URL components with 129 # no syntax checking. 130 # 131 # This method is usually called from URI::parse, which checks 132 # the validity of each component. 133 # 134 def initialize(*arg) 135 super(*arg) 136 137 @to = nil 138 @headers = [] 139 140 if MAILTO_REGEXP =~ @opaque 141 if arg[-1] 142 self.to = $1 143 self.headers = $2 144 else 145 set_to($1) 146 set_headers($2) 147 end 148 149 else 150 raise InvalidComponentError, 151 "unrecognised opaque part for mailtoURL: #{@opaque}" 152 end 153 end 154 155 # The primary e-mail address of the URL, as a String 156 attr_reader :to 157 158 # E-mail headers set by the URL, as an Array of Arrays 159 attr_reader :headers 160 161 # check the to +v+ component against either 162 # * URI::Parser Regexp for :OPAQUE 163 # * MAILBOX_PATTERN 164 def check_to(v) 165 return true unless v 166 return true if v.size == 0 167 168 if parser.regexp[:OPAQUE] !~ v || /\A#{MAILBOX_PATTERN}*\z/o !~ v 169 raise InvalidComponentError, 170 "bad component(expected opaque component): #{v}" 171 end 172 173 return true 174 end 175 private :check_to 176 177 # private setter for to +v+ 178 def set_to(v) 179 @to = v 180 end 181 protected :set_to 182 183 # setter for to +v+ 184 def to=(v) 185 check_to(v) 186 set_to(v) 187 v 188 end 189 190 # check the headers +v+ component against either 191 # * URI::Parser Regexp for :OPAQUE 192 # * HEADER_PATTERN 193 def check_headers(v) 194 return true unless v 195 return true if v.size == 0 196 197 if parser.regexp[:OPAQUE] !~ v || 198 /\A(#{HEADER_PATTERN}(?:\&#{HEADER_PATTERN})*)\z/o !~ v 199 raise InvalidComponentError, 200 "bad component(expected opaque component): #{v}" 201 end 202 203 return true 204 end 205 private :check_headers 206 207 # private setter for headers +v+ 208 def set_headers(v) 209 @headers = [] 210 if v 211 v.scan(HEADER_REGEXP) do |x| 212 @headers << x.split(/=/o, 2) 213 end 214 end 215 end 216 protected :set_headers 217 218 # setter for headers +v+ 219 def headers=(v) 220 check_headers(v) 221 set_headers(v) 222 v 223 end 224 225 # Constructs String from URI 226 def to_s 227 @scheme + ':' + 228 if @to 229 @to 230 else 231 '' 232 end + 233 if @headers.size > 0 234 '?' + @headers.collect{|x| x.join('=')}.join('&') 235 else 236 '' 237 end + 238 if @fragment 239 '#' + @fragment 240 else 241 '' 242 end 243 end 244 245 # Returns the RFC822 e-mail text equivalent of the URL, as a String. 246 # 247 # Example: 248 # 249 # require 'uri' 250 # 251 # uri = URI.parse("mailto:ruby-list@ruby-lang.org?Subject=subscribe&cc=myaddr") 252 # uri.to_mailtext 253 # # => "To: ruby-list@ruby-lang.org\nSubject: subscribe\nCc: myaddr\n\n\n" 254 # 255 def to_mailtext 256 to = parser.unescape(@to) 257 head = '' 258 body = '' 259 @headers.each do |x| 260 case x[0] 261 when 'body' 262 body = parser.unescape(x[1]) 263 when 'to' 264 to << ', ' + parser.unescape(x[1]) 265 else 266 head << parser.unescape(x[0]).capitalize + ': ' + 267 parser.unescape(x[1]) + "\n" 268 end 269 end 270 271 return "To: #{to} 272#{head} 273#{body} 274" 275 end 276 alias to_rfc822text to_mailtext 277 end 278 279 @@schemes['MAILTO'] = MailTo 280end 281