Class: MIME::Entity

Inherits:
Object show all
Defined in:
lib/mime/entity_tmail.rb,
lib/mime/entity.rb

Direct Known Subclasses

Message

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(one = nil, two = nil) ⇒ Entity

Returns a new instance of Entity.



14
15
16
17
18
19
20
21
22
23
24
25
26
27
# File 'lib/mime/entity_tmail.rb', line 14

def initialize(one=nil,two=nil)
  if one.is_a?(Hash) || two.is_a?(Hash) # Intent is to generate a message from parameters
    @headers = one.is_a?(Hash) ? one : two
    set_content one if one.is_a?(String)
    @encoding = 'quoted-printable' unless encoding
  elsif one.is_a?(String) # Intent is to parse a message body
    @raw = one.gsub(/\r/,'').gsub(/\n/, "\r\n") # normalizes end-of-line characters
    @tmail = TMail::Mail.parse(@raw)
    from_tmail(@tmail)
  elsif one.is_a?(TMail::Mail)
    @tmail = one
    from_tmail(@tmail)
  end
end

Instance Attribute Details

#contentObject

An Entity has Content.

IF the Content-Type is a multipart type,
the content will be one or more Entities.


81
82
83
# File 'lib/mime/entity_tmail.rb', line 81

def content
  @content
end

#encodingObject



117
118
119
# File 'lib/mime/entity_tmail.rb', line 117

def encoding
  @encoding ||= headers['content-transfer-encoding'] || nil
end

#multipart_typeObject (readonly)

An Entity has Content.

IF the Content-Type is a multipart type,
the content will be one or more Entities.


81
82
83
# File 'lib/mime/entity_tmail.rb', line 81

def multipart_type
  @multipart_type
end

Instance Method Details

#attachment?Boolean Also known as: file?

Returns:

  • (Boolean)


106
107
108
# File 'lib/mime/entity_tmail.rb', line 106

def attachment?
  @tmail.disposition_is_attachment? || headers['content-disposition'] =~ /^form-data;.* filename=[\"\']?[^\"\']+[\"\']?/ if headers['content-disposition']
end

#decoded_contentObject

Converts this data structure into a string, but decoded if necessary



181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/mime/entity_tmail.rb', line 181

def decoded_content
  return nil if @content.is_a?(Array)
  case encoding.to_s.downcase
  when 'quoted-printable'
    @content.unpack('M')[0]
  when 'base64'
    @content.unpack('m')[0]
  # when '7bit'
  #   # should get this 7bit encoding done too...
  else
    @content
  end
end

#find_part(options) ⇒ Object



129
130
131
# File 'lib/mime/entity_tmail.rb', line 129

def find_part(options)
  find_parts(options,true).first
end

#find_parts(options) ⇒ Object



132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
# File 'lib/mime/entity_tmail.rb', line 132

def find_parts(options,find_only_one=false)
  parts = []
  return nil unless (options[:content_type] && headers['content-type']) || (options[:content_disposition] && headers['content-disposition'])
    # Do I match your search?
    iam = true
    iam = false if options[:content_type] && headers['content-type'] !~ /^#{options[:content_type]}(?=;|$)/
    iam = false if options[:content_disposition] && headers['content-disposition'] !~ /^#{options[:content_disposition]}(?=;|$)/
    parts << self if iam
    return parts unless parts.empty?
    # Do any of my children match your search?
    content.each do |part|
      parts.concat part.find_parts(options,find_only_one)
      return parts if !parts.empty? && find_only_one
    end if multipart?
  return parts
end

#from_parsed(parsed) ⇒ Object

This means we have a structure from IETF::RFC2045. Entity is: [headers, content], while content may be an array of Entities. Or, :boundary, :content

Raises:

  • (ArgumentError)


36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/mime/entity_tmail.rb', line 36

def from_parsed(parsed)
  case parsed
  when Array
    if parsed[0].is_a?(Hash) && (parsed[1].is_a?(Hash) || parsed[1].is_a?(String))
      @headers = parsed[0]
      @content = parsed[1].is_a?(Hash) ? parsed[1][:content].collect {|p| Entity.new.from_parsed(p)} : parsed[1]
      if parsed[1].is_a?(Hash)
        @multipart_type = parsed[1][:type]
        @multipart_boundary = parsed[1][:boundary]
        raise "IETF PARSING FAIL! (empty boundary)" if @multipart_boundary == ''
      end
    else
      raise "IETF PARSING FAIL! ('A' structure)"
    end
    return self
  when Hash
    if parsed.has_key?(:type) && parsed.has_key?(:boundary) && parsed.has_key?(:content)
      @content = parsed[:content].is_a?(Array) ? parsed[:content].collect {|p| Entity.new.from_parsed(p)} : parsed[:content]
    else
      raise "IETF PARSING FAIL! ('H' structure)"
    end
    return self
  end
  raise ArgumentError, "Must pass in either: [an array with two elements: headers(hash) and content(string or array)] OR [a hash containing :type, :boundary, and :content(being the former or a string)]"
end

#from_tmail(tmail) ⇒ Object

Raises:

  • (ArgumentError)


61
62
63
64
65
66
67
68
69
# File 'lib/mime/entity_tmail.rb', line 61

def from_tmail(tmail)
  raise ArgumentError, "Expecting a TMail::Mail object." unless tmail.is_a?(TMail::Mail)
  @headers ||= Hash.new {|h,k| @tmail.header[k].to_s }
  if multipart?
    @content = @tmail.parts.collect { |tpart| Entity.new.from_tmail(tpart) }
  else
    set_content @tmail.body # TMail has already decoded it, but we need it still encoded
  end
end

#headersObject

An Entity has Headers.



75
76
77
# File 'lib/mime/entity_tmail.rb', line 75

def headers
  @headers ||= {}
end

#inspectObject



29
30
31
# File 'lib/mime/entity_tmail.rb', line 29

def inspect
  "<#{self.class.name}##{object_id} Headers:{#{headers.collect {|k,v| "#{k}=#{v}"}.join(' ')}} content:#{multipart? ? 'multipart' : 'flat'}>"
end

#multipart?Boolean

Macro Methods #

Returns:

  • (Boolean)


86
87
88
89
# File 'lib/mime/entity_tmail.rb', line 86

def multipart?
  return @tmail.multipart? if @tmail
  !!(headers['content-type'] =~ /multipart\//) if headers['content-type']
end

#multipart_boundaryObject

Auto-generates a boundary if one doesn’t yet exist.



96
97
98
99
100
101
102
103
104
105
# File 'lib/mime/entity_tmail.rb', line 96

def multipart_boundary
  return nil unless multipart?
  unless @multipart_boundary ||= (@tmail && @tmail.header['content-type'] ? @tmail.header['content-type'].params['boundary'] : nil)
    # Content-Type: multipart/mixed; boundary=000e0cd28d1282f4ba04788017e5
    @multipart_boundary = String.random(25)
    headers['content-type'] = "multipart/#{multipart_type}; boundary=#{@multipart_boundary}"
    @multipart_boundary
  end
  @multipart_boundary
end

#parsedObject



28
29
30
# File 'lib/mime/entity.rb', line 28

def parsed
  IETF::RFC2045.parse_rfc2045_from(@raw)
end

#part_filenameObject



110
111
112
113
114
115
# File 'lib/mime/entity_tmail.rb', line 110

def part_filename
  # Content-Disposition: attachment; filename="summary.txt"
  if headers['content-disposition'] =~ /; filename=[\"\']?([^\"\']+)/
    $1
  end if headers['content-disposition']
end

#save_to_file(path = nil) ⇒ Object



149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/mime/entity_tmail.rb', line 149

def save_to_file(path=nil)
  filename = path if path && !File.exists?(path) # If path doesn't exist, assume it's a filename
  filename ||= path + '/' + part_filename if path && attachment? # If path does exist, and we're saving an attachment, use the attachment filename
  filename ||= (attachment? ? part_filename : path) # If there is no path and we're saving an attachment, use the attachment filename; otherwise use path (whether it is present or not)
  filename ||= '.' # No path supplied, and not saving an attachment. We'll just save it in the current directory.
  if File.directory?(filename)
    i = 0
    begin
      i += 1
      filename = filename + "/attachment-#{i}"
    end until !File.exists(filename)
  end
  # After all that trouble to get a filename to save to...
  File.open(filename, 'w') do |file|
    file << decoded_content
  end
end

#set_content(raw) ⇒ Object Also known as: content=

You can set new content, and it will be saved in encoded form.



196
197
198
199
200
201
202
203
204
205
206
# File 'lib/mime/entity_tmail.rb', line 196

def set_content(raw)
  @content = raw.is_a?(Array) ? raw :
    case encoding.to_s.downcase
    when 'quoted-printable'
      [raw].pack('M')
    when 'base64'
      [raw].pack('m')
    else
      raw
    end
end

#to_sObject

Renders this data structure into a string, encoded



171
172
173
174
175
176
177
178
# File 'lib/mime/entity_tmail.rb', line 171

def to_s
  multipart_boundary # initialize the boundary if necessary, so it will be included in the headers
  headers.inject('') {|a,(k,v)| a << "#{MIME.capitalize_header(k)}: #{v}\r\n"} + "\r\n" + if content.is_a?(Array)
    "\r\n--#{multipart_boundary}\r\n" + content.collect {|part| part.to_s }.join("\r\n--#{multipart_boundary}\r\n") + "\r\n--#{multipart_boundary}--\r\n"
  else
    content.to_s
  end
end