body.rb 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291
  1. # encoding: utf-8
  2. module Mail
  3. # = Body
  4. #
  5. # The body is where the text of the email is stored. Mail treats the body
  6. # as a single object. The body itself has no information about boundaries
  7. # used in the MIME standard, it just looks at it's content as either a single
  8. # block of text, or (if it is a multipart message) as an array of blocks o text.
  9. #
  10. # A body has to be told to split itself up into a multipart message by calling
  11. # #split with the correct boundary. This is because the body object has no way
  12. # of knowing what the correct boundary is for itself (there could be many
  13. # boundaries in a body in the case of a nested MIME text).
  14. #
  15. # Once split is called, Mail::Body will slice itself up on this boundary,
  16. # assigning anything that appears before the first part to the preamble, and
  17. # anything that appears after the closing boundary to the epilogue, then
  18. # each part gets initialized into a Mail::Part object.
  19. #
  20. # The boundary that is used to split up the Body is also stored in the Body
  21. # object for use on encoding itself back out to a string. You can
  22. # overwrite this if it needs to be changed.
  23. #
  24. # On encoding, the body will return the preamble, then each part joined by
  25. # the boundary, followed by a closing boundary string and then the epilogue.
  26. class Body
  27. def initialize(string = '')
  28. @boundary = nil
  29. @preamble = nil
  30. @epilogue = nil
  31. @charset = nil
  32. @part_sort_order = [ "text/plain", "text/enriched", "text/html" ]
  33. @parts = Mail::PartsList.new
  34. if string.blank?
  35. @raw_source = ''
  36. else
  37. # Do join first incase we have been given an Array in Ruby 1.9
  38. if string.respond_to?(:join)
  39. @raw_source = string.join('')
  40. elsif string.respond_to?(:to_s)
  41. @raw_source = string.to_s
  42. else
  43. raise "You can only assign a string or an object that responds_to? :join or :to_s to a body."
  44. end
  45. end
  46. @encoding = (only_us_ascii? ? '7bit' : '8bit')
  47. set_charset
  48. end
  49. # Matches this body with another body. Also matches the decoded value of this
  50. # body with a string.
  51. #
  52. # Examples:
  53. #
  54. # body = Mail::Body.new('The body')
  55. # body == body #=> true
  56. #
  57. # body = Mail::Body.new('The body')
  58. # body == 'The body' #=> true
  59. #
  60. # body = Mail::Body.new("VGhlIGJvZHk=\n")
  61. # body.encoding = 'base64'
  62. # body == "The body" #=> true
  63. def ==(other)
  64. if other.class == String
  65. self.decoded == other
  66. else
  67. super
  68. end
  69. end
  70. # Accepts a string and performs a regular expression against the decoded text
  71. #
  72. # Examples:
  73. #
  74. # body = Mail::Body.new('The body')
  75. # body =~ /The/ #=> 0
  76. #
  77. # body = Mail::Body.new("VGhlIGJvZHk=\n")
  78. # body.encoding = 'base64'
  79. # body =~ /The/ #=> 0
  80. def =~(regexp)
  81. self.decoded =~ regexp
  82. end
  83. # Accepts a string and performs a regular expression against the decoded text
  84. #
  85. # Examples:
  86. #
  87. # body = Mail::Body.new('The body')
  88. # body.match(/The/) #=> #<MatchData "The">
  89. #
  90. # body = Mail::Body.new("VGhlIGJvZHk=\n")
  91. # body.encoding = 'base64'
  92. # body.match(/The/) #=> #<MatchData "The">
  93. def match(regexp)
  94. self.decoded.match(regexp)
  95. end
  96. # Accepts anything that responds to #to_s and checks if it's a substring of the decoded text
  97. #
  98. # Examples:
  99. #
  100. # body = Mail::Body.new('The body')
  101. # body.include?('The') #=> true
  102. #
  103. # body = Mail::Body.new("VGhlIGJvZHk=\n")
  104. # body.encoding = 'base64'
  105. # body.include?('The') #=> true
  106. def include?(other)
  107. self.decoded.include?(other.to_s)
  108. end
  109. # Allows you to set the sort order of the parts, overriding the default sort order.
  110. # Defaults to 'text/plain', then 'text/enriched', then 'text/html' with any other content
  111. # type coming after.
  112. def set_sort_order(order)
  113. @part_sort_order = order
  114. end
  115. # Allows you to sort the parts according to the default sort order, or the sort order you
  116. # set with :set_sort_order.
  117. #
  118. # sort_parts! is also called from :encode, so there is no need for you to call this explicitly
  119. def sort_parts!
  120. @parts.each do |p|
  121. p.body.set_sort_order(@part_sort_order)
  122. @parts.sort!(@part_sort_order)
  123. p.body.sort_parts!
  124. end
  125. end
  126. # Returns the raw source that the body was initialized with, without
  127. # any tampering
  128. def raw_source
  129. @raw_source
  130. end
  131. def get_best_encoding(target)
  132. target_encoding = Mail::Encodings.get_encoding(target)
  133. target_encoding.get_best_compatible(encoding, raw_source)
  134. end
  135. # Returns a body encoded using transfer_encoding. Multipart always uses an
  136. # identiy encoding (i.e. no encoding).
  137. # Calling this directly is not a good idea, but supported for compatibility
  138. # TODO: Validate that preamble and epilogue are valid for requested encoding
  139. def encoded(transfer_encoding = '8bit')
  140. if multipart?
  141. self.sort_parts!
  142. encoded_parts = parts.map { |p| p.encoded }
  143. ([preamble] + encoded_parts).join(crlf_boundary) + end_boundary + epilogue.to_s
  144. else
  145. be = get_best_encoding(transfer_encoding)
  146. dec = Mail::Encodings::get_encoding(encoding)
  147. enc = Mail::Encodings::get_encoding(be)
  148. if transfer_encoding == encoding and dec.nil?
  149. # Cannot decode, so skip normalization
  150. raw_source
  151. else
  152. # Decode then encode to normalize and allow transforming
  153. # from base64 to Q-P and vice versa
  154. decoded = dec.decode(raw_source)
  155. if defined?(Encoding) && charset && charset != "US-ASCII"
  156. decoded.encode!(charset)
  157. decoded.force_encoding('BINARY') unless Encoding.find(charset).ascii_compatible?
  158. end
  159. enc.encode(decoded)
  160. end
  161. end
  162. end
  163. def decoded
  164. if !Encodings.defined?(encoding)
  165. raise UnknownEncodingType, "Don't know how to decode #{encoding}, please call #encoded and decode it yourself."
  166. else
  167. Encodings.get_encoding(encoding).decode(raw_source)
  168. end
  169. end
  170. def to_s
  171. decoded
  172. end
  173. def charset
  174. @charset
  175. end
  176. def charset=( val )
  177. @charset = val
  178. end
  179. def encoding(val = nil)
  180. if val
  181. self.encoding = val
  182. else
  183. @encoding
  184. end
  185. end
  186. def encoding=( val )
  187. @encoding = if val == "text" || val.blank?
  188. (only_us_ascii? ? '7bit' : '8bit')
  189. else
  190. val
  191. end
  192. end
  193. # Returns the preamble (any text that is before the first MIME boundary)
  194. def preamble
  195. @preamble
  196. end
  197. # Sets the preamble to a string (adds text before the first MIME boundary)
  198. def preamble=( val )
  199. @preamble = val
  200. end
  201. # Returns the epilogue (any text that is after the last MIME boundary)
  202. def epilogue
  203. @epilogue
  204. end
  205. # Sets the epilogue to a string (adds text after the last MIME boundary)
  206. def epilogue=( val )
  207. @epilogue = val
  208. end
  209. # Returns true if there are parts defined in the body
  210. def multipart?
  211. true unless parts.empty?
  212. end
  213. # Returns the boundary used by the body
  214. def boundary
  215. @boundary
  216. end
  217. # Allows you to change the boundary of this Body object
  218. def boundary=( val )
  219. @boundary = val
  220. end
  221. def parts
  222. @parts
  223. end
  224. def <<( val )
  225. if @parts
  226. @parts << val
  227. else
  228. @parts = Mail::PartsList.new[val]
  229. end
  230. end
  231. def split!(boundary)
  232. self.boundary = boundary
  233. parts = raw_source.split("--#{boundary}")
  234. # Make the preamble equal to the preamble (if any)
  235. self.preamble = parts[0].to_s.strip
  236. # Make the epilogue equal to the epilogue (if any)
  237. self.epilogue = parts[-1].to_s.sub('--', '').strip
  238. parts[1...-1].to_a.each { |part| @parts << Mail::Part.new(part) }
  239. self
  240. end
  241. def only_us_ascii?
  242. !(raw_source =~ /[^\x01-\x7f]/)
  243. end
  244. def empty?
  245. !!raw_source.to_s.empty?
  246. end
  247. private
  248. def crlf_boundary
  249. "\r\n\r\n--#{boundary}\r\n"
  250. end
  251. def end_boundary
  252. "\r\n\r\n--#{boundary}--\r\n"
  253. end
  254. def set_charset
  255. only_us_ascii? ? @charset = 'US-ASCII' : @charset = nil
  256. end
  257. end
  258. end