Class: Mapi::Mime
- Inherits:
-
Object
- Object
- Mapi::Mime
- Defined in:
- lib/mapi/mime.rb
Instance Attribute Summary collapse
- #body ⇒ String readonly
- #content_type ⇒ String readonly
- #epilogue ⇒ String readonly
- #headers ⇒ Hash{String => Array} readonly
- #parts ⇒ Mime readonly
- #preamble ⇒ String readonly
Class Method Summary collapse
- .make_boundary(i, extra_obj = Mime) ⇒ String
- .split_header(header) ⇒ Array(String, Hash{String => String})
-
.to_encoded_word(str) ⇒ String?
Compose encoded-word (rfc2047) for non-ASCII text.
Instance Method Summary collapse
-
#initialize(str, ignore_body = false) ⇒ Mime
constructor
A new instance of Mime.
- #inspect ⇒ String
- #multipart? ⇒ Boolean
-
#to_s(opts = {}) ⇒ String
Compose rfc822 eml file.
- #to_tree ⇒ String
Constructor Details
#initialize(str, ignore_body = false) ⇒ Mime
Returns a new instance of Mime.
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 78 79 80 81 82 83 84 85 86 87 |
# File 'lib/mapi/mime.rb', line 46 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 ⇒ String (readonly)
31 32 33 |
# File 'lib/mapi/mime.rb', line 31 def body @body end |
#content_type ⇒ String (readonly)
35 36 37 |
# File 'lib/mapi/mime.rb', line 35 def content_type @content_type end |
#epilogue ⇒ String (readonly)
39 40 41 |
# File 'lib/mapi/mime.rb', line 39 def epilogue @epilogue end |
#headers ⇒ Hash{String => Array} (readonly)
29 30 31 |
# File 'lib/mapi/mime.rb', line 29 def headers @headers end |
#preamble ⇒ String (readonly)
37 38 39 |
# File 'lib/mapi/mime.rb', line 37 def preamble @preamble end |
Class Method Details
.make_boundary(i, extra_obj = Mime) ⇒ String
199 200 201 |
# File 'lib/mapi/mime.rb', line 199 def self.make_boundary i, extra_obj = Mime "----_=_NextPart_#{'%03d' % i}_#{'%08x' % extra_obj.object_id}.#{'%08x' % Time.now}" end |
.split_header(header) ⇒ Array(String, Hash{String => String})
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/mapi/mime.rb', line 179 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 |
.to_encoded_word(str) ⇒ String?
Compose encoded-word (rfc2047) for non-ASCII text
166 167 168 169 170 171 172 173 174 175 |
# File 'lib/mapi/mime.rb', line 166 def self.to_encoded_word str # We can assume that str can produce valid byte array in regardless of encoding. # Check if non-printable characters (including CR/LF) are inside. if str && str.bytes.any? {|byte| byte <= 31 || 127 <= byte} sprintf("=?%s?B?%s?=", str.encoding.name, Base64.strict_encode64(str)) else str end end |
Instance Method Details
#inspect ⇒ String
95 96 97 98 |
# File 'lib/mapi/mime.rb', line 95 def inspect # add some extra here. "#<Mime content_type=#{@content_type.inspect}>" end |
#multipart? ⇒ Boolean
90 91 92 |
# File 'lib/mapi/mime.rb', line 90 def multipart? @content_type && @content_type =~ /^multipart/ ? true : false end |
#to_s(opts = {}) ⇒ String
Compose rfc822 eml file
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 |
# File 'lib/mapi/mime.rb', line 120 def to_s opts={} opts = {:boundary_counter => 0}.merge opts body_encoder = proc { |body| body.bytes.pack("C*") } 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('; ')] else raw_content_type = @headers['Content-Type'][0] if raw_content_type content_type, attrs = Mime.split_header raw_content_type case content_type.split("/").first() when "text" attrs["charset"] = @body.encoding.name @headers['Content-Type'] = [([content_type] + attrs.map { |key, val| %{#{key}="#{val}"} }).join('; ')] end end end # ensure non ASCII chars are well encoded (like rfc2047) by upper source value_encoder = Proc.new { |val| val.encode("ASCII") } str = '' @headers.each do |key, vals| case vals when String val = vals str << "#{key}: #{value_encoder.call(val)}\r\n" when Array vals.each { |val| str << "#{key}: #{value_encoder.call(val)}\r\n" } end end str << "\r\n" str << body_encoder.call(@body) end |
#to_tree ⇒ String
101 102 103 104 105 106 107 108 109 110 111 112 113 114 |
# File 'lib/mapi/mime.rb', line 101 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 |