001package org.apache.commons.ssl.org.bouncycastle.asn1.x500.style;
002
003import java.io.IOException;
004import java.util.Enumeration;
005import java.util.Hashtable;
006
007import org.apache.commons.ssl.org.bouncycastle.asn1.ASN1Encodable;
008import org.apache.commons.ssl.org.bouncycastle.asn1.ASN1ObjectIdentifier;
009import org.apache.commons.ssl.org.bouncycastle.asn1.DERUTF8String;
010import org.apache.commons.ssl.org.bouncycastle.asn1.x500.AttributeTypeAndValue;
011import org.apache.commons.ssl.org.bouncycastle.asn1.x500.RDN;
012import org.apache.commons.ssl.org.bouncycastle.asn1.x500.X500Name;
013import org.apache.commons.ssl.org.bouncycastle.asn1.x500.X500NameStyle;
014
015/**
016 * This class provides some default behavior and common implementation for a
017 * X500NameStyle. It should be easily extendable to support implementing the
018 * desired X500NameStyle.
019 */
020public abstract class AbstractX500NameStyle
021    implements X500NameStyle
022{
023
024    /**
025     * Tool function to shallow copy a Hashtable.
026     *
027     * @param paramsMap table to copy
028     * @return the copy of the table
029     */
030    public static Hashtable copyHashTable(Hashtable paramsMap)
031    {
032        Hashtable newTable = new Hashtable();
033
034        Enumeration keys = paramsMap.keys();
035        while (keys.hasMoreElements())
036        {
037            Object key = keys.nextElement();
038            newTable.put(key, paramsMap.get(key));
039        }
040
041        return newTable;
042    }
043
044    private int calcHashCode(ASN1Encodable enc)
045    {
046        String value = IETFUtils.valueToString(enc);
047        value = IETFUtils.canonicalize(value);
048        return value.hashCode();
049    }
050
051    public int calculateHashCode(X500Name name)
052    {
053        int hashCodeValue = 0;
054        RDN[] rdns = name.getRDNs();
055
056        // this needs to be order independent, like equals
057        for (int i = 0; i != rdns.length; i++)
058        {
059            if (rdns[i].isMultiValued())
060            {
061                AttributeTypeAndValue[] atv = rdns[i].getTypesAndValues();
062
063                for (int j = 0; j != atv.length; j++)
064                {
065                    hashCodeValue ^= atv[j].getType().hashCode();
066                    hashCodeValue ^= calcHashCode(atv[j].getValue());
067                }
068            }
069            else
070            {
071                hashCodeValue ^= rdns[i].getFirst().getType().hashCode();
072                hashCodeValue ^= calcHashCode(rdns[i].getFirst().getValue());
073            }
074        }
075
076        return hashCodeValue;
077    }
078
079
080    /**
081     * For all string values starting with '#' is assumed, that these are
082     * already valid ASN.1 objects encoded in hex.
083     * <p>
084     * All other string values are send to
085     * {@link AbstractX500NameStyle#encodeStringValue(ASN1ObjectIdentifier, String)}.
086     * </p>
087     * Subclasses should overwrite
088     * {@link AbstractX500NameStyle#encodeStringValue(ASN1ObjectIdentifier, String)}
089     * to change the encoding of specific types.
090     *
091     * @param oid the DN name of the value.
092     * @param value the String representation of the value.
093     */
094    public ASN1Encodable stringToValue(ASN1ObjectIdentifier oid, String value)
095    {
096        if (value.length() != 0 && value.charAt(0) == '#')
097        {
098            try
099            {
100                return IETFUtils.valueFromHexString(value, 1);
101            }
102            catch (IOException e)
103            {
104                throw new RuntimeException("can't recode value for oid " + oid.getId());
105            }
106        }
107
108        if (value.length() != 0 && value.charAt(0) == '\\')
109        {
110            value = value.substring(1);
111        }
112
113        return encodeStringValue(oid, value);
114    }
115
116    /**
117     * Encoded every value into a UTF8String.
118     * <p>
119     * Subclasses should overwrite
120     * this method to change the encoding of specific types.
121     * </p>
122     *
123     * @param oid the DN oid of the value
124     * @param value the String representation of the value
125     * @return a the value encoded into a ASN.1 object. Never returns <code>null</code>.
126     */
127    protected ASN1Encodable encodeStringValue(ASN1ObjectIdentifier oid, String value)
128    {
129        return new DERUTF8String(value);
130    }
131
132    public boolean areEqual(X500Name name1, X500Name name2)
133    {
134        RDN[] rdns1 = name1.getRDNs();
135        RDN[] rdns2 = name2.getRDNs();
136
137        if (rdns1.length != rdns2.length)
138        {
139            return false;
140        }
141
142        boolean reverse = false;
143
144        if (rdns1[0].getFirst() != null && rdns2[0].getFirst() != null)
145        {
146            reverse = !rdns1[0].getFirst().getType().equals(rdns2[0].getFirst().getType());  // guess forward
147        }
148
149        for (int i = 0; i != rdns1.length; i++)
150        {
151            if (!foundMatch(reverse, rdns1[i], rdns2))
152            {
153                return false;
154            }
155        }
156
157        return true;
158    }
159
160    private boolean foundMatch(boolean reverse, RDN rdn, RDN[] possRDNs)
161    {
162        if (reverse)
163        {
164            for (int i = possRDNs.length - 1; i >= 0; i--)
165            {
166                if (possRDNs[i] != null && rdnAreEqual(rdn, possRDNs[i]))
167                {
168                    possRDNs[i] = null;
169                    return true;
170                }
171            }
172        }
173        else
174        {
175            for (int i = 0; i != possRDNs.length; i++)
176            {
177                if (possRDNs[i] != null && rdnAreEqual(rdn, possRDNs[i]))
178                {
179                    possRDNs[i] = null;
180                    return true;
181                }
182            }
183        }
184
185        return false;
186    }
187
188    protected boolean rdnAreEqual(RDN rdn1, RDN rdn2)
189    {
190        return IETFUtils.rDNAreEqual(rdn1, rdn2);
191    }
192}