smail 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,95 @@
1
+ class SMail
2
+ class Header #:nodoc: all
3
+ attr_reader :crlf, :head
4
+
5
+ def initialize(headers, smail)
6
+ @crlf = smail.crlf || "\r\n"
7
+
8
+ @order = Array.new
9
+ @head = Hash.new
10
+ headers.each do |header|
11
+ @order << header.first
12
+ if @head[header.first].nil?
13
+ @head[header.first] = Array.new
14
+ end
15
+ @head[header.first] << header.last
16
+ end
17
+
18
+ @header_names = @head.collect {|header,value| [header.downcase, header] }
19
+ end
20
+
21
+ def to_s #:nodoc:
22
+ header_pairs.collect {|pair| fold(pair.join(": "))}.join(@crlf) + @crlf
23
+ end
24
+
25
+ def header_names
26
+ @header_names.collect {|pair| pair.last }
27
+ end
28
+
29
+ def header_pairs
30
+ headers = []
31
+ seen = {}
32
+ seen.default = -1
33
+ @order.each do |header|
34
+ headers << [header, @head[header][seen[header] += 1]]
35
+ end
36
+
37
+ headers
38
+ end
39
+
40
+ def header(field)
41
+ return unless names = @header_names.assoc(field.downcase)
42
+ headers(field).first
43
+ end
44
+
45
+ def headers(field)
46
+ return unless names = @header_names.assoc(field.downcase)
47
+
48
+ @head[names.last]
49
+ end
50
+
51
+ def header_set(field, lines)
52
+ unless field =~ /^[\x21-\x39\x3b-\x7e]+$/
53
+ raise(ArgumentError, "Field name contains illegal characters")
54
+ end
55
+ unless field =~ /^[\w-]+$/
56
+ raise(ArgumentError, "Field name is not limited to hyphens and alphanumerics")
57
+ end
58
+
59
+ old_length = 0
60
+ if @header_names.assoc(field.downcase)
61
+ old_length = @head[@header_names.assoc(field.downcase).last].length
62
+ else
63
+ @header_names << [field.downcase, field]
64
+ # @order << field # FIXME: add new fields onto the end
65
+ end
66
+
67
+ @head[@header_names.assoc(field.downcase).last] = lines
68
+ if lines.length < old_length
69
+ # Remove the last x instances of field from @order
70
+ headers_to_remove = old_length - lines.length
71
+ headers_seen = 0
72
+ @order.collect! {|field_name|
73
+ if field_name.downcase == field.downcase
74
+ headers_seen += 1
75
+ next if headers_seen > headers_to_remove
76
+ end
77
+ field_name
78
+ }.compact!
79
+ elsif lines.length > old_length
80
+ # Add x instances of field to the end of @order
81
+ @order = @order + ([field] * (lines.length - old_length))
82
+ end
83
+
84
+ headers(field)
85
+ end
86
+
87
+ private
88
+
89
+ def fold(line)
90
+ # FIXME: this needs to actually fold the line
91
+ line.gsub( /^(.{1,78})(\s+|$)/ ){ $2.empty? ? $1 : "#{$1}#{@crlf}\t" }
92
+ end
93
+
94
+ end
95
+ end
@@ -0,0 +1,55 @@
1
+ class SMail
2
+
3
+ private
4
+
5
+ # We are liberal in what we accept.
6
+ PATTERN_CRLF = "\n\r|\r\n|\n|\r" #:nodoc:
7
+
8
+ RE_CRLF = Regexp.new(PATTERN_CRLF, Regexp::MULTILINE) #:nodoc:
9
+
10
+ def split_head_from_body(text)
11
+ # The body is a sequence of characters after the header separated by an empty line
12
+ if text =~ /(.*?(#{PATTERN_CRLF}))\2/m
13
+ return $1, $' || '', $2
14
+ else # The body is, of course, optional.
15
+ return text, "", "\r\n"
16
+ end
17
+ end
18
+
19
+ def read_header(head)
20
+ headers = headers_to_list(head)
21
+
22
+ SMail::Header.new(headers, self)
23
+ end
24
+
25
+ # Header fields are lines composed of a field name, followed by a colon (":"),
26
+ # followed by a field body, and terminated by CRLF. A field name MUST be
27
+ # composed of printable US-ASCII characters (i.e., characters that have values
28
+ # between 33 and 126, inclusive), except colon. A field body may be composed
29
+ # of any US-ASCII characters, except for CR and LF.
30
+
31
+ # However, a field body may contain CRLF when used in header "folding" and
32
+ # "unfolding" as described in section 2.2.3.
33
+
34
+ def headers_to_list(head)
35
+ headers = Array.new
36
+
37
+ head.split(RE_CRLF).each do |line|
38
+ if line.gsub!(/^\s+/, '') or line !~ /([^:]+):\s*(.*)/
39
+ # This is a continuation line, we fold it on to the end of the previous header
40
+ next if headers.empty? # Most likely an mbox From line, skip it
41
+
42
+ #headers.last.last << (headers.last.empty? ? line : " #{line}")
43
+ if headers.last.empty?
44
+ headers.last.last << line
45
+ else
46
+ headers.last.last << " #{line}"
47
+ end
48
+ else
49
+ headers << [$1, $2]
50
+ end
51
+ end
52
+
53
+ headers
54
+ end
55
+ end
@@ -0,0 +1,61 @@
1
+ # SMail
2
+ #
3
+ # A very simple library for parsing email messages.
4
+ #
5
+ # Based on Email::Simple from CPAN
6
+ #
7
+ # No decoding of any fields or the body is attempted, see SMail::MIME.
8
+
9
+ class SMail
10
+ # The line ending found in this email.
11
+ attr_reader :crlf
12
+ # The body text of the email.
13
+ attr_accessor :body
14
+
15
+ def initialize(text = '')
16
+ head, @body, @crlf = split_head_from_body(text)
17
+
18
+ @head = read_header(head)
19
+ end
20
+
21
+ # Returns the first value for the named header
22
+ def header(field)
23
+ @head.header(field)
24
+ end
25
+
26
+ # Returns an array containing every value for the named header, for the first instance
27
+ # see header
28
+ def headers(field)
29
+ @head.headers(field)
30
+ end
31
+
32
+ # Sets the header to contain the given data, if there is more than one existing header
33
+ # the extra headers are removed.
34
+ def header_set(field, line)
35
+ headers_set(field, line).first
36
+ end
37
+
38
+ # Sets the headers to contain the given data, passing in multiple lines results in
39
+ # multiple headers and order is retained.
40
+ def headers_set(field, *lines)
41
+ @head.header_set(field, lines)
42
+ end
43
+
44
+ def header=(header) #:nodoc:
45
+ # FIXME: takes a string and splits it??
46
+ end
47
+
48
+ # Returns the list of header names currently in the message. The order is not significant.
49
+ def header_names
50
+ @head.header_names
51
+ end
52
+
53
+ # Returns a list of pairs describing the contents of the header.
54
+ def header_pairs
55
+ @head.header_pairs
56
+ end
57
+
58
+ def to_s #:nodoc:
59
+ @head.to_s + @crlf + @body
60
+ end
61
+ end
@@ -0,0 +1,9 @@
1
+ class SMail
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 4
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
data/lib/smail.rb ADDED
@@ -0,0 +1,2 @@
1
+ # $Id: email_controller.rb 856 2006-10-26 03:09:39Z pete $
2
+ Dir[File.join(File.dirname(__FILE__), 'smail/**/*.rb')].sort.each { |lib| require lib }
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: smail
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.5
5
+ platform: ruby
6
+ authors:
7
+ - Matthew Walker
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-11-17 00:00:00 +11:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: matthew@walker.wattle.id.au
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - lib/smail.rb
26
+ - lib/smail/header.rb
27
+ - lib/smail/parser.rb
28
+ - lib/smail/smail.rb
29
+ - lib/smail/version.rb
30
+ has_rdoc: true
31
+ homepage: http://gemcutter.org/gems/smail
32
+ licenses: []
33
+
34
+ post_install_message:
35
+ rdoc_options: []
36
+
37
+ require_paths:
38
+ - lib
39
+ required_ruby_version: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: "0"
44
+ version:
45
+ required_rubygems_version: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ version:
51
+ requirements: []
52
+
53
+ rubyforge_project:
54
+ rubygems_version: 1.3.5
55
+ signing_key:
56
+ specification_version: 3
57
+ summary: A simple RFC2822 email parser
58
+ test_files: []
59
+