Skip to content

Commit

Permalink
Refactor to account for some Sprint messages having attached files and
Browse files Browse the repository at this point in the history
rather than being hosted on their CDN.
  • Loading branch information
monde committed Jul 4, 2012
1 parent dd48f0a commit 33ff5da
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 157 deletions.
69 changes: 42 additions & 27 deletions lib/mms2r/media.rb
Expand Up @@ -343,33 +343,16 @@ def default_html
# note: purge must be explicitly called to remove the media files
# mms2r extracts from an mms message.

def process() # :yields: media_type, file
def process # :yields: media_type, file
unless @was_processed
log("#{self.class} processing", :info)

parts = mail.multipart? ? mail.parts : [mail]

# Double check for multipart/related, if it exists replace it with its
# children parts. Do this twice as multipart/alternative can have
# children and we want to fold everything down
for i in 1..2
flat = []
parts.each do |p|
if p.multipart?
p.parts.each {|mp| flat << mp }
else
flat << p
end
end
parts = flat.dup
end

# get to work
parts.each do |p|
t = p.part_type?
unless ignore_media?(t,p)
t,f = process_media(p)
add_file(t,f) unless t.nil? || f.nil?
parts = self.folded_parts(mail)
parts.each do |part|
if part.part_type? == 'text/html'
process_html_part(part)
else
process_part(part)
end
end

Expand Down Expand Up @@ -436,6 +419,25 @@ def process_media(part)
return type, file
end

##
# Helper to decide if a part should be kept or ignored

def process_part(part)
return if ignore_media?(part.part_type?, part)

type, file = process_media(part)
add_file(type, file) unless type.nil? || file.nil?
end

##
# Helper to decide if a html part should be kept or ignored.
# We are defining it here primarily for the benefit so that Sprint
# can override a special case for processing.

def process_html_part(part)
process_part(part)
end

##
# Helper for process_media template method to transform text.
# See the transform section in the discussion of the built-in
Expand Down Expand Up @@ -490,7 +492,7 @@ def temp_file(part)
# Purges the unique MMS2R::Media.media_dir directory created
# for this producer and all of the media that it contains.

def purge()
def purge
log("#{self.class} purging #{@media_dir} and all its contents", :info)
FileUtils.rm_rf(@media_dir)
end
Expand All @@ -507,7 +509,7 @@ def add_file(type, file)
# Helper to temp_file to create a unique temporary directory that is a
# child of tmp_dir This version is based on the message_id of the mail.

def msg_tmp_dir()
def msg_tmp_dir
@dir_count += 1
dir = File.expand_path(File.join(@media_dir, "#{@dir_count}"))
FileUtils.mkdir_p(dir)
Expand Down Expand Up @@ -762,7 +764,7 @@ def initialize_config(config)
self.class.initialize_config(config)
end

private
protected

##
# accessor for the config
Expand All @@ -780,6 +782,19 @@ def type_from_filename(filename)
ent.nil? ? nil : ent.first
end

##
# Helper to fold all the parts of multipart mail down into a flat array.
# multipart/related and multipart/alternative parts can have child parts.
def folded_parts(parts)
return folded_parts([parts]) unless parts.respond_to?(:each)

result = [] # NOTE could use #tap but want 1.8.7 compat
parts.each do |part|
result << (part.multipart? ? folded_parts(part.parts) : part)
end
result.flatten
end

##
# used by #default_media and #text to return the biggest attachment type
# listed in the types array
Expand Down
69 changes: 21 additions & 48 deletions lib/mms2r/media/sprint.rb
Expand Up @@ -28,61 +28,34 @@ class Media

module Sprint

##
# Override process() because Sprint doesn't attach media (images, video,
# etc.) to its MMS. Media such as images and videos are hosted on a
# Sprint content server. MMS2R::Media::Sprint has to pick apart an
# HTML attachment to find the URL to the media on Sprint's content
# server and download each piece of content. Any text message part of
# the MMS if it exists is embedded in the html.

def process
unless @was_processed
log("#{self.class} processing", :info)
#sprint MMS are multipart
parts = @mail.parts

#find the payload html
doc = nil
parts.each do |p|
next unless p.part_type? == 'text/html'
d = Nokogiri(p.body.decoded)
title = d.at('title').inner_html
if title =~ /You have new Picture Mail!/
doc = d
@is_video = (p.body.decoded =~ /type=&quot;VIDEO&quot;&gt;/m ? true : false)
end
end
return if doc.nil? # it was a dud
@is_video ||= false

# break it down
sprint_phone_number(doc)
sprint_process_text(doc)
sprint_process_media(doc)

@was_processed = true
end

# when process acts upon a block
if block_given?
media.each do |k, v|
yield(k, v)
end
end
protected

##
# Helper to process old style media on the Sprint CDN which didn't attach
# media (images, video, etc.) to its MMS. Media such as images and
# videos are hosted on a Sprint content server. MMS2R::Media::Sprint has
# to pick apart an HTML attachment to find the URL to the media on
# Sprint's content server and download each piece of content. Any text
# message part of the MMS if it exists is embedded in the html.

def process_html_part(part)
doc = Nokogiri(part.body.decoded)

is_video = (part.body.decoded =~ /type=&quot;VIDEO&quot;&gt;/m ? true : false)
sprint_process_media(doc, is_video)
sprint_process_text(doc)
sprint_phone_number(doc)
end

private

##
# Digs out where Sprint hides the phone number

def sprint_phone_number(doc)
c = doc.search("/html/head/comment()").last
t = c.content.gsub(/\s+/m," ").strip
#@number returned in parent's #number
@number = / name=&quot;MDN&quot;&gt;(\d+)&lt;/.match(t)[1]
matched = / name=&quot;MDN&quot;&gt;(\d+)&lt;/.match(t)
@number = matched[1] if matched
end

##
Expand Down Expand Up @@ -157,7 +130,7 @@ def sprint_process_text(doc)
##
# Fetch all the media that is referred to in the doc

def sprint_process_media(doc)
def sprint_process_media(doc, is_video=false)
srcs = Array.new
# collect all the images in the document, even though
# they are <img> tag some might actually refer to video.
Expand All @@ -183,14 +156,14 @@ def sprint_process_media(doc)
begin

uri = URI.parse(CGI.unescapeHTML(src))
unless @is_video
unless is_video
query={}
uri.query.split('&').each{|a| p=a.split('='); query[p[0]] = p[1]}
query.delete_if{|k, v| k == 'limitsize' || k == 'squareoutput' }
uri.query = query.map{|k,v| "#{k}=#{v}"}.join("&")
end
# sprint is a ghetto, they expect to see &amp; for video request
uri.query = uri.query.gsub(/&/, "&amp;") if @is_video
uri.query = uri.query.gsub(/&/, "&amp;") if is_video

connection = Net::HTTP.new(uri.host, uri.port)
#connection.set_debug_output $stdout
Expand Down
92 changes: 11 additions & 81 deletions test/test_mms2r_media.rb
Expand Up @@ -311,18 +311,10 @@ def test_type_from_filename_should_be_nil

def test_attachment_should_return_duck_typed_file
mms = MMS2R::Media.new stub_mail
temp_big = temp_text_file("hello world")
size = File.size(temp_text_file("hello world"))
temp_small = temp_text_file("hello")
mms.stubs(:media).returns({'text/plain' => [temp_small, temp_big]})
duck_file = mms.send(:attachment, ['text'])
assert_not_nil duck_file
assert_equal true, File::exist?(duck_file)
assert_equal true, File::exist?(temp_big)
assert_equal temp_big, duck_file.local_path
assert_equal File.basename(temp_big), duck_file.original_filename
assert_equal size, duck_file.size
assert_equal 1, duck_file.size
assert_equal 'text/plain', duck_file.content_type
assert_equal "a", open(mms.media['text/plain'].first).read
end

def test_empty_body
Expand Down Expand Up @@ -632,86 +624,24 @@ def test_process_with_multipart_double_parts
mms.purge
end

def test_process_with_multipart_alternative_parts
mail = stub_mail

plain = stub('plain', :filename => 'message.txt', :content_type => 'text/plain', :part_type? => 'text/plain', :body => Mail::Body.new('a'), :main_type => 'text')
plain.stubs(:multipart?).at_least_once.returns(false)

html = stub('html', :filename => 'message.html', :content_type => 'text/html', :part_type? => 'text/html', :body => Mail::Body.new('a'), :main_type => 'text')
html.stubs(:multipart?).at_least_once.returns(false)

multi = stub('multi', :content_type => 'multipart/alternative', :part_type? => 'multipart/alternative', :parts => [plain, html])
multi.stubs(:multipart?).at_least_once.returns(true)

mail.stubs(:multipart?).at_least_once.returns(true)
mail.stubs(:parts).at_least_once.returns([multi])

# the multipart/alternative should get flattend to text and html
mms = MMS2R::Media.new(mail)
assert_equal 2, mms.media.size
assert_equal 2, mms.media.size
assert_not_nil mms.media['text/plain']
assert_not_nil mms.media['text/html']
assert_equal 1, mms.media['text/plain'].size
assert_equal 1, mms.media['text/html'].size
assert_equal 'message.txt', File.basename(mms.media['text/plain'].first)
assert_equal 'message.html', File.basename(mms.media['text/html'].first)
assert_equal true, File.exist?(mms.media['text/plain'].first)
assert_equal true, File.exist?(mms.media['text/html'].first)
assert_equal 1, File.size(mms.media['text/plain'].first)
assert_equal 1, File.size(mms.media['text/html'].first)
mms.purge
def test_folding_with_multipart_alternative_parts
mail = mail('helio-message-01.mail')
mms = MMS2R::Media.new(Mail.new)
assert_equal 5, mms.send(:folded_parts, mail.parts).size
end

def test_process_when_media_is_ignored
mail = stub_mail
plain = stub('plain', :filename => 'message.txt', :content_type => 'text/plain', :part_type? => 'text/plain', :body => Mail::Body.new(''), :main_type => 'text')
plain.stubs(:multipart?).at_least_once.returns(false)

html = stub('html', :filename => 'message.html', :content_type => 'text/html', :part_type? => 'text/html', :body => Mail::Body.new(''), :main_type => 'text')
html.stubs(:multipart?).at_least_once.returns(false)


multi = stub('multi', :content_type => 'multipart/alternative', :part_type? => 'multipart/alternative', :parts => [plain, html])
multi.stubs(:multipart?).at_least_once.returns(true)

mail.stubs(:multipart?).at_least_once.returns(true)
mail.stubs(:parts).at_least_once.returns([multi])

mms = MMS2R::Media.new(mail, :process => :lazy)
mms.stubs(:config).returns({'ignore' => {'text/plain' => ['message.txt'],
'text/html' => ['message.html']}})
assert_nothing_raised { mms.process }
# the multipart/alternative should get flattend to text and html and then
# what's flattened is ignored
assert_equal 0, mms.media.size
mms.purge
# TODO - I'd like to get away from mocks and test on real data, and
# this is covered repeatedly for various samples from the carrier
end

def test_process_when_yielding_to_a_block
mail = stub_mail

plain = stub('plain', :filename => 'message.txt', :content_type => 'text/plain', :part_type? => 'text/plain', :body => Mail::Body.new('a'), :main_type => 'text')
plain.stubs(:multipart?).at_least_once.returns(false)

html = stub('html', :filename => 'message.html', :content_type => 'text/html', :part_type? => 'text/html', :body => Mail::Body.new('b'), :main_type => 'text')
html.stubs(:multipart?).at_least_once.returns(false)

multi = stub('multi', :content_type => 'multipart/alternative', :part_type? => 'multipart/alternative', :parts => [plain, html])
multi.stubs(:multipart?).at_least_once.returns(true)

mail.stubs(:multipart?).at_least_once.returns(true)
mail.stubs(:parts).at_least_once.returns([multi])

# the multipart/alternative should get flattend to text and html
mail = mail('att-image-01.mail')
mms = MMS2R::Media.new(mail)
assert_equal 2, mms.media.size
mms.process do |type, files|
assert_equal 1, files.size
assert_equal true, type == 'text/plain' || type == 'text/html'
assert_equal true, File.basename(files.first) == 'message.txt' ||
File.basename(files.first) == 'message.html'
assert_equal true, type == 'image/jpeg'
assert_equal true, File.basename(files.first) == 'Photo_12.jpg'
assert_equal true, File::exist?(files.first)
end
mms.purge
Expand Down
1 change: 1 addition & 0 deletions test/test_mms_myhelio_com.rb
Expand Up @@ -21,6 +21,7 @@ def test_only_valid_content_should_be_retained_for_mms_with_text
assert_equal "mms.myhelio.com", mms.carrier
assert_equal 1, mms.media.size
assert_equal 1, mms.media['text/plain'].size
assert_equal "Test message", open(mms.media['text/plain'].first).read
mms.purge
end

Expand Down
1 change: 0 additions & 1 deletion test/test_pm_sprint_com.rb
Expand Up @@ -259,7 +259,6 @@ def test_message_is_missing_in_mail_purged_from_content_server

assert_equal '5135455555', mms.number
assert_equal "pm.sprint.com", mms.carrier

assert_equal 0, mms.media.size

mms.purge
Expand Down

0 comments on commit 33ff5da

Please sign in to comment.