Added: 
james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/tagvalue/PublicKeyRecordImpl.java
URL: 
http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/tagvalue/PublicKeyRecordImpl.java?rev=822372&view=auto
==============================================================================
--- 
james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/tagvalue/PublicKeyRecordImpl.java
 (added)
+++ 
james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/tagvalue/PublicKeyRecordImpl.java
 Tue Oct  6 17:45:01 2009
@@ -0,0 +1,193 @@
+/****************************************************************
+ * 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.tagvalue;
+
+import java.security.KeyFactory;
+import java.security.NoSuchAlgorithmException;
+import java.security.PublicKey;
+import java.security.interfaces.RSAPublicKey;
+import java.security.spec.InvalidKeySpecException;
+import java.security.spec.X509EncodedKeySpec;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.james.jdkim.PublicKeyRecord;
+import org.apache.james.jdkim.SignatureRecord;
+
+public class PublicKeyRecordImpl extends TagValue implements PublicKeyRecord {
+       
+       private static final String atom = "[a-zA-Z0-9!#$%&'*+/=?^_`{}|~-]+";
+       // TODO this should support CFWS: are they supported in DKIM for real?
+       private static final String dotAtomText = "("+atom+")?\\*?("+atom+")?";
+       private static final Pattern granularityPattern = 
Pattern.compile("^"+dotAtomText+"$");
+
+       // SPEC: hyphenated-word =  ALPHA [ *(ALPHA / DIGIT / "-") (ALPHA / 
DIGIT) ]
+       private static Pattern hyphenatedWordPattern = 
Pattern.compile("^[a-zA-Z]([a-zA-Z0-9-]*[a-zA-Z0-9])?$");
+
+       public PublicKeyRecordImpl(String data) {
+               super(data);
+       }
+       
+       protected void init() {
+               // extensions may override this to use TreeMaps in order to 
keep track of orders
+               tagValues = new LinkedHashMap();
+               mandatoryTags.add("p");
+               defaults.put("v", "DKIM1");
+               defaults.put("g", "*");
+               defaults.put("h", ANY);
+               defaults.put("k", "rsa");
+               defaults.put("s", "*");
+               defaults.put("t", "");
+       }
+       
+       // TODO do we treat v=NONDKIM1 records, syntax error records and 
v=DKIM1 in the middle records
+       //       in the same way?
+       public void validate() {
+               super.validate();
+               if (tagValues.containsKey("v")) {
+                       // if "v" is specified it must be the first tag
+                       String firstKey = (String) ((LinkedHashMap) 
tagValues).keySet().iterator().next();
+                       if (!"v".equals(firstKey)) throw new 
IllegalStateException("Existing v= tag MUST be the first in the record list 
("+firstKey+")");
+               }
+               if (!"DKIM1".equals(getValue("v"))) throw new 
IllegalStateException("Unknown version for v= (expected DKIM1): 
"+getValue("v"));
+               if ("".equals(getValue("p"))) throw new 
IllegalStateException("Revoked key. 'p=' in record");
+       }
+
+       /**
+        * @see 
org.apache.james.jdkim.PublicKeyRecord#isHashMethodSupported(java.lang.CharSequence)
+        */
+       public boolean isHashMethodSupported(CharSequence hash) {
+               List hashes = getAcceptableHashMethods();
+               if (hashes == null) return true;
+               return isInListCaseInsensitive(hash, hashes);
+       }
+
+       /**
+        * @see 
org.apache.james.jdkim.PublicKeyRecord#isKeyTypeSupported(java.lang.CharSequence)
+        */
+       public boolean isKeyTypeSupported(CharSequence hash) {
+               List hashes = getAcceptableKeyTypes();
+               return isInListCaseInsensitive(hash, hashes);
+       }
+       
+       /**
+        * @see 
org.apache.james.jdkim.PublicKeyRecord#getAcceptableHashMethods()
+        */
+       public List/* String */ getAcceptableHashMethods() {
+               if (ANY.equals(getValue("h"))) return null;
+               return stringToColonSeparatedList(getValue("h").toString(), 
hyphenatedWordPattern);
+       }
+       /**
+        * @see org.apache.james.jdkim.PublicKeyRecord#getAcceptableKeyTypes()
+        */
+       public List/* String */ getAcceptableKeyTypes() {
+               return stringToColonSeparatedList(getValue("k").toString(), 
hyphenatedWordPattern);
+       }
+
+
+       /**
+        * @see org.apache.james.jdkim.PublicKeyRecord#getGranularityPattern()
+        */
+       public Pattern getGranularityPattern() {
+               String g = getValue("g").toString();
+               int pStar = g.indexOf('*');
+               if (VALIDATION) {
+                       if (!granularityPattern.matcher(g).matches()) throw new 
IllegalStateException("Syntax error in granularity: "+g);
+               }
+               if (g.length() == 0) {
+                       // TODO this works but smells too much as an hack.
+                       // in case of "g=" with nothing specified then we 
return a pattern that won't match
+                       // SPEC: An empty "g=" value never matches any 
addresses.
+                       return Pattern.compile("@");
+               } else if (pStar != -1) {
+                       if (g.indexOf('*',pStar+1) != -1) throw new 
IllegalStateException("Invalid granularity using more than one wildcard: "+g);
+                       String pattern = "^"+Pattern.quote(g.subSequence(0, 
pStar).toString())+".*"+Pattern.quote(g.subSequence(pStar+1, 
g.length()).toString())+"$";
+                       return Pattern.compile(pattern);
+               } else {
+                       return Pattern.compile("^"+Pattern.quote(g)+"$");
+               }
+       }
+
+       public List getFlags() {
+               String flags = getValue("t").toString();
+               String[] flagsStrings = flags.split(":");
+               List res = new ArrayList();
+               for (int i = 0; i < flagsStrings.length; i++) {
+                       res.add(trimFWS(flagsStrings[i], 0, 
flagsStrings[i].length()-1, true).toString());
+               }
+               return res;
+       }
+
+       public boolean isDenySubdomains() {
+               return getFlags().contains("s");
+       }
+
+       public boolean isTesting() {
+               return getFlags().contains("y");
+       }
+
+
+       /**
+        * @see org.apache.james.jdkim.PublicKeyRecord#getPublicKey()
+        */
+       public PublicKey getPublicKey() {
+               try {
+                       String p = getValue("p").toString();
+                       byte[] key = Base64.decodeBase64( p.getBytes() );
+                       KeyFactory keyFactory;
+                       keyFactory = 
KeyFactory.getInstance(getValue("k").toString());
+                       X509EncodedKeySpec pubSpec = new 
X509EncodedKeySpec(key);
+                       RSAPublicKey rsaKey;
+                       rsaKey = (RSAPublicKey) 
keyFactory.generatePublic(pubSpec);
+                       return rsaKey;
+               } catch (NoSuchAlgorithmException e) {
+                       throw new IllegalStateException("Unknown algorithm",e);
+               } catch (InvalidKeySpecException e) {
+                       throw new IllegalStateException("Invalid key spec",e);
+               }
+       }
+
+       /**
+        * @see 
org.apache.james.jdkim.PublicKeyRecord#apply(org.apache.james.jdkim.SignatureRecord)
+        */
+       public void apply(SignatureRecord sign) {
+               if 
(!getGranularityPattern().matcher(sign.getIdentityLocalPart()).matches()) {
+                       throw new IllegalStateException("inapplicable key for 
g="+getValue("g")+" and identity local="+sign.getIdentityLocalPart()+" Pattern: 
"+getGranularityPattern().pattern());
+               }
+               
+               if (!isHashMethodSupported(sign.getHashMethod())) {
+                       throw new IllegalStateException("inappropriate hash 
method h="+getValue("h")+" and 
a="+sign.getHashKeyType()+"/"+sign.getHashMethod());
+               }
+               if (!isKeyTypeSupported(sign.getHashKeyType())) {
+                       throw new IllegalStateException("inappropriate key type 
k="+getValue("k")+" and a="+sign.getHashKeyType()+"/"+sign.getHashMethod());
+               }
+               
+               if (isDenySubdomains()) {
+                       if 
(!sign.getIdentity().toString().toLowerCase().endsWith(("@"+sign.getDToken()).toLowerCase()))
 {
+                               throw new IllegalStateException("AUID in 
subdomain of SDID is not allowed by the public key record.");
+                       }
+               }
+               
+       }
+
+}

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

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

Added: 
james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/tagvalue/SignatureRecordImpl.java
URL: 
http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/tagvalue/SignatureRecordImpl.java?rev=822372&view=auto
==============================================================================
--- 
james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/tagvalue/SignatureRecordImpl.java
 (added)
+++ 
james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/tagvalue/SignatureRecordImpl.java
 Tue Oct  6 17:45:01 2009
@@ -0,0 +1,237 @@
+/****************************************************************
+ * 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.tagvalue;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.apache.james.jdkim.CodecUtil;
+import org.apache.james.jdkim.SignatureRecord;
+
+import 
com.sun.org.apache.xml.internal.security.exceptions.Base64DecodingException;
+import com.sun.org.apache.xml.internal.security.utils.Base64;
+
+
+public class SignatureRecordImpl extends TagValue implements SignatureRecord {
+
+       // TODO ftext is defined as a sequence of at least one in %d33-57 or 
%d59-126
+       private static Pattern hdrNamePattern = Pattern.compile("^[^: 
\r\n\t]+$");
+
+       public SignatureRecordImpl(String data) {
+               super(data);
+       }
+       
+       protected void init() {
+               super.init();
+               
+               mandatoryTags.add("v");
+               mandatoryTags.add("a");
+               mandatoryTags.add("b");
+               mandatoryTags.add("bh");
+               mandatoryTags.add("d");
+               mandatoryTags.add("h");
+               mandatoryTags.add("s");
+
+               defaults.put("c", "simple/simple");
+               defaults.put("l", ALL);
+               defaults.put("q", "dns/txt");
+       }
+       
+       /**
+        * @see org.apache.james.jdkim.SignatureRecord#validate()
+        */
+       public void validate() throws IllegalStateException {
+               super.validate();
+               // TODO: what about v=0.5 and no v= at all?
+               // do specs allow parsing? what should we check?
+               if (!"1".equals(getValue("v"))) throw new 
IllegalStateException("Invalid DKIM-Signature version (expected '1'): 
"+getValue("v"));
+               if (getValue("h").length() == 0) throw new 
IllegalStateException("Tag h= cannot be empty.");
+               if 
(!getIdentity().toString().toLowerCase().endsWith(("@"+getValue("d")).toLowerCase())
+                               && 
!getIdentity().toString().toLowerCase().endsWith(("."+getValue("d")).toLowerCase()))
 throw new IllegalStateException("Domain mismatch");
+
+               // when "x=" exists and signature expired then return PERMFAIL 
(signature expired)
+               if (getValue("x") != null) {
+                       long expiration = 
Long.parseLong(getValue("x").toString());
+                       long lifetime = (expiration - 
System.currentTimeMillis() / 1000);
+                       String measure = "s";
+                       if (lifetime < 0) {
+                               lifetime = -lifetime;
+                               if (lifetime > 600) {
+                                       lifetime = lifetime / 60;
+                                       measure = "m";
+                                       if (lifetime > 600) {
+                                               lifetime = lifetime / 60;
+                                               measure = "h";
+                                               if (lifetime > 120) {
+                                                       lifetime = lifetime / 
24;
+                                                       measure = "d";
+                                                       if (lifetime > 90) {
+                                                               lifetime = 
lifetime / 30;
+                                                               measure =" 
months";
+                                                               if (lifetime > 
24) {
+                                                                       
lifetime = lifetime / 12;
+                                                                       measure 
= " years";
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+                               throw new IllegalStateException("Signature is 
expired since "+lifetime+measure+".");
+                       }
+               }
+               
+               // when "h=" does not contain "from" return PERMFAIL (From 
field not signed).
+               if (!isInListCaseInsensitive("from", getHeaders())) throw new 
IllegalStateException("From field not signed");
+               // TODO support ignoring signature for certain d values 
(externally to this class).
+       }
+       
+       /**
+        * @see org.apache.james.jdkim.SignatureRecord#getHeaders()
+        */
+       public List/* CharSequence */ getHeaders() {
+               return stringToColonSeparatedList(getValue("h").toString(), 
hdrNamePattern);
+       }
+
+       // If i= is unspecified the default is @d
+       protected CharSequence getDefault(String tag) {
+               if ("i".equals(tag)) {
+                       return "@"+getValue("d");
+               } else return super.getDefault(tag);
+       }
+
+       /**
+        * @see org.apache.james.jdkim.SignatureRecord#getIdentityLocalPart()
+        */
+       public CharSequence getIdentityLocalPart() {
+               String identity = getIdentity().toString();
+               int pAt = identity.indexOf('@');
+               return identity.subSequence(0, pAt);
+       }
+       
+       public CharSequence getIdentity() {
+               return CodecUtil.dkimQuotedPrintableDecode(getValue("i"));
+       }
+       
+       /**
+        * @see org.apache.james.jdkim.SignatureRecord#getHashKeyType()
+        */
+       public CharSequence getHashKeyType() {
+               String a = getValue("a").toString();
+               int pHyphen = a.indexOf('-');
+               // TODO x-sig-a-tag-h   = ALPHA *(ALPHA / DIGIT)
+               if (pHyphen == -1) throw new IllegalStateException("Invalid 
hash algorythm (key type): "+a);
+               return a.subSequence(0, pHyphen);
+       }
+       
+       /**
+        * @see org.apache.james.jdkim.SignatureRecord#getHashMethod()
+        */
+       public CharSequence getHashMethod() {
+               String a = getValue("a").toString();
+               int pHyphen = a.indexOf('-');
+               // TODO x-sig-a-tag-h   = ALPHA *(ALPHA / DIGIT)
+               if (pHyphen == -1) throw new IllegalStateException("Invalid 
hash method: "+a);
+               return a.subSequence(pHyphen+1, a.length());
+       }
+       
+       /**
+        * @see org.apache.james.jdkim.SignatureRecord#getHashAlgo()
+        */
+       public CharSequence getHashAlgo() {
+               String a = getValue("a").toString();
+               int pHyphen = a.indexOf('-');
+               if (pHyphen == -1) throw new IllegalStateException("Invalid 
hash method: "+a);
+               if (a.length() > pHyphen+3 && a.charAt(pHyphen+1) == 's' && 
a.charAt(pHyphen+2) == 'h' && a.charAt(pHyphen+3) == 'a') {
+                       return "sha-"+a.subSequence(pHyphen+4, a.length());
+               } else return a.subSequence(pHyphen+1, a.length());
+       }
+       
+       /**
+        * @see org.apache.james.jdkim.SignatureRecord#getSelector()
+        */
+       public CharSequence getSelector() {
+               return getValue("s");
+       }
+
+       /**
+        * @see org.apache.james.jdkim.SignatureRecord#getDToken()
+        */
+       public CharSequence getDToken() {
+               return getValue("d");
+       }
+
+       public byte[] getBodyHash() {
+               try {
+                       return 
Base64.decode(getValue("bh").toString().getBytes());
+               } catch (Base64DecodingException e) {
+                       // TODO not the best thing
+                       throw new IllegalStateException("Base64.decode.failed", 
e);
+               }
+       }
+
+       public byte[] getSignature() {
+               try {
+                       return 
Base64.decode(getValue("b").toString().getBytes());
+               } catch (Base64DecodingException e) {
+                       // TODO not the best thing
+                       throw new IllegalStateException("Base64.decode.failed", 
e);
+               }
+       }
+
+       public int getBodyHashLimit() {
+               String limit = getValue("l").toString();
+               if (ALL.equals(limit)) return -1;
+               else return Integer.parseInt(limit);
+       }
+
+       public String getBodyCanonicalisationMethod() {
+               String c = getValue("c").toString();
+               int pSlash = c.toString().indexOf("/");
+               if (pSlash != -1) {
+                       return c.substring(pSlash+1);
+               } else {
+                       return "simple";
+               }
+       }
+
+       public String getHeaderCanonicalisationMethod() {
+               String c = getValue("c").toString();
+               int pSlash = c.toString().indexOf("/");
+               if (pSlash != -1) {
+                       return c.substring(0, pSlash);
+               } else {
+                       return c;
+               }
+       }
+       
+       public List getRecordLookupMethods() {
+               String flags = getValue("q").toString();
+               String[] flagsStrings = flags.split(":");
+               List res = new LinkedList();
+               for (int i = 0; i < flagsStrings.length; i++) {
+                       // TODO add validation method[/option]
+                       // if (VALIDATION)
+                       res.add(trimFWS(flagsStrings[i], 0, 
flagsStrings[i].length()-1, true).toString());
+               }
+               return res;
+       }
+
+}

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

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

Added: 
james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/tagvalue/TagValue.java
URL: 
http://svn.apache.org/viewvc/james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/tagvalue/TagValue.java?rev=822372&view=auto
==============================================================================
--- 
james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/tagvalue/TagValue.java
 (added)
+++ 
james/jdkim/trunk/main/src/main/java/org/apache/james/jdkim/tagvalue/TagValue.java
 Tue Oct  6 17:45:01 2009
@@ -0,0 +1,225 @@
+/****************************************************************
+ * 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.tagvalue;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+/**
+ * This class handle a tag=value list string as defined by DKIM specification
+ * It also supports mandatoryTags and default values as a commodity to 
subclasses.
+ */
+public class TagValue {
+       
+       private static final boolean DEBUG = false;
+       protected static final boolean VALIDATION = true;
+
+       private static Pattern tagPattern = 
Pattern.compile("^[A-Za-z][A-Za-z0-9_]*$");
+       private static final String tval = "[^; \t\r\n]+";
+       // validate value chars
+       private static Pattern valuePattern = 
Pattern.compile("^("+tval+"((\r\n[\t ]|[\t ])+"+tval+")*)?$");
+       
+       // we may use a TreeMap because we may need to know original order.
+       protected Map/* String, CharSequence */ tagValues;
+       
+       protected Set/* String */ mandatoryTags = new HashSet();
+       protected Map/* String, CharSequence */ defaults = new HashMap();
+       
+       
+       protected CharSequence trimFWS(CharSequence data, int tStart, int 
tStop, boolean trimWSP) {
+               if (DEBUG) 
System.out.println("1["+data+"]"+tStart+"|"+tStop+"="+data.subSequence(tStart, 
tStop+1)+"]");
+               // rimozione di FWS a inizio selezione
+               while (tStart < tStop && (data.charAt(tStart) == ' ' || 
data.charAt(tStart) == '\t') || 
+                               (tStart < tStop - 2 && data.charAt(tStart) == 
'\r' && data.charAt(tStart+1) == '\n' && (data.charAt(tStart+2) == ' ' || 
data.charAt(tStart+2) == '\t'))) {
+                       if (data.charAt(tStart) == '\r') tStart += 3;
+                       else tStart++;
+               }
+
+               if (DEBUG) 
System.out.println("2["+data+"]"+tStart+"|"+tStop+"="+data.subSequence(tStart, 
tStop+1)+"]");
+               // rimozione di FWS a fine selezione.
+               while (tStart < tStop && (data.charAt(tStop) == ' ' || 
data.charAt(tStop) == '\t')) {
+                       tStop--;
+                       if ((tStart <= tStop-1 && data.charAt(tStop) == '\n' && 
data.charAt(tStop-1) == '\r') || (tStart < tStop && (data.charAt(tStop) == ' ' 
|| data.charAt(tStop) == '\t'))) {
+                               if (data.charAt(tStop) == '\n') tStop -= 2;
+                               else tStop--;
+                       }
+               }
+
+               if (DEBUG) 
System.out.println("3["+data+"]"+tStart+"|"+tStop+"="+data.subSequence(tStart, 
tStop+1)+"]");
+               if (trimWSP) {
+                       return trimWSP(data, tStart, tStop);
+               } else {
+                       return data.subSequence(tStart, tStop+1);
+               }
+       }
+       
+       private CharSequence trimWSP(CharSequence data, int vStart, int vStop) {
+               if (vStop < vStart-1) throw new IllegalArgumentException("Stop 
must be >= than start");
+               while (vStart <= vStop && (data.charAt(vStart) == ' ' || 
data.charAt(vStart) == '\t')) vStart++;
+               while (vStart <= vStop && (data.charAt(vStop) == ' ' || 
data.charAt(vStop) == '\t')) vStop--;
+               return data.subSequence(vStart, vStop+1);
+       }
+       
+       public TagValue(String data) {
+               init();
+               parse(data);
+       }
+       
+       protected void init() {
+               // extensions may override this to use TreeMaps in order to 
keep track of orders
+               tagValues = new HashMap();
+       }
+
+       /**
+        * subclasses have to make sure tagValues is initialized during init().
+        * @param data the string to be parsed
+        */
+       protected void parse(String data) {
+               for (int i = 0; i < data.length(); i++) {
+                       int equal = data.indexOf('=', i);
+                       if (equal == -1) {
+                               // TODO check whether this is correct or not
+                               // this allow FWS/WSP after the final ";"
+                               String rest = data.substring(i);
+                               if (rest.length() > 0 && trimFWS(rest, 0, 
rest.length()-1, true).length() > 0) {
+                                       throw new 
IllegalStateException("Unexpected termination at position "+i+": "+data);
+                               }
+                               i = data.length();
+                               continue;
+                       }
+                       // we could start from "equals" but we start from "i" 
in 
+                       // order to spot invalid values before validation.
+                       int next = data.indexOf(';', i);
+                       if (next == -1) {
+                               next = data.length();
+                       }
+                       
+                       if (equal > next) {
+                               throw new IllegalStateException("Found ';' 
before '=' in "+data);
+                       }
+                       
+                       CharSequence tag = trimFWS(data, i, equal-1, 
true).toString();
+                       if (VALIDATION && !tagPattern.matcher(tag).matches()) {
+                               throw new IllegalStateException("Syntax error 
in tag: "+tag);
+                       }
+                       String tagString = tag.toString();
+                       if (tagValues.containsKey(tagString)) {
+                               throw new IllegalStateException("Syntax error 
(duplicate tag): "+tag);
+                       }
+
+                       CharSequence value = trimFWS(data, equal+1, next-1, 
true);
+                       if (VALIDATION && 
!valuePattern.matcher(value).matches()) {
+                               throw new IllegalStateException("Syntax error 
in value: "+value);
+                       }
+                       
+                       tagValues.put(tagString, value);
+                       i = next;
+               }
+       }
+       
+       public int hashCode() {
+               final int prime = 31;
+               int result = 1;
+               result = prime * result
+                               + ((tagValues == null) ? 0 : 
tagValues.hashCode());
+               return result;
+       }
+
+       public boolean equals(Object obj) {
+               if (this == obj)
+                       return true;
+               if (obj == null)
+                       return false;
+               if (getClass() != obj.getClass())
+                       return false;
+               TagValue other = (TagValue) obj;
+               if (tagValues == null) {
+                       if (other.tagValues != null)
+                               return false;
+               } else if (!tagValues.equals(other.tagValues))
+                       return false;
+               return true;
+       }
+
+       public Set getTags() {
+               return tagValues.keySet();
+       }
+       
+       protected CharSequence getValue(String key) {
+               CharSequence val = (CharSequence) tagValues.get(key);
+               if (val == null) return getDefault(key);
+               else return val;
+       }
+
+       protected CharSequence getDefault(String key) {
+               return (CharSequence) defaults.get(key);
+       }
+       
+       public void validate() {
+               // check mandatory fields
+               for (Iterator i = mandatoryTags.iterator(); i.hasNext(); ) {
+                       String tag = (String) i.next();
+                       if (getValue(tag)==null) throw new 
IllegalStateException("Missing mandatory tag: "+tag);
+               }
+       }
+
+       protected List stringToColonSeparatedList(String h, Pattern pattern) {
+               List headers = new ArrayList();
+               for (int i = 0; i < h.length(); i++) {
+                       int p = h.indexOf(':', i);
+                       if (p == -1) p = h.length();
+                       CharSequence cs = trimFWS(h, i, p-1, false);
+                       if (VALIDATION) {
+                               if (!pattern.matcher(cs).matches()) throw new 
IllegalStateException("Syntax error in field name: "+cs);
+                       }
+                       headers.add(cs);
+                       i = p;
+               }
+               return headers;
+       }
+
+       protected boolean isInListCaseInsensitive(CharSequence hash, List 
hashes) {
+               for (Iterator i = hashes.iterator(); i.hasNext(); ) {
+                       CharSequence suppHash = (CharSequence) i.next();
+                       if 
(hash.toString().equalsIgnoreCase(suppHash.toString())) return true;
+               }
+               return false;
+       }
+
+       public String toString() {
+               StringBuffer res = new StringBuffer();
+               Set s = getTags();
+               for (Iterator i = s.iterator(); i.hasNext(); ) {
+                       String tag = (String) i.next();
+                       res.append(tag);
+                       res.append("=");
+                       res.append(getValue(tag));
+                       res.append("; ");
+               }
+               return res.toString();
+       }
+
+}

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

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



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

Reply via email to