View Javadoc

1   /*
2    * Copyright 2002-2005 the original author or authors.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
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          // Name encoding table -------------------------------------
53  
54          // all below 0x20 (control chars)
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          // nameEscapeTable['\''] = "//";
67          nameEscapeTable['\"'] = "//\"";
68          // nameEscapeTable['/'] = "//" + toTwoCharHex('/');
69          nameEscapeTable['//'] = "////";
70  
71          // Filter encoding table -------------------------------------
72  
73          // fill with char itself
74          for (char c = 0; c < filterEscapeTable.length; c++) {
75              filterEscapeTable[c] = String.valueOf(c);
76          }
77  
78          // escapes (RFC2254)
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         // make buffer roomy
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                 // default: add the char
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/> '&lt;' [less than] -
134      * "\&lt;" <br/> '&gt;' [greater than] - "\&gt;" <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         // make buffer roomy
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             // space first or last
156             if (c == ' ' && (i == 0 || i == last)) {
157                 encodedValue.append("// ");
158                 continue;
159             }
160 
161             if (c < nameEscapeTable.length) {
162                 // check in table for escapes
163                 String esc = nameEscapeTable[c];
164 
165                 if (esc != null) {
166                     encodedValue.append(esc);
167                     continue;
168                 }
169             }
170 
171             // default: add the char
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         // make buffer same size
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             // group 1
204             if (matcher.group(1) != null) {
205                 // parse as hex = base 16
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                 // just add, we stripped away the \
216                 decoded.append(matcher.group(2));
217             } else if (matcher.group(3) != null) {
218                 // just add
219                 decoded.append(matcher.group(3));
220             }
221 
222         }
223         // end is match + +1 so it should be same as length, i.e.
224         // last + 1
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 }