forked from mikel/mail
-
Notifications
You must be signed in to change notification settings - Fork 0
/
unstructured_field.rb
182 lines (163 loc) · 6.44 KB
/
unstructured_field.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# encoding: utf-8
require 'mail/fields/common/common_field'
module Mail
# Provides access to an unstructured header field
#
# ===Per RFC 2822:
# 2.2.1. Unstructured Header Field Bodies
#
# Some field bodies in this standard are defined simply as
# "unstructured" (which is specified below as any US-ASCII characters,
# except for CR and LF) with no further restrictions. These are
# referred to as unstructured field bodies. Semantically, unstructured
# field bodies are simply to be treated as a single line of characters
# with no further processing (except for header "folding" and
# "unfolding" as described in section 2.2.3).
class UnstructuredField
include Mail::CommonField
include Mail::Utilities
def initialize(name, value, charset = nil)
self.charset = charset
@errors = []
if charset
self.charset = charset
else
if value.to_s.respond_to?(:encoding)
self.charset = value.to_s.encoding
else
self.charset = $KCODE
end
end
self.name = name
self.value = value
self
end
def charset
@charset
end
def charset=(val)
@charset = val
end
def errors
@errors
end
def encoded
do_encode(self.name)
end
def decoded
do_decode
end
def default
decoded
end
def parse # An unstructured field does not parse
self
end
private
def do_encode(name)
value.nil? ? '' : "#{wrapped_value}\r\n"
end
def do_decode
result = value.blank? ? nil : Encodings.decode_encode(value, :decode)
result.encode!(value.encoding || "UTF-8") if RUBY_VERSION >= '1.9' && !result.blank?
result
end
# 2.2.3. Long Header Fields
#
# Each header field is logically a single line of characters comprising
# the field name, the colon, and the field body. For convenience
# however, and to deal with the 998/78 character limitations per line,
# the field body portion of a header field can be split into a multiple
# line representation; this is called "folding". The general rule is
# that wherever this standard allows for folding white space (not
# simply WSP characters), a CRLF may be inserted before any WSP. For
# example, the header field:
#
# Subject: This is a test
#
# can be represented as:
#
# Subject: This
# is a test
#
# Note: Though structured field bodies are defined in such a way that
# folding can take place between many of the lexical tokens (and even
# within some of the lexical tokens), folding SHOULD be limited to
# placing the CRLF at higher-level syntactic breaks. For instance, if
# a field body is defined as comma-separated values, it is recommended
# that folding occur after the comma separating the structured items in
# preference to other places where the field could be folded, even if
# it is allowed elsewhere.
def wrapped_value # :nodoc:
@folded_line = []
@unfolded_line = decoded.to_s.split(/[ \t]/)
fold("#{name}: ".length)
wrap_lines(name, @folded_line)
end
# 6.2. Display of 'encoded-word's
#
# When displaying a particular header field that contains multiple
# 'encoded-word's, any 'linear-white-space' that separates a pair of
# adjacent 'encoded-word's is ignored. (This is to allow the use of
# multiple 'encoded-word's to represent long strings of unencoded text,
# without having to separate 'encoded-word's where spaces occur in the
# unencoded text.)
def wrap_lines(name, folded_lines)
result = []
index = 0
result[index] = "#{name}: #{folded_lines.shift}"
result.concat(folded_lines)
result.join("\r\n\s")
end
def fold(prepend = 0) # :nodoc:
encoding = @charset.to_s.upcase.gsub('_', '-')
while !@unfolded_line.empty?
encoded = false
limit = 78 - prepend
line = ""
while !@unfolded_line.empty?
break unless word = @unfolded_line.first.dup
# Remember whether it was non-ascii before we encode it ('cause then we can't tell anymore)
non_ascii = word.not_ascii_only?
encoded_word = encode(word)
# Skip to next line if we're going to go past the limit
# Unless this is the first word, in which case we're going to add it anyway
# Note: This means that a word that's longer than 998 characters is going to break the spec. Please fix if this is a problem for you.
# (The fix, it seems, would be to use encoded-word encoding on it, because that way you can break it across multiple lines and
# the linebreak will be ignored)
break if !line.empty? && (line.length + encoded_word.length + 1 > limit)
# If word was the first non-ascii word, we're going to make the entire line encoded and we're going to reduce the limit accordingly
if non_ascii && !encoded
encoded = true
encoded_word_safify!(line)
limit = limit - 8 - encoding.length # minus the =?...?Q?...?= part, the possible leading white-space, and the name of the encoding
end
# Remove the word from the queue ...
@unfolded_line.shift
# ... add it in encoded form to the current line
line << " " unless line.empty?
encoded_word_safify!(encoded_word) if encoded
line << encoded_word
end
# Add leading whitespace if both this and the last line were encoded, because whitespace between two encoded-words is ignored when decoding
line = " " + line if encoded && @folded_line.last && @folded_line.last.index('=?') == 0
# Encode the line if necessary
line = "=?#{encoding}?Q?#{line.gsub(/ /, '_')}?=" if encoded
# Add the line to the output and reset the prepend
@folded_line << line
prepend = 0
end
end
def encode(value)
value.encode!(charset) if defined?(Encoding) && charset
(value.not_ascii_only? ? [value].pack("M").gsub("=\n", '') : value).gsub("\r", "=0D").gsub("\n", "=0A")
end
def encoded_word_safify!(value)
value.gsub!(/"/, '=22')
value.gsub!(/\(/, '=28')
value.gsub!(/\)/, '=29')
value.gsub!(/\?/, '=3F')
value.gsub!(/_/, '=5F')
end
end
end