mirror of
https://github.com/rapid7/metasploit-framework
synced 2024-08-28 23:26:18 +02:00
Use rex-mime gem
MS-1710
This commit is contained in:
parent
8b9661d8e4
commit
e36cfa54b1
@ -35,6 +35,7 @@ PATH
|
||||
redcarpet
|
||||
rex-arch
|
||||
rex-java
|
||||
rex-mime
|
||||
rex-ole
|
||||
rex-powershell
|
||||
rex-random_identifier
|
||||
@ -180,7 +181,7 @@ GEM
|
||||
multi_json (1.12.1)
|
||||
multi_test (0.1.2)
|
||||
multipart-post (2.0.0)
|
||||
nessus_rest (0.1.3)
|
||||
nessus_rest (0.1.4)
|
||||
net-ssh (3.2.0)
|
||||
network_interface (0.0.1)
|
||||
nokogiri (1.6.8)
|
||||
@ -224,15 +225,17 @@ GEM
|
||||
thor (>= 0.18.1, < 2.0)
|
||||
rake (11.2.2)
|
||||
rb-readline-r7 (0.5.2.0)
|
||||
recog (2.0.21)
|
||||
recog (2.0.22)
|
||||
nokogiri
|
||||
redcarpet (3.3.4)
|
||||
rex-arch (0.1.1)
|
||||
rex-text
|
||||
rex-java (0.1.2)
|
||||
rex-mime (0.1.1)
|
||||
rex-text
|
||||
rex-ole (0.1.2)
|
||||
rex-text
|
||||
rex-powershell (0.1.1)
|
||||
rex-powershell (0.1.64)
|
||||
rex-random_identifier
|
||||
rex-text
|
||||
rex-random_identifier (0.1.0)
|
||||
|
@ -57,6 +57,8 @@ require 'rex/java'
|
||||
require 'rex/struct2'
|
||||
# Library for working with OLE
|
||||
require 'rex/ole'
|
||||
# Library for creating and/or parsing MIME messages
|
||||
require 'rex/mime'
|
||||
|
||||
# Generic classes
|
||||
require 'rex/exceptions'
|
||||
|
@ -1,10 +0,0 @@
|
||||
# -*- coding: binary -*-
|
||||
module Rex
|
||||
module MIME
|
||||
|
||||
require 'rex/mime/header'
|
||||
require 'rex/mime/part'
|
||||
require 'rex/mime/message'
|
||||
|
||||
end
|
||||
end
|
@ -1,17 +0,0 @@
|
||||
# -*- coding: binary -*-
|
||||
module Rex
|
||||
module MIME
|
||||
# Set of helpers methods to deal with SMTP encoding related topics.
|
||||
module Encoding
|
||||
|
||||
# Enforces CRLF on the input data
|
||||
#
|
||||
# @param data [String] The data to CRLF enforce.
|
||||
# @return [String] CRLF enforced data.
|
||||
def force_crlf(data)
|
||||
data.gsub("\r", '').gsub("\n", "\r\n")
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
@ -1,79 +0,0 @@
|
||||
# -*- coding: binary -*-
|
||||
module Rex
|
||||
module MIME
|
||||
class Header
|
||||
|
||||
require 'rex/text'
|
||||
|
||||
attr_accessor :headers
|
||||
|
||||
def initialize(data='')
|
||||
self.headers = []
|
||||
parse(data)
|
||||
end
|
||||
|
||||
def parse(data)
|
||||
prev = nil
|
||||
data.gsub("\r", '').split("\n").each do |line|
|
||||
|
||||
# Handle header folding
|
||||
if (line =~ /^\s+/)
|
||||
# Ignore if there is no previous header
|
||||
next if not prev
|
||||
next if not self.headers[prev]
|
||||
self.headers[prev][1] << line.strip
|
||||
next
|
||||
end
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
def to_s
|
||||
self.headers.map{ |pair| "#{pair[0]}: #{pair[1]}\r\n" }.join
|
||||
end
|
||||
|
||||
def find(idx)
|
||||
if (idx.class == ::Fixnum)
|
||||
return self.headers[idx]
|
||||
else
|
||||
self.headers.each do |pair|
|
||||
if (pair[0] == idx.to_s)
|
||||
return pair
|
||||
end
|
||||
end
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def set(var, val)
|
||||
hdr = self.find(var) || self.add(var, '')
|
||||
hdr[1] = val
|
||||
end
|
||||
|
||||
def add(var, val)
|
||||
self.headers << [var, val]
|
||||
self.headers[-1]
|
||||
end
|
||||
|
||||
def remove(idx)
|
||||
if (idx.class == ::Fixnum)
|
||||
self.headers.delete_at(idx)
|
||||
else
|
||||
self.headers.each_index do |i|
|
||||
pair = self.headers[i]
|
||||
if (pair[0] == idx.to_s)
|
||||
self.headers.delete_at(i)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,144 +0,0 @@
|
||||
# -*- coding: binary -*-
|
||||
module Rex
|
||||
module MIME
|
||||
class Message
|
||||
|
||||
require 'rex/mime/header'
|
||||
require 'rex/mime/part'
|
||||
require 'rex/mime/encoding'
|
||||
require 'rex/text'
|
||||
|
||||
include Rex::MIME::Encoding
|
||||
|
||||
attr_accessor :header, :parts, :bound, :content
|
||||
|
||||
|
||||
def initialize(data=nil)
|
||||
self.header = Rex::MIME::Header.new
|
||||
self.parts = []
|
||||
self.bound = "_Part_#{rand(1024)}_#{rand(0xffffffff)}_#{rand(0xffffffff)}"
|
||||
self.content = ''
|
||||
if data
|
||||
head,body = data.split(/\r?\n\r?\n/, 2)
|
||||
|
||||
self.header.parse(head)
|
||||
ctype = self.header.find('Content-Type')
|
||||
|
||||
if ctype && ctype[1] && 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" unless self.content.empty?
|
||||
|
||||
chunks.each do |chunk|
|
||||
break if chunk == "--"
|
||||
head,body = chunk.split(/\r?\n\r?\n/, 2)
|
||||
part = Rex::MIME::Part.new
|
||||
part.header.parse(head)
|
||||
part.content = body.gsub(/\s+$/, '')
|
||||
self.parts << part
|
||||
end
|
||||
else
|
||||
self.content = body.to_s.gsub(/\s+$/, '') + "\r\n"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def to
|
||||
(self.header.find('To') || [nil, nil])[1]
|
||||
end
|
||||
|
||||
def to=(val)
|
||||
self.header.set("To", val)
|
||||
end
|
||||
|
||||
def from=(val)
|
||||
self.header.set("From", val)
|
||||
end
|
||||
|
||||
def from
|
||||
(self.header.find('From') || [nil, nil])[1]
|
||||
end
|
||||
|
||||
def subject=(val)
|
||||
self.header.set("Subject", val)
|
||||
end
|
||||
|
||||
def subject
|
||||
(self.header.find('Subject') || [nil, nil])[1]
|
||||
end
|
||||
|
||||
def mime_defaults
|
||||
self.header.set("MIME-Version", "1.0")
|
||||
self.header.set("Content-Type", "multipart/mixed; boundary=\"#{self.bound}\"")
|
||||
self.header.set("Subject", '') # placeholder
|
||||
self.header.set("Date", Time.now.strftime("%a,%e %b %Y %H:%M:%S %z"))
|
||||
self.header.set("Message-ID",
|
||||
"<"+
|
||||
Rex::Text.rand_text_alphanumeric(rand(20)+40)+
|
||||
"@"+
|
||||
Rex::Text.rand_text_alpha(rand(20)+3)+
|
||||
">"
|
||||
)
|
||||
self.header.set("From", '') # placeholder
|
||||
self.header.set("To", '') # placeholder
|
||||
end
|
||||
|
||||
|
||||
def add_part(data='', content_type='text/plain', transfer_encoding="8bit", content_disposition=nil)
|
||||
part = Rex::MIME::Part.new
|
||||
|
||||
if content_disposition
|
||||
part.header.set("Content-Disposition", content_disposition)
|
||||
end
|
||||
|
||||
part.header.set("Content-Type", content_type) if content_type
|
||||
|
||||
if transfer_encoding
|
||||
part.header.set("Content-Transfer-Encoding", transfer_encoding)
|
||||
end
|
||||
|
||||
part.content = data
|
||||
self.parts << part
|
||||
part
|
||||
end
|
||||
|
||||
def add_part_attachment(data, name)
|
||||
self.add_part(
|
||||
Rex::Text.encode_base64(data, "\r\n"),
|
||||
"application/octet-stream; name=\"#{name}\"",
|
||||
"base64",
|
||||
"attachment; filename=\"#{name}\""
|
||||
)
|
||||
end
|
||||
|
||||
|
||||
def add_part_inline_attachment(data, name)
|
||||
self.add_part(
|
||||
Rex::Text.encode_base64(data, "\r\n"),
|
||||
"application/octet-stream; name=\"#{name}\"",
|
||||
"base64",
|
||||
"inline; filename=\"#{name}\""
|
||||
)
|
||||
end
|
||||
|
||||
def to_s
|
||||
header_string = self.header.to_s
|
||||
|
||||
msg = header_string.empty? ? '' : force_crlf(self.header.to_s + "\r\n")
|
||||
msg << force_crlf(self.content + "\r\n") unless self.content.to_s.strip.empty?
|
||||
|
||||
self.parts.each do |part|
|
||||
msg << force_crlf("--" + self.bound + "\r\n")
|
||||
msg << part.to_s
|
||||
end
|
||||
|
||||
msg << force_crlf("--" + self.bound + "--\r\n") if self.parts.length > 0
|
||||
|
||||
msg
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -1,50 +0,0 @@
|
||||
# -*- coding: binary -*-
|
||||
module Rex
|
||||
module MIME
|
||||
class Part
|
||||
|
||||
require 'rex/mime/header'
|
||||
require 'rex/mime/encoding'
|
||||
|
||||
include Rex::MIME::Encoding
|
||||
|
||||
attr_accessor :header, :content
|
||||
|
||||
def initialize
|
||||
self.header = Rex::MIME::Header.new
|
||||
self.content = ''
|
||||
end
|
||||
|
||||
def to_s
|
||||
self.header.to_s + "\r\n" + content_encoded + "\r\n"
|
||||
end
|
||||
|
||||
# Returns the part content with any necessary encoding or transformation
|
||||
# applied.
|
||||
#
|
||||
# @return [String] Content with encoding or transformations applied.
|
||||
def content_encoded
|
||||
binary_content? ? content : force_crlf(content)
|
||||
end
|
||||
|
||||
# Answers if the part content is binary.
|
||||
#
|
||||
# @return [Boolean] true if the part content is binary, false otherwise.
|
||||
def binary_content?
|
||||
transfer_encoding && transfer_encoding == 'binary'
|
||||
end
|
||||
|
||||
# Returns the Content-Transfer-Encoding of the part.
|
||||
#
|
||||
# @return [nil] if the part hasn't Content-Transfer-Encoding.
|
||||
# @return [String] The Content-Transfer-Encoding or the part.
|
||||
def transfer_encoding
|
||||
h = header.find('Content-Transfer-Encoding')
|
||||
return nil if h.nil?
|
||||
|
||||
h[1]
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
end
|
@ -132,6 +132,8 @@ Gem::Specification.new do |spec|
|
||||
spec.add_runtime_dependency 'rex-arch'
|
||||
# Library for working with OLE.
|
||||
spec.add_runtime_dependency 'rex-ole'
|
||||
# Library for creating and/or parsing MIME messages.
|
||||
spec.add_runtime_dependency 'rex-mime'
|
||||
|
||||
# rb-readline doesn't work with Ruby Installer due to error with Fiddle:
|
||||
# NoMethodError undefined method `dlopen' for Fiddle:Module
|
||||
|
@ -1,32 +0,0 @@
|
||||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'rex/mime'
|
||||
|
||||
RSpec.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
|
@ -1,151 +0,0 @@
|
||||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'rex/mime'
|
||||
|
||||
RSpec.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
|
@ -1,411 +0,0 @@
|
||||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'rex/mime'
|
||||
require 'rex/text'
|
||||
|
||||
RSpec.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 = "--_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 empty string if Rex::MIME::Message is empty" do
|
||||
expect(subject.to_s).to be_empty
|
||||
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
|
@ -1,92 +0,0 @@
|
||||
# -*- coding:binary -*-
|
||||
require 'spec_helper'
|
||||
|
||||
require 'rex/mime'
|
||||
|
||||
RSpec.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
|
Loading…
Reference in New Issue
Block a user