Class: MIME::Entity

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

Direct Known Subclasses

Message

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(arg = nil) ⇒ Entity

Returns a new instance of Entity.



13
14
15
16
17
18
19
20
# File 'lib/mime/entity.rb', line 13

def initialize(arg=nil)
  if arg.is_a?(String)
    @raw = arg
    from_parsed(IETF::RFC2045.parse_rfc2045_from(@raw))
  elsif arg.is_a?(Hash)
    @headers = arg
  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.



68
69
70
# File 'lib/mime/entity.rb', line 68

def content
  @content
end

#encodingObject

Returns the value of attribute encoding.



101
102
103
# File 'lib/mime/entity.rb', line 101

def encoding
  @encoding
end

#multipart_typeObject (readonly)

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



68
69
70
# File 'lib/mime/entity.rb', line 68

def multipart_type
  @multipart_type
end

Instance Method Details

#attachment?Boolean Also known as: file?

Returns:

  • (Boolean)


91
92
93
# File 'lib/mime/entity.rb', line 91

def attachment?
  headers['content-disposition'] =~ /^attachment(?=;|$)/ || headers['content-disposition'] =~ /^form-data;.* filename=[\"\']?[^\"\']+[\"\']?/
end

#decoded_contentObject

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



154
155
156
157
158
159
160
161
162
163
164
# File 'lib/mime/entity.rb', line 154

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]
  else
    @content
  end
end

#find_part(options) ⇒ Object



105
106
107
# File 'lib/mime/entity.rb', line 105

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

#find_parts(options) ⇒ Object



108
109
110
111
112
113
114
115
116
117
118
119
120
# File 'lib/mime/entity.rb', line 108

def find_parts(options)
  parts = []
    # 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
    # Do any of my children match your search?
    content.each do |part|
      parts.concat part.find_parts(options)
    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)


33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/mime/entity.rb', line 33

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]
      end
    else
      raise "IETF PARSING FAIL!"
    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!"
    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

#headersObject

An Entity has Headers.



62
63
64
# File 'lib/mime/entity.rb', line 62

def headers
  @headers ||= {}
end

#inspectObject



22
23
24
# File 'lib/mime/entity.rb', line 22

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)


73
74
75
# File 'lib/mime/entity.rb', line 73

def multipart?
  !!(headers['content-type'] =~ /multipart\//)
end

#multipart_boundaryObject

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



82
83
84
85
86
87
88
89
90
# File 'lib/mime/entity.rb', line 82

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

#parsedObject



26
27
28
# File 'lib/mime/entity.rb', line 26

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

#part_filenameObject



95
96
97
98
99
100
# File 'lib/mime/entity.rb', line 95

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

#save_to_file(path = nil) ⇒ Object



122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
# File 'lib/mime/entity.rb', line 122

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

#to_sObject

Renders this data structure into a string, encoded



144
145
146
147
148
149
150
151
# File 'lib/mime/entity.rb', line 144

def to_s
  multipart_boundary # initialize the boundary if necessary
  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