1/*
2 *  Licensed to the Apache Software Foundation (ASF) under one or more
3 *  contributor license agreements.  See the NOTICE file distributed with
4 *  this work for additional information regarding copyright ownership.
5 *  The ASF licenses this file to You under the Apache License, Version 2.0
6 *  (the "License"); you may not use this file except in compliance with
7 *  the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 */
17
18package javax.security.auth.x500;
19
20import java.io.IOException;
21import java.io.InputStream;
22import java.io.ObjectInputStream;
23import java.io.ObjectOutputStream;
24import java.io.Serializable;
25import java.security.Principal;
26import java.util.Map;
27import org.apache.harmony.security.x501.Name;
28
29/**
30 * Represents an X.500 principal, which holds the distinguished name of some
31 * network entity. An example of a distinguished name is {@code "O=SomeOrg,
32 * OU=SomeOrgUnit, C=US"}. The class can be instantiated from a byte representation
33 * of an object identifier (OID), an ASN.1 DER-encoded version, or a simple
34 * string holding the distinguished name. The representations must follow either
35 * RFC 2253, RFC 1779, or RFC2459.
36 */
37public final class X500Principal implements Serializable, Principal {
38
39    private static final long serialVersionUID = -500463348111345721L;
40
41    /**
42     * Defines a constant for the canonical string format of distinguished
43     * names.
44     */
45    public static final String CANONICAL = "CANONICAL";
46
47    /**
48     * Defines a constant for the RFC 1779 string format of distinguished
49     * names.
50     */
51    public static final String RFC1779 = "RFC1779";
52
53    /**
54     * Defines a constant for the RFC 2253 string format of distinguished
55     * names.
56     */
57    public static final String RFC2253 = "RFC2253";
58
59    //Distinguished Name
60    private transient Name dn;
61
62    /**
63     * Creates a new X500Principal from a given ASN.1 DER encoding of a
64     * distinguished name.
65     *
66     * @param name
67     *            the ASN.1 DER-encoded distinguished name
68     *
69     * @throws IllegalArgumentException
70     *             if the ASN.1 DER-encoded distinguished name is incorrect
71     */
72    public X500Principal(byte[] name) {
73        if (name == null) {
74            throw new IllegalArgumentException("Name cannot be null");
75        }
76        try {
77            // FIXME dn = new Name(name);
78            dn = (Name) Name.ASN1.decode(name);
79        } catch (IOException e) {
80            throw incorrectInputEncoding(e);
81        }
82    }
83
84    /**
85     * Creates a new X500Principal from a given ASN.1 DER encoding of a
86     * distinguished name.
87     *
88     * @param in
89     *            an {@code InputStream} holding the ASN.1 DER-encoded
90     *            distinguished name
91     *
92     * @throws IllegalArgumentException
93     *             if the ASN.1 DER-encoded distinguished name is incorrect
94     */
95    public X500Principal(InputStream in) {
96        if (in == null) {
97            throw new NullPointerException("in == null");
98        }
99        try {
100            // FIXME dn = new Name(is);
101            dn = (Name) Name.ASN1.decode(in);
102        } catch (IOException e) {
103            throw incorrectInputEncoding(e);
104        }
105    }
106
107    private IllegalArgumentException incorrectInputEncoding(IOException e) {
108        IllegalArgumentException iae = new IllegalArgumentException("Incorrect input encoding");
109        iae.initCause(e);
110        throw iae;
111    }
112
113    /**
114     * Creates a new X500Principal from a string representation of a
115     * distinguished name.
116     *
117     * @param name
118     *            the string representation of the distinguished name
119     *
120     * @throws IllegalArgumentException
121     *             if the string representation of the distinguished name is
122     *             incorrect
123     */
124    public X500Principal(String name) {
125        if (name == null) {
126            throw new NullPointerException("name == null");
127        }
128        try {
129            dn = new Name(name);
130        } catch (IOException e) {
131            throw incorrectInputName(e, name);
132        }
133    }
134
135    public X500Principal(String name, Map<String,String> keywordMap){
136        if (name == null) {
137            throw new NullPointerException("name == null");
138        }
139        try {
140            dn = new Name(substituteNameFromMap(name, keywordMap));
141        } catch (IOException e) {
142            throw incorrectInputName(e, name);
143        }
144    }
145
146    private IllegalArgumentException incorrectInputName(IOException e, String name) {
147        IllegalArgumentException iae = new IllegalArgumentException("Incorrect input name:" + name);
148        iae.initCause(e);
149        throw iae;
150    }
151
152    private transient String canonicalName;
153    private synchronized String getCanonicalName() {
154        if (canonicalName == null) {
155            canonicalName = dn.getName(CANONICAL);
156        }
157        return canonicalName;
158    }
159
160    @Override
161    public boolean equals(Object o) {
162        if (this == o) {
163            return true;
164        }
165        if (o == null || this.getClass() != o.getClass()) {
166            return false;
167        }
168        X500Principal principal = (X500Principal) o;
169        return getCanonicalName().equals(principal.getCanonicalName());
170    }
171
172    /**
173     * Returns an ASN.1 DER-encoded representation of the distinguished name
174     * contained in this X.500 principal.
175     *
176     * @return the ASN.1 DER-encoded representation
177     */
178    public byte[] getEncoded() {
179        byte[] src = dn.getEncoded();
180        byte[] dst = new byte[src.length];
181        System.arraycopy(src, 0, dst, 0, dst.length);
182        return dst;
183    }
184
185    /**
186     * Returns a human-readable string representation of the distinguished name
187     * contained in this X.500 principal.
188     *
189     * @return the string representation
190     */
191    public String getName() {
192        return dn.getName(RFC2253);
193    }
194
195    /**
196     * Returns a string representation of the distinguished name contained in
197     * this X.500 principal. The format of the representation can be chosen.
198     * Valid arguments are {@link #RFC1779}, {@link #RFC2253}, and
199     * {@link #CANONICAL}. The representations are specified in RFC 1779 and RFC
200     * 2253, respectively. The canonical form is based on RFC 2253, but adds
201     * some canonicalizing operations like removing leading and trailing
202     * whitespace, lower-casing the whole name, and bringing it into a
203     * normalized Unicode representation.
204     *
205     * @param format
206     *            the name of the format to use for the representation
207     *
208     * @return the string representation
209     *
210     * @throws IllegalArgumentException
211     *             if the {@code format} argument is not one of the three
212     *             mentioned above
213     */
214    public String getName(String format) {
215        if (CANONICAL.equals(format)) {
216            return getCanonicalName();
217        }
218
219        return dn.getName(format);
220    }
221
222    public String getName(String format, Map<String, String> oidMap) {
223        String rfc1779Name = dn.getName(RFC1779);
224        String rfc2253Name = dn.getName(RFC2253);
225
226        if (format.equalsIgnoreCase("RFC1779")) {
227            StringBuilder resultName = new StringBuilder(rfc1779Name);
228            int fromIndex = resultName.length();
229            int equalIndex = -1;
230            while (-1 != (equalIndex = resultName.lastIndexOf("=", fromIndex))) {
231                int commaIndex = resultName.lastIndexOf(",", equalIndex);
232                String subName = resultName.substring(commaIndex + 1,
233                        equalIndex).trim();
234                if (subName.length() > 4
235                        && subName.substring(0, 4).equals("OID.")) {
236                    String subSubName = subName.substring(4);
237                    if (oidMap.containsKey(subSubName)) {
238                        String replaceName = oidMap.get(subSubName);
239                        if(commaIndex > 0) replaceName = " " + replaceName;
240                        resultName.replace(commaIndex + 1, equalIndex, replaceName);
241                    }
242                }
243                fromIndex = commaIndex;
244            }
245            return resultName.toString();
246        } else if (format.equalsIgnoreCase("RFC2253")) {
247            StringBuilder resultName = new StringBuilder(rfc2253Name);
248            StringBuilder subsidyName = new StringBuilder(rfc1779Name);
249
250            int fromIndex = resultName.length();
251            int subsidyFromIndex = subsidyName.length();
252            int equalIndex = -1;
253            int subsidyEqualIndex = -1;
254            while (-1 != (equalIndex = resultName.lastIndexOf("=", fromIndex))) {
255                subsidyEqualIndex = subsidyName.lastIndexOf("=",
256                        subsidyFromIndex);
257                int commaIndex = resultName.lastIndexOf(",", equalIndex);
258                String subName = resultName.substring(commaIndex + 1,
259                        equalIndex).trim();
260                if (oidMap.containsKey(subName)) {
261                    int subOrignalEndIndex = resultName
262                            .indexOf(",", equalIndex);
263                    if (subOrignalEndIndex == -1)
264                        subOrignalEndIndex = resultName.length();
265                    int subGoalEndIndex = subsidyName.indexOf(",",
266                            subsidyEqualIndex);
267                    if (subGoalEndIndex == -1)
268                        subGoalEndIndex = subsidyName.length();
269                    resultName.replace(equalIndex + 1, subOrignalEndIndex,
270                            subsidyName.substring(subsidyEqualIndex + 1,
271                                    subGoalEndIndex));
272                    resultName.replace(commaIndex + 1, equalIndex, oidMap
273                            .get(subName));
274                }
275                fromIndex = commaIndex;
276                subsidyFromIndex = subsidyEqualIndex - 1;
277            }
278            return resultName.toString();
279        } else {
280            throw new IllegalArgumentException("invalid format specified: " + format);
281        }
282    }
283
284    @Override
285    public int hashCode() {
286        return getCanonicalName().hashCode();
287    }
288
289    @Override
290    public String toString() {
291        return dn.getName(RFC1779);
292    }
293
294    private void writeObject(ObjectOutputStream out) throws IOException {
295        out.writeObject(dn.getEncoded());
296    }
297
298    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
299        dn = (Name) Name.ASN1.decode((byte[]) in.readObject());
300    }
301
302    private String substituteNameFromMap(String name, Map<String, String> keywordMap) {
303        StringBuilder sbName = new StringBuilder(name);
304        int fromIndex = sbName.length();
305        int equalIndex;
306        while (-1 != (equalIndex = sbName.lastIndexOf("=", fromIndex))) {
307            int commaIndex = sbName.lastIndexOf(",", equalIndex);
308            String subName = sbName.substring(commaIndex + 1, equalIndex).trim();
309            if (keywordMap.containsKey(subName)) {
310                sbName.replace(commaIndex + 1, equalIndex, keywordMap.get(subName));
311            }
312            fromIndex = commaIndex;
313        }
314        return sbName.toString();
315    }
316}
317