001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.harmony.jndi.internal.parser;
019:
020: import java.io.IOException;
021: import java.util.Iterator;
022: import java.util.List;
023:
024: import javax.naming.InvalidNameException;
025:
026: import org.apache.harmony.jndi.internal.nls.Messages;
027:
028: /**
029: * Class used to parse RDN components of a Distinguished name
030: */
031: public class LdapRdnParser implements LdapParser {
032:
033: private static String name = null;
034:
035: LdapTypeAndValueList list = new LdapTypeAndValueList();
036:
037: private List<AttributeTypeAndValuePair> attrList;
038:
039: private List<AttributeTypeAndValuePair> listAll;
040:
041: private RelaxedDnParser parser = null;
042:
043: /**
044: * Constructor
045: */
046: public LdapRdnParser(String name) {
047: if (name.endsWith("+")) {
048: LdapRdnParser.name = name.substring(0, name
049: .lastIndexOf('+'));
050: } else {
051: LdapRdnParser.name = name;
052: }
053: }
054:
055: /**
056: * Used to get the list of Type/Values
057: */
058: public List getList() throws InvalidNameException {
059: try {
060: checkTypeRestrictions(name);
061: parser = new RelaxedDnParser(name);
062: listAll = parser.parse();
063: } catch (IOException e) {
064: throw (InvalidNameException) (new InvalidNameException(
065: Messages.getString("ldap.17") + name).initCause(e));
066: }
067:
068: attrList = (List) listAll.get(0);
069: for (Iterator<AttributeTypeAndValuePair> iter = attrList
070: .iterator(); iter.hasNext();) {
071: AttributeTypeAndValuePair element = iter.next();
072: list.put(element.getType(), element.getValue());
073: }
074: return list.toAttributeList();
075: }
076:
077: private void checkTypeRestrictions(String rdn)
078: throws InvalidNameException {
079: int in = rdn.indexOf("=");
080: try {
081: if (rdn.substring(0, in).length() == 0) {
082: throw new InvalidNameException(Messages
083: .getString("ldap.18")
084: + rdn);
085: }
086: } catch (StringIndexOutOfBoundsException e) {
087: throw new InvalidNameException(Messages
088: .getString("ldap.17")
089: + rdn);
090: }
091: }
092:
093: /**
094: * returns a string escaped according to the rules specified in RFC 2253.
095: *
096: * @param obj
097: * the String or byteArray to escape
098: * @return The escaped value
099: * @throws ClassCastException
100: * when obj is not an instance of String or ByteArray
101: */
102: public static String escapeValue(Object obj) {
103: if (obj instanceof String) {
104: String val = String.valueOf(obj);
105: return getEscaped(val.toCharArray());
106: } else if (obj instanceof byte[]) {
107: return getHexValues((byte[]) obj);
108: } else {
109: throw new ClassCastException(Messages.getString("ldap.19"));
110: }
111: }
112:
113: private static String getEscaped(char[] chars) {
114: StringBuffer sb = new StringBuffer();
115: int leftSpaceCnt = 0, rightSpaceCnt = 0;
116: int pos = chars.length - 1;
117: while (pos >= 0 && chars[pos] == ' ') {
118: rightSpaceCnt++;
119: pos--;
120: }
121: for (int i = 0; i <= pos && chars[i] == ' '; i++) {
122: leftSpaceCnt++;
123: sb.append("\\ ");
124: }
125: for (int i = leftSpaceCnt; i < chars.length - rightSpaceCnt; i++) {
126: if (isSpecialChar(chars, i)) {
127: sb.append('\\');
128: }
129: sb.append(new Character(chars[i]));
130: }
131: for (int i = 0; i < rightSpaceCnt; i++) {
132: sb.append("\\ ");
133: }
134: return sb.toString();
135: }
136:
137: private static String getHexValues(byte[] byteArray) {
138: StringBuffer sb = new StringBuffer();
139: for (int i = 0; i < byteArray.length; i++) {
140: sb.append(Integer.toHexString(byteArray[i] >> 4 & 0x0F));
141: sb.append(Integer.toHexString(byteArray[i] & 0x0F));
142: }
143: return '#' + sb.toString();
144: }
145:
146: /**
147: * Given an attribute value string formated according to the rules specified
148: * in RFC 2253, returns the unescaped value.
149: *
150: * @param val
151: * String to unescape
152: * @return the unescaped value (String or ByteArray)
153: */
154: public static Object unescapeValue(String val) {
155: if (val.startsWith("#") && numeralCounter(val)) {
156: return getByteFromHexString(val);
157: } else if (val.startsWith("#")) {
158: return getByteFromHexString(val);
159: } else {
160: String tmpVal = val.trim();
161: if (tmpVal.length() > 0 && tmpVal.charAt(0) == '\"'
162: && tmpVal.charAt(tmpVal.length() - 1) == '\"') {
163: if (tmpVal.length() == 1) {
164: val = "";
165: } else {
166: val = tmpVal.substring(1, tmpVal.length() - 1);
167: }
168: }
169: char[] chars;
170: int pos = val.length() - 1;
171: boolean trailingSpace = false;
172: while (pos >= 0 && val.charAt(pos) == ' ') {
173: trailingSpace = true;
174: pos--;
175: }
176: if (pos >= 0 && val.charAt(pos) == '\\' && trailingSpace) {
177: chars = (new String(val.trim()) + " ").toCharArray();
178: } else {
179: chars = new String(val.trim()).toCharArray();
180: }
181: return getUnEscapedValues(chars);
182: }
183:
184: }
185:
186: private static boolean numeralCounter(String val) {
187: int counter = 0;
188: for (int i = 0; i < val.length(); i++) {
189: if (val.charAt(i) == '#') {
190: counter++;
191: }
192: }
193: return counter == 1;
194: }
195:
196: private static String getUnEscapedValues(char[] chars) {
197: StringBuffer sb = new StringBuffer();
198: boolean trailing20h = false;
199: for (int i = 0; i < chars.length; i++) {
200: trailing20h = false;
201: if (chars[i] != '\\') {
202: sb.append(chars[i]);
203: } else {
204: try {
205: if (chars[i + 1] == ' ') {
206: continue;
207: }
208: if (chars[i + 1] == '\\') {
209: sb.append('\\');
210: i = i + 1;
211: continue;
212: }
213: if (!isSpecialChar(chars, i + 1)
214: && !isSpecialChar(chars, i + 2)) {
215: try {
216: sb.append(RelaxedDnParser
217: .hexToUTF8(new String(chars, i + 1,
218: 2)));
219: if (sb.charAt(sb.length() - 1) == ' ') {
220: trailing20h = true;
221: }
222: i = i + 2;
223: } catch (IOException e) {
224: throw new IllegalArgumentException(Messages
225: .getString("ldap.1A"));
226: }
227: }
228: } catch (ArrayIndexOutOfBoundsException e) {
229: sb.append(chars[i]);
230: }
231: }
232: }
233: if (trailing20h && sb.length() > 0
234: && sb.charAt(sb.length() - 1) == ' ') {
235: sb = sb.deleteCharAt(sb.length() - 1);
236: }
237: return sb.toString();
238: }
239:
240: private static byte[] getByteFromHexString(String val) {
241: String str = val.substring(val.indexOf("#") + 1);
242: if (str.length() % 2 != 0) {
243: throw new IllegalArgumentException(Messages
244: .getString("ldap.1A"));
245: }
246: byte[] ret = null;
247: try {
248: ret = new byte[str.length() / 2];
249: for (int i = 0; i < ret.length; i++) {
250: ret[i] = (byte) Integer.parseInt(str.substring(2 * i,
251: 2 * i + 2), 16);
252: }
253: } catch (NumberFormatException e) {
254: throw new IllegalArgumentException(Messages
255: .getString("ldap.17")
256: + val);
257: }
258: return ret;
259: }
260:
261: private static boolean isSpecialChar(char[] chars, int index) {
262: switch (chars[index]) {
263: case '"':
264: case '\\':
265: case ',':
266: case '=':
267: case '+':
268: case '<':
269: case '>':
270: case '#':
271: case ';':
272: return true;
273: default:
274: return false;
275: }
276: }
277: }
|