-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathindex.js
219 lines (184 loc) · 5.51 KB
/
index.js
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
'use strict';
const MechanismError = require('./mechanismerror');
const MECHANISMS = require('./mechanisms');
const PREFIXES = require('./prefixes');
const versionRegex = /^v=spf1/i;
const mechanismRegex = /(\+|-|~|\?)?(.+)/i;
// * Values that will be set for every mechanism:
// Prefix
// Type
// Value
// PrefixDesc
// Description
function parseTerm(term, messages) {
// Match up the prospective mechanism against the mechanism regex
let parts = term.match(mechanismRegex);
let record = {};
// It matched! Let's try to see which specific mechanism type it matches
if (parts !== null) {
// Break up the parts into their pieces
let prefix = parts[1];
let mechanism = parts[2];
// Check qualifier
if (prefix) {
record.prefix = prefix;
record.prefixdesc = PREFIXES[prefix];
}
else if (versionRegex.test(mechanism)) {
record.prefix = 'v';
}
else {
// Default to "pass" qualifier
record.prefix = '+';
record.prefixdesc = PREFIXES['+'];
}
let found = false;
for (let name in MECHANISMS) {
if (Object.prototype.hasOwnProperty.call(MECHANISMS, name)) {
const settings = MECHANISMS[name];
// Matches mechanism spec
if (settings.pattern.test(mechanism)) {
found = true;
record.type = name;
record.description = settings.description;
if (settings.validate) {
try {
let value = settings.validate.call(settings, mechanism);
if (typeof value !== 'undefined' && value !== null) {
record.value = value;
}
}
catch (err) {
if (err instanceof MechanismError) {
// Error validating mechanism
messages.push({
message: err.message,
type: err.type
});
break;
}
// else {
// throw err;
// }
}
}
break;
}
}
}
if (!found) {
messages.push({
message: `Unknown standalone term '${mechanism}'`,
type: 'error'
});
}
}
// NOTE: I don't think this branch could ever be hit...
// else {
// // Term didn't match mechanism regex
// messages.push({
// message: `Unknown term "${term}"`,
// type: 'error'
// });
//
// return;
// }
return record;
}
function parse(record) {
// Remove whitespace
record = record.trim();
let records = {
mechanisms: [],
messages: [],
// Valid flag will be changed at end of function
valid: false
};
if (!versionRegex.test(record)) {
// throw new Error();
records.messages.push({
message: 'No valid version found, record must start with \'v=spf1\'',
type: 'error'
});
return records;
}
let terms = record.split(/\s+/);
// Give an error for duplicate Modifiers
let duplicateMods = terms
.filter(x => new RegExp('=').test(x))
.map(x => x.match(/^(.*?)=/)[1])
.filter((x, i, arr) => {
return arr.includes(x, i + 1);
});
if (duplicateMods && duplicateMods.length > 0) {
records.messages.push({
type: 'error',
message: `Modifiers like "${duplicateMods[0]}" may appear only once in an SPF string`
});
return records;
}
// Give warning for duplicate mechanisms
let duplicateMechs = terms
.map(x => x.replace(/^(\+|-|~|\?)/, ''))
.filter((x, i, arr) => {
return arr.includes(x, i + 1);
});
if (duplicateMechs && duplicateMechs.length > 0) {
records.messages.push({
type: 'warning',
message: 'One or more duplicate mechanisms were found in the policy'
});
}
for (let term of terms) {
let mechanism = parseTerm(term, records.messages);
if (mechanism) {
records.mechanisms.push(mechanism);
}
}
// See if there's an "all" or "redirect" at the end of the policy
if (records.mechanisms.length > 0) {
// More than one modifier like redirect or exp is invalid
// if (records.mechanisms.filter(x => x.type === 'redirect').length > 1 || records.mechanisms.filter(x => x.type === 'exp').length > 1) {
// records.messages.push({
// type: 'error',
// message: 'Modifiers like "redirect" and "exp" can only appear once in an SPF string'
// });
// return records;
// }
// let lastMech = records.mechanisms[records.mechanisms.length - 1];
let redirectMech = records.mechanisms.find(x => x.type === 'redirect');
let allMech = records.mechanisms.find(x => x.type === 'all');
// if (lastMech.type !== 'all' && lastMech !== 'redirect') {
if (!allMech && !redirectMech) {
records.messages.push({
type: 'warning',
message: 'SPF strings should always either use an "all" mechanism or a "redirect" modifier to explicitly terminate processing.'
});
}
// Give a warning if "all" is not last mechanism in policy
let allIdx = records.mechanisms.findIndex(x => x.type === 'all');
if (allIdx > -1) {
if (allIdx < records.mechanisms.length - 1) {
records.messages.push({
type: 'warning',
message: 'One or more mechanisms were found after the "all" mechanism. These mechanisms will be ignored'
});
}
}
// Give a warning if there's a redirect modifier AND an "all" mechanism
if (redirectMech && allMech) {
records.messages.push({
type: 'warning',
message: 'The "redirect" modifier will not be used, because the SPF string contains an "all" mechanism. A "redirect" modifier is only used after all mechanisms fail to match, but "all" will always match'
});
}
}
// If there are no messages, delete the key from "records"
if (!Object.keys(records.messages).length > 0) {
delete records.messages;
}
records.valid = true;
return records;
}
parse.parseTerm = parseTerm;
module.exports = parse;