1. package javax.naming.ldap;
  2. import java.util.List;
  3. import java.util.ArrayList;
  4. import javax.naming.InvalidNameException;
  5. /*
  6. * RFC2253Parser implements a recursive descent parser for a single DN.
  7. */
  8. final class Rfc2253Parser {
  9. private final String name; // DN being parsed
  10. private final char[] chars; // characters in LDAP name being parsed
  11. private final int len; // length of "chars"
  12. private int cur = 0; // index of first unconsumed char in "chars"
  13. /*
  14. * Given an LDAP DN in string form, returns a parser for it.
  15. */
  16. Rfc2253Parser(String name) {
  17. this.name = name;
  18. len = name.length();
  19. chars = name.toCharArray();
  20. }
  21. /*
  22. * Parses the DN, returning a List of its RDNs.
  23. */
  24. // public List<Rdn> getDN() throws InvalidNameException {
  25. List parseDn() throws InvalidNameException {
  26. cur = 0;
  27. // ArrayList<Rdn> rdns =
  28. // new ArrayList<Rdn>(len / 3 + 10); // leave room for growth
  29. ArrayList rdns =
  30. new ArrayList(len / 3 + 10); // leave room for growth
  31. if (len == 0) {
  32. return rdns;
  33. }
  34. rdns.add(doParse(new Rdn()));
  35. while (cur < len) {
  36. if (chars[cur] == ',' || chars[cur] == ';') {
  37. ++cur;
  38. rdns.add(0, doParse(new Rdn()));
  39. } else {
  40. throw new InvalidNameException("Invalid name: " + name);
  41. }
  42. }
  43. return rdns;
  44. }
  45. /*
  46. * Parses the DN, if it is known to contain a single RDN.
  47. */
  48. Rdn parseRdn() throws InvalidNameException {
  49. return parseRdn(new Rdn());
  50. }
  51. /*
  52. * Parses the DN, if it is known to contain a single RDN.
  53. */
  54. Rdn parseRdn(Rdn rdn) throws InvalidNameException {
  55. rdn = doParse(rdn);
  56. if (cur < len) {
  57. throw new InvalidNameException("Invalid RDN: " + name);
  58. }
  59. return rdn;
  60. }
  61. /*
  62. * Parses the next RDN and returns it. Throws an exception if
  63. * none is found. Leading and trailing whitespace is consumed.
  64. */
  65. private Rdn doParse(Rdn rdn) throws InvalidNameException {
  66. while (cur < len) {
  67. consumeWhitespace();
  68. String attrType = parseAttrType();
  69. consumeWhitespace();
  70. if (cur >= len || chars[cur] != '=') {
  71. throw new InvalidNameException("Invalid name: " + name);
  72. }
  73. ++cur; // consume '='
  74. consumeWhitespace();
  75. String value = parseAttrValue();
  76. consumeWhitespace();
  77. rdn.put(attrType, Rdn.unescapeValue(value));
  78. if (cur >= len || chars[cur] != '+') {
  79. break;
  80. }
  81. ++cur; // consume '+'
  82. }
  83. rdn.sort();
  84. return rdn;
  85. }
  86. /*
  87. * Returns the attribute type that begins at the next unconsumed
  88. * char. No leading whitespace is expected.
  89. * This routine is more generous than RFC 2253. It accepts
  90. * attribute types composed of any nonempty combination of Unicode
  91. * letters, Unicode digits, '.', '-', and internal space characters.
  92. */
  93. private String parseAttrType() throws InvalidNameException {
  94. final int beg = cur;
  95. while (cur < len) {
  96. char c = chars[cur];
  97. if (Character.isLetterOrDigit(c) ||
  98. c == '.' ||
  99. c == '-' ||
  100. c == ' ') {
  101. ++cur;
  102. } else {
  103. break;
  104. }
  105. }
  106. // Back out any trailing spaces.
  107. while ((cur > beg) && (chars[cur - 1] == ' ')) {
  108. --cur;
  109. }
  110. if (beg == cur) {
  111. throw new InvalidNameException("Invalid name: " + name);
  112. }
  113. return new String(chars, beg, cur - beg);
  114. }
  115. /*
  116. * Returns the attribute value that begins at the next unconsumed
  117. * char. No leading whitespace is expected.
  118. */
  119. private String parseAttrValue() throws InvalidNameException {
  120. if (cur < len && chars[cur] == '#') {
  121. return parseBinaryAttrValue();
  122. } else if (cur < len && chars[cur] == '"') {
  123. return parseQuotedAttrValue();
  124. } else {
  125. return parseStringAttrValue();
  126. }
  127. }
  128. private String parseBinaryAttrValue() throws InvalidNameException {
  129. final int beg = cur;
  130. ++cur; // consume '#'
  131. while ((cur < len) &&
  132. Character.isLetterOrDigit(chars[cur])) {
  133. ++cur;
  134. }
  135. return new String(chars, beg, cur - beg);
  136. }
  137. private String parseQuotedAttrValue() throws InvalidNameException {
  138. final int beg = cur;
  139. ++cur; // consume '"'
  140. while ((cur < len) && chars[cur] != '"') {
  141. if (chars[cur] == '\\') {
  142. ++cur; // consume backslash, then what follows
  143. }
  144. ++cur;
  145. }
  146. if (cur >= len) { // no closing quote
  147. throw new InvalidNameException("Invalid name: " + name);
  148. }
  149. ++cur; // consume closing quote
  150. return new String(chars, beg, cur - beg);
  151. }
  152. private String parseStringAttrValue() throws InvalidNameException {
  153. final int beg = cur;
  154. int esc = -1; // index of the most recently escaped character
  155. while ((cur < len) && !atTerminator()) {
  156. if (chars[cur] == '\\') {
  157. ++cur; // consume backslash, then what follows
  158. esc = cur;
  159. }
  160. ++cur;
  161. }
  162. if (cur > len) { // 'twas backslash followed by nothing
  163. throw new InvalidNameException("Invalid name: " + name);
  164. }
  165. // Trim off (unescaped) trailing whitespace.
  166. int end;
  167. for (end = cur; end > beg; end--) {
  168. if (!isWhitespace(chars[end - 1]) || (esc == end - 1)) {
  169. break;
  170. }
  171. }
  172. return new String(chars, beg, end - beg);
  173. }
  174. private void consumeWhitespace() {
  175. while ((cur < len) && isWhitespace(chars[cur])) {
  176. ++cur;
  177. }
  178. }
  179. /*
  180. * Returns true if next unconsumed character is one that terminates
  181. * a string attribute value.
  182. */
  183. private boolean atTerminator() {
  184. return (cur < len &&
  185. (chars[cur] == ',' ||
  186. chars[cur] == ';' ||
  187. chars[cur] == '+'));
  188. }
  189. /*
  190. * Best guess as to what RFC 2253 means by "whitespace".
  191. */
  192. private static boolean isWhitespace(char c) {
  193. return (c == ' ' || c == '\r');
  194. }
  195. }