You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by ba...@apache.org on 2009/10/06 19:45:02 UTC

svn commit: r822372 [1/2] - in /james/jdkim/trunk/main/src: ./ main/ main/java/ main/java/org/ main/java/org/apache/ main/java/org/apache/james/ main/java/org/apache/james/jdkim/ main/java/org/apache/james/jdkim/canon/ main/java/org/apache/james/jdkim/...

Author: bago
Date: Tue Oct  6 17:45:01 2009
New Revision: 822372

URL: http://svn.apache.org/viewvc?rev=822372&view=rev
Log:
The code for the main library. It needs some refactoring/repackaging but works.

Added:
    james/jdkim/trunk/main/src/
    james/jdkim/trunk/main/src/main/
    james/jdkim/trunk/main/src/main/java/
    james/jdkim/trunk/main/src/main/java/org/
    james/jdkim/trunk/main/src/main/java/org/apache/
    james/jdkim/trunk/main/src/main/java/org/apache/james/
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/BodyHashJob.java   (with props)
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/CodecUtil.java   (with props)
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMCommon.java   (with props)
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java   (with props)
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java   (with props)
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DNSPublicKeyRecordRetriever.java   (with props)
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/FailException.java   (with props)
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/Headers.java   (with props)
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/Message.java   (with props)
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/MultiplexingPublicKeyRecordRetriever.java   (with props)
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/PermFailException.java   (with props)
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/PublicKeyRecord.java   (with props)
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/PublicKeyRecordRetriever.java   (with props)
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/SignatureRecord.java   (with props)
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/TempFailException.java   (with props)
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/CompoundOutputStream.java   (with props)
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/DigestOutputStream.java   (with props)
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/LimitedOutputStream.java   (with props)
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/RelaxedBodyCanonicalizer.java   (with props)
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/SimpleBodyCanonicalizer.java   (with props)
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/tagvalue/
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/tagvalue/PublicKeyRecordImpl.java   (with props)
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/tagvalue/SignatureRecordImpl.java   (with props)
    james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/tagvalue/TagValue.java   (with props)

Added: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/BodyHashJob.java
URL: http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/BodyHashJob.java?rev=822372&view=auto
==============================================================================
--- james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/BodyHashJob.java (added)
+++ james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/BodyHashJob.java Tue Oct  6 17:45:01 2009
@@ -0,0 +1,65 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim;
+
+import java.io.OutputStream;
+
+import org.apache.james.jdkim.canon.DigestOutputStream;
+
+public class BodyHashJob {
+
+	private SignatureRecord sign;
+	private DigestOutputStream digesterOS;
+	private OutputStream out;
+	private String field;
+
+	public OutputStream getOutputStream() {
+		return out;
+	}
+
+	public SignatureRecord getSignatureRecord() {
+		return sign;
+	}
+
+	public DigestOutputStream getDigesterOutputStream() {
+		return digesterOS;
+	}
+
+	public void setSignatureRecord(SignatureRecord sign) {
+		this.sign = sign;
+	}
+
+	public void setDigestOutputStream(DigestOutputStream dout) {
+		this.digesterOS = dout;
+	}
+
+	public void setOutputStream(OutputStream out) {
+		this.out = out;
+	}
+
+	public void setField(String f) {
+		this.field = f;
+	}
+	
+	public String getField() {
+		return this.field;
+	}
+
+}

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/BodyHashJob.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/BodyHashJob.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/CodecUtil.java
URL: http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/CodecUtil.java?rev=822372&view=auto
==============================================================================
--- james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/CodecUtil.java (added)
+++ james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/CodecUtil.java Tue Oct  6 17:45:01 2009
@@ -0,0 +1,83 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim;
+
+import java.util.Arrays;
+
+public class CodecUtil {
+
+	public static String dkimQuotedPrintableDecode(CharSequence input) throws IllegalArgumentException {
+		StringBuffer sb = new StringBuffer(input.length());
+		// TODO should we fail on WSP that is not part of FWS?
+		// the specification in 2.6 DKIM-Quoted-Printable is not
+		// clear
+		int state = 0;
+		int start = 0;
+		int d = 0;
+		boolean lastWasNL = false;
+		for (int i = 0; i < input.length(); i++) {
+			if (lastWasNL && input.charAt(i) != ' ' && input.charAt(i) != '\t' ) {
+				throw new IllegalArgumentException("Unexpected LF not part of an FWS");
+			}
+			lastWasNL = false;
+			switch (state) {
+			case 0:
+				switch (input.charAt(i)) {
+				case ' ':
+				case '\t':
+				case '\r':
+				case '\n':
+						if ('\n' == input.charAt(i)) lastWasNL = true;
+					sb.append(input.subSequence(start, i));
+					start = i+1;
+					// ignoring whitespace by now.
+					break;
+				case '=':
+					sb.append(input.subSequence(start, i));
+					state = 1;
+					break;
+				}
+				break;
+			case 1:
+			case 2:
+				if (input.charAt(i) >= '0' && input.charAt(i) <= '9' || input.charAt(i) >= 'A' && input.charAt(i) <= 'F') {
+					int v = Arrays.binarySearch("0123456789ABCDEF".getBytes(), (byte) input.charAt(i));
+					if (state == 1) {
+						state = 2;
+						d = v;
+					} else {
+						d = d*16+v;
+						sb.append((char) d);
+						state = 0;
+						start = i+1;
+					}
+				} else {
+					throw new IllegalArgumentException("Invalid input sequence at "+i);
+				}
+			}
+		}
+		if (state != 0) {
+			throw new IllegalArgumentException("Invalid quoted printable termination");
+		}
+		sb.append(input.subSequence(start, input.length()));
+		return sb.toString();
+	}
+
+}

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/CodecUtil.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/CodecUtil.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMCommon.java
URL: http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMCommon.java?rev=822372&view=auto
==============================================================================
--- james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMCommon.java (added)
+++ james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMCommon.java Tue Oct  6 17:45:01 2009
@@ -0,0 +1,159 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.james.jdkim.canon.DebugOutputStream;
+import org.apache.james.jdkim.canon.DigestOutputStream;
+import org.apache.james.jdkim.canon.LimitedOutputStream;
+import org.apache.james.jdkim.canon.RelaxedBodyCanonicalizer;
+import org.apache.james.jdkim.canon.SimpleBodyCanonicalizer;
+import org.apache.james.jdkim.tagvalue.SignatureRecordImpl;
+
+public class DKIMCommon {
+
+	private static final boolean DEEP_DEBUG = false;
+
+	public static void updateSignature(Signature signature, boolean relaxed,
+			CharSequence header, String fv) throws SignatureException {
+		if (relaxed) {
+			if (DEEP_DEBUG)
+				System.out
+						.println("#" + header.toString().toLowerCase() + ":-");
+			signature.update(header.toString().toLowerCase().getBytes());
+			signature.update(":".getBytes());
+			String headerValue = fv.substring(fv.indexOf(':') + 1);
+			headerValue = headerValue.replaceAll("\r\n[\t ]", " ");
+			headerValue = headerValue.replaceAll("[\t ]+", " ");
+			headerValue = headerValue.trim();
+			signature.update(headerValue.getBytes());
+			if (DEEP_DEBUG)
+				System.out.println("#" + headerValue + "#");
+		} else {
+			signature.update(fv.getBytes());
+			if (DEEP_DEBUG)
+				System.out.println("#" + fv + "#");
+		}
+	}
+
+	protected static void signatureCheck(Headers h, SignatureRecord sign,
+			List headers, String signatureStub, Signature signature)
+			throws SignatureException {
+		// TODO make this check better (parse the c field inside sign)
+		boolean relaxedHeaders = "relaxed".equals(sign
+				.getHeaderCanonicalisationMethod());
+
+		// NOTE: this could be improved by using iterators.
+		// NOTE: also this rely on the list returned by Message being in
+		// insertion order
+		Map/* String, Integer */processedHeader = new HashMap();
+
+		for (Iterator i = headers.iterator(); i.hasNext();) {
+			CharSequence header = (CharSequence) i.next();
+			// TODO check this getter is case insensitive
+			List hl = h.getFields(header.toString());
+			if (hl != null && hl.size() > 0) {
+				Integer done = (Integer) processedHeader.get(header.toString());
+				if (done == null)
+					done = new Integer(0); /* Integer.valueOf(0) */
+				int doneHeaders = done.intValue() + 1;
+				if (doneHeaders <= hl.size()) {
+					String fv = (String) hl.get(hl.size() - doneHeaders);
+					updateSignature(signature, relaxedHeaders, header, fv);
+					signature.update("\r\n".getBytes());
+					processedHeader.put(header.toString(), new Integer(
+							doneHeaders));
+				}
+			}
+		}
+
+		updateSignature(signature, relaxedHeaders, "dkim-signature",
+				signatureStub);
+	}
+
+	public SignatureRecord newSignatureRecord(String record) {
+		return new SignatureRecordImpl(record);
+	}
+
+	static OutputStream prepareCanonicalizerOutputStream(int limit,
+			boolean relaxedBody, OutputStream dout) {
+		OutputStream out = dout;
+		if (limit != -1)
+			out = new LimitedOutputStream(out, limit);
+		if (relaxedBody)
+			out = new RelaxedBodyCanonicalizer(out);
+		else
+			out = new SimpleBodyCanonicalizer(out);
+		return out;
+	}
+
+	public static void streamCopy(InputStream bodyIs, OutputStream out)
+			throws IOException {
+		byte[] buffer = new byte[2048];
+		int read;
+		while ((read = bodyIs.read(buffer)) > 0) {
+			out.write(buffer, 0, read);
+		}
+		bodyIs.close();
+		out.close();
+	}
+
+	public static BodyHashJob prepareBodyHashJob(SignatureRecord sign,
+			String f) throws NoSuchAlgorithmException {
+		MessageDigest md = MessageDigest.getInstance(sign.getHashAlgo().toString());
+		
+		BodyHashJob bhj = new BodyHashJob();
+
+		int limit = sign.getBodyHashLimit();
+
+		// TODO enhance this to use a lookup service.
+		boolean relaxedBody = "relaxed".equals(sign
+				.getBodyCanonicalisationMethod());
+
+		DigestOutputStream dout = new DigestOutputStream(md);
+		
+		OutputStream out = dout;
+		if (DEEP_DEBUG) out = new DebugOutputStream(out);
+		out = DKIMCommon.prepareCanonicalizerOutputStream(limit,
+				relaxedBody, out);
+
+		bhj.setSignatureRecord(sign);
+		bhj.setDigestOutputStream(dout);
+		bhj.setOutputStream(out);
+		bhj.setField(f);
+		return bhj;
+	}
+
+	public DKIMCommon() {
+		super();
+	}
+
+}
\ No newline at end of file

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMCommon.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMCommon.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java
URL: http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java?rev=822372&view=auto
==============================================================================
--- james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java (added)
+++ james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java Tue Oct  6 17:45:01 2009
@@ -0,0 +1,142 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.InvalidKeyException;
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.util.List;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.james.mime4j.MimeException;
+
+public class DKIMSigner extends DKIMCommon {
+
+	private PrivateKey privateKey;
+	private String signatureRecordTemplate;
+
+	public DKIMSigner(String signatureRecordTemplate, PrivateKey privateKey) {
+		this.privateKey = privateKey;
+		this.signatureRecordTemplate = signatureRecordTemplate;
+	}
+	
+	public String sign(InputStream is)
+			throws IOException, FailException {
+		Message message;
+		try {
+			try {
+				message = new Message(is);
+			} catch (MimeException e1) {
+				throw new PermFailException("MIME parsing exception: "+e1.getMessage(), e1);
+			}
+	
+			/*
+			Field field;
+			try {
+				field = UnstructuredField.parse("DKIM-Signature: "+signatureRecordTemplate);
+			} catch (MimeException e1) {
+				throw new PermFailException("Mime parsing exception "+e1.getMessage(), e1);
+			}
+			*/
+			SignatureRecord srt = newSignatureRecord(signatureRecordTemplate);
+			try {
+				BodyHashJob bhj = DKIMCommon.prepareBodyHashJob(srt, "DKIM-Signature: "+signatureRecordTemplate);
+
+				// simultaneous computation of all the hashes.
+				DKIMCommon.streamCopy(message.getBodyInputStream(), bhj.getOutputStream());
+		
+				return sign(message, bhj);
+			} catch (NoSuchAlgorithmException e) {
+				throw new PermFailException("Unknown algorythm: "+e.getMessage(), e);
+			}
+	
+		} finally {
+			is.close();
+		}
+	}
+
+	public String sign(Headers message, BodyHashJob bhj)
+			throws PermFailException {
+		byte[] computedHash = bhj.getDigesterOutputStream().getDigest();
+		String newField = "DKIM-Signature: "+signatureRecordTemplate.replaceAll("bh=[^;]*", "bh="+new String(Base64.encodeBase64(computedHash)));
+
+		List headers = bhj.getSignatureRecord().getHeaders();
+		try {
+			// TODO handle b= in SignatureRecord.
+			// whenever any tag is changed the b should be invalidated and the text representation lost.
+			// whenever the b value is regenerated it should also be associated with the right test representation.
+			// we need a method to "regenerate the text representation" and to retrieve it when it is valid.
+			byte[] signatureHash = signatureSign(message, newField,
+					bhj.getSignatureRecord(), privateKey, headers);
+			newField = newField.replaceAll("b=[^;]*", "b="+new String(Base64.encodeBase64(signatureHash)));
+			return newField;
+		} catch (InvalidKeyException e) {
+			throw new PermFailException("Invalid key: "+e.getMessage(), e);
+		} catch (NoSuchAlgorithmException e) {
+			throw new PermFailException("Unknown algorythm: "+e.getMessage(), e);
+		} catch (SignatureException e) {
+			throw new PermFailException("Signing exception: "+e.getMessage(), e);
+		}
+	}
+
+	private byte[] signatureSign(Headers h, String signatureStub, SignatureRecord sign,
+			PrivateKey key, List headers)
+			throws NoSuchAlgorithmException, InvalidKeyException,
+			SignatureException {
+	
+		Signature signature = Signature.getInstance(sign.getHashMethod().toString().toUpperCase()+"with"+sign.getHashKeyType().toString().toUpperCase());
+		signature.initSign(key);
+		signatureCheck(h, sign, headers, signatureStub, signature);
+		return signature.sign();
+	}
+
+	/**
+	 * Generate a PrivateKey from a Base64 encoded private key.
+	 * 
+	 * In order to generate a valid PKCS8 key when you have a PEM key you can do this:
+	 * <code>
+	 * openssl pkcs8 -topk8 -inform PEM -in rsapriv.pem -outform DER -nocrypt -out rsapriv.der
+	 * </code>
+	 * And then base64 encode the content.
+	 * 
+	 * @param privateKeyPKCS8 a Base64 encoded string of the RSA key in PKCS8 format
+	 * @return the PrivateKey
+	 * @throws NoSuchAlgorithmException if RSA is unknown
+	 * @throws InvalidKeySpecException on bad input key
+	 */
+	public static PrivateKey getPrivateKey(String privateKeyPKCS8)
+			throws NoSuchAlgorithmException, InvalidKeySpecException {
+		byte[] encKey = Base64.decodeBase64( privateKeyPKCS8.getBytes() );
+	    // byte[] encKey = privateKey.getBytes();
+	    PKCS8EncodedKeySpec privSpec = new PKCS8EncodedKeySpec(encKey);
+	    KeyFactory keyFactory;
+		keyFactory = KeyFactory.getInstance("RSA");
+	    PrivateKey privKey = keyFactory.generatePrivate(privSpec);
+		return privKey;
+	}
+
+}

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMSigner.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java
URL: http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java?rev=822372&view=auto
==============================================================================
--- james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java (added)
+++ james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java Tue Oct  6 17:45:01 2009
@@ -0,0 +1,269 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Signature;
+import java.security.SignatureException;
+import java.util.Arrays;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.james.jdkim.canon.CompoundOutputStream;
+import org.apache.james.jdkim.tagvalue.PublicKeyRecordImpl;
+import org.apache.james.mime4j.MimeException;
+
+public class DKIMVerifier extends DKIMCommon {
+
+	private PublicKeyRecordRetriever publicKeyRecordRetriever;
+
+	public DKIMVerifier() {
+		this.publicKeyRecordRetriever = new MultiplexingPublicKeyRecordRetriever("dns", new DNSPublicKeyRecordRetriever());
+	}
+	
+	public DKIMVerifier(PublicKeyRecordRetriever publicKeyRecordRetriever) {
+		this.publicKeyRecordRetriever = publicKeyRecordRetriever;
+	}
+
+
+	protected PublicKeyRecord newPublicKeyRecord(String record) {
+		return new PublicKeyRecordImpl(record);
+	}
+
+	protected PublicKeyRecordRetriever getPublicKeyRecordRetriever() throws PermFailException {
+		return publicKeyRecordRetriever;
+	}
+
+	public PublicKeyRecord publicKeySelector(List records) throws PermFailException {
+		String lastError = null;
+		if (records == null || records.size() == 0) {
+			lastError = "no key for signature";
+		} else {
+			for (Iterator i = records.iterator(); i.hasNext();) {
+				String record = (String) i.next();
+				try {
+					PublicKeyRecord pk = newPublicKeyRecord(record);
+					pk.validate();
+					// we expect a single valid record, otherwise the result
+					// is unpredictable.
+					// in case of multiple valid records we use the first one.
+					return pk;
+				} catch (IllegalStateException e) {
+					// do this at last.
+					lastError = "invalid key for signature: "+e.getMessage();
+				}
+			}
+		}
+		// return PERMFAIL ($error).
+		throw new PermFailException(lastError);
+	}
+
+	/**
+	 * Iterates through signature's declared lookup method
+	 *
+	 * @param sign the signature record
+	 * @return
+	 * @throws TempFailException
+	 * @throws PermFailException
+	 */
+	public PublicKeyRecord publicRecordLookup(SignatureRecord sign)
+			throws TempFailException, PermFailException {
+		// System.out.println(sign);
+		PublicKeyRecord key = null;
+		TempFailException lastTempFailure = null;
+		PermFailException lastPermFailure = null;
+		for(Iterator rlm = sign.getRecordLookupMethods().iterator(); key == null && rlm.hasNext(); ) {
+			String method = (String) rlm.next();
+			try {
+				PublicKeyRecordRetriever pkrr = getPublicKeyRecordRetriever();
+				List records = pkrr.getRecords(method, sign.getSelector().toString(), sign.getDToken().toString());
+				PublicKeyRecord tempKey = publicKeySelector(records);
+				// checks wether the key is applicable to the signature
+				// TODO check with the IETF group to understand if this is the right thing to do.
+				// TODO loggin
+				tempKey.apply(sign);
+				key = tempKey;
+			} catch (IllegalStateException e) {
+				lastPermFailure = new PermFailException("Inapplicable key: "+e.getMessage(), e);
+			} catch (TempFailException tf) {
+				lastTempFailure = tf;
+			} catch (PermFailException pf) {
+				lastPermFailure = pf;
+			}
+		}
+		if (key == null) {
+			if (lastTempFailure != null) throw lastTempFailure;
+			else if (lastPermFailure != null) throw lastPermFailure;
+			// this is unexpected because the publicKeySelector always returns null or exception
+			else throw new PermFailException("no key for signature [unexpected condition]");
+		}
+		return key;
+	}
+
+	public void verify(InputStream is)
+			throws IOException, FailException {
+		Message message;
+		try {
+			message = new Message(is);
+		} catch (MimeException e1) {
+			throw new PermFailException("Mime parsing exception: "+e1.getMessage(), e1);
+		}
+		// System.out.println(message.getFields("DKIM-Signature"));
+		List fields = message.getFields("DKIM-Signature");
+		// if (fields.size() > 1) throw new RuntimeException("here we are!");
+		if (fields.size() > 0) {
+			// For each DKIM-signature we prepare an hashjob.
+			// We calculate all hashes concurrently so to read
+			// the inputstream only once.
+			List/* BodyHashJob */ bodyHashJobs = new LinkedList();
+			List/* OutputStream */ outputStreams = new LinkedList();
+			Map/* String, Exception */ signatureExceptions = new Hashtable();
+			for (Iterator i = fields.iterator(); i.hasNext(); ) {
+				String fval = (String) i.next();
+				try {
+					int pos = fval.indexOf(':');
+					if (pos > 0) {
+						String v = fval.substring(pos + 1, fval.length());
+						SignatureRecord sign;
+						try {
+							sign = newSignatureRecord(v);
+							// validate
+							sign.validate();
+						} catch (IllegalStateException e) {
+							throw new PermFailException(e.getMessage());
+						}
+			
+						// TODO here we could check more parameters for validation
+						// before running a network operation like the dns lookup.
+						// e.g: the canonicalization method could be checked now.
+						
+						PublicKeyRecord key = publicRecordLookup(sign);
+			
+						List headers = sign.getHeaders();
+			
+						boolean verified = signatureVerify(message, fval,
+								sign, key, headers);
+						
+						if (!verified) throw new PermFailException("Header signature does not verify");
+			
+						// we track all canonicalizations+limit+bodyHash we
+						// see so to be able to check all of them in a single stream run.
+						BodyHashJob bhj = DKIMCommon.prepareBodyHashJob(sign, fval);
+						
+						bodyHashJobs.add(bhj);
+						outputStreams.add(bhj.getOutputStream());
+			
+					} else {
+						throw new PermFailException("unexpected bad signature field");
+					}
+				} catch (TempFailException e) {
+					signatureExceptions.put(fval, e);
+				} catch (PermFailException e) {
+					signatureExceptions.put(fval, e);
+				} catch (InvalidKeyException e) {
+					signatureExceptions.put(fval, new PermFailException(e.getMessage(), e));
+				} catch (NoSuchAlgorithmException e) {
+					signatureExceptions.put(fval, new PermFailException(e.getMessage(), e));
+				} catch (SignatureException e) {
+					signatureExceptions.put(fval, new PermFailException(e.getMessage(), e));
+				}
+			}
+			
+			OutputStream o;
+			if (bodyHashJobs.size() == 0) {
+				// TODO loops signatureExceptions to give a more complete response.
+				if (signatureExceptions.size() == 1) {
+					throw (FailException) signatureExceptions.values().iterator().next();
+				} else {
+					// System.out.println(signatureExceptions);
+					throw new PermFailException("found "+signatureExceptions.size()+" invalid signatures");
+				}
+			} else if (bodyHashJobs.size() == 1) {
+				o = (OutputStream) outputStreams.get(0);
+			} else {
+				o = new CompoundOutputStream(outputStreams);
+			}
+
+			// simultaneous computation of all the hashes.
+			DKIMCommon.streamCopy(message.getBodyInputStream(), o);
+
+			List/* BodyHashJob */ verifiedSignatures = new LinkedList();
+			for (Iterator i = bodyHashJobs.iterator(); i.hasNext(); ) {
+				BodyHashJob bhj = (BodyHashJob) i.next();
+	
+				byte[] computedHash = bhj.getDigesterOutputStream().getDigest();
+				byte[] expectedBodyHash = bhj.getSignatureRecord().getBodyHash();
+				
+				if (!Arrays.equals(expectedBodyHash, computedHash)) {
+					signatureExceptions.put(bhj.getField().toString(), new PermFailException("Computed bodyhash is different from the expected one"));
+				} else {
+					verifiedSignatures.add(bhj);
+				}
+			}
+			
+			if (verifiedSignatures.size() == 0) {
+				if (signatureExceptions.size() == 1) {
+					throw (FailException) signatureExceptions.values().iterator().next();
+				} else {
+					throw new PermFailException("found "+signatureExceptions.size()+" non verifying signatures");
+				}
+			} else {
+				// TODO list good and bad signatures.
+				for (Iterator i = signatureExceptions.keySet().iterator(); i.hasNext(); ) {
+					String f = (String) i.next();
+					System.out.println("DKIM-Error: "+((FailException) signatureExceptions.get(f)).getMessage()+" FIELD: "+f);
+				}
+				for (Iterator i = verifiedSignatures.iterator(); i.hasNext(); ) {
+					BodyHashJob bhj = (BodyHashJob) i.next();
+					System.out.println("DKIM-Pass: "+bhj.getSignatureRecord());
+				}
+			}
+			
+		} else {
+			throw new PermFailException("DKIM-Signature field not found");
+		}
+	
+		is.close();
+	}
+
+	private boolean signatureVerify(Headers h, String dkimSignature, SignatureRecord sign,
+			PublicKeyRecord key, List headers)
+			throws NoSuchAlgorithmException, InvalidKeyException,
+			SignatureException {
+		byte[] decoded = sign.getSignature();
+	
+		String signatureStub = dkimSignature.replaceAll("b=[^;]*", "b=");
+	
+		Signature signature = Signature.getInstance(sign.getHashMethod().toString().toUpperCase()+"with"+sign.getHashKeyType().toString().toUpperCase());
+		signature.initVerify(key.getPublicKey());
+	
+		signatureCheck(h, sign, headers, signatureStub, signature);
+		
+		return signature.verify(decoded);
+	}
+
+}

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DNSPublicKeyRecordRetriever.java
URL: http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DNSPublicKeyRecordRetriever.java?rev=822372&view=auto
==============================================================================
--- james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DNSPublicKeyRecordRetriever.java (added)
+++ james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DNSPublicKeyRecordRetriever.java Tue Oct  6 17:45:01 2009
@@ -0,0 +1,109 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.xbill.DNS.Lookup;
+import org.xbill.DNS.Record;
+import org.xbill.DNS.Resolver;
+import org.xbill.DNS.TXTRecord;
+import org.xbill.DNS.TextParseException;
+import org.xbill.DNS.Type;
+
+public class DNSPublicKeyRecordRetriever implements PublicKeyRecordRetriever {
+
+	// The resolver used for the lookup
+    protected Resolver resolver;
+	
+	public DNSPublicKeyRecordRetriever() {
+		this(Lookup.getDefaultResolver());
+	}
+
+	public DNSPublicKeyRecordRetriever(Resolver resolver) {
+		this.resolver = resolver;
+	}
+
+	public List/* String */ getRecords(CharSequence methodAndOptions, CharSequence selector, CharSequence token) throws TempFailException, PermFailException {
+		if (!"dns/txt".equals(methodAndOptions)) throw new PermFailException("Only dns/txt is supported: "+methodAndOptions+" options unsupported.");
+		try {
+            Lookup query = new Lookup(selector+"._domainkey."+token, Type.TXT);
+            query.setResolver(resolver);
+
+            Record[] rr = query.run();
+            int queryResult = query.getResult();
+            
+
+            if (queryResult == Lookup.TRY_AGAIN) {
+                throw new TempFailException(query.getErrorString());
+            }
+            
+            List/* String */ records = convertRecordsToList(rr);
+            return records;
+		} catch (TextParseException e) {
+			// TODO log
+			return null;
+		}
+	}
+
+    /**
+     * Convert the given TXT Record array to a String List 
+     * 
+     * @param rr Record array
+     * @return list
+     */
+	public static List/* String */ convertRecordsToList(Record[] rr) {
+        List/* String */ records;
+        if (rr != null && rr.length > 0) {
+            records = new ArrayList/* String */();
+            for (int i = 0; i < rr.length; i++) {
+                switch (rr[i].getType()) {
+                    case Type.TXT:
+                        TXTRecord txt = (TXTRecord) rr[i];
+                        if (txt.getStrings().size() == 1) {
+                            // TODO we need a better fix for this, like using getStringsAsByteArray
+                        	// it's not clear whether this is a bug in dnsjava or not.
+                            records.add(((String)txt.getStrings().get(0)).replaceAll("\\\\", ""));
+                        } else {
+                            StringBuffer sb = new StringBuffer();
+                            for (Iterator/* String */ it = txt.getStrings().iterator(); it
+                                    .hasNext();) {
+                                String k = (String) it.next();
+                                // TODO we need a better fix for this, like using getStringsAsByteArray
+                            	// it's not clear whether this is a bug in dnsjava or not.
+                                k = k.replaceAll("\\\\", "");
+                                sb.append(k);
+                            }
+                            records.add(sb.toString());
+                        }
+                        break;
+                    default:
+                        return null;
+                }
+            }
+        } else {
+            records = null;
+        }
+        return records;
+    }
+
+}

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DNSPublicKeyRecordRetriever.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/DNSPublicKeyRecordRetriever.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/FailException.java
URL: http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/FailException.java?rev=822372&view=auto
==============================================================================
--- james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/FailException.java (added)
+++ james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/FailException.java Tue Oct  6 17:45:01 2009
@@ -0,0 +1,34 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim;
+
+public class FailException extends Exception {
+
+	private static final long serialVersionUID = 1584103235607992818L;
+
+	public FailException(String error) {
+		super(error);
+	}
+
+	public FailException(String string, Exception e) {
+		super(string, e);
+	}
+
+}

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/FailException.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/FailException.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/Headers.java
URL: http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/Headers.java?rev=822372&view=auto
==============================================================================
--- james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/Headers.java (added)
+++ james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/Headers.java Tue Oct  6 17:45:01 2009
@@ -0,0 +1,42 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim;
+
+import java.util.List;
+
+public interface Headers {
+
+	/**
+	 * Gets the fields of this header. The returned list will not be modifiable.
+	 * 
+	 * @return the list of <code>Field</code> objects.
+	 */
+	public abstract List/* String */ getFields();
+
+	/**
+	 * Gets all <code>Field</code>s having the specified field name.
+	 * 
+	 * @param name
+	 *            the field name (e.g. From, Subject).
+	 * @return the list of fields.
+	 */
+	public abstract List/* String */ getFields(final String name);
+
+}
\ No newline at end of file

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/Headers.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/Headers.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/Message.java
URL: http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/Message.java?rev=822372&view=auto
==============================================================================
--- james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/Message.java (added)
+++ james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/Message.java Tue Oct  6 17:45:01 2009
@@ -0,0 +1,208 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim;
+
+import org.apache.james.mime4j.MimeException;
+import org.apache.james.mime4j.MimeIOException;
+import org.apache.james.mime4j.io.EOLConvertingInputStream;
+import org.apache.james.mime4j.parser.Field;
+import org.apache.james.mime4j.parser.MimeEntityConfig;
+import org.apache.james.mime4j.parser.MimeTokenStream;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The header of an entity (see RFC 2045).
+ * 
+ * TODO: we have to handle correct ordered extraction for fields.
+ */
+public class Message implements Iterable, Headers {
+
+	private List fields = new LinkedList();
+	private Map fieldMap = new HashMap();
+	private InputStream bodyIs = null;
+
+	/**
+	 * Creates a new empty <code>Header</code>.
+	 */
+	public Message() {
+	}
+
+	/**
+	 * Creates a new <code>Header</code> from the specified stream.
+	 * 
+	 * @param is
+	 *            the stream to read the header from.
+	 * 
+	 * @throws IOException
+	 *             on I/O errors.
+	 * @throws MimeIOException
+	 *             on MIME protocol violations.
+	 */
+	public Message(InputStream is) throws IOException, MimeException {
+		MimeEntityConfig mec = new MimeEntityConfig();
+		mec.setMaxLineLen(10000);
+		MimeTokenStream stream = new ExtendedMimeTokenStream(mec);
+		stream.setRecursionMode(MimeTokenStream.M_FLAT);
+		// DKIM requires no isolated CR or LF, so we alter them at source.
+		stream.parse(new EOLConvertingInputStream(is));
+		for (int state = stream.getState(); state != MimeTokenStream.T_END_OF_STREAM; state = stream
+				.next()) {
+			switch (state) {
+			// a field
+			case MimeTokenStream.T_FIELD:
+				addField(stream.getField());
+				break;
+
+			// expected ignored tokens
+			case MimeTokenStream.T_START_MESSAGE:
+			case MimeTokenStream.T_END_MESSAGE:
+			case MimeTokenStream.T_START_HEADER:
+			case MimeTokenStream.T_END_HEADER:
+				break;
+
+			// the body stream
+			case MimeTokenStream.T_BODY:
+				this.bodyIs = stream.getInputStream();
+				break;
+
+			default:
+				throw new IllegalStateException("Unexpected stream message: "
+						+ state);
+			}
+			// stop parsing after header
+			if (bodyIs != null)
+				break;
+		}
+
+	}
+
+	public InputStream getBodyInputStream() {
+		return bodyIs;
+	}
+
+	public void setBodyInputStream(InputStream is) {
+		bodyIs = is;
+	}
+
+	/**
+	 * Adds a field to the end of the list of fields.
+	 * 
+	 * @param field
+	 *            the field to add.
+	 */
+	public void addField(Field field) {
+		List values = (List) fieldMap.get(field.getName().toLowerCase());
+		if (values == null) {
+			values = new LinkedList();
+			fieldMap.put(field.getName().toLowerCase(), values);
+		}
+		values.add(new String(field.getRaw().toByteArray()));
+		fields.add(new String(field.getRaw().toByteArray()));
+	}
+
+	/* (non-Javadoc)
+	 * @see org.apache.james.jdkim.Headers#getFields()
+	 */
+	public List getFields() {
+		return Collections.unmodifiableList(fields);
+	}
+
+	/* (non-Javadoc)
+	 * @see org.apache.james.jdkim.Headers#getField(java.lang.String)
+	 */
+	public String getField(String name) {
+		List l = (List) fieldMap.get(name.toLowerCase());
+		if (l != null && !l.isEmpty()) {
+			return (String) l.get(0);
+		}
+		return null;
+	}
+
+	/* (non-Javadoc)
+	 * @see org.apache.james.jdkim.Headers#getFields(java.lang.String)
+	 */
+	public List getFields(final String name) {
+		final String lowerCaseName = name.toLowerCase();
+		final List l = (List) fieldMap.get(lowerCaseName);
+		final List results;
+		if (l == null || l.isEmpty()) {
+			results = Collections.emptyList();
+		} else {
+			results = Collections.unmodifiableList(l);
+		}
+		return results;
+	}
+
+	/**
+	 * Returns an iterator over the list of fields of this header.
+	 * 
+	 * @return an iterator.
+	 */
+	public Iterator iterator() {
+		return Collections.unmodifiableList(fields).iterator();
+	}
+
+
+	/**
+	 * Return Header Object as String representation. Each headerline is
+	 * seperated by "\r\n"
+	 * 
+	 * @return headers
+	 */
+	public String toString() {
+		StringBuffer str = new StringBuffer(128);
+		for (Iterator i = fields.iterator(); i.hasNext();) {
+			String field = (String) i.next();
+			str.append(field);
+		}
+		InputStream is = getBodyInputStream();
+		if (is != null) {
+			str.append("\r\n");
+			byte[] buff = new byte[128];
+			int read;
+			try {
+				while ((read = is.read(buff)) > 0) {
+					str.append(new String(buff, 0, read));
+				}
+			} catch (IOException e) {
+			}
+		}
+		return str.toString();
+	}
+
+	/**
+	 * Extends this to publish the constructor
+	 */
+	private final class ExtendedMimeTokenStream extends MimeTokenStream {
+
+		public ExtendedMimeTokenStream(MimeEntityConfig mec) {
+			super(mec);
+		}
+	}
+
+}
\ No newline at end of file

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/Message.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/Message.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/MultiplexingPublicKeyRecordRetriever.java
URL: http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/MultiplexingPublicKeyRecordRetriever.java?rev=822372&view=auto
==============================================================================
--- james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/MultiplexingPublicKeyRecordRetriever.java (added)
+++ james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/MultiplexingPublicKeyRecordRetriever.java Tue Oct  6 17:45:01 2009
@@ -0,0 +1,55 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class MultiplexingPublicKeyRecordRetriever implements PublicKeyRecordRetriever {
+	
+	private Map/* String, PublicKeyRecordRetriever */ retrievers;
+	
+	public MultiplexingPublicKeyRecordRetriever() {
+		retrievers = new HashMap();
+	}
+	public MultiplexingPublicKeyRecordRetriever(String methodName, PublicKeyRecordRetriever pkrr) {
+		this();
+		addRetriever(methodName, pkrr);
+	}
+	
+	public void addRetriever(String methodName, PublicKeyRecordRetriever pkrr) {
+		retrievers.put(methodName, pkrr);
+	}
+
+	public List getRecords(CharSequence methodAndOption,
+			CharSequence selector, CharSequence token)
+			throws TempFailException, PermFailException {
+		int pos = methodAndOption.toString().indexOf('/');
+		String method = pos != -1 ? methodAndOption.subSequence(0, pos).toString() : methodAndOption.toString();
+		PublicKeyRecordRetriever pkrr = (PublicKeyRecordRetriever) retrievers.get(method);
+		if (pkrr != null) {
+			return pkrr.getRecords(methodAndOption, selector, token);
+		} else {
+			throw new PermFailException("Unknown public key record retrieving method: "+methodAndOption);
+		}
+	}
+	
+}
\ No newline at end of file

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/MultiplexingPublicKeyRecordRetriever.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/MultiplexingPublicKeyRecordRetriever.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/PermFailException.java
URL: http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/PermFailException.java?rev=822372&view=auto
==============================================================================
--- james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/PermFailException.java (added)
+++ james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/PermFailException.java Tue Oct  6 17:45:01 2009
@@ -0,0 +1,34 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim;
+
+public class PermFailException extends FailException {
+
+	private static final long serialVersionUID = 1304736020453821093L;
+
+	public PermFailException(String error) {
+		super(error);
+	}
+
+	public PermFailException(String string, Exception e) {
+		super(string, e);
+	}
+
+}

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/PermFailException.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/PermFailException.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/PublicKeyRecord.java
URL: http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/PublicKeyRecord.java?rev=822372&view=auto
==============================================================================
--- james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/PublicKeyRecord.java (added)
+++ james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/PublicKeyRecord.java Tue Oct  6 17:45:01 2009
@@ -0,0 +1,63 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim;
+
+import java.security.PublicKey;
+import java.util.List;
+import java.util.regex.Pattern;
+
+public interface PublicKeyRecord {
+
+	public final static String ANY = ";any;";
+
+	public abstract void validate();
+
+	public abstract boolean isHashMethodSupported(CharSequence hash);
+
+	public abstract boolean isKeyTypeSupported(CharSequence hash);
+
+	/**
+	 * @return null if "any", otherwise a list of supported methods
+	 */
+	public abstract List/* String */getAcceptableHashMethods();
+
+	/**
+	 * @return null if "any", otherwise a list of supported methods
+	 */
+	public abstract List/* String */getAcceptableKeyTypes();
+
+	public abstract Pattern getGranularityPattern();
+
+	public abstract PublicKey getPublicKey();
+
+	/**
+	 * throws an exception when the key is not suitable to match tje signature.
+	 * @param sign a signature to be tested
+	 */
+	public abstract void apply(SignatureRecord sign);
+
+	
+	public abstract List getFlags();
+	
+	public abstract boolean isTesting();
+	
+	public abstract boolean isDenySubdomains();
+
+}
\ No newline at end of file

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/PublicKeyRecord.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/PublicKeyRecord.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/PublicKeyRecordRetriever.java
URL: http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/PublicKeyRecordRetriever.java?rev=822372&view=auto
==============================================================================
--- james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/PublicKeyRecordRetriever.java (added)
+++ james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/PublicKeyRecordRetriever.java Tue Oct  6 17:45:01 2009
@@ -0,0 +1,36 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim;
+
+import java.util.List;
+
+public interface PublicKeyRecordRetriever {
+	
+	/**
+	 * @param method/options the options declared for the lookup method.
+	 * @param selector the value of "s=" tag
+	 * @param token the value of the "d=" tag
+	 * @return A list of strings representing 0 to multiple records
+	 * @throws TempFailException in case of timeout and other network errors.
+	 * @throws PermFailException in case of unsupported options
+	 */
+	public List/* String */ getRecords(CharSequence methodAndOption, CharSequence selector, CharSequence token) throws TempFailException, PermFailException;
+
+}

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/PublicKeyRecordRetriever.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/PublicKeyRecordRetriever.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/SignatureRecord.java
URL: http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/SignatureRecord.java?rev=822372&view=auto
==============================================================================
--- james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/SignatureRecord.java (added)
+++ james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/SignatureRecord.java Tue Oct  6 17:45:01 2009
@@ -0,0 +1,58 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim;
+
+import java.util.List;
+
+public interface SignatureRecord {
+
+	public final static String ALL = ";all;";
+
+	public abstract void validate();
+
+	public abstract List/* CharSequence */getHeaders();
+
+	public abstract CharSequence getIdentityLocalPart();
+
+	public abstract CharSequence getIdentity();
+
+	public abstract CharSequence getHashKeyType();
+
+	public abstract CharSequence getHashMethod();
+	
+	public abstract CharSequence getHashAlgo();
+
+	public abstract CharSequence getSelector();
+
+	public abstract CharSequence getDToken();
+	
+	public abstract byte[] getBodyHash();
+	
+	public abstract int getBodyHashLimit();
+
+	public abstract byte[] getSignature();
+	
+	public abstract String getHeaderCanonicalisationMethod();
+	
+	public abstract String getBodyCanonicalisationMethod();
+
+	public abstract List getRecordLookupMethods();
+
+}
\ No newline at end of file

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/SignatureRecord.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/SignatureRecord.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/TempFailException.java
URL: http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/TempFailException.java?rev=822372&view=auto
==============================================================================
--- james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/TempFailException.java (added)
+++ james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/TempFailException.java Tue Oct  6 17:45:01 2009
@@ -0,0 +1,30 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim;
+
+public class TempFailException extends FailException {
+
+	private static final long serialVersionUID = 1304733570453821093L;
+
+	public TempFailException(String error) {
+		super(error);
+	}
+
+}

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/TempFailException.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/TempFailException.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/CompoundOutputStream.java
URL: http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/CompoundOutputStream.java?rev=822372&view=auto
==============================================================================
--- james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/CompoundOutputStream.java (added)
+++ james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/CompoundOutputStream.java Tue Oct  6 17:45:01 2009
@@ -0,0 +1,55 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim.canon;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.Iterator;
+import java.util.List;
+
+public class CompoundOutputStream extends OutputStream {
+	
+	List/* OutputStream */ outputStreams;
+
+	public CompoundOutputStream(List outputStreams) {
+		this.outputStreams = outputStreams;
+	}
+
+	public void close() throws IOException {
+		for (Iterator i = outputStreams.iterator(); i.hasNext(); ) ((OutputStream) i.next()).close();
+	}
+
+	public void flush() throws IOException {
+		for (Iterator i = outputStreams.iterator(); i.hasNext(); ) ((OutputStream) i.next()).flush();
+	}
+
+	public void write(byte[] b, int off, int len) throws IOException {
+		for (Iterator i = outputStreams.iterator(); i.hasNext(); ) ((OutputStream) i.next()).write(b, off, len);
+	}
+
+	public void write(byte[] b) throws IOException {
+		for (Iterator i = outputStreams.iterator(); i.hasNext(); ) ((OutputStream) i.next()).write(b);
+	}
+
+	public void write(int b) throws IOException {
+		for (Iterator i = outputStreams.iterator(); i.hasNext(); ) ((OutputStream) i.next()).write(b);
+	}
+
+}

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/CompoundOutputStream.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/CompoundOutputStream.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/DigestOutputStream.java
URL: http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/DigestOutputStream.java?rev=822372&view=auto
==============================================================================
--- james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/DigestOutputStream.java (added)
+++ james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/DigestOutputStream.java Tue Oct  6 17:45:01 2009
@@ -0,0 +1,74 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim.canon;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.MessageDigest;
+
+/**
+ * DigestOutputStream is used as a filter stream or as the ending stream
+ * in order to calculate a digest of a stream.
+ */
+public class DigestOutputStream extends FilterOutputStream {
+	
+	private MessageDigest md;
+
+	public DigestOutputStream(MessageDigest md) {
+		this(md, null);
+	}
+
+	public DigestOutputStream(MessageDigest md, OutputStream out) {
+		super(out);
+		this.md = md;
+	}
+
+	public void write(int arg0) throws IOException {
+		md.update((byte) arg0);
+		if (out != null) out.write(arg0);
+	}
+
+	public void write(byte[] b, int off, int len) throws IOException {
+		md.update(b, off, len);
+		if (out != null) out.write(b, off, len);
+	}
+
+	public void close() throws IOException {
+		if (out != null) super.close();
+	}
+
+	public void flush() throws IOException {
+		if (out != null) super.flush();
+	}
+
+	public void write(byte[] b) throws IOException {
+		md.update(b);
+		if (out != null) out.write(b);
+	}
+	
+	/**
+	 * @return the stream digest as a byte array
+	 */
+	public byte[] getDigest() {
+		return md.digest();
+	}
+	
+}
\ No newline at end of file

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/DigestOutputStream.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/DigestOutputStream.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/LimitedOutputStream.java
URL: http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/LimitedOutputStream.java?rev=822372&view=auto
==============================================================================
--- james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/LimitedOutputStream.java (added)
+++ james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/LimitedOutputStream.java Tue Oct  6 17:45:01 2009
@@ -0,0 +1,75 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim.canon;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Pass data to the underlying system until a given amount of bytes
+ * is reached.
+ */
+public class LimitedOutputStream extends FilterOutputStream {
+
+	private int limit;
+	private int computedBytes;
+
+	/**
+	 * @param out an output stream that will receive the "trucated" stream.
+	 * @param limit a positive integer of the number of bytes to be passed to the underlying stream
+	 */
+	public LimitedOutputStream(OutputStream out, int limit) {
+		super(out);
+		this.limit = limit;
+		this.computedBytes = 0;
+	}
+
+	public void write(byte[] b, int off, int len) throws IOException {
+		if (len > limit - computedBytes) {
+			len = limit - computedBytes;
+		}
+		if (len > 0) {
+			out.write(b, off, len);
+			computedBytes += len;
+		}
+	}
+
+	public void write(int b) throws IOException {
+		if (computedBytes < limit) {
+			out.write(b);
+			computedBytes++;
+		}
+	}
+	
+	/**
+	 * @return the number of bytes passed to the underlying stream
+	 */
+	public int getComputedBytes() {
+		return computedBytes;
+	}
+	
+	/**
+	 * @return true if the limit has been reached and no data is being passed to the underlying stream.
+	 */
+	public boolean isLimited() {
+		return computedBytes >= limit;
+	}
+}
\ No newline at end of file

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/LimitedOutputStream.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/LimitedOutputStream.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/RelaxedBodyCanonicalizer.java
URL: http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/RelaxedBodyCanonicalizer.java?rev=822372&view=auto
==============================================================================
--- james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/RelaxedBodyCanonicalizer.java (added)
+++ james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/RelaxedBodyCanonicalizer.java Tue Oct  6 17:45:01 2009
@@ -0,0 +1,100 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim.canon;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Implements Relaxed canonicalization for the body as defined in RFC4871 -
+ * 3.4.4. The "relaxed" Body Canonicalization Algorithm
+ */
+public class RelaxedBodyCanonicalizer extends FilterOutputStream {
+
+	private boolean pendingSpaces;
+
+	public RelaxedBodyCanonicalizer(OutputStream out) {
+		super(new SimpleBodyCanonicalizer(out));
+		pendingSpaces = false;
+	}
+
+	public void write(byte[] buffer, int off, int len) throws IOException {
+		int start = off;
+		int end = len + off;
+		for (int k = off; k < end; k++) {
+			if (pendingSpaces) {
+				if (buffer[k] != ' ' && buffer[k] != '\t') {
+					if (buffer[k] != '\r')
+						out.write(' ');
+					pendingSpaces = false;
+					len = len - k + start;
+					start = k;
+				}
+			} else {
+				if (buffer[k] == ' ' || buffer[k] == '\t') {
+					if (k + 1 < end && buffer[k] == ' ' && buffer[k + 1] != ' '
+							&& buffer[k + 1] != '\t' && buffer[k + 1] != '\r') {
+						// optimization: we skip single spaces
+						// make sure we optimize only when we are on a space.
+					} else {
+						// compute everything from start to end;
+						out.write(buffer, start, k - start);
+						pendingSpaces = true;
+					}
+				}
+			}
+		}
+		if (!pendingSpaces) {
+			out.write(buffer, start, len);
+		}
+	}
+
+	public void write(int b) throws IOException {
+		if (pendingSpaces) {
+			if (b != ' ' && b != '\t') {
+				if (b != '\r')
+					out.write(' ');
+				pendingSpaces = false;
+				out.write(b);
+			}
+		} else {
+			if (b == ' ' || b == '\t') {
+				pendingSpaces = true;
+			} else {
+				out.write(b);
+			}
+		}
+	}
+
+	public void close() throws IOException {
+		complete();
+		super.close();
+	}
+
+	/**
+	 * Called internally to make sure we output the buffered whitespace if any.
+	 */
+	private void complete() throws IOException {
+		if (pendingSpaces)
+			out.write(' ');
+	}
+
+}
\ No newline at end of file

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/RelaxedBodyCanonicalizer.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/RelaxedBodyCanonicalizer.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/SimpleBodyCanonicalizer.java
URL: http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/SimpleBodyCanonicalizer.java?rev=822372&view=auto
==============================================================================
--- james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/SimpleBodyCanonicalizer.java (added)
+++ james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/SimpleBodyCanonicalizer.java Tue Oct  6 17:45:01 2009
@@ -0,0 +1,118 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim.canon;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Implements Simple canonicalization for the body as defined in RFC4871 -
+ * 3.4.3.  The "simple" Body Canonicalization Algorithm
+ */
+public class SimpleBodyCanonicalizer extends FilterOutputStream {
+	
+	private static final boolean DEEP_DEBUG = false;
+	
+	private boolean lastWasCR;
+	private int countCRLF;
+
+	public SimpleBodyCanonicalizer(OutputStream arg0) {
+		super(arg0);
+	}
+
+	public void write(byte[] b, int off, int len) throws IOException {
+		if (len <= 0) return;
+		if (DEEP_DEBUG) System.out.println("I:("+lastWasCR+"|"+countCRLF+") ["+new String(b, off, len)+"]");
+		if (lastWasCR) {
+			if (len > 0 && b[off] == '\n') {
+				countCRLF++; lastWasCR = false; off++; len--;
+			} else {
+				// TODO output the lone \r ? (this condition should never happen
+				// as we expect only CRLF in a compliant 7bit email.
+				out.write('\r');
+				lastWasCR = false;
+			}
+		}
+		int newCountCRLF = 0;
+		boolean newLastWasCR = false;
+		if (len >= 1 && b[off+len-1] == '\r') {
+			newLastWasCR = true;
+			len--;
+		}
+		while (len >= 2 && b[off+len-1] == '\n' && b[off+len-2] == '\r') {
+			len-=2;
+			newCountCRLF++;
+		}
+		if (len > 0) {
+			dumpCRLF();
+			out.write(b, off, len);
+		}
+		countCRLF+=newCountCRLF;
+		lastWasCR=newLastWasCR;
+	}
+
+	public void write(int b) throws IOException {
+		if (DEEP_DEBUG) System.out.println("B:("+lastWasCR+"|"+countCRLF+") ["+new String(""+(char) b)+"]");
+		if (lastWasCR && '\n' == b) {
+			lastWasCR = false;
+			countCRLF++;
+		} else {
+			if (!lastWasCR && '\r' == b) {
+				lastWasCR = true;
+			} else {
+				dumpCRLF();
+				if ('\r' == b) lastWasCR = true;
+				else out.write(b);
+			}
+		}
+	}
+
+	public void close() throws IOException {
+		complete();
+		super.close();
+	}
+
+	private void complete() throws IOException {
+		if (DEEP_DEBUG) System.out.println("C:("+lastWasCR+"|"+countCRLF+")");
+		if (lastWasCR) {
+			// if the last char was a CR we'll let dumpCRLF
+			// to output the missing \n
+			lastWasCR = false;
+		}
+		countCRLF = 1;
+		dumpCRLF();
+	}
+
+	private void dumpCRLF() throws IOException {
+		if (DEEP_DEBUG) System.out.println("D:("+lastWasCR+"|"+countCRLF+")");
+		if (lastWasCR) {
+			out.write('\r');
+			lastWasCR = false;
+		}
+		while (countCRLF > 0) {
+			out.write("\r\n".getBytes());
+			countCRLF--;
+		}
+	}
+	
+	
+	
+}
\ No newline at end of file

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/SimpleBodyCanonicalizer.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/canon/SimpleBodyCanonicalizer.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain



---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org