/*
* 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.harmony.jndi.internal.parser;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.naming.ldap.Rdn;
import org.apache.harmony.jndi.internal.nls.Messages;
import org.apache.harmony.security.x509.DNParser;
/**
* A relative distinguished name parser that does less checking than DNParser
*/
public class RelaxedDnParser extends DNParser {
private static final char[] specialChars = new char[] { '!', '$', '%', '&',
'\'', '(', ')', '*', '/', ':', '<', '=', '>', '?', '@', '[', ']',
'^', '_', '`', '{', '|', '}', '~', '\"', '#', '+', ',', '\\', ';' };
/**
* Constructor
*
* @param dn
* the String to parse
* @throws IOException
*/
public RelaxedDnParser(String dn) throws IOException {
super(dn);
}
@Override
protected String nextAT() throws IOException {
int equalIndex = pos;
try {
while (chars[equalIndex] != '=') {
equalIndex++; // points to the equals on the current attribute
// type pair
}
} catch (ArrayIndexOutOfBoundsException e) {
throw new IOException(Messages.getString("ldap.1C")); //$NON-NLS-1$
}
// Check for quotations and special chars on the type
for (int i = pos; i < equalIndex; i++) {
if (chars[i] == '\\') {
throw new IOException(Messages.getString("ldap.1D")); //$NON-NLS-1$
}
for (int j = 0; j < specialChars.length; j++) {
if (chars[i] == specialChars[j]) {
throw new IOException(Messages.getString("ldap.1E")); //$NON-NLS-1$
}
}
}
return super.nextAT();
}
protected String hexAV() throws IOException {
beg = pos; // store '#' position
pos++;
while (true) {
// check for end of attribute value
// looks for space and component separators
if (pos == length || chars[pos] == '+' || chars[pos] == ','
|| chars[pos] == ';') {
end = pos;
break;
}
if (chars[pos] >= 'A' && chars[pos] <= 'F') {
chars[pos] += 32; // to low case
}
pos++;
}
while (end > beg + 1 && chars[end - 1] == ' ') {
end--;
}
// verify length of hex string
// encoded byte array must be even number
int hexLen = end - beg; // skip first '#' char
// get byte encoding from string representation
encoded = new byte[hexLen / 2];
for (int i = 0, p = beg + 1; i < encoded.length; p += 2, i++) {
encoded[i] = (byte) getByte(p);
}
return new String(chars, beg, hexLen);
}
/**
* Begin to parse the string given in the constructor
*/
@Override
public List parse() throws IOException {
List list = new ArrayList();
String attValue;
String attType = nextAT();
List atav = new ArrayList();
String charCopy = new String(chars);
while (true) {
if (pos == length) {
// empty Attribute Value
atav.add(new AttributeTypeAndValuePair(attType, ""));
list.add(0, atav);
return list;
}
switch (chars[pos]) {
case '"':
attValue = quotedAV();
if (attValue.length() > 0
&& attValue.charAt(attValue.length() - 1) == ' ') {
int p = pos - 1;
while (p >= 0 && charCopy.charAt(p) == ' ') {
p--;
}
if (p >= 3
&& charCopy.substring(p - 3, p + 1).equals(
"\\20\"")) {
attValue = attValue.substring(0,
attValue.length() - 1);
}
}
atav.add(new AttributeTypeAndValuePair(attType, attValue));
break;
case '#':
attValue = hexAV();
atav.add(new AttributeTypeAndValuePair(attType, Rdn
.unescapeValue(attValue)));
break;
case '+':
// empty attribute value
atav.add(new AttributeTypeAndValuePair(attType, ""));
break;
default:
attValue = escapedAV();
if (attValue.length() > 0
&& attValue.charAt(attValue.length() - 1) == ' ') {
int p = pos - 1;
while (p >= 0 && charCopy.charAt(p) == ' ') {
p--;
}
if (p >= 2
&& charCopy.substring(p - 2, p + 1).equals(
"\\20")) {
attValue = attValue.substring(0,
attValue.length() - 1);
}
}
atav.add(new AttributeTypeAndValuePair(attType, attValue));
}
if (pos >= length) {
list.add(0, atav);
return list;
}
if (chars[pos] == ',' || chars[pos] == ';') {
throw new IOException(Messages.getString("ldap.1F"));
}
pos++;
attType = nextAT();
}
}
protected char getEscaped() throws IOException {
pos++;
if (pos == length) {
throw new IOException(Messages.getString("ldap.1F"));
}
switch (chars[pos]) {
case '"':
return chars[pos];
case '\\':
hasQE = true;
return hexToUTF8(new String(chars, pos++, 2));
case ',':
case '=':
case '+':
case '<':
case '>':
case '#':
case ';':
case ' ':
// FIXME: escaping is allowed only for leading or trailing space
// char
return chars[pos];
default:
/*
* RFC doesn't explicitly say that escaped hex pair is
* interpreted as UTF-8 char. It only contains an example of
* such DN.
*/
return super.getUTF8();
}
}
/**
* Converts an String in Hexadecimal to a character
*
* @param hex
* the Hexadecimal string
* @throws IOException
* when an invalid Digit is passed or when the size is different
* than 2
* @return the corresponding character
*/
public static Character hexToUTF8(String hex) throws IOException {
try {
return new Character((char) Integer.parseInt(hex, 16));
} catch (NumberFormatException e) {
throw new IOException(Messages.getString("ldap.20")); //$NON-NLS-1$
}
}
}