EMMA Coverage Report (generated Thu Nov 19 17:07:02 CET 2009)
[all classes][org.apache.james.jdkim.tagvalue]

COVERAGE SUMMARY FOR SOURCE FILE [SignatureRecordImpl.java]

nameclass, %method, %block, %line, %
SignatureRecordImpl.java100% (1/1)100% (23/23)96%  (689/721)96%  (130/136)

COVERAGE BREAKDOWN BY CLASS AND METHOD

nameclass, %method, %block, %line, %
     
class SignatureRecordImpl100% (1/1)100% (23/23)96%  (689/721)96%  (130/136)
getHashKeyType (): CharSequence 100% (1/1)59%  (17/29)80%  (4/5)
getBodyHashLimit (): int 100% (1/1)79%  (11/14)75%  (3/4)
getHashAlgo (): CharSequence 100% (1/1)89%  (66/74)86%  (6/7)
getHeaderCanonicalisationMethod (): String 100% (1/1)90%  (18/20)80%  (4/5)
getBodyCanonicalisationMethod (): String 100% (1/1)90%  (19/21)80%  (4/5)
validate (): void 100% (1/1)97%  (169/174)97%  (31/32)
<static initializer> 100% (1/1)100% (4/4)100% (1/1)
SignatureRecordImpl (String): void 100% (1/1)100% (4/4)100% (2/2)
dkimQuotedPrintableDecode (CharSequence): String 100% (1/1)100% (160/160)100% (34/34)
getBodyHash (): byte [] 100% (1/1)100% (7/7)100% (1/1)
getDToken (): CharSequence 100% (1/1)100% (4/4)100% (1/1)
getDefault (String): CharSequence 100% (1/1)100% (19/19)100% (3/3)
getHashMethod (): CharSequence 100% (1/1)100% (32/32)100% (5/5)
getHeaders (): List 100% (1/1)100% (8/8)100% (1/1)
getIdentity (): CharSequence 100% (1/1)100% (5/5)100% (1/1)
getIdentityLocalPart (): CharSequence 100% (1/1)100% (13/13)100% (3/3)
getRecordLookupMethods (): List 100% (1/1)100% (40/40)100% (6/6)
getSelector (): CharSequence 100% (1/1)100% (4/4)100% (1/1)
getSignature (): byte [] 100% (1/1)100% (7/7)100% (1/1)
init (): void 100% (1/1)100% (54/54)100% (11/11)
setBodyHash (byte []): void 100% (1/1)100% (11/11)100% (3/3)
setSignature (byte []): void 100% (1/1)100% (11/11)100% (3/3)
toUnsignedString (): String 100% (1/1)100% (6/6)100% (1/1)

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 
20package org.apache.james.jdkim.tagvalue;
21 
22import java.util.Arrays;
23import java.util.LinkedList;
24import java.util.List;
25import java.util.regex.Pattern;
26 
27import org.apache.commons.codec.binary.Base64;
28import org.apache.james.jdkim.api.SignatureRecord;
29 
30public class SignatureRecordImpl extends TagValue implements SignatureRecord {
31 
32    // TODO ftext is defined as a sequence of at least one in %d33-57 or
33    // %d59-126
34    private static Pattern hdrNamePattern = Pattern.compile("^[^: \r\n\t]+$");
35 
36    public SignatureRecordImpl(String data) {
37        super(data);
38    }
39 
40    protected void init() {
41        mandatoryTags.add("v");
42        mandatoryTags.add("a");
43        mandatoryTags.add("b");
44        mandatoryTags.add("bh");
45        mandatoryTags.add("d");
46        mandatoryTags.add("h");
47        mandatoryTags.add("s");
48 
49        defaults.put("c", SIMPLE+"/"+SIMPLE);
50        defaults.put("l", ALL);
51        defaults.put("q", "dns/txt");
52    }
53 
54    /**
55     * @see org.apache.james.jdkim.api.SignatureRecord#validate()
56     */
57    public void validate() throws IllegalStateException {
58        super.validate();
59        // TODO: what about v=0.5 and no v= at all?
60        // do specs allow parsing? what should we check?
61        if (!"1".equals(getValue("v")))
62            throw new IllegalStateException(
63                    "Invalid DKIM-Signature version (expected '1'): "
64                            + getValue("v"));
65        if (getValue("h").length() == 0)
66            throw new IllegalStateException("Tag h= cannot be empty.");
67        if (!getIdentity().toString().toLowerCase().endsWith(
68                ("@" + getValue("d")).toLowerCase())
69                && !getIdentity().toString().toLowerCase().endsWith(
70                        ("." + getValue("d")).toLowerCase()))
71            throw new IllegalStateException("Domain mismatch");
72 
73        // when "x=" exists and signature expired then return PERMFAIL
74        // (signature expired)
75        if (getValue("x") != null) {
76            long expiration = Long.parseLong(getValue("x").toString());
77            long lifetime = (expiration - System.currentTimeMillis() / 1000);
78            String measure = "s";
79            if (lifetime < 0) {
80                lifetime = -lifetime;
81                if (lifetime > 600) {
82                    lifetime = lifetime / 60;
83                    measure = "m";
84                    if (lifetime > 600) {
85                        lifetime = lifetime / 60;
86                        measure = "h";
87                        if (lifetime > 120) {
88                            lifetime = lifetime / 24;
89                            measure = "d";
90                            if (lifetime > 90) {
91                                lifetime = lifetime / 30;
92                                measure = " months";
93                                if (lifetime > 24) {
94                                    lifetime = lifetime / 12;
95                                    measure = " years";
96                                }
97                            }
98                        }
99                    }
100                }
101                throw new IllegalStateException("Signature is expired since "
102                        + lifetime + measure + ".");
103            }
104        }
105 
106        // when "h=" does not contain "from" return PERMFAIL (From field not
107        // signed).
108        if (!isInListCaseInsensitive("from", getHeaders()))
109            throw new IllegalStateException("From field not signed");
110        // TODO support ignoring signature for certain d values (externally to
111        // this class).
112    }
113 
114    /**
115     * @see org.apache.james.jdkim.api.SignatureRecord#getHeaders()
116     */
117    public List/* CharSequence */getHeaders() {
118        return stringToColonSeparatedList(getValue("h").toString(),
119                hdrNamePattern);
120    }
121 
122    // If i= is unspecified the default is @d
123    protected CharSequence getDefault(String tag) {
124        if ("i".equals(tag)) {
125            return "@" + getValue("d");
126        } else
127            return super.getDefault(tag);
128    }
129 
130    /**
131     * @see org.apache.james.jdkim.api.SignatureRecord#getIdentityLocalPart()
132     */
133    public CharSequence getIdentityLocalPart() {
134        String identity = getIdentity().toString();
135        int pAt = identity.indexOf('@');
136        return identity.subSequence(0, pAt);
137    }
138 
139    public CharSequence getIdentity() {
140        return dkimQuotedPrintableDecode(getValue("i"));
141    }
142 
143    public static String dkimQuotedPrintableDecode(CharSequence input)
144            throws IllegalArgumentException {
145        StringBuffer sb = new StringBuffer(input.length());
146        // TODO should we fail on WSP that is not part of FWS?
147        // the specification in 2.6 DKIM-Quoted-Printable is not
148        // clear
149        int state = 0;
150        int start = 0;
151        int d = 0;
152        boolean lastWasNL = false;
153        for (int i = 0; i < input.length(); i++) {
154            if (lastWasNL && input.charAt(i) != ' ' && input.charAt(i) != '\t') {
155                throw new IllegalArgumentException(
156                        "Unexpected LF not part of an FWS");
157            }
158            lastWasNL = false;
159            switch (state) {
160            case 0:
161                switch (input.charAt(i)) {
162                case ' ':
163                case '\t':
164                case '\r':
165                case '\n':
166                    if ('\n' == input.charAt(i))
167                        lastWasNL = true;
168                    sb.append(input.subSequence(start, i));
169                    start = i + 1;
170                    // ignoring whitespace by now.
171                    break;
172                case '=':
173                    sb.append(input.subSequence(start, i));
174                    state = 1;
175                    break;
176                }
177                break;
178            case 1:
179            case 2:
180                if (input.charAt(i) >= '0' && input.charAt(i) <= '9'
181                        || input.charAt(i) >= 'A' && input.charAt(i) <= 'F') {
182                    int v = Arrays.binarySearch("0123456789ABCDEF".getBytes(),
183                            (byte) input.charAt(i));
184                    if (state == 1) {
185                        state = 2;
186                        d = v;
187                    } else {
188                        d = d * 16 + v;
189                        sb.append((char) d);
190                        state = 0;
191                        start = i + 1;
192                    }
193                } else {
194                    throw new IllegalArgumentException(
195                            "Invalid input sequence at " + i);
196                }
197            }
198        }
199        if (state != 0) {
200            throw new IllegalArgumentException(
201                    "Invalid quoted printable termination");
202        }
203        sb.append(input.subSequence(start, input.length()));
204        return sb.toString();
205    }
206 
207    /**
208     * @see org.apache.james.jdkim.api.SignatureRecord#getHashKeyType()
209     */
210    public CharSequence getHashKeyType() {
211        String a = getValue("a").toString();
212        int pHyphen = a.indexOf('-');
213        // TODO x-sig-a-tag-h = ALPHA *(ALPHA / DIGIT)
214        if (pHyphen == -1)
215            throw new IllegalStateException(
216                    "Invalid hash algorythm (key type): " + a);
217        return a.subSequence(0, pHyphen);
218    }
219 
220    /**
221     * @see org.apache.james.jdkim.api.SignatureRecord#getHashMethod()
222     */
223    public CharSequence getHashMethod() {
224        String a = getValue("a").toString();
225        int pHyphen = a.indexOf('-');
226        // TODO x-sig-a-tag-h = ALPHA *(ALPHA / DIGIT)
227        if (pHyphen == -1)
228            throw new IllegalStateException("Invalid hash method: " + a);
229        return a.subSequence(pHyphen + 1, a.length());
230    }
231 
232    /**
233     * @see org.apache.james.jdkim.api.SignatureRecord#getHashAlgo()
234     */
235    public CharSequence getHashAlgo() {
236        String a = getValue("a").toString();
237        int pHyphen = a.indexOf('-');
238        if (pHyphen == -1)
239            throw new IllegalStateException("Invalid hash method: " + a);
240        if (a.length() > pHyphen + 3 && a.charAt(pHyphen + 1) == 's'
241                && a.charAt(pHyphen + 2) == 'h' && a.charAt(pHyphen + 3) == 'a') {
242            return "sha-" + a.subSequence(pHyphen + 4, a.length());
243        } else
244            return a.subSequence(pHyphen + 1, a.length());
245    }
246 
247    /**
248     * @see org.apache.james.jdkim.api.SignatureRecord#getSelector()
249     */
250    public CharSequence getSelector() {
251        return getValue("s");
252    }
253 
254    /**
255     * @see org.apache.james.jdkim.api.SignatureRecord#getDToken()
256     */
257    public CharSequence getDToken() {
258        return getValue("d");
259    }
260 
261    public byte[] getBodyHash() {
262        return Base64.decodeBase64(getValue("bh").toString().getBytes());
263    }
264 
265    public byte[] getSignature() {
266        return Base64.decodeBase64(getValue("b").toString().getBytes());
267    }
268 
269    public int getBodyHashLimit() {
270        String limit = getValue("l").toString();
271        if (ALL.equals(limit))
272            return -1;
273        else
274            return Integer.parseInt(limit);
275    }
276 
277    public String getBodyCanonicalisationMethod() {
278        String c = getValue("c").toString();
279        int pSlash = c.toString().indexOf("/");
280        if (pSlash != -1) {
281            return c.substring(pSlash + 1);
282        } else {
283            return SIMPLE;
284        }
285    }
286 
287    public String getHeaderCanonicalisationMethod() {
288        String c = getValue("c").toString();
289        int pSlash = c.toString().indexOf("/");
290        if (pSlash != -1) {
291            return c.substring(0, pSlash);
292        } else {
293            return c;
294        }
295    }
296 
297    public List getRecordLookupMethods() {
298        String flags = getValue("q").toString();
299        String[] flagsStrings = flags.split(":");
300        List res = new LinkedList();
301        for (int i = 0; i < flagsStrings.length; i++) {
302            // TODO add validation method[/option]
303            // if (VALIDATION)
304            res.add(trimFWS(flagsStrings[i], 0, flagsStrings[i].length() - 1,
305                    true).toString());
306        }
307        return res;
308    }
309 
310    public void setSignature(byte[] newSignature) {
311        String signature = new String(Base64.encodeBase64(newSignature));
312        setValue("b", signature);
313    }
314 
315    public void setBodyHash(byte[] newBodyHash) {
316        String bodyHash = new String(Base64.encodeBase64(newBodyHash));
317        setValue("bh", bodyHash);
318    }
319 
320    public String toUnsignedString() {
321        return toString().replaceFirst("b=[^;]*", "b=");
322    }
323 
324 
325}

[all classes][org.apache.james.jdkim.tagvalue]
EMMA 2.0.5312 (C) Vladimir Roubtsov