winmail.dat 附件在 Rails 应用中使用 ActionMailer 损坏
winmail.dat attachment gets corrupted using ActionMailer in Rails app
我在 Rails 应用程序的 Ruby 中使用 ActionMailer 来阅读电子邮件(ruby 1.9.3,rails 3.2.13)。
我有一封附有 winmail.dat 文件 (ms-tnef) 的电子邮件,我正在使用 tnef gem 提取其内容。
问题是当我从邮件中读取附件时,附件已损坏,tnef 无法从中提取文件。
$ tnef winmail.dat
ERROR: invalid checksum, input file may be corrupted
使用任何邮件应用程序提取 winmail.dat 附件,提取的 winmail.dat 与 tnef 一起工作正常,我得到了它的内容。
比较这两个文件我注意到:
- 原始文件更大(76k 对 72k)
- 它们在换行符上有所不同:原始文件具有 windows 格式 (0D 0A),rails 保存的文件具有 linux 格式 (0A)
我写了这个测试:
it 'should extract winmail.dat from email and extract its contents' do
file_path = "#{::Rails.root}/spec/files/winmail-dat-001.eml"
message = Mail::Message.new(File.read(file_path))
anexo = message.attachments[0]
files = []
Tnef.unpack(anexo) do |file|
files << File.basename(file)
end
puts files.inspect
files.size.should == 2
end
失败并显示以下消息:
WARNING: invalid checksum, input file may be corrupted
Invalid RTF CRC, input file may be corrupted
WARNING: invalid checksum, input file may be corrupted
Assertion failed: ((attr->lvl_type == LVL_MESSAGE) || (attr->lvl_type == LVL_ATTACHMENT)), function attr_read, file attr.c, line 240.
Errno::EPIPE: Broken pipe
anexo = message.attachments[0]
=> #<Mail::Part:2159872060, Multipart: false, Headers: <Content-Type: application/ms-tnef; name="winmail.dat">, <Content-Transfer-Encoding: quoted-printable>, <Content-Disposition: attachment; filename="winmail.dat">>
我试着把它存到磁盘上,再读一遍,结果还是一样
it 'should extract winmail.dat from email and extract its contents' do
file_path = "#{::Rails.root}/spec/files/winmail-dat-001.eml"
message = Mail::Message.new(File.read(file_path))
anexo = message.attachments[0]
tmpfile_name = "#{::Rails.root}/tmp/#{anexo.filename}"
File.open(tmpfile_name, 'w+b', 0644) { |f| f.write anexo.body.decoded }
anexo = File.open(tmpfile_name)
files = []
Tnef.unpack(anexo) do |file|
files << File.basename(file)
end
puts files.inspect
files.size.should == 2
end
我该如何阅读附件?
方法anexo.body.decoded调用最适合编码(Mail::Encodings
)的decode方法附件,在你的例子中 quoted_printable。
其中一些编码(7bit、8bit 和 quoted_printable) , 执行转换,将不同类型的换行符更改为平台特定的换行符。
*quoted_printable" 调用 .to_lf 破坏了 winmail.dat 文件
# Decode the string from Quoted-Printable. Cope with hard line breaks
# that were incorrectly encoded as hex instead of literal CRLF.
def self.decode(str)
str.gsub(/(?:=0D=0A|=0D|=0A)\r\n/, "\r\n").unpack("M*").first.to_lf
end
mail/core_extensions/string.rb:
def to_lf
to_str.gsub(/\n|\r\n|\r/) { "\n" }
end
为了解决这个问题,您执行了没有最后一个 .to_lf 的相同编码。
为此,您可以创建一个不会损坏您的文件的新编码,并使用它来对您的附件进行编码。
创建文件:
lib/encodings/tnef_encoding.rb
require 'mail/encodings/7bit'
module Mail
module Encodings
# Encoding to handle Microsoft TNEF format
# It's pretty similar to quoted_printable, except for the 'to_lf' (decode) and 'to_crlf' (encode)
class TnefEncoding < SevenBit
NAME='tnef'
PRIORITY = 2
def self.can_encode?(str)
EightBit.can_encode? str
end
def self.decode(str)
# **difference here** removed '.to_lf'
str.gsub(/(?:=0D=0A|=0D|=0A)\r\n/, "\r\n").unpack("M*").first
end
def self.encode(str)
# **difference here** removed '.to_crlf'
[str.to_lf].pack("M")
end
def self.cost(str)
# These bytes probably do not need encoding
c = str.count("\x9\xA\xD\x20-\x3C\x3E-\x7E")
# Everything else turns into =XX where XX is a
# two digit hex number (taking 3 bytes)
total = (str.bytesize - c)*3 + c
total.to_f/str.bytesize
end
private
Encodings.register(NAME, self)
end
end
end
要使用您的自定义编码,您必须首先注册它:
Mail::Encodings.register('tnef', Mail::Encodings::TnefEncoding)
然后,将其设置为附件的首选编码:
anexo.body.encoding('tnef')
那么您的测试将变成:
it 'should extract winmail.dat from email and extract its contents' do
file_path = "#{::Rails.root}/spec/files/winmail-dat-001.eml"
message = Mail::Message.new(File.read(file_path))
anexo = message.attachments[0]
tmpfile_name = "#{::Rails.root}/tmp/#{anexo.filename}"
Mail::Encodings.register('tnef', Mail::Encodings::TnefEncoding)
anexo.body.encoding('tnef')
File.open(tmpfile_name, 'w+b', 0644) { |f| f.write anexo.body.decoded }
anexo = File.open(tmpfile_name)
files = []
Tnef.unpack(anexo) do |file|
files << File.basename(file)
end
puts files.inspect
files.size.should == 2
end
希望对您有所帮助!
我在 Rails 应用程序的 Ruby 中使用 ActionMailer 来阅读电子邮件(ruby 1.9.3,rails 3.2.13)。 我有一封附有 winmail.dat 文件 (ms-tnef) 的电子邮件,我正在使用 tnef gem 提取其内容。
问题是当我从邮件中读取附件时,附件已损坏,tnef 无法从中提取文件。
$ tnef winmail.dat
ERROR: invalid checksum, input file may be corrupted
使用任何邮件应用程序提取 winmail.dat 附件,提取的 winmail.dat 与 tnef 一起工作正常,我得到了它的内容。
比较这两个文件我注意到: - 原始文件更大(76k 对 72k) - 它们在换行符上有所不同:原始文件具有 windows 格式 (0D 0A),rails 保存的文件具有 linux 格式 (0A)
我写了这个测试:
it 'should extract winmail.dat from email and extract its contents' do
file_path = "#{::Rails.root}/spec/files/winmail-dat-001.eml"
message = Mail::Message.new(File.read(file_path))
anexo = message.attachments[0]
files = []
Tnef.unpack(anexo) do |file|
files << File.basename(file)
end
puts files.inspect
files.size.should == 2
end
失败并显示以下消息:
WARNING: invalid checksum, input file may be corrupted
Invalid RTF CRC, input file may be corrupted
WARNING: invalid checksum, input file may be corrupted
Assertion failed: ((attr->lvl_type == LVL_MESSAGE) || (attr->lvl_type == LVL_ATTACHMENT)), function attr_read, file attr.c, line 240.
Errno::EPIPE: Broken pipe
anexo = message.attachments[0]
=> #<Mail::Part:2159872060, Multipart: false, Headers: <Content-Type: application/ms-tnef; name="winmail.dat">, <Content-Transfer-Encoding: quoted-printable>, <Content-Disposition: attachment; filename="winmail.dat">>
我试着把它存到磁盘上,再读一遍,结果还是一样
it 'should extract winmail.dat from email and extract its contents' do
file_path = "#{::Rails.root}/spec/files/winmail-dat-001.eml"
message = Mail::Message.new(File.read(file_path))
anexo = message.attachments[0]
tmpfile_name = "#{::Rails.root}/tmp/#{anexo.filename}"
File.open(tmpfile_name, 'w+b', 0644) { |f| f.write anexo.body.decoded }
anexo = File.open(tmpfile_name)
files = []
Tnef.unpack(anexo) do |file|
files << File.basename(file)
end
puts files.inspect
files.size.should == 2
end
我该如何阅读附件?
方法anexo.body.decoded调用最适合编码(Mail::Encodings
)的decode方法附件,在你的例子中 quoted_printable。
其中一些编码(7bit、8bit 和 quoted_printable) , 执行转换,将不同类型的换行符更改为平台特定的换行符。 *quoted_printable" 调用 .to_lf 破坏了 winmail.dat 文件
# Decode the string from Quoted-Printable. Cope with hard line breaks
# that were incorrectly encoded as hex instead of literal CRLF.
def self.decode(str)
str.gsub(/(?:=0D=0A|=0D|=0A)\r\n/, "\r\n").unpack("M*").first.to_lf
end
mail/core_extensions/string.rb:
def to_lf
to_str.gsub(/\n|\r\n|\r/) { "\n" }
end
为了解决这个问题,您执行了没有最后一个 .to_lf 的相同编码。 为此,您可以创建一个不会损坏您的文件的新编码,并使用它来对您的附件进行编码。
创建文件: lib/encodings/tnef_encoding.rb
require 'mail/encodings/7bit'
module Mail
module Encodings
# Encoding to handle Microsoft TNEF format
# It's pretty similar to quoted_printable, except for the 'to_lf' (decode) and 'to_crlf' (encode)
class TnefEncoding < SevenBit
NAME='tnef'
PRIORITY = 2
def self.can_encode?(str)
EightBit.can_encode? str
end
def self.decode(str)
# **difference here** removed '.to_lf'
str.gsub(/(?:=0D=0A|=0D|=0A)\r\n/, "\r\n").unpack("M*").first
end
def self.encode(str)
# **difference here** removed '.to_crlf'
[str.to_lf].pack("M")
end
def self.cost(str)
# These bytes probably do not need encoding
c = str.count("\x9\xA\xD\x20-\x3C\x3E-\x7E")
# Everything else turns into =XX where XX is a
# two digit hex number (taking 3 bytes)
total = (str.bytesize - c)*3 + c
total.to_f/str.bytesize
end
private
Encodings.register(NAME, self)
end
end
end
要使用您的自定义编码,您必须首先注册它:
Mail::Encodings.register('tnef', Mail::Encodings::TnefEncoding)
然后,将其设置为附件的首选编码:
anexo.body.encoding('tnef')
那么您的测试将变成:
it 'should extract winmail.dat from email and extract its contents' do
file_path = "#{::Rails.root}/spec/files/winmail-dat-001.eml"
message = Mail::Message.new(File.read(file_path))
anexo = message.attachments[0]
tmpfile_name = "#{::Rails.root}/tmp/#{anexo.filename}"
Mail::Encodings.register('tnef', Mail::Encodings::TnefEncoding)
anexo.body.encoding('tnef')
File.open(tmpfile_name, 'w+b', 0644) { |f| f.write anexo.body.decoded }
anexo = File.open(tmpfile_name)
files = []
Tnef.unpack(anexo) do |file|
files << File.basename(file)
end
puts files.inspect
files.size.should == 2
end
希望对您有所帮助!