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