1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package net.sf.ldaptemplate.support;
18
19 import java.util.regex.Matcher;
20 import java.util.regex.Pattern;
21
22 import net.sf.ldaptemplate.BadLdapGrammarException;
23
24 import org.apache.commons.lang.StringUtils;
25
26 /***
27 * Helper class to encode and decode ldap names and values.
28 *
29 * @author Adam Skogman
30 */
31 public class LdapEncoder {
32
33 static private String[] nameEscapeTable = new String[96];
34
35 static private String[] filterEscapeTable = new String['//' + 1];
36
37 /***
38 * Pattern for matching escaped ldap name values.
39 *
40 * Double escaping: \ -> // (in pattern) -> //// (in java string literal)
41 *
42 * Group 1: Hex escapes = \XX -> \p{XDigit}{2} Group 2: Ordinary escapes =
43 * \x -> \. Group 3: Anything but \ [^//]
44 *
45 * Note that the \ is not part of the match.
46 */
47 static private final Pattern VALUE_DECODE_PATTERN = Pattern
48 .compile("(?:////(//p{XDigit}{2}))|(?:////(.))|([^////])");
49
50 static {
51
52
53
54
55 for (char c = 0; c < ' '; c++) {
56 nameEscapeTable[c] = "//" + toTwoCharHex(c);
57 }
58
59 nameEscapeTable['#'] = "//#";
60 nameEscapeTable[','] = "//,";
61 nameEscapeTable[';'] = "//;";
62 nameEscapeTable['='] = "//=";
63 nameEscapeTable['+'] = "//+";
64 nameEscapeTable['<'] = "//<";
65 nameEscapeTable['>'] = "//>";
66
67 nameEscapeTable['\"'] = "//\"";
68
69 nameEscapeTable['//'] = "////";
70
71
72
73
74 for (char c = 0; c < filterEscapeTable.length; c++) {
75 filterEscapeTable[c] = String.valueOf(c);
76 }
77
78
79 filterEscapeTable['*'] = "//2a";
80 filterEscapeTable['('] = "//28";
81 filterEscapeTable[')'] = "//29";
82 filterEscapeTable['//'] = "//5c";
83 filterEscapeTable[0] = "//00";
84
85 }
86
87 static protected String toTwoCharHex(char c) {
88
89 String raw = Integer.toHexString(c).toUpperCase();
90
91 if (raw.length() > 1)
92 return raw;
93 else
94 return "0" + raw;
95 }
96
97 /***
98 * All static methods
99 */
100 private LdapEncoder() {
101 }
102
103 static public String filterEncode(String value) {
104
105 if (value == null)
106 return null;
107
108
109 StringBuffer encodedValue = new StringBuffer(value.length() * 2);
110
111 int length = value.length();
112
113 for (int i = 0; i < length; i++) {
114
115 char c = value.charAt(i);
116
117 if (c < filterEscapeTable.length) {
118 encodedValue.append(filterEscapeTable[c]);
119 } else {
120
121 encodedValue.append(c);
122 }
123 }
124
125 return encodedValue.toString();
126 }
127
128 /***
129 * LDAP Encodes a value for use with a DN. Escapes for LDAP, not JNDI!
130 *
131 * <br/>Escapes:<br/> ' ' [space] - "\ " [if first or last] <br/> '#'
132 * [hash] - "\#" <br/> ',' [comma] - "\," <br/> ';' [semicolon] - "\;" <br/> '=
133 * [equals] - "\=" <br/> '+' [plus] - "\+" <br/> '<' [less than] -
134 * "\<" <br/> '>' [greater than] - "\>" <br/> '"' [double quote] -
135 * "\"" <br/> '\' [backslash] - "//" <br/>
136 *
137 * @param value
138 * @return The escaped value
139 */
140 static public String nameEncode(String value) {
141
142 if (value == null)
143 return null;
144
145
146 StringBuffer encodedValue = new StringBuffer(value.length() * 2);
147
148 int length = value.length();
149 int last = length - 1;
150
151 for (int i = 0; i < length; i++) {
152
153 char c = value.charAt(i);
154
155
156 if (c == ' ' && (i == 0 || i == last)) {
157 encodedValue.append("// ");
158 continue;
159 }
160
161 if (c < nameEscapeTable.length) {
162
163 String esc = nameEscapeTable[c];
164
165 if (esc != null) {
166 encodedValue.append(esc);
167 continue;
168 }
169 }
170
171
172 encodedValue.append(c);
173 }
174
175 return encodedValue.toString();
176
177 }
178
179 /***
180 * Decodes a value. Converts escaped chars to ordinary chars.
181 *
182 * @param value
183 * Trimmed value, so no leading an trailing blanks, except an
184 * escaped space last.
185 * @return The decoded value as a string.
186 * @throws BadLdapGrammarException
187 */
188 static public String nameDecode(String value)
189 throws BadLdapGrammarException {
190
191 if (value == null)
192 return null;
193
194
195 StringBuffer decoded = new StringBuffer(value.length());
196
197 Matcher matcher = VALUE_DECODE_PATTERN.matcher(value);
198
199 int end = 0;
200
201 while (matcher.find()) {
202 end = matcher.end();
203
204 if (matcher.group(1) != null) {
205
206 try {
207 char c = (char) Integer.parseInt(matcher.group(1), 16);
208 decoded.append(c);
209 } catch (NumberFormatException e) {
210 throw new BadLdapGrammarException(
211 "Escaped hex value Could not be parsed. Found '//"
212 + matcher.group(1) + "'");
213 }
214 } else if (matcher.group(2) != null) {
215
216 decoded.append(matcher.group(2));
217 } else if (matcher.group(3) != null) {
218
219 decoded.append(matcher.group(3));
220 }
221
222 }
223
224
225 if (end < value.length()) {
226 throw new BadLdapGrammarException(
227 "RDN could not be parsed fully, remaining '"
228 + StringUtils.substring(value, end) + "'");
229 }
230
231 return decoded.toString();
232
233 }
234
235 }