mime 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. data/README +256 -0
  2. data/Rakefile +34 -0
  3. data/lib/mime.rb +32 -0
  4. data/lib/mime/composite_media_type.rb +169 -0
  5. data/lib/mime/content_types.rb +96 -0
  6. data/lib/mime/discrete_media_factory.rb +69 -0
  7. data/lib/mime/discrete_media_type.rb +79 -0
  8. data/lib/mime/error.rb +32 -0
  9. data/lib/mime/header_container.rb +34 -0
  10. data/lib/mime/headers/internet.rb +90 -0
  11. data/lib/mime/headers/mime.rb +118 -0
  12. data/lib/mime/media_type.rb +45 -0
  13. data/lib/mime/message.rb +51 -0
  14. data/lib/mime/parser.rb +16 -0
  15. data/test/mime_test.rb +386 -0
  16. data/test/scaffold/application.msg +8 -0
  17. data/test/scaffold/audio.msg +8 -0
  18. data/test/scaffold/book.pdf +0 -0
  19. data/test/scaffold/data.xml +17 -0
  20. data/test/scaffold/image.jpg +0 -0
  21. data/test/scaffold/image.msg +0 -0
  22. data/test/scaffold/index.html +6 -0
  23. data/test/scaffold/main.css +0 -0
  24. data/test/scaffold/mini.mov +0 -0
  25. data/test/scaffold/multipart_alternative.msg +17 -0
  26. data/test/scaffold/multipart_alternative_related.msg +0 -0
  27. data/test/scaffold/multipart_form_data_file.msg +0 -0
  28. data/test/scaffold/multipart_form_data_file_and_text.msg +0 -0
  29. data/test/scaffold/multipart_form_data_mixed.msg +0 -0
  30. data/test/scaffold/multipart_form_data_text.msg +40 -0
  31. data/test/scaffold/multipart_mixed_inline_and_attachment.msg +0 -0
  32. data/test/scaffold/multipart_mixed_inline_and_attachment2.msg +0 -0
  33. data/test/scaffold/multipart_related.msg +0 -0
  34. data/test/scaffold/plain_text_email.msg +9 -0
  35. data/test/scaffold/ruby.png +0 -0
  36. data/test/scaffold/song.mp3 +0 -0
  37. data/test/scaffold/text.msg +7 -0
  38. data/test/scaffold/unknown.yyy +1 -0
  39. data/test/scaffold/video.msg +8 -0
  40. metadata +92 -0
@@ -0,0 +1,96 @@
1
+ module MIME
2
+
3
+ #
4
+ # Handles MIME content type detection using file name extension.
5
+ #
6
+ # === See Also
7
+ #
8
+ # * http://w3schools.com/media/media_mimeref.asp for more content types
9
+ # * rubygem MIME::Types for extensive content type lookup
10
+ #
11
+ module ContentTypes
12
+
13
+ #
14
+ # Content types are in the form of <em>media-type/sub-type</em>. Each
15
+ # content type has one or more file extentions associated with it.
16
+ #
17
+ # The content types below are listed in alphabetical order and the
18
+ # extention arrays are in order of preference.
19
+ #
20
+ # The following content types were stolen from the NGiNX webserver. NGiNX
21
+ # is great server, check it out at http://nginx.net.
22
+ #
23
+ CONTENT_TYPES = {
24
+ 'application/atom+xml' => %w(atom),
25
+ 'application/java-archive' => %w(jar war ear),
26
+ 'application/mac-binhex40' => %w(hqx),
27
+ 'application/msword' => %w(doc),
28
+ 'application/octet-stream' => %w(bin exe dll deb dmg eot iso img msi msp msm),
29
+ 'application/pdf' => %w(pdf),
30
+ 'application/postscript' => %w(ps eps ai),
31
+ 'application/rtf' => %w(rtf),
32
+ 'application/vnd.ms-excel' => %w(xls),
33
+ 'application/vnd.ms-powerpoint' => %w(ppt),
34
+ 'application/vnd.wap.wmlc' => %w(wmlc),
35
+ 'application/vnd.wap.xhtml+xml' => %w(xhtml),
36
+ 'application/x-cocoa' => %w(cco),
37
+ 'application/x-java-archive-diff' => %w(jardiff),
38
+ 'application/x-java-jnlp-file' => %w(jnlp),
39
+ 'application/x-javascript' => %w(js),
40
+ 'application/x-makeself' => %w(run),
41
+ 'application/x-perl' => %w(pl pm),
42
+ 'application/x-pilot' => %w(prc pdb),
43
+ 'application/x-rar-compressed' => %w(rar),
44
+ 'application/x-redhat-package-manager' => %w(rpm),
45
+ 'application/x-sea' => %w(sea),
46
+ 'application/x-shockwave-flash' => %w(swf),
47
+ 'application/x-stuffit' => %w(sit),
48
+ 'application/x-tcl' => %w(tcl tk),
49
+ 'application/x-x509-ca-cert' => %w(crt pem der),
50
+ 'application/x-xpinstall' => %w(xpi),
51
+ 'application/zip' => %w(zip),
52
+
53
+ 'audio/midi' => %w(mid midi kar),
54
+ 'audio/mpeg' => %w(mp3),
55
+ 'audio/x-realaudio' => %w(ra),
56
+
57
+ 'image/gif' => %w(gif),
58
+ 'image/jpeg' => %w(jpg jpeg),
59
+ 'image/png' => %w(png),
60
+ 'image/tiff' => %w(tif tiff),
61
+ 'image/vnd.wap.wbmp' => %w(wbmp),
62
+ 'image/x-icon' => %w(ico),
63
+ 'image/x-jng' => %w(jng),
64
+ 'image/x-ms-bmp' => %w(bmp),
65
+ 'image/svg+xml' => %w(svg),
66
+
67
+ 'text/css' => %w(css),
68
+ 'text/html' => %w(html htm shtml),
69
+ 'text/mathml' => %w(mml),
70
+ 'text/plain' => %w(txt),
71
+ 'text/x-component' => %w(htc),
72
+ 'text/xml' => %w(xml rss),
73
+
74
+ 'video/3gpp' => %w(3gpp 3gp),
75
+ 'video/mpeg' => %w(mpeg mpg),
76
+ 'video/quicktime' => %w(mov),
77
+ 'video/x-flv' => %w(flv),
78
+ 'video/x-mng' => %w(mng),
79
+ 'video/x-ms-asf' => %w(asx asf),
80
+ 'video/x-ms-wmv' => %w(wmv),
81
+ 'video/x-msvideo' => %w(avi)
82
+ }
83
+
84
+ #
85
+ # Return the MIME content type of the +file+ path or object.
86
+ #
87
+ def file_type file
88
+ filename = file.respond_to?(:path) ? file.path : file
89
+ extension = File.extname(filename)[1..-1] # array index removes period
90
+ typ_exts = CONTENT_TYPES.find {|type, extentions| extentions.include? extension}
91
+ typ_exts ? typ_exts.first : typ_exts # returns +type+ from above or nil
92
+ end
93
+
94
+ end
95
+
96
+ end
@@ -0,0 +1,69 @@
1
+ module MIME
2
+
3
+ #
4
+ # Class object used only for initializing derived DiscreteMediaType objects.
5
+ #
6
+ class DiscreteMediaFactory
7
+
8
+ class << self
9
+
10
+ include ContentTypes
11
+
12
+ #
13
+ # Creates a corresponding DiscreteMediaType subclass object for the given
14
+ # +file+ based on +file+'s filename extension. +file+ can be a file path
15
+ # or File object.
16
+ #
17
+ # +content_type+ can be specified in order to override the auto detected
18
+ # content type. If the +content_type+ cannot be detected, an
19
+ # UnknownContentError exception will be raised.
20
+ #
21
+ # Creates and sets the singleton method +path+ on the created object. The
22
+ # +path+ method is utilized by other methods in the MIME library,
23
+ # therefore, eliminating redundant and explicit filename assignments.
24
+ #
25
+ # ==== Comparison Example
26
+ #
27
+ # entity1 = open('/tmp/file1.txt')
28
+ # entity2 = DiscreteMediaFactory.create('/tmp/file2.txt')
29
+ #
30
+ # mixed_msg = Multipart::Mixed.new
31
+ # mixed_msg.attach_entity(entity1.read, entity.path)
32
+ # mixed_msg.attach_entity(entity2) # no path needed
33
+ #
34
+ def create file, content_type = nil
35
+ if file.is_a? File
36
+ cntnt = file.read
37
+ ctype = content_type || file_type(file.path)
38
+ fname = file.path
39
+ else
40
+ cntnt = IO.read(file)
41
+ ctype = content_type || file_type(file)
42
+ fname = file
43
+ end
44
+
45
+ raise UnknownContentError unless ctype
46
+
47
+ media_obj =
48
+ case ctype.match(/^(\w+)\//)[1]
49
+ when 'application'; ApplicationMedia.new(cntnt, ctype)
50
+ when 'audio' ; AudioMedia.new(cntnt, ctype)
51
+ when 'image' ; ImageMedia.new(cntnt, ctype)
52
+ when 'text' ; TextMedia.new(cntnt, ctype)
53
+ when 'video' ; VideoMedia.new(cntnt, ctype)
54
+ end
55
+
56
+ class << media_obj; attr_accessor :path end
57
+ media_obj.path = fname
58
+ media_obj
59
+ end
60
+
61
+ end
62
+
63
+ def initialize
64
+ AbstractClassError.no_instantiation self, DiscreteMediaFactory
65
+ end
66
+
67
+ end
68
+
69
+ end
@@ -0,0 +1,79 @@
1
+ module MIME
2
+
3
+ #
4
+ # Discrete media types must be handled by non-MIME mechanisms; they are
5
+ # opaque to MIME processors. Therefore, the body of a DiscreteMediaType
6
+ # object does not need further MIME processing.
7
+ #
8
+ # This class is abstract.
9
+ #
10
+ class DiscreteMediaType < MediaType
11
+
12
+ def initialize body, content_type = 'application/octet-stream'
13
+ AbstractClassError.no_instantiation(self, DiscreteMediaType)
14
+ super
15
+ end
16
+
17
+ end
18
+
19
+ #
20
+ # ApplicationMedia is intended for discrete data that is to be processed by
21
+ # some type of application program. The body contains information which must
22
+ # be processed by an application before it is viewable or usable by a user.
23
+ #
24
+ # ApplicationMedia is the catch all class. If your content cannot be
25
+ # identified as another DiscreteMediaType, then it is application media.
26
+ #
27
+ # See DiscreteMediaType.new for initialization parameters.
28
+ #
29
+ class ApplicationMedia < DiscreteMediaType
30
+ end
31
+
32
+ #
33
+ # AudioMedia is intended for discrete audio content. The content subtype
34
+ # indicates the specific audio format.
35
+ #
36
+ # See DiscreteMediaType.new for initialization parameters.
37
+ #
38
+ class AudioMedia < DiscreteMediaType
39
+ end
40
+
41
+ #
42
+ # ImageMedia is intented for discrete image content. The content subtype
43
+ # indicates the specific image format.
44
+ #
45
+ # See DiscreteMediaType.new for initialization parameters.
46
+ #
47
+ class ImageMedia < DiscreteMediaType
48
+ end
49
+
50
+ #
51
+ # TextMedia is intended for content which is principally textual in form.
52
+ #
53
+ class TextMedia < DiscreteMediaType
54
+
55
+ #
56
+ # Return a new TextMedia object containing +body+ with the content type of
57
+ # +content_type+.
58
+ #
59
+ # To specify the character set of +body+, a _charset_ parameter may be
60
+ # appended to +content_type+ using a semi-colon delimiter.
61
+ #
62
+ def initialize body, content_type = 'text/plain; charset=us-ascii'
63
+ super
64
+ end
65
+
66
+ end
67
+
68
+ #
69
+ # VideoMedia is intended for discrete video content. The content subtype
70
+ # indicates the specific video format. The RFC describes video media as
71
+ # content that contains a time-varying-picture image, possibly with color and
72
+ # coordinated sound.
73
+ #
74
+ # See DiscreteMediaType.new for initialization parameters.
75
+ #
76
+ class VideoMedia < DiscreteMediaType
77
+ end
78
+
79
+ end
@@ -0,0 +1,32 @@
1
+ module MIME
2
+
3
+ #
4
+ # General purpose MIME errors.
5
+ #
6
+ class Error < StandardError ; end
7
+
8
+ #
9
+ # Undetectable MIME content type error.
10
+ #
11
+ class UnknownContentError < Error ; end
12
+
13
+ #
14
+ # Abstract class object initialization error. Many classes in the MIME
15
+ # library are abstract and will raise this error.
16
+ #
17
+ class AbstractClassError < Error
18
+
19
+ #
20
+ # A helper method for detecting the intialization of an object in an
21
+ # abstract class. +myself+ must always be *self* and +klass+ is the
22
+ # abstract class object.
23
+ #
24
+ def self.no_instantiation myself, klass
25
+ if myself.class == klass
26
+ raise AbstractClassError.new('cannot construct abstract class')
27
+ end
28
+ end
29
+
30
+ end
31
+
32
+ end
@@ -0,0 +1,34 @@
1
+ module MIME
2
+
3
+ #
4
+ # Storage for RFC 2822 headers.
5
+ #
6
+ class HeaderContainer
7
+
8
+ #
9
+ # Return a new header container object.
10
+ #
11
+ def initialize
12
+ @headers = Hash.new
13
+ end
14
+
15
+ #
16
+ # Convert all headers to their string equivalents and join them using the
17
+ # RFC 2822 CRLF line separator.
18
+ #
19
+ def to_s
20
+ @headers.collect do |name, value|
21
+ "#{name}: #{value}"
22
+ end.join("\r\n")
23
+ end
24
+
25
+ #
26
+ # Add the +name+/+value+ pair to the header container.
27
+ #
28
+ def add name, value
29
+ @headers.store(name, value)
30
+ end
31
+
32
+ end
33
+
34
+ end
@@ -0,0 +1,90 @@
1
+ module MIME
2
+ module Headers
3
+
4
+ #
5
+ # The RFC 2822 Internet message header fields.
6
+ #
7
+ module Internet
8
+
9
+ attr_reader :date, :from, :to
10
+ attr_reader :cc, :bcc, :reply_to, :message_id
11
+ attr_reader :comments, :keywords, :subject
12
+
13
+
14
+ #
15
+ # Required Headers
16
+ #
17
+
18
+ def date= date
19
+ @date = date
20
+ headers.add('Date', @date)
21
+ end
22
+
23
+ def from= list
24
+ @from = stringify_email_list(list)
25
+ headers.add('From', @from)
26
+ end
27
+
28
+ def to= list
29
+ @to = stringify_email_list(list)
30
+ headers.add('To', @to)
31
+ end
32
+
33
+
34
+ #
35
+ # Optional Headers
36
+ #
37
+
38
+ def cc= list
39
+ @cc = stringify_email_list(list)
40
+ headers.add('Cc', @cc)
41
+ end
42
+
43
+ def bcc= list
44
+ @bcc = stringify_email_list(list)
45
+ headers.add('Bcc', @bcc)
46
+ end
47
+
48
+ def reply_to= list
49
+ @reply_to = stringify_email_list(list)
50
+ headers.add('Reply-To', @reply_to)
51
+ end
52
+
53
+ #
54
+ # The message +id+ must contain an embedded "@" symbol. An example +id+
55
+ # might be <em>some-unique-id@domain.com</em>.
56
+ #
57
+ def message_id= id
58
+ @message_id = "<#{id}>"
59
+ headers.add('Message-ID', @message_id)
60
+ end
61
+
62
+ def comments= comments
63
+ @comments = comments
64
+ headers.add('Comments', @comments)
65
+ end
66
+
67
+ def keywords= keywords
68
+ @keywords = keywords
69
+ headers.add('Keywords', @keywords)
70
+ end
71
+
72
+ def subject= subject
73
+ @subject = subject
74
+ headers.add('Subject', @subject)
75
+ end
76
+
77
+
78
+ private
79
+
80
+ #
81
+ # +list+ may be a single email address or a Hash of _email_ => _name_
82
+ # pairs. Set _name_ to nil when it is unknown.
83
+ #
84
+ def stringify_email_list list
85
+ list.map {|email, name| name ? "#{name} <#{email}>" : email}.join(', ')
86
+ end
87
+
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,118 @@
1
+ module MIME
2
+ module Headers
3
+
4
+ #
5
+ # The RFC 2045 MIME message header fields.
6
+ #
7
+ module MIME
8
+
9
+ attr_reader :mime_version,
10
+ :content_description,
11
+ :content_disposition,
12
+ :content_id,
13
+ :content_transfer_encoding,
14
+ :content_type
15
+
16
+
17
+ #
18
+ # Describes the content, which can be useful for non-MIME clients.
19
+ #
20
+ def content_description= description
21
+ @content_description = description
22
+ headers.add('Content-Description', description)
23
+ end
24
+
25
+ #
26
+ # Specifies the disposition of the content relative to its enclosing
27
+ # message. Valid values for +disposition+ are _inline_ and _attachment_.
28
+ # Parameters can also be specified here; see the RFC for details.
29
+ #
30
+ # RFC 2183 Communicating Presentation Information in Internet Messages.
31
+ #
32
+ def content_disposition= disposition
33
+ @content_disposition = disposition
34
+ headers.add('Content-Disposition', disposition)
35
+ end
36
+
37
+ #
38
+ # Globally unique ID that identifies a top-level message or message
39
+ # entity. Content IDs can be used for referencing or caching purposes.
40
+ #
41
+ def content_id= id
42
+ @content_id = id
43
+ headers.add('Content-ID', "<#{id}>")
44
+ end
45
+
46
+ #
47
+ # The mechanism used for encoding the top-level message content.
48
+ #
49
+ # Common Encoding Mechanisms
50
+ # 1. 7bit
51
+ # 2. 8bit
52
+ # 3. binary
53
+ # 4. quoted-printable
54
+ # 5. base64
55
+ #
56
+ def content_transfer_encoding= encoding
57
+ @content_transfer_encoding = encoding
58
+ headers.add('Content-Transfer-Encoding', encoding)
59
+ end
60
+
61
+ #
62
+ # Specifies the media type and subtype of the content. +type+ will have
63
+ # the form <em>media-type/subtype</em>.
64
+ #
65
+ # Common Content Types
66
+ # 1. application/octet-stream
67
+ # 2. audio/mpeg
68
+ # 3. image/jpeg
69
+ # 4. text/plain
70
+ # 5. video/mpeg
71
+ #
72
+ def content_type= type
73
+ @content_type = type
74
+ headers.add('Content-Type', type)
75
+ end
76
+
77
+ #
78
+ # Currently only version 1.0 exists.
79
+ #
80
+ def mime_version= version
81
+ @mime_version = version
82
+ headers.add('MIME-Version', version)
83
+ end
84
+
85
+ protected
86
+
87
+ #
88
+ # +type+ is the disposition type of either "inline" or "attachment".
89
+ # +params+ is a Hash with zero or more of the following keys:
90
+ #
91
+ # * filename => name of file
92
+ # * creation-date => RFC2822 data-time
93
+ # * modification-date => RFC2822 data-time
94
+ # * read-date => RFC2822 data-time
95
+ # * size => file size in octets
96
+ #
97
+ # The values for the *-date keys may use Time::rfc2822.
98
+ #
99
+ def set_content_disposition type, params = {}
100
+ disposition = type
101
+
102
+ if params['filename']
103
+ params['filename'] = File.basename(params['filename'])
104
+ elsif self.respond_to?(:path)
105
+ params['filename'] = File.basename(self.path)
106
+ end
107
+
108
+ params.each do |name, value|
109
+ disposition << %Q[; #{name}="#{value}"] if value
110
+ end
111
+
112
+ self.content_disposition = disposition
113
+ end
114
+
115
+ end
116
+
117
+ end
118
+ end