mime 0.1

Sign up to get free protection for your applications and to get access to all the features.
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