Class: Mime
- Inherits:
-
Object
- Object
- Mime
- Defined in:
- lib/mime.rb
Overview
Introduction
A basic mime class for really basic and probably non-standard parsing and construction of MIME messages.
Intended for two main purposes in this project:
-
As the container that is used to build up the message for eventual serialization as an eml.
-
For assistance in parsing the
transport_message_headers
provided in .msg files, which are then kept through to the final eml.
TODO
-
Better streaming support, rather than an all-in-string approach.
-
Add
OrderedHash
optionally, to not lose ordering in headers. -
A fair bit remains to be done for this class, its fairly immature. But generally I’d like to see it be more generally useful.
-
All sorts of correctness issues, encoding particular.
-
Duplication of work in net/http.rb’s
HTTPHeader
? Don’t know if the overlap is sufficient. I don’t want to lower case things, just for starters. -
Mime was the original place I wrote #to_tree, intended as a quick debug hack.
Constant Summary collapse
- Hash =
begin require 'orderedhash' OrderedHash rescue LoadError Hash end
Instance Attribute Summary collapse
-
#body ⇒ Object
readonly
Returns the value of attribute body.
-
#content_type ⇒ Object
readonly
Returns the value of attribute content_type.
-
#epilogue ⇒ Object
readonly
Returns the value of attribute epilogue.
-
#headers ⇒ Object
readonly
Returns the value of attribute headers.
-
#parts ⇒ Object
readonly
Returns the value of attribute parts.
-
#preamble ⇒ Object
readonly
Returns the value of attribute preamble.
Class Method Summary collapse
-
.make_boundary(i, extra_obj = Mime) ⇒ Object
i
is some value that should be unique for all multipart boundaries for a given message. - .split_header(header) ⇒ Object
Instance Method Summary collapse
-
#initialize(str, ignore_body = false) ⇒ Mime
constructor
Create a Mime object using
str
as an initial serialization, which must contain headers and a body (even if empty). - #inspect ⇒ Object
- #multipart? ⇒ Boolean
- #to_s(opts = {}) ⇒ Object
- #to_tree ⇒ Object
Constructor Details
#initialize(str, ignore_body = false) ⇒ Mime
Create a Mime object using str
as an initial serialization, which must contain headers and a body (even if empty). Needs work.
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 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
# File 'lib/mime.rb', line 36 def initialize str, ignore_body=false headers, @body = $~[1..-1] if str[/(.*?\r?\n)(?:\r?\n(.*))?\Z/m] @headers = Hash.new { |hash, key| hash[key] = [] } @body ||= '' headers.to_s.scan(/^\S+:\s*.*(?:\n\t.*)*/).each do |header| @headers[header[/(\S+):/, 1]] << header[/\S+:\s*(.*)/m, 1].gsub(/\s+/m, ' ').strip # this is kind of wrong end # don't have to have content type i suppose @content_type, attrs = nil, {} if content_type = @headers['Content-Type'][0] @content_type, attrs = Mime.split_header content_type end return if ignore_body if multipart? if body.empty? @preamble = '' @epilogue = '' @parts = [] else # we need to split the message at the boundary boundary = attrs['boundary'] or raise "no boundary for multipart message" # splitting the body: parts = body.split(/--#{Regexp.quote boundary}/m) unless parts[-1] =~ /^--/; warn "bad multipart boundary (missing trailing --)" else parts[-1][0..1] = '' end parts.each_with_index do |part, i| part =~ /^(\r?\n)?(.*?)(\r?\n)?\Z/m part.replace $2 warn "bad multipart boundary" if (1...parts.length-1) === i and !($1 && $3) end @preamble = parts.shift @epilogue = parts.pop @parts = parts.map { |part| Mime.new part } end end end |
Instance Attribute Details
#body ⇒ Object (readonly)
Returns the value of attribute body.
32 33 34 |
# File 'lib/mime.rb', line 32 def body @body end |
#content_type ⇒ Object (readonly)
Returns the value of attribute content_type.
32 33 34 |
# File 'lib/mime.rb', line 32 def content_type @content_type end |
#epilogue ⇒ Object (readonly)
Returns the value of attribute epilogue.
32 33 34 |
# File 'lib/mime.rb', line 32 def epilogue @epilogue end |
#headers ⇒ Object (readonly)
Returns the value of attribute headers.
32 33 34 |
# File 'lib/mime.rb', line 32 def headers @headers end |
#parts ⇒ Object (readonly)
Returns the value of attribute parts.
32 33 34 |
# File 'lib/mime.rb', line 32 def parts @parts end |
#preamble ⇒ Object (readonly)
Returns the value of attribute preamble.
32 33 34 |
# File 'lib/mime.rb', line 32 def preamble @preamble end |
Class Method Details
.make_boundary(i, extra_obj = Mime) ⇒ Object
i
is some value that should be unique for all multipart boundaries for a given message
139 140 141 |
# File 'lib/mime.rb', line 139 def self.make_boundary i, extra_obj = Mime "----_=_NextPart_#{'%03d' % i}_#{'%08x' % extra_obj.object_id}.#{'%08x' % Time.now}" end |
.split_header(header) ⇒ Object
121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 |
# File 'lib/mime.rb', line 121 def self.split_header header # FIXME: haven't read standard. not sure what its supposed to do with " in the name, or if other # escapes are allowed. can't test on windows as " isn't allowed anyway. can be fixed with more # accurate parser later. # maybe move to some sort of Header class. but not all headers should be of it i suppose. # at least add a join_header then, taking name and {}. for use in Mime#to_s (for boundary # rewrite), and Attachment#to_mime, among others... attrs = {} header.scan(/;\s*([^\s=]+)\s*=\s*("[^"]*"|[^\s;]*)\s*/m).each do |key, value| if attrs[key]; warn "ignoring duplicate header attribute #{key.inspect}" else attrs[key] = value[/^"/] ? value[1..-2] : value end end [header[/^[^;]+/].strip, attrs] end |
Instance Method Details
#inspect ⇒ Object
83 84 85 86 |
# File 'lib/mime.rb', line 83 def inspect # add some extra here. "#<Mime content_type=#{@content_type.inspect}>" end |
#multipart? ⇒ Boolean
79 80 81 |
# File 'lib/mime.rb', line 79 def multipart? @content_type && @content_type =~ /^multipart/ ? true : false end |
#to_s(opts = {}) ⇒ Object
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 |
# File 'lib/mime.rb', line 103 def to_s opts={} opts = {:boundary_counter => 0}.merge opts if multipart? boundary = Mime.make_boundary opts[:boundary_counter] += 1, self @body = [preamble, parts.map { |part| "\r\n" + part.to_s(opts) + "\r\n" }, "--\r\n" + epilogue]. flatten.join("\r\n--" + boundary) content_type, attrs = Mime.split_header @headers['Content-Type'][0] attrs['boundary'] = boundary @headers['Content-Type'] = [([content_type] + attrs.map { |key, val| %{#{key}="#{val}"} }).join('; ')] end str = '' @headers.each do |key, vals| vals.each { |val| str << "#{key}: #{val}\r\n" } end str << "\r\n" + @body end |
#to_tree ⇒ Object
88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/mime.rb', line 88 def to_tree if multipart? str = "- #{inspect}\n" parts.each_with_index do |part, i| last = i == parts.length - 1 part.to_tree.split(/\n/).each_with_index do |line, j| str << " #{last ? (j == 0 ? "\\" : ' ') : '|'}" + line + "\n" end end str else "- #{inspect}\n" end end |