forked from kaisellgren/mailer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ir_header.dart
243 lines (195 loc) · 7.1 KB
/
ir_header.dart
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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
part of 'internal_representation.dart';
abstract class _IRHeader extends _IROutput {
final String _name;
static final _b64prefix = convert.utf8.encode('=?utf-8?B?'),
_b64postfix = convert.utf8.encode('?='),
_$eol = convert.utf8.encode(eol),
_$eolSpace = convert.utf8.encode('$eol '),
_$spaceLt = convert.utf8.encode(' <'),
_$gt = convert.utf8.encode('>'),
_$commaSpace = convert.utf8.encode(', '),
_$colonSpace = convert.utf8.encode(': ');
static final int _b64Length = _b64prefix.length + _b64postfix.length;
Stream<List<int>> _outValue(String? value) async* {
yield convert.utf8.encode(_name);
yield _$colonSpace;
if (value != null) yield convert.utf8.encode(value);
yield _$eol;
}
// Outputs this header's name and the given [value] encoded as base64.
// Every chunk starts with ' ' and ends with eol.
// Call _outValueB64 after an eol.
Stream<List<int>> _outValueB64(String value) async* {
yield convert.utf8.encode(_name);
yield _$colonSpace;
yield* _outB64(value);
yield _$eol;
}
/// Outputs the given [addresses].
Stream<List<int>> _outAddressesValue(Iterable<Address> addresses,
_IRMetaInformation irMetaInformation) async* {
yield convert.utf8.encode(_name);
yield _$colonSpace;
var len = 2, //2 = _$commaSpace
second = false;
for (final address in addresses) {
final name = address.sanitizedName, maddr = address.sanitizedAddress;
var adrlen = maddr.length;
if (name != null) {
adrlen += name.length + 3;
} //not accurate but good enough
if (second) {
yield _$commaSpace;
if (len + adrlen > maxEncodedLength) {
len = 2;
yield _$eolSpace;
}
} else {
second = true;
}
if (name == null) {
yield convert.utf8.encode(maddr);
} else {
if (_shallB64(name, irMetaInformation)) {
yield* _outB64(name);
} else {
yield convert.utf8.encode(name);
}
yield _$spaceLt;
yield convert.utf8.encode(maddr);
yield _$gt;
}
len += adrlen;
}
yield _$eol;
}
// Outputs the given [value] encoded as base64.
static Stream<List<int>> _outB64(String value) async* {
// Encode with base64.
var availableLengthForBase64 = maxEncodedLength - _b64Length;
// Length after base64: ceil(n / 3) * 4
var lengthBeforeBase64 = (availableLengthForBase64 ~/ 4) * 3;
var availableLength = lengthBeforeBase64;
// At least 10 chars (random length).
if (availableLength < 10) availableLength = 10;
var second = false;
for (var d in split(convert.utf8.encode(value), availableLength)) {
if (second) {
yield _$eolSpace;
} else {
second = true;
}
yield _b64prefix;
yield convert.utf8.encode(convert.base64.encode(d));
yield _b64postfix;
}
}
static bool _shallB64(String value, _IRMetaInformation irMetaInformation) {
// If we have a maxLineLength is it the length of utf8 characters or
// the length of utf8 bytes?
// Just to be safe we'll count the bytes.
var byteLength = convert.utf8.encode(value).length;
return (byteLength > maxLineLength ||
!isPrintableRegExp.hasMatch(value) ||
// Make sure that text which looks like an encoded text is encoded.
value.contains('=?') ||
(!irMetaInformation.capabilities.smtpUtf8 &&
value.contains(RegExp(r'[^\x20-\x7E]'))));
}
/*
Stream<List<int>> _outValue8(List<int> value) => Stream.fromIterable(
[_name, ': '].map(utf8.encode).followedBy([value, _eol8]));
*/
_IRHeader(this._name);
}
class _IRHeaderText extends _IRHeader {
final String _value;
_IRHeaderText(String name, this._value) : super(name);
@override
Stream<List<int>> out(_IRMetaInformation irMetaInformation) =>
_IRHeader._shallB64(_value, irMetaInformation)
? _outValueB64(_value)
: _outValue(_value);
}
class _IRHeaderAddress extends _IRHeader {
final Address _address;
_IRHeaderAddress(String name, this._address) : super(name);
@override
Stream<List<int>> out(_IRMetaInformation irMetaInformation) =>
_outAddressesValue([_address], irMetaInformation);
}
class _IRHeaderAddresses extends _IRHeader {
final Iterable<Address> _addresses;
_IRHeaderAddresses(String name, this._addresses) : super(name);
@override
Stream<List<int>> out(_IRMetaInformation irMetaInformation) =>
_outAddressesValue(_addresses, irMetaInformation);
}
class _IRHeaderContentType extends _IRHeader {
final String _boundary;
final _MultipartType _multipartType;
_IRHeaderContentType(this._boundary, this._multipartType)
: super('content-type');
@override
Stream<List<int>> out(_IRMetaInformation irMetaInformation) {
return _outValue(
'multipart/${_describeEnum(_multipartType)};boundary="$_boundary"');
}
}
class _IRHeaderDate extends _IRHeader {
final DateTime _dateTime;
static final DateFormat _dateFormat =
DateFormat('EEE, dd MMM yyyy HH:mm:ss +0000');
_IRHeaderDate(String name, this._dateTime) : super(name);
@override
Stream<List<int>> out(_IRMetaInformation irMetaInformation) =>
_outValue(_dateFormat.format(_dateTime.toUtc()));
}
Iterable<_IRHeader> _buildHeaders(Message message) {
const noCustom = ['content-type', 'mime-version'];
final headers = <_IRHeader>[];
var msgHeader = message.headers;
// Add all custom headers which are not in [noCustom].
msgHeader.forEach((name, value) {
name = name.toLowerCase();
if (noCustom.contains(name)) return;
if (value is String && value.contains('@')) {
headers.add(_IRHeaderAddress(name, Address(value)));
} else if (value is String) {
headers.add(_IRHeaderText(name, value));
} else if (value is DateTime) {
headers.add(_IRHeaderDate(name, value));
} else if (value is Address) {
headers.add(_IRHeaderAddress(name, value));
} else if (value is Iterable<Address>) {
headers.add(_IRHeaderAddresses(name, value));
} else if (value is Iterable<String> &&
value.every((s) => (s).contains('@'))) {
headers.add(_IRHeaderAddresses(name, value.map((a) => Address(a))));
} else {
throw InvalidHeaderException('Type of value for $name is invalid');
}
});
if (!msgHeader.containsKey('subject') && message.subject != null) {
headers.add(_IRHeaderText('subject', message.subject!));
}
if (!msgHeader.containsKey('from')) {
headers.add(_IRHeaderAddress('from', message.fromAsAddress));
}
if (!msgHeader.containsKey('to')) {
var tos = message.recipientsAsAddresses;
if (tos.isNotEmpty) headers.add(_IRHeaderAddresses('to', tos));
}
if (!msgHeader.containsKey('cc')) {
var ccs = message.ccsAsAddresses;
if (ccs.isNotEmpty) headers.add(_IRHeaderAddresses('cc', ccs));
}
if (!msgHeader.containsKey('date')) {
headers.add(_IRHeaderDate('date', DateTime.now()));
}
if (!msgHeader.containsKey('x-mailer')) {
headers.add(_IRHeaderText('x-mailer', 'Dart Mailer library'));
}
headers.add(_IRHeaderText('mime-version', '1.0'));
return headers;
}