smail 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/smail/header.rb +95 -0
- data/lib/smail/parser.rb +55 -0
- data/lib/smail/smail.rb +61 -0
- data/lib/smail/version.rb +9 -0
- data/lib/smail.rb +2 -0
- metadata +59 -0
data/lib/smail/header.rb
ADDED
@@ -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
|
data/lib/smail/parser.rb
ADDED
@@ -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
|
data/lib/smail/smail.rb
ADDED
@@ -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
|
data/lib/smail.rb
ADDED
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
|
+
|