mwalker-smail 0.0.3
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.
- 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 +57 -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, $3 || '', $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,57 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: mwalker-smail
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.3
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Matthew Walker
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
|
|
12
|
+
date: 2009-05-16 00:00:00 -07: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: false
|
|
31
|
+
homepage: http://github.com/mwalker/smail
|
|
32
|
+
post_install_message:
|
|
33
|
+
rdoc_options: []
|
|
34
|
+
|
|
35
|
+
require_paths:
|
|
36
|
+
- lib
|
|
37
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
38
|
+
requirements:
|
|
39
|
+
- - ">="
|
|
40
|
+
- !ruby/object:Gem::Version
|
|
41
|
+
version: "0"
|
|
42
|
+
version:
|
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: "0"
|
|
48
|
+
version:
|
|
49
|
+
requirements: []
|
|
50
|
+
|
|
51
|
+
rubyforge_project:
|
|
52
|
+
rubygems_version: 1.2.0
|
|
53
|
+
signing_key:
|
|
54
|
+
specification_version: 2
|
|
55
|
+
summary: A simple RFC2822 email parser
|
|
56
|
+
test_files: []
|
|
57
|
+
|