1 /*****************************************************************
2 * Licensed to the Apache Software Foundation (ASF) under one *
3 * or more contributor license agreements. See the NOTICE file *
4 * distributed with this work for additional information *
5 * regarding copyright ownership. The ASF licenses this file *
6 * to you under the Apache License, Version 2.0 (the *
7 * "License"); you may not use this file except in compliance *
8 * with the License. You may obtain a copy of the License at *
9 * *
10 * http://www.apache.org/licenses/LICENSE-2.0 *
11 * *
12 * Unless required by applicable law or agreed to in writing, *
13 * software distributed under the License is distributed on an *
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
15 * KIND, either express or implied. See the License for the *
16 * specific language governing permissions and limitations *
17 * under the License. *
18 ****************************************************************/
19
20
21 package org.apache.james.jspf.parser;
22
23 import org.apache.james.jspf.core.Configuration;
24 import org.apache.james.jspf.core.Directive;
25 import org.apache.james.jspf.core.Logger;
26 import org.apache.james.jspf.core.Mechanism;
27 import org.apache.james.jspf.core.Modifier;
28 import org.apache.james.jspf.core.SPF1Constants;
29 import org.apache.james.jspf.core.SPF1Record;
30 import org.apache.james.jspf.core.SPFRecordParser;
31 import org.apache.james.jspf.exceptions.NeutralException;
32 import org.apache.james.jspf.exceptions.NoneException;
33 import org.apache.james.jspf.exceptions.PermErrorException;
34
35 import java.util.ArrayList;
36 import java.util.Collection;
37 import java.util.Collections;
38 import java.util.Iterator;
39 import java.util.List;
40 import java.util.regex.Matcher;
41 import java.util.regex.Pattern;
42
43 /***
44 * This class is used to parse SPF1-Records from their textual form to an
45 * SPF1Record object that is composed by 2 collections: directives and
46 * modifiers.
47 *
48 * The parsing is modular and get informations from Mechanism and Modifiers
49 * classes declared in the org/apache/james/jspf/parser/jspf.default.terms file.
50 *
51 * Each term implementation provide its own REGEX in the REGEX static public
52 * field. This parser simply join all the regexp in a single "alternative"
53 * pattern and count the number of catch groups (brackets) assigned to each
54 * regex fragment.
55 *
56 * SO it creates a big regex and an array where it store what term is associated
57 * to each catch group of the big regex.
58 *
59 * If the regex matches the input vspf1 record then it start looking for the
60 * matched group (not null) and lookup the term that created that part of the
61 * regex.
62 *
63 * With this informations it creates a new instance of the term and, if the term
64 * is Configurable it calls the config() method passing to it only the specific
65 * subset of the MatchResult (using the MatchResultSubset).
66 *
67 * TODO doubts about the specification - redirect or exp with no domain-spec are
68 * evaluated as an unknown-modifiers according to the current spec (it does not
69 * make too much sense) - top-label is defined differently in various specs.
70 * We'll have to review the code. -
71 * http://data.iana.org/TLD/tlds-alpha-by-domain.txt (we should probably beeter
72 * use and alpha sequence being at least 2 chars - Somewhere is defined as "."
73 * TLD [ "." ] - Otherwise defined as ( *alphanum ALPHA *alphanum ) / (
74 * 1*alphanum "-" *( * alphanum / "-" ) alphanum )
75 *
76 * @see org.apache.james.jspf.core.SPF1Record
77 *
78 */
79 public class DefaultSPF1Parser implements SPFRecordParser {
80
81 /***
82 * Regex based on http://www.ietf.org/rfc/rfc4408.txt.
83 * This will be the next official SPF-Spec
84 */
85
86
87
88
89
90
91 /***
92 * ABNF: qualifier = "+" / "-" / "?" / "~"
93 */
94 private static final String QUALIFIER_PATTERN = "[" + "//"
95 + SPF1Constants.PASS + "//" + SPF1Constants.FAIL + "//"
96 + SPF1Constants.NEUTRAL + "//" + SPF1Constants.SOFTFAIL + "]";
97
98 private Pattern termsSeparatorPattern = null;
99
100 private Pattern termPattern = null;
101
102 private int TERM_STEP_REGEX_QUALIFIER_POS;
103
104 private int TERM_STEP_REGEX_MECHANISM_POS;
105
106 private int TERM_STEP_REGEX_MODIFIER_POS;
107
108 private List matchResultPositions;
109
110 private Logger log;
111
112 private TermsFactory termsFactory;
113
114 /***
115 * Constructor. Creates all the values needed to run the parsing
116 *
117 * @param loggerThe logger to use
118 */
119 public DefaultSPF1Parser(Logger logger, TermsFactory termsFactory) {
120 this.log = logger;
121 this.termsFactory = termsFactory;
122
123 /***
124 * ABNF: mechanism = ( all / include / A / MX / PTR / IP4 / IP6 / exists )
125 */
126 String MECHANISM_REGEX = createRegex(termsFactory.getMechanismsCollection());
127
128 /***
129 * ABNF: modifier = redirect / explanation / unknown-modifier
130 */
131 String MODIFIER_REGEX = "(" + createRegex(termsFactory.getModifiersCollection()) + ")";
132
133 /***
134 * ABNF: directive = [ qualifier ] mechanism
135 */
136 String DIRECTIVE_REGEX = "(" + QUALIFIER_PATTERN + "?)("
137 + MECHANISM_REGEX + ")";
138
139 /***
140 * ABNF: ( directive / modifier )
141 */
142 String TERM_REGEX = "(?:" + MODIFIER_REGEX + "|" + DIRECTIVE_REGEX
143 + ")";
144
145 /***
146 * ABNF: 1*SP
147 */
148 String TERMS_SEPARATOR_REGEX = "[ ]+";
149
150 termsSeparatorPattern = Pattern.compile(TERMS_SEPARATOR_REGEX);
151 termPattern = Pattern.compile(TERM_REGEX);
152
153 initializePositions();
154 }
155
156 /***
157 * Fill in the matchResultPositions ArrayList. This array simply map each
158 * regex matchgroup to the Term class that originated that part of the
159 * regex.
160 */
161 private void initializePositions() {
162 ArrayList matchResultPositions = new ArrayList();
163
164
165 int posIndex = 0;
166 matchResultPositions.ensureCapacity(posIndex + 1);
167 matchResultPositions.add(posIndex, null);
168
169 Iterator i;
170
171 TERM_STEP_REGEX_MODIFIER_POS = ++posIndex;
172 matchResultPositions.ensureCapacity(posIndex + 1);
173 matchResultPositions.add(TERM_STEP_REGEX_MODIFIER_POS, null);
174 i = termsFactory.getModifiersCollection().iterator();
175 while (i.hasNext()) {
176 TermDefinition td = (TermDefinition) i.next();
177 int size = td.getMatchSize() + 1;
178 for (int k = 0; k < size; k++) {
179 posIndex++;
180 matchResultPositions.ensureCapacity(posIndex + 1);
181 matchResultPositions.add(posIndex, td);
182 }
183 }
184
185 TERM_STEP_REGEX_QUALIFIER_POS = ++posIndex;
186 matchResultPositions.ensureCapacity(posIndex + 1);
187 matchResultPositions.add(posIndex, null);
188
189 TERM_STEP_REGEX_MECHANISM_POS = ++posIndex;
190 matchResultPositions.ensureCapacity(posIndex + 1);
191 matchResultPositions.add(TERM_STEP_REGEX_MECHANISM_POS, null);
192 i = termsFactory.getMechanismsCollection().iterator();
193 while (i.hasNext()) {
194 TermDefinition td = (TermDefinition) i.next();
195 int size = td.getMatchSize() + 1;
196 for (int k = 0; k < size; k++) {
197 posIndex++;
198 matchResultPositions.ensureCapacity(posIndex + 1);
199 matchResultPositions.add(posIndex, td);
200 }
201 }
202
203 if (log.isDebugEnabled()) {
204 log.debug("Parsing catch group positions: Modifiers["
205 + TERM_STEP_REGEX_MODIFIER_POS + "] Qualifier["
206 + TERM_STEP_REGEX_QUALIFIER_POS + "] Mechanism["
207 + TERM_STEP_REGEX_MECHANISM_POS + "]");
208 for (int k = 0; k < matchResultPositions.size(); k++) {
209 log
210 .debug(k
211 + ") "
212 + (matchResultPositions.get(k) != null ? ((TermDefinition) matchResultPositions
213 .get(k)).getPattern().pattern()
214 : null));
215 }
216 }
217
218 this.matchResultPositions = Collections.synchronizedList(matchResultPositions);
219 }
220
221 /***
222 * Loop the classes searching for a String static field named
223 * staticFieldName and create an OR regeex like this:
224 * (?:FIELD1|FIELD2|FIELD3)
225 *
226 * @param classes
227 * classes to analyze
228 * @param staticFieldName
229 * static field to concatenate
230 * @return regex The regex
231 */
232 private String createRegex(Collection commandMap) {
233 StringBuffer modifierRegex = new StringBuffer();
234 Iterator i = commandMap.iterator();
235 boolean first = true;
236 while (i.hasNext()) {
237 if (first) {
238 modifierRegex.append("(?:(");
239 first = false;
240 } else {
241 modifierRegex.append(")|(");
242 }
243 Pattern pattern = ((TermDefinition) i.next()).getPattern();
244 modifierRegex.append(pattern.pattern());
245 }
246 modifierRegex.append("))");
247 return modifierRegex.toString();
248 }
249
250 /***
251 * @see org.apache.james.jspf.parser.SPFRecordParser#parse(java.lang.String)
252 */
253 public SPF1Record parse(String spfRecord) throws PermErrorException,
254 NoneException, NeutralException {
255
256 log.debug("Start parsing SPF-Record: " + spfRecord);
257
258 SPF1Record result = new SPF1Record();
259
260
261 if (spfRecord.toLowerCase().startsWith(SPF1Constants.SPF_VERSION + " ") || spfRecord.equalsIgnoreCase(SPF1Constants.SPF_VERSION)) {
262 if (!spfRecord.toLowerCase().startsWith(SPF1Constants.SPF_VERSION + " ")) throw new NeutralException("Empty SPF Record");
263 } else {
264 throw new NoneException("No valid SPF Record: " + spfRecord);
265 }
266
267
268 String[] terms = termsSeparatorPattern.split(spfRecord.replaceFirst(
269 SPF1Constants.SPF_VERSION, ""));
270
271
272 for (int i = 0; i < terms.length; i++) {
273 if (terms[i].length() > 0) {
274 Matcher termMatcher = termPattern.matcher(terms[i]);
275 if (!termMatcher.matches()) {
276 throw new PermErrorException("Term [" + terms[i]
277 + "] is not syntactically valid: "
278 + termPattern.pattern());
279 }
280
281
282
283 String modifierString = termMatcher
284 .group(TERM_STEP_REGEX_MODIFIER_POS);
285
286 if (modifierString != null) {
287
288 Modifier mod = (Modifier) lookupAndCreateTerm(termMatcher,
289 TERM_STEP_REGEX_MODIFIER_POS);
290
291 if (mod.enforceSingleInstance()) {
292 Iterator it = result.getModifiers().iterator();
293 while (it.hasNext()) {
294 if (it.next().getClass().equals(mod.getClass())) {
295 throw new PermErrorException("More than one "
296 + modifierString
297 + " found in SPF-Record");
298 }
299 }
300 }
301
302 result.getModifiers().add(mod);
303
304 } else {
305
306 String qualifier = termMatcher
307 .group(TERM_STEP_REGEX_QUALIFIER_POS);
308
309 Object mech = lookupAndCreateTerm(termMatcher,
310 TERM_STEP_REGEX_MECHANISM_POS);
311
312 result.getDirectives().add(
313 new Directive(qualifier, (Mechanism) mech, log.getChildLogger(qualifier+"directive")));
314
315 }
316
317 }
318 }
319
320 return result;
321 }
322
323 /***
324 * @param res
325 * the MatchResult
326 * @param start
327 * the position where the terms starts
328 * @return
329 * @throws PermErrorException
330 */
331 private Object lookupAndCreateTerm(Matcher res, int start)
332 throws PermErrorException {
333 for (int k = start + 1; k < res.groupCount(); k++) {
334 if (res.group(k) != null && k != TERM_STEP_REGEX_QUALIFIER_POS) {
335 TermDefinition c = (TermDefinition) matchResultPositions.get(k);
336 Configuration subres = new MatcherBasedConfiguration(res, k, c
337 .getMatchSize());
338 try {
339 return termsFactory.createTerm(c.getTermDef(), subres);
340 } catch (InstantiationException e) {
341 e.printStackTrace();
342
343 throw new IllegalStateException("Unexpected error creating term: " + e.getMessage());
344 }
345
346 }
347 }
348 return null;
349 }
350
351 }