Class: Astrotrain::Message
- Inherits:
-
Object
- Object
- Astrotrain::Message
- Defined in:
- lib/astrotrain/message.rb
Overview
Wrapper around a TMail object
Constant Summary collapse
- EMAIL_REGEX =
/[\w\.\_\%\+\-]+[^\s\.]@[\w\-\_\.]+/
Class Attribute Summary collapse
-
.recipient_header_order ⇒ Object
Returns the value of attribute recipient_header_order.
-
.skipped_headers ⇒ Object
Returns the value of attribute skipped_headers.
Instance Attribute Summary collapse
-
#mail ⇒ Object
readonly
Reference to the internal Mail object that parsed the raw email.
-
#path ⇒ Object
readonly
Refebrence to the original file that this Mail came from.
Class Method Summary collapse
-
.parse(raw) ⇒ Object
Public: Parses the raw email headers into a Astrotrain::Message instance.
-
.read(path) ⇒ Object
Public: Parses the raw email headers into a Astrotrain::Message instance.
-
.unescape(s) ⇒ Object
Stolen from Rack/Camping, remove the “+” => “ ” translation.
Instance Method Summary collapse
-
#address_list_for(emails, retried = false) ⇒ Object
Uses Mail::AddressList to parse the given comma separated emails.
-
#attachments ⇒ Object
Public: Gets the attachments in the email.
-
#body ⇒ Object
Public: Gets the plain/text body of the email.
-
#cc ⇒ Object
Public: Unquotes and converts the Cc header to UTF-8.
-
#convert_to_utf8(s) ⇒ Object
Converts a given String to UTF-8.
-
#from ⇒ Object
(also: #sender)
Public: Unquotes and converts the From header to UTF-8.
-
#headers ⇒ Object
Public: Builds a hash of headers, skipping the keys specified in #skipped_headers.
-
#html ⇒ Object
Public: Gets the html body of the email.
-
#initialize(mail, path = nil) ⇒ Message
constructor
A new instance of Message.
-
#message_id ⇒ Object
Public: Gets the unique message-id for the email, with the surrounding ‘<` and `>` parsed out.
-
#process_message_body ⇒ Object
Parses the mail’s parts, assembling the plain/HTML Strings, as well as any attachments.
-
#recipients(order = nil) ⇒ Object
Public: Gets the recipients of an email using the To/Delivered-To/X-Original-To headers.
-
#recipients_from_body ⇒ Object
Parses out all email addresses from the body of the email.
-
#recipients_from_delivered_to ⇒ Object
Parses the ‘Delivered-To’ header for email address.
-
#recipients_from_original_to ⇒ Object
Parses the ‘X-Original-To’ header for email address.
-
#recipients_from_to ⇒ Object
Parses the ‘To’ header for email address.
-
#scan_parts(message) ⇒ Object
Recursive method to scan all the parts of the given part.
-
#subject ⇒ Object
Public: Unquotes and converts the Subject header to UTF-8.
-
#to ⇒ Object
Public: Unquotes and converts the To header to UTF-8.
-
#unquoted_address_header(key) ⇒ Object
Parses the given header for email addresses.
-
#unquoted_header(key) ⇒ Object
Parses the quoted header values: ‘=?…?=`.
Constructor Details
#initialize(mail, path = nil) ⇒ Message
Returns a new instance of Message.
48 49 50 51 52 53 |
# File 'lib/astrotrain/message.rb', line 48 def initialize(mail, path = nil) @body = @html = @attachments = nil @path = path @mail = mail @recipients = {} end |
Class Attribute Details
.recipient_header_order ⇒ Object
Returns the value of attribute recipient_header_order.
18 19 20 |
# File 'lib/astrotrain/message.rb', line 18 def recipient_header_order @recipient_header_order end |
.skipped_headers ⇒ Object
Returns the value of attribute skipped_headers.
18 19 20 |
# File 'lib/astrotrain/message.rb', line 18 def skipped_headers @skipped_headers end |
Instance Attribute Details
#mail ⇒ Object (readonly)
Reference to the internal Mail object that parsed the raw email.
12 13 14 |
# File 'lib/astrotrain/message.rb', line 12 def mail @mail end |
#path ⇒ Object (readonly)
Refebrence to the original file that this Mail came from.
15 16 17 |
# File 'lib/astrotrain/message.rb', line 15 def path @path end |
Class Method Details
.parse(raw) ⇒ Object
Public: Parses the raw email headers into a Astrotrain::Message instance.
raw - String of the email content
Returns Astrotrain::Message instance.
44 45 46 |
# File 'lib/astrotrain/message.rb', line 44 def self.parse(raw) new(::Mail.new(raw)) end |
.read(path) ⇒ Object
Public: Parses the raw email headers into a Astrotrain::Message instance.
path - String path to the file.
Returns Astrotrain::Message instance.
35 36 37 |
# File 'lib/astrotrain/message.rb', line 35 def self.read(path) new(::Mail.read(path), path) end |
.unescape(s) ⇒ Object
Stolen from Rack/Camping, remove the “+” => “ ” translation
248 249 250 251 252 253 254 255 |
# File 'lib/astrotrain/message.rb', line 248 def self.unescape(s) s.gsub!(/((?:%[0-9a-fA-F]{2})+)/n) do original = $1.dup replacement = [$1.delete('%')].pack('H*') replacement.as_utf8.valid? ? replacement : original end s end |
Instance Method Details
#address_list_for(emails, retried = false) ⇒ Object
Uses Mail::AddressList to parse the given comma separated emails.
emails - Array of String email headers.
Example: ["[email protected], Bar <[email protected]>", ...]
Returns Array of Mail::Address objects
233 234 235 236 237 238 239 240 241 242 243 244 245 |
# File 'lib/astrotrain/message.rb', line 233 def address_list_for(emails, retried = false) emails = emails * ", " list = Mail::AddressList.new(self.class.unescape(emails)) addrs = list.addresses.each { |a| a.decoded } addrs.uniq! addrs rescue Mail::Field::ParseError if retried raise else address_list_for(emails.scan(EMAIL_REGEX), true) end end |
#attachments ⇒ Object
Public: Gets the attachments in the email.
Returns Array of Astrotrain::Attachment objects.
135 136 137 138 |
# File 'lib/astrotrain/message.rb', line 135 def if !@attachments @attachments end |
#body ⇒ Object
Public: Gets the plain/text body of the email.
Returns String
119 120 121 122 |
# File 'lib/astrotrain/message.rb', line 119 def body if !@body @body end |
#cc ⇒ Object
Public: Unquotes and converts the Cc header to UTF-8.
Returns Array of Mail::Address objects
97 98 99 |
# File 'lib/astrotrain/message.rb', line 97 def cc @cc ||= unquoted_address_header(:cc) end |
#convert_to_utf8(s) ⇒ Object
Converts a given String to UTF-8. If the message has no charset assigned, we’ll attempt to detect it then convert it to UTF-8.
s - unconverted String in the wrong character set
Returns converted String.
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 |
# File 'lib/astrotrain/message.rb', line 310 def convert_to_utf8(s) # If this string is already valid UTF-8 just hand it back if s.as_utf8.valid? s.force_encoding("UTF-8") if s.respond_to?(:force_encoding) return s end # First lets try to detect the encoding if the message didn't specify if !@mail.charset && detection = CharlockHolmes::EncodingDetector.detect(s) @mail.charset = detection[:encoding] end # if the encoding was already set or we just detected it AND it's not already # set to UTF-8 - try to transcode the body into UTF-8 if @mail.charset && @mail.charset != 'UTF-8' s = CharlockHolmes::Converter.convert s, @mail.charset, 'UTF-8' end # By the time we get here, `s` is either UTF-8 or we need to force it to be # But, even if it's UTF-8 we could be in the case where the charset on the # message was set to UTF-8 but is in fact invalid. # So for either case, we want to make sure the output is valid UTF-8 - even # if it means mutating the invalid string. # Also we're not reusing the String::UTF8 version of `s` from above here # because by this point, it may be a new string. s.as_utf8.clean.as_raw end |
#from ⇒ Object Also known as: sender
Public: Unquotes and converts the From header to UTF-8.
Returns Array of Mail::Address objects
82 83 84 |
# File 'lib/astrotrain/message.rb', line 82 def from @from ||= unquoted_address_header(:from) end |
#headers ⇒ Object
Public: Builds a hash of headers, skipping the keys specified in #skipped_headers. If header values cannot be parsed, the original raw value is provided.
Returns Hash of the headers with String keys and values.
145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/astrotrain/message.rb', line 145 def headers @headers ||= begin @mail.header.fields.inject({}) do |memo, field| name = field.name.downcase.to_s next memo if self.class.skipped_headers.include?(name) header = unquoted_header(name) memo.update(name => self.class.unescape(header)) end end end |
#html ⇒ Object
Public: Gets the html body of the email.
Returns String
127 128 129 130 |
# File 'lib/astrotrain/message.rb', line 127 def html if !@html @html end |
#message_id ⇒ Object
Public: Gets the unique message-id for the email, with the surrounding ‘<` and `>` parsed out.
Returns String
112 113 114 |
# File 'lib/astrotrain/message.rb', line 112 def @mail. end |
#process_message_body ⇒ Object
Parses the mail’s parts, assembling the plain/HTML Strings, as well as any attachments.
Returns nothing.
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 |
# File 'lib/astrotrain/message.rb', line 261 def @attachments = [] if @mail.multipart? @body, @html = [], [] scan_parts(@mail) @body = @body.join("\n") @html = @html.join("\n") else if @mail.content_type =~ /text\/html/ @html = @mail.body.to_s @body = '' else @body = @mail.body.to_s @html = '' end end @body = convert_to_utf8(@body) @html = convert_to_utf8(@html) end |
#recipients(order = nil) ⇒ Object
Public: Gets the recipients of an email using the To/Delivered-To/X-Original-To headers. It’s not always straightforward which email we want when dealing with filters and forward rules.
order - Array of email header names that specifies the order that the
list of recipient emails is assembled. Valid strings are:
'original_to', 'delivered_to', and 'to'.
Returns Array of possible recipients.
64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/astrotrain/message.rb', line 64 def recipients(order = nil) if !@recipients.key?(order) order = self.class.recipient_header_order if order.blank? recipients = [] emails = order.inject([]) do |memo, key| memo.push *send("recipients_from_#{key}") end @recipients[order] = emails.map! { |em| em.address } @recipients[order].uniq! end @recipients[order] end |
#recipients_from_body ⇒ Object
Parses out all email addresses from the body of the email.
Returns Array of Mail::Address objects
183 184 185 186 187 188 189 190 |
# File 'lib/astrotrain/message.rb', line 183 def recipients_from_body @recipients_from_body ||= begin emails_from_body = body.scan(EMAIL_REGEX) address_list_for(emails_from_body, true) rescue Mail::Field::ParseError [] end end |
#recipients_from_delivered_to ⇒ Object
Parses the ‘Delivered-To’ header for email address.
Returns Array of Mail::Address objects
169 170 171 |
# File 'lib/astrotrain/message.rb', line 169 def recipients_from_delivered_to @recipients_from_delivered_to ||= unquoted_address_header('delivered-to') end |
#recipients_from_original_to ⇒ Object
Parses the ‘X-Original-To’ header for email address.
Returns Array of Mail::Address objects
176 177 178 |
# File 'lib/astrotrain/message.rb', line 176 def recipients_from_original_to @recipients_from_original_to ||= unquoted_address_header('x-original-to') end |
#recipients_from_to ⇒ Object
Parses the ‘To’ header for email address.
Returns Array of Mail::Address objects
162 163 164 |
# File 'lib/astrotrain/message.rb', line 162 def recipients_from_to to end |
#scan_parts(message) ⇒ Object
Recursive method to scan all the parts of the given part.
Returns nothing.
285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 |
# File 'lib/astrotrain/message.rb', line 285 def scan_parts() .parts.each do |part| if part.multipart? scan_parts(part) else case part.content_type when /text\/plain/ @body << part.body.to_s when /text\/html/ @html << part.body.to_s else att = Attachment.new(part) @attachments << att if att.attached? end end end end |
#subject ⇒ Object
Public: Unquotes and converts the Subject header to UTF-8.
Returns String
104 105 106 |
# File 'lib/astrotrain/message.rb', line 104 def subject @mail.subject end |
#to ⇒ Object
Public: Unquotes and converts the To header to UTF-8.
Returns Array of Mail::Address objects
90 91 92 |
# File 'lib/astrotrain/message.rb', line 90 def to @to ||= unquoted_address_header(:to) end |
#unquoted_address_header(key) ⇒ Object
Parses the given header for email addresses. Handles the case where some keys return arrays if there are multiple values.
key - String or Symbol header name
Returns Array of Mail::Address objects
214 215 216 217 218 219 220 221 222 223 224 225 |
# File 'lib/astrotrain/message.rb', line 214 def unquoted_address_header(key) if header = @mail[key] emails = if header.respond_to?(:value) [header.value] else header.map { |h| h.value } end address_list_for(emails) else [] end end |
#unquoted_header(key) ⇒ Object
Parses the quoted header values: ‘=?…?=`.
key - String or Symbol header name
Returns unquoted String.
197 198 199 200 201 202 203 204 205 206 |
# File 'lib/astrotrain/message.rb', line 197 def unquoted_header(key) if header = @mail[key] value = header.respond_to?(:map) ? header.map { |h| h.value }.join("\n") : header.value Mail::Encodings.value_decode(value) else '' end end |