Land #3680, add specs for Rex::MIME

bug/bundler_fix
HD Moore 2014-09-13 00:34:39 -05:00
commit 6bd3675f03
6 changed files with 691 additions and 4 deletions

View File

@ -25,8 +25,9 @@ class Header
next
end
var,val = line.split(':')
next if not val
var, val = line.split(':', 2)
next if val.nil?
self.headers << [ var.to_s.strip, val.to_s.strip ]
prev = self.headers.length - 1
end

View File

@ -24,9 +24,8 @@ class Message
self.header.parse(head)
ctype = self.header.find('Content-Type')
if ctype and ctype[1] and ctype[1] =~ /multipart\/mixed;\s*boundary=([^\s]+)/
if ctype and ctype[1] and ctype[1] =~ /multipart\/mixed;\s*boundary="?([A-Za-z0-9'\(\)\+\_,\-\.\/:=\?^\s]+)"?/
self.bound = $1
chunks = body.to_s.split(/--#{self.bound}(--)?\r?\n/)
self.content = chunks.shift.to_s.gsub(/\s+$/, '')
self.content << "\r\n" if not self.content.empty?

View File

@ -0,0 +1,32 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'rex/mime'
describe Rex::MIME::Encoding do
subject do
mod = Class.new
mod.extend described_class
mod
end
describe "#force_crlf" do
it "deletes \\r characters" do
expect(subject.force_crlf("Test\r1\r")).to_not include("\\r")
end
it "substitutes \\n characters by \\r\\n sequences" do
expect(subject.force_crlf("Test 2\n")).to end_with("\r\n")
end
it "preserves \r\n sequences" do
expect(subject.force_crlf("\r\nTest 3\r\n")).to eq("\r\nTest 3\r\n")
end
it "first deletes \\r characters, then substitutes \\n characters" do
expect(subject.force_crlf("\rTest 4\r\n\r\r\n")).to eq("Test 4\r\n\r\n")
end
end
end

View File

@ -0,0 +1,151 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'rex/mime'
describe Rex::MIME::Header do
let(:mime_headers_test) do
<<-EOS
Content-Type: text/plain;
Content-Disposition: attachment; filename="test.txt"
EOS
end
subject do
described_class.new
end
describe "#initialize" do
subject(:header_class) do
described_class.allocate
end
it "returns an Array" do
expect(header_class.send(:initialize)).to be_a(Array)
end
it "creates an empty headers array by default" do
expect(header_class.send(:initialize)).to be_empty
end
it "populates headers array with data from argument" do
header_class.send(:initialize, mime_headers_test)
expect(header_class.headers.length).to be(2)
end
end
describe "#add" do
it "returns the added entry" do
expect(subject.add('var', 'val')).to eq(['var', 'val'])
end
it "adds a new entry into the headers array" do
subject.add('var', 'val')
expect(subject.headers.length).to eq(1)
end
end
describe "#set" do
it "returns the set value" do
expect(subject.set('var', 'val')).to eq('val')
end
it "modifies the header entry if it exists" do
subject.add('var', 'val')
subject.set('var', 'val2')
expect(subject.headers.length).to eq(1)
expect(subject.headers[0]).to eq(['var', 'val2'])
end
it "creates the header entry if doesn't exist" do
subject.set('var2', 'val2')
expect(subject.headers.length).to eq(1)
expect(subject.headers[0]).to eq(['var2', 'val2'])
end
end
describe "#remove" do
it "doesn't remove any header if index doesn't exist" do
subject.add('var', 'val')
subject.remove(10000)
expect(subject.headers.length).to eq(1)
end
it "doesn't remove any header if var name doesn't exist" do
subject.add('var', 'val')
subject.remove('var2')
expect(subject.headers.length).to eq(1)
end
it "removes header entry if index exists" do
subject.add('var', 'val')
subject.remove(0)
expect(subject.headers.length).to eq(0)
end
it "removes any header entry with var name" do
subject.add('var', 'val')
subject.add('var2', 'val2')
subject.add('var', 'val3')
subject.remove('var')
expect(subject.headers.length).to eq(1)
end
end
describe "#find" do
it "returns nil if header index doesn't exist" do
expect(subject.find(1)).to be_nil
end
it "returns nil if header var name doesn't exist" do
expect(subject.find('var')).to be_nil
end
it "returns the header at index if exists" do
subject.add('var', 'val')
expect(subject.find(0)).to eq(['var', 'val'])
end
it "returns the first header with var name if exists" do
subject.add('var', 'val')
subject.add('var', 'val2')
subject.add('var', 'val3')
expect(subject.find('var')).to eq(['var', 'val'])
end
end
describe "#to_s" do
it "returns empty String if there aren't headers" do
expect(subject.to_s).to be_empty
end
it "returns string with headers separated by \\r\\n sequences" do
subject.add('var', 'val')
subject.add('var', 'val2')
subject.add('var3', 'val3')
expect(subject.to_s).to eq("var: val\r\nvar: val2\r\nvar3: val3\r\n")
end
end
describe "#parse" do
let(:complex_header) do
'Date: Wed,20 Aug 2014 08:45:38 -0500'
end
it "parses headers separated by lines" do
subject.parse(mime_headers_test)
expect(subject.headers.length).to eq(2)
end
it "parses headers names and values separated by :" do
subject.parse(mime_headers_test)
expect(subject.headers).to eq([['Content-Type', 'text/plain;'], ['Content-Disposition', 'attachment; filename="test.txt"']])
end
it "parses headers with ':' characters in the value" do
subject.parse(complex_header)
expect(subject.headers).to eq([['Date', 'Wed,20 Aug 2014 08:45:38 -0500']])
end
end
end

View File

@ -0,0 +1,412 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'rex/mime'
require 'rex/text'
describe Rex::MIME::Message do
subject do
described_class.new
end
describe "#initialize" do
subject(:message_class) do
described_class.allocate
end
let(:raw_message) do
message = "MIME-Version: 1.0\r\n"
message << "Content-Type: multipart/mixed; boundary=\"_Part_12_3195573780_381739540\"\r\n"
message << "Subject: Pull Request\r\n"
message << "Date: Wed,20 Aug 2014 08:45:38 -0500\r\n"
message << "Message-ID: <WRobqc7gEyQVIQwEkLS7FN3ZNhS1Xj9pU2szC24rggMg@tqUqGjjSLEvssbwm>\r\n"
message << "From: contributor@msfdev.int\r\n"
message << "To: msfdev@msfdev.int\r\n"
message << "\r\n"
message << "--_Part_12_3195573780_381739540\r\n"
message << "Content-Disposition: inline; filename=\"content\"\r\n"
message << "Content-Type: application/octet-stream; name=\"content\"\r\n"
message << "Content-Transfer-Encoding: base64\r\n"
message << "\r\n"
message << "Q29udGVudHM=\r\n"
message << "\r\n"
message << "--_Part_12_3195573780_381739540--\r\n"
message
end
it "creates a new Rex::MIME::Header" do
message_class.send(:initialize)
expect(message_class.header).to be_a(Rex::MIME::Header)
end
it "creates an empty array of parts" do
message_class.send(:initialize)
expect(message_class.parts).to be_empty
end
it "creates a random bound" do
message_class.send(:initialize)
expect(message_class.bound).to include('_Part_')
end
it "allows to populate headers from argument" do
message_class.send(:initialize, raw_message)
expect(message_class.header.headers.length).to eq(7)
end
it "allows to create a MIME-Version header from argument" do
message_class.send(:initialize, raw_message)
expect(message_class.header.find('MIME-Version')).to eq(['MIME-Version', '1.0'])
end
it "allows to create a Content-Type header from argument" do
message_class.send(:initialize, raw_message)
expect(message_class.header.find('Content-Type')).to eq(['Content-Type', "multipart/mixed; boundary=\"_Part_12_3195573780_381739540\""])
end
it "allows to create a Subject header from argument" do
message_class.send(:initialize, raw_message)
expect(message_class.header.find('Subject')).to eq(['Subject', 'Pull Request'])
end
it "allows to create a Date header from argument" do
message_class.send(:initialize, raw_message)
expect(message_class.header.find('Date')).to eq(['Date', 'Wed,20 Aug 2014 08:45:38 -0500'])
end
it "allows to create a Message-ID header from argument" do
message_class.send(:initialize, raw_message)
expect(message_class.header.find('Message-ID')).to eq(['Message-ID', '<WRobqc7gEyQVIQwEkLS7FN3ZNhS1Xj9pU2szC24rggMg@tqUqGjjSLEvssbwm>'])
end
it "allows to create a From header from argument" do
message_class.send(:initialize, raw_message)
expect(message_class.header.find('From')).to eq(['From', 'contributor@msfdev.int'])
end
it "allows to create a To header from argument" do
message_class.send(:initialize, raw_message)
expect(message_class.header.find('To')).to eq(['To', 'msfdev@msfdev.int'])
end
it "allows to populate parts from argument" do
message_class.send(:initialize, raw_message)
expect(message_class.parts.length).to eq(1)
end
it "allows to populate parts headers from argument" do
message_class.send(:initialize, raw_message)
part = message_class.parts[0]
expect(part.header.headers.length).to eq(3)
end
it "allows to populate parts contents from argument" do
message_class.send(:initialize, raw_message)
part = message_class.parts[0]
expect(part.content).to eq("Q29udGVudHM=")
end
end
describe "#to" do
it "returns nil if To: header doesn't exist" do
expect(subject.to).to be_nil
end
it "returns the To: header value if it exists" do
subject.header.add('To', 'msfdev')
expect(subject.to).to eq('msfdev')
end
end
describe "#to=" do
it "sets the To: header value" do
subject.to = 'msfdev'
expect(subject.to).to eq('msfdev')
end
end
describe "#from" do
it "returns nil if From: header doesn't exist" do
expect(subject.from).to be_nil
end
it "returns the From: header value if it exists" do
subject.header.add('From', 'msfdev')
expect(subject.from).to eq('msfdev')
end
end
describe "#from=" do
it "sets the From: header value" do
subject.from = 'msfdev'
expect(subject.from).to eq('msfdev')
end
end
describe "#subject" do
it "returns nil if Subject: header doesn't exist" do
expect(subject.subject).to be_nil
end
it "returns the Subject: header value if it exists" do
subject.header.add('Subject', 'msfdev')
expect(subject.subject).to eq('msfdev')
end
end
describe "#subject=" do
it "sets the Subject: header value" do
subject.subject = 'msfdev'
expect(subject.subject).to eq('msfdev')
end
end
describe "#mime_defaults" do
it "sets the MIME-Version header" do
subject.mime_defaults
expect(subject.header.find('MIME-Version')).to_not be_nil
end
it "sets the MIME-Version header to '1.0'" do
subject.mime_defaults
expect(subject.header.find('MIME-Version')).to eq(['MIME-Version', '1.0'])
end
it "sets the Content-Type header" do
subject.mime_defaults
expect(subject.header.find('Content-Type')).to_not be_nil
end
it "sets the Content-Type header to multipart/mixed" do
subject.mime_defaults
expect(subject.header.find('Content-Type')[1]).to include('multipart/mixed')
end
it "sets the Subject header" do
subject.mime_defaults
expect(subject.header.find('Subject')).to_not be_nil
end
it "sets the Subject header to empty string" do
subject.mime_defaults
expect(subject.header.find('Subject')).to eq(['Subject', ''])
end
it "sets the Message-ID header" do
subject.mime_defaults
expect(subject.header.find('Message-ID')).to_not be_nil
end
it "sets the From header" do
subject.mime_defaults
expect(subject.header.find('From')).to_not be_nil
end
it "sets the From header to empty string" do
subject.mime_defaults
expect(subject.header.find('From')).to eq(['From', ''])
end
it "sets the To header" do
subject.mime_defaults
expect(subject.header.find('To')).to_not be_nil
end
it "sets the To header to empty string" do
subject.mime_defaults
expect(subject.header.find('To')).to eq(['To', ''])
end
end
describe "#add_part" do
subject(:part) do
described_class.new.add_part(*args)
end
let(:args) { [] }
it "returns the new part" do
expect(part).to be_a(Rex::MIME::Part)
end
it "set part's Content-Type to text/plain by default" do
expect(part.header.find('Content-Type')[1]).to eq('text/plain')
end
it "set part's Content-Transfer-Encoding to 8bit by default" do
expect(part.header.find('Content-Transfer-Encoding')[1]).to eq('8bit')
end
it "doesn't set part's Content-Disposition by default" do
expect(part.header.find('Content-Disposition')).to be_nil
end
context "with Content-Type argument" do
let(:args) { ['', 'application/pdf'] }
it "creates a part Content-Type header" do
expect(part.header.find('Content-Type')[1]).to eq('application/pdf')
end
end
context "with Content-Transfer-Encoding argument" do
let(:args) { ['', 'application/pdf', 'binary'] }
it "creates a part Content-Transfer-Encoding header" do
expect(part.header.find('Content-Transfer-Encoding')[1]).to eq('binary')
end
end
context "with Content-Disposition argument" do
let(:args) { ['', 'application/pdf', 'binary', 'attachment; filename="fname.ext"'] }
it "creates a part Content-Disposition header" do
expect(part.header.find('Content-Disposition')[1]).to eq('attachment; filename="fname.ext"')
end
end
context "with content argument" do
let(:args) { ['msfdev'] }
it "creates part content" do
expect(part.content).to eq('msfdev')
end
end
end
describe "#add_part_attachment" do
it "requires data argument" do
expect { subject.add_part_attachment }.to raise_error(ArgumentError)
end
it "requires name argument" do
expect { subject.add_part_attachment('data') }.to raise_error(ArgumentError)
end
it 'returns the new Rex::MIME::Part' do
expect(subject.add_part_attachment('data', 'name')).to be_a(Rex::MIME::Part)
end
it 'encodes the part content with base64' do
part = subject.add_part_attachment('data', 'name')
expect(part.content).to eq(Rex::Text.encode_base64('data', "\r\n"))
end
it 'setup Content-Type as application/octet-stream' do
part = subject.add_part_attachment('data', 'name')
expect(part.header.find('Content-Type')[1]).to eq('application/octet-stream; name="name"')
end
it 'setup Content-Transfer-Encoding as base64' do
part = subject.add_part_attachment('data', 'name')
expect(part.header.find('Content-Transfer-Encoding')[1]).to eq('base64')
end
it 'setup Content-Disposition as attachment' do
part = subject.add_part_attachment('data', 'name')
expect(part.header.find('Content-Disposition')[1]).to eq('attachment; filename="name"')
end
end
describe "#add_part_inline_attachment" do
it "requires data argument" do
expect { subject.add_part_inline_attachment }.to raise_error(ArgumentError)
end
it "requires name argument" do
expect { subject.add_part_inline_attachment('data') }.to raise_error(ArgumentError)
end
it 'returns the new Rex::MIME::Part' do
expect(subject.add_part_inline_attachment('data', 'name')).to be_a(Rex::MIME::Part)
end
it 'encodes the part content with base64' do
part = subject.add_part_inline_attachment('data', 'name')
expect(part.content).to eq(Rex::Text.encode_base64('data', "\r\n"))
end
it 'setup Content-Type as application/octet-stream' do
part = subject.add_part_inline_attachment('data', 'name')
expect(part.header.find('Content-Type')[1]).to eq('application/octet-stream; name="name"')
end
it 'setup Content-Transfer-Encoding as base64' do
part = subject.add_part_inline_attachment('data', 'name')
expect(part.header.find('Content-Transfer-Encoding')[1]).to eq('base64')
end
it 'setup Content-Disposition as attachment' do
part = subject.add_part_inline_attachment('data', 'name')
expect(part.header.find('Content-Disposition')[1]).to eq('inline; filename="name"')
end
end
describe "#to_s" do
let(:regexp_mail) do
regex = "MIME-Version: 1.0\r\n"
regex << "Content-Type: multipart/mixed; boundary=\"_Part_.*\"\r\n"
regex << "Subject: Pull Request\r\n"
regex << "Date: .*\r\n"
regex << "Message-ID: <.*@.*>\r\n"
regex << "From: contributor@msfdev.int\r\n"
regex << "To: msfdev@msfdev.int\r\n"
regex << "\r\n"
regex << "--_Part_.*\r\n"
regex << "Content-Disposition: inline\r\n"
regex << "Content-Type: text/plain\r\n"
regex << "Content-Transfer-Encoding: base64\r\n"
regex << "\r\n"
regex << "Q29udGVudHM=\r\n"
regex << "\r\n"
regex << "--_Part_.*--\r\n"
Regexp.new(regex)
end
let(:regexp_web) do
regex = "\r\n"
regex << "--_Part_.*\r\n"
regex << "Content-Disposition: form-data; name=\"action\"\r\n"
regex << "\r\n"
regex << "save\r\n"
regex << "--_Part_.*\r\n"
regex << "Content-Disposition: form-data; name=\"file\"; filename=\"test.txt\"\r\n"
regex << "Content-Type: application/octet-stream\r\n"
regex << "\r\n"
regex << "Contents\r\n"
regex << "--_Part_.*\r\n"
regex << "Content-Disposition: form-data; name=\"title\"\r\n"
regex << "\r\n"
regex << "Title\r\n"
regex << "--_Part_.*--\r\n"
Regexp.new(regex)
end
it "returns \\r\\n if Rex::MIME::Message is empty" do
expect(subject.to_s).to eq("\r\n")
end
it "generates valid MIME email messages" do
subject.mime_defaults
subject.from = "contributor@msfdev.int"
subject.to = "msfdev@msfdev.int"
subject.subject = "Pull Request"
subject.add_part(Rex::Text.encode_base64("Contents", "\r\n"), "text/plain", "base64", "inline")
expect(regexp_mail.match(subject.to_s)).to_not be_nil
end
it "generates valid MIME web forms" do
subject.add_part("save", nil, nil, "form-data; name=\"action\"")
subject.add_part("Contents", "application/octet-stream", nil, "form-data; name=\"file\"; filename=\"test.txt\"")
subject.add_part("Title", nil, nil, "form-data; name=\"title\"")
expect(regexp_web.match(subject.to_s)).to_not be_nil
end
end
end

View File

@ -0,0 +1,92 @@
# -*- coding:binary -*-
require 'spec_helper'
require 'rex/mime'
describe Rex::MIME::Part do
subject do
described_class.new
end
describe "#initialize" do
subject(:part_class) do
described_class.allocate
end
it "initializes the Rex::MIME::Header object" do
part_class.send(:initialize)
expect(part_class.header).to be_a(Rex::MIME::Header)
end
it "initializes the Rex::MIME::Header with an empty array of headers" do
part_class.send(:initialize)
expect(part_class.header.headers).to be_empty
end
it "Initializes content with an empty String" do
part_class.send(:initialize)
expect(part_class.content).to be_empty
end
end
describe "#transfer_encoding" do
it "returns nil if the part hasn't a Content-Transfer-Encoding header" do
expect(subject.transfer_encoding).to be_nil
end
it "returns the transfer encoding value if a Content-Transfer-Encoding header exists" do
subject.header.add('Content-Transfer-Encoding', 'base64')
expect(subject.transfer_encoding).to eq('base64')
end
end
describe "#binary_content?" do
it "returns false if transfer encoding isn't defined" do
expect(subject.binary_content?).to be_falsey
end
it "returns false if transfer encoding isn't binary" do
subject.header.add('Content-Transfer-Encoding', 'base64')
expect(subject.binary_content?).to be_falsey
end
it "returns true if transfer encoding is binary" do
subject.header.add('Content-Transfer-Encoding', 'binary')
expect(subject.binary_content?).to be_truthy
end
end
describe "#content_encoded" do
let(:content_test) do
"\rTest1\n"
end
it "returns the exact content if transfer encoding is binary" do
subject.header.add('Content-Transfer-Encoding', 'binary')
subject.content = content_test
expect(subject.content_encoded).to eq(content_test)
end
it "returns the content crlf encoded if transfer encoding isn't binary" do
subject.content = content_test
expect(subject.content_encoded).to eq("Test1\r\n")
end
end
describe "#to_s" do
it "returns headers and content separated by two \\r\\n sequences" do
subject.header.add('var', 'val')
subject.content = 'content'
expect(subject.to_s).to eq("var: val\r\n\r\ncontent\r\n")
end
it "returns two \\r\\n sequences if part is empty" do
expect(subject.to_s).to eq("\r\n\r\n")
end
it "ends with \\r\\n sequence" do
expect(subject.to_s).to end_with("\r\n")
end
end
end