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