Class: Mapi::Mime

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

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(str, ignore_body = false) ⇒ Mime

Returns a new instance of Mime.

Parameters:

  • str (String)
  • ignore_body (Boolean) (defaults to: false)


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

#bodyString (readonly)

Returns:

  • (String)


31
32
33
# File 'lib/mapi/mime.rb', line 31

def body
  @body
end

#content_typeString (readonly)

Returns:

  • (String)


35
36
37
# File 'lib/mapi/mime.rb', line 35

def content_type
  @content_type
end

#epilogueString (readonly)

Returns:

  • (String)


39
40
41
# File 'lib/mapi/mime.rb', line 39

def epilogue
  @epilogue
end

#headersHash{String => Array} (readonly)

Returns:

  • (Hash{String => Array})


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

def headers
  @headers
end

#partsMime (readonly)

Returns:



33
34
35
# File 'lib/mapi/mime.rb', line 33

def parts
  @parts
end

#preambleString (readonly)

Returns:

  • (String)


37
38
39
# File 'lib/mapi/mime.rb', line 37

def preamble
  @preamble
end

Class Method Details

.make_boundary(i, extra_obj = Mime) ⇒ String

Returns:

  • (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})

Parameters:

  • header (String)

Returns:

  • (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

Parameters:

  • str (String, nil)

Returns:

  • (String, nil)


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

#inspectString

Returns:

  • (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

Returns:

  • (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

Parameters:

  • opts (Hash) (defaults to: {})

Returns:

  • (String)


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_treeString

Returns:

  • (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