001: /*
002: * Copyright 1999-2007 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.jndi.ldap;
027:
028: import javax.naming.NamingException;
029: import javax.naming.directory.InvalidSearchFilterException;
030:
031: import java.io.IOException;
032:
033: /**
034: * LDAP (RFC-1960) and LDAPv3 (RFC-2254) search filters.
035: *
036: * @author Vincent Ryan
037: * @author Jagane Sundar
038: * @author Rosanna Lee
039: */
040:
041: final class Filter {
042:
043: /**
044: * First convert filter string into byte[].
045: * For LDAP v3, the conversion uses Unicode -> UTF8
046: * For LDAP v2, the conversion uses Unicode -> ISO 8859 (Latin-1)
047: *
048: * Then parse the byte[] as a filter, converting \hh to
049: * a single byte, and encoding the resulting filter
050: * into the supplied BER buffer
051: */
052: static void encodeFilterString(BerEncoder ber, String filterStr,
053: boolean isLdapv3) throws IOException, NamingException {
054:
055: if ((filterStr == null) || (filterStr.equals(""))) {
056: throw new InvalidSearchFilterException("Empty filter");
057: }
058: byte[] filter;
059: int filterLen;
060: if (isLdapv3) {
061: filter = filterStr.getBytes("UTF8");
062: } else {
063: filter = filterStr.getBytes("8859_1");
064: }
065: filterLen = filter.length;
066: if (dbg) {
067: dbgIndent = 0;
068: System.err.println("String filter: " + filterStr);
069: System.err.println("size: " + filterLen);
070: dprint("original: ", filter, 0, filterLen);
071: }
072:
073: encodeFilter(ber, filter, 0, filterLen);
074: }
075:
076: private static void encodeFilter(BerEncoder ber, byte[] filter,
077: int filterStart, int filterEnd) throws IOException,
078: NamingException {
079:
080: if (dbg) {
081: dprint("encFilter: ", filter, filterStart, filterEnd);
082: dbgIndent++;
083: }
084:
085: if ((filterEnd - filterStart) <= 0) {
086: throw new InvalidSearchFilterException("Empty filter");
087: }
088:
089: int nextOffset;
090: int parens, balance;
091: boolean escape;
092:
093: parens = 0;
094:
095: int filtOffset[] = new int[1];
096:
097: for (filtOffset[0] = filterStart; filtOffset[0] < filterEnd; filtOffset[0]++) {
098: switch (filter[filtOffset[0]]) {
099: case '(':
100: filtOffset[0]++;
101: parens++;
102: switch (filter[filtOffset[0]]) {
103: case '&':
104: encodeComplexFilter(ber, filter, LDAP_FILTER_AND,
105: filtOffset, filterEnd);
106: parens--;
107: break;
108:
109: case '|':
110: encodeComplexFilter(ber, filter, LDAP_FILTER_OR,
111: filtOffset, filterEnd);
112: parens--;
113: break;
114:
115: case '!':
116: encodeComplexFilter(ber, filter, LDAP_FILTER_NOT,
117: filtOffset, filterEnd);
118: parens--;
119: break;
120:
121: default:
122: balance = 1;
123: escape = false;
124: nextOffset = filtOffset[0];
125: while (nextOffset < filterEnd && balance > 0) {
126: if (!escape) {
127: if (filter[nextOffset] == '(')
128: balance++;
129: else if (filter[nextOffset] == ')')
130: balance--;
131: }
132: if (filter[nextOffset] == '\\' && !escape)
133: escape = true;
134: else
135: escape = false;
136: if (balance > 0)
137: nextOffset++;
138: }
139: if (balance != 0)
140: throw new InvalidSearchFilterException(
141: "Unbalanced parenthesis");
142:
143: encodeSimpleFilter(ber, filter, filtOffset[0],
144: nextOffset);
145:
146: // points to right parens; for loop will increment beyond parens
147: filtOffset[0] = nextOffset;
148:
149: parens--;
150: break;
151:
152: }
153: break;
154:
155: case ')':
156: //
157: // End of sequence
158: //
159: ber.endSeq();
160: filtOffset[0]++;
161: parens--;
162: break;
163:
164: case ' ':
165: filtOffset[0]++;
166: break;
167:
168: default: // assume simple type=value filter
169: encodeSimpleFilter(ber, filter, filtOffset[0],
170: filterEnd);
171: filtOffset[0] = filterEnd; // force break from outer
172: break;
173: }
174: }
175:
176: if (parens > 0) {
177: throw new InvalidSearchFilterException(
178: "Unbalanced parenthesis");
179: }
180:
181: if (dbg) {
182: dbgIndent--;
183: }
184:
185: }
186:
187: /**
188: * convert character 'c' that represents a hexadecimal digit to an integer.
189: * if 'c' is not a hexidecimal digit [0-9A-Fa-f], -1 is returned.
190: * otherwise the converted value is returned.
191: */
192: private static int hexchar2int(byte c) {
193: if (c >= '0' && c <= '9') {
194: return (c - '0');
195: }
196: if (c >= 'A' && c <= 'F') {
197: return (c - 'A' + 10);
198: }
199: if (c >= 'a' && c <= 'f') {
200: return (c - 'a' + 10);
201: }
202: return (-1);
203: }
204:
205: // called by the LdapClient.compare method
206: static byte[] unescapeFilterValue(byte[] orig, int start, int end)
207: throws NamingException {
208: boolean escape = false, escStart = false;
209: int ival;
210: byte ch;
211:
212: if (dbg) {
213: dprint("unescape: ", orig, start, end);
214: }
215:
216: int len = end - start;
217: byte tbuf[] = new byte[len];
218: int j = 0;
219: for (int i = start; i < end; i++) {
220: ch = orig[i];
221: if (escape) {
222: // Try LDAP V3 escape (\xx)
223: if ((ival = hexchar2int(ch)) < 0) {
224:
225: /**
226: * If there is no hex char following a '\' when
227: * parsing a LDAP v3 filter (illegal by v3 way)
228: * we fallback to the way we unescape in v2.
229: */
230: if (escStart) {
231: // V2: \* \( \)
232: escape = false;
233: tbuf[j++] = ch;
234: } else {
235: // escaping already started but we can't find 2nd hex
236: throw new InvalidSearchFilterException(
237: "invalid escape sequence: " + orig);
238: }
239: } else {
240: if (escStart) {
241: tbuf[j] = (byte) (ival << 4);
242: escStart = false;
243: } else {
244: tbuf[j++] |= (byte) ival;
245: escape = false;
246: }
247: }
248: } else if (ch != '\\') {
249: tbuf[j++] = ch;
250: escape = false;
251: } else {
252: escStart = escape = true;
253: }
254: }
255: byte[] answer = new byte[j];
256: System.arraycopy(tbuf, 0, answer, 0, j);
257: if (dbg) {
258: Ber.dumpBER(System.err, null, answer, 0, j);
259: }
260: return answer;
261: }
262:
263: private static int indexOf(byte[] str, char ch, int start, int end) {
264: for (int i = start; i < end; i++) {
265: if (str[i] == ch)
266: return i;
267: }
268: return -1;
269: }
270:
271: private static int indexOf(byte[] str, String target, int start,
272: int end) {
273: int where = indexOf(str, target.charAt(0), start, end);
274: if (where >= 0) {
275: for (int i = 1; i < target.length(); i++) {
276: if (str[where + i] != target.charAt(i)) {
277: return -1;
278: }
279: }
280: }
281: return where;
282: }
283:
284: private static int findUnescaped(byte[] str, char ch, int start,
285: int end) {
286: while (start < end) {
287: int where = indexOf(str, ch, start, end);
288:
289: /*
290: * Count the immediate preceding '\' to find out if
291: * this is an escaped '*'. This is a made-up way for
292: * parsing an escaped '*' in v2. This is how the other leading
293: * SDK vendors interpret v2.
294: * For v3 we fallback to the way we parse "\*" in v2.
295: * It's not legal in v3 to use "\*" to escape '*'; the right
296: * way is to use "\2a" instead.
297: */
298: int backSlashPos;
299: int backSlashCnt = 0;
300: for (backSlashPos = where - 1; ((backSlashPos >= start) && (str[backSlashPos] == '\\')); backSlashPos--, backSlashCnt++)
301: ;
302:
303: // if at start of string, or not there at all, or if not escaped
304: if (where == start || where == -1
305: || ((backSlashCnt % 2) == 0))
306: return where;
307:
308: // start search after escaped star
309: start = where + 1;
310: }
311: return -1;
312: }
313:
314: private static void encodeSimpleFilter(BerEncoder ber,
315: byte[] filter, int filtStart, int filtEnd)
316: throws IOException, NamingException {
317:
318: if (dbg) {
319: dprint("encSimpleFilter: ", filter, filtStart, filtEnd);
320: dbgIndent++;
321: }
322:
323: String type, value;
324: int valueStart, valueEnd, typeStart, typeEnd;
325:
326: int eq;
327: if ((eq = indexOf(filter, '=', filtStart, filtEnd)) == -1) {
328: throw new InvalidSearchFilterException("Missing 'equals'");
329: }
330:
331: valueStart = eq + 1; // value starts after equal sign
332: valueEnd = filtEnd;
333: typeStart = filtStart; // beginning of string
334:
335: int ftype;
336:
337: switch (filter[eq - 1]) {
338: case '<':
339: ftype = LDAP_FILTER_LE;
340: typeEnd = eq - 1;
341: break;
342: case '>':
343: ftype = LDAP_FILTER_GE;
344: typeEnd = eq - 1;
345: break;
346: case '~':
347: ftype = LDAP_FILTER_APPROX;
348: typeEnd = eq - 1;
349: break;
350: case ':':
351: ftype = LDAP_FILTER_EXT;
352: typeEnd = eq - 1;
353: break;
354: default:
355: typeEnd = eq;
356: if (findUnescaped(filter, '*', valueStart, valueEnd) == -1) {
357: ftype = LDAP_FILTER_EQUALITY;
358: } else if (filter[valueStart] == '*'
359: && valueStart == (valueEnd - 1)) {
360: ftype = LDAP_FILTER_PRESENT;
361: } else {
362: encodeSubstringFilter(ber, filter, typeStart, typeEnd,
363: valueStart, valueEnd);
364: return;
365: }
366: break;
367: }
368: if (dbg) {
369: System.err.println("type: " + typeStart + ", " + typeEnd);
370: System.err
371: .println("value: " + valueStart + ", " + valueEnd);
372: }
373:
374: if (ftype == LDAP_FILTER_PRESENT) {
375: ber.encodeOctetString(filter, ftype, typeStart, typeEnd
376: - typeStart);
377: } else if (ftype == LDAP_FILTER_EXT) {
378: encodeExtensibleMatch(ber, filter, typeStart, typeEnd,
379: valueStart, valueEnd);
380: } else {
381: ber.beginSeq(ftype);
382: ber.encodeOctetString(filter, Ber.ASN_OCTET_STR, typeStart,
383: typeEnd - typeStart);
384: ber.encodeOctetString(unescapeFilterValue(filter,
385: valueStart, valueEnd), Ber.ASN_OCTET_STR);
386: ber.endSeq();
387: }
388:
389: if (dbg) {
390: dbgIndent--;
391: }
392: }
393:
394: private static void encodeSubstringFilter(BerEncoder ber,
395: byte[] filter, int typeStart, int typeEnd, int valueStart,
396: int valueEnd) throws IOException, NamingException {
397:
398: if (dbg) {
399: dprint("encSubstringFilter: type ", filter, typeStart,
400: typeEnd);
401: dprint(", val : ", filter, valueStart, valueEnd);
402: dbgIndent++;
403: }
404:
405: ber.beginSeq(LDAP_FILTER_SUBSTRINGS);
406: ber.encodeOctetString(filter, Ber.ASN_OCTET_STR, typeStart,
407: typeEnd - typeStart);
408: ber.beginSeq(LdapClient.LBER_SEQUENCE);
409: int index;
410: int previndex = valueStart;
411: while ((index = findUnescaped(filter, '*', previndex, valueEnd)) != -1) {
412: if (previndex == valueStart) {
413: if (previndex < index) {
414: if (dbg)
415: System.err.println("initial: " + previndex
416: + "," + index);
417: ber.encodeOctetString(unescapeFilterValue(filter,
418: previndex, index), LDAP_SUBSTRING_INITIAL);
419: }
420: } else {
421: if (previndex < index) {
422: if (dbg)
423: System.err.println("any: " + previndex + ","
424: + index);
425: ber.encodeOctetString(unescapeFilterValue(filter,
426: previndex, index), LDAP_SUBSTRING_ANY);
427: }
428: }
429: previndex = index + 1;
430: }
431: if (previndex < valueEnd) {
432: if (dbg)
433: System.err.println("final: " + previndex + ","
434: + valueEnd);
435: ber.encodeOctetString(unescapeFilterValue(filter,
436: previndex, valueEnd), LDAP_SUBSTRING_FINAL);
437: }
438: ber.endSeq();
439: ber.endSeq();
440:
441: if (dbg) {
442: dbgIndent--;
443: }
444: }
445:
446: private static void encodeComplexFilter(BerEncoder ber,
447: byte[] filter, int filterType, int filtOffset[], int filtEnd)
448: throws IOException, NamingException {
449:
450: //
451: // We have a complex filter of type "&(type=val)(type=val)"
452: // with filtOffset[0] pointing to the &
453: //
454:
455: if (dbg) {
456: dprint("encComplexFilter: ", filter, filtOffset[0], filtEnd);
457: dprint(", type: " + Integer.toString(filterType, 16));
458: dbgIndent++;
459: }
460:
461: filtOffset[0]++;
462:
463: ber.beginSeq(filterType);
464:
465: int[] parens = findRightParen(filter, filtOffset, filtEnd);
466: encodeFilterList(ber, filter, parens[0], parens[1]);
467:
468: ber.endSeq();
469:
470: if (dbg) {
471: dbgIndent--;
472: }
473:
474: }
475:
476: //
477: // filter at filtOffset[0] - 1 points to a (. Find ) that matches it
478: // and return substring between the parens. Adjust filtOffset[0] to
479: // point to char after right paren
480: //
481: private static int[] findRightParen(byte[] filter,
482: int filtOffset[], int end) throws IOException,
483: NamingException {
484:
485: int balance = 1;
486: boolean escape = false;
487: int nextOffset = filtOffset[0];
488:
489: while (nextOffset < end && balance > 0) {
490: if (!escape) {
491: if (filter[nextOffset] == '(')
492: balance++;
493: else if (filter[nextOffset] == ')')
494: balance--;
495: }
496: if (filter[nextOffset] == '\\' && !escape)
497: escape = true;
498: else
499: escape = false;
500: if (balance > 0)
501: nextOffset++;
502: }
503: if (balance != 0) {
504: throw new InvalidSearchFilterException(
505: "Unbalanced parenthesis");
506: }
507:
508: // String tmp = filter.substring(filtOffset[0], nextOffset);
509:
510: int[] tmp = new int[] { filtOffset[0], nextOffset };
511:
512: filtOffset[0] = nextOffset + 1;
513:
514: return tmp;
515:
516: }
517:
518: //
519: // Encode filter list of type "(filter1)(filter2)..."
520: //
521: private static void encodeFilterList(BerEncoder ber, byte[] filter,
522: int start, int end) throws IOException, NamingException {
523:
524: if (dbg) {
525: dprint("encFilterList: ", filter, start, end);
526: dbgIndent++;
527: }
528:
529: int filtOffset[] = new int[1];
530:
531: for (filtOffset[0] = start; filtOffset[0] < end; filtOffset[0]++) {
532: if (Character.isSpaceChar((char) filter[filtOffset[0]]))
533: continue;
534:
535: if (filter[filtOffset[0]] == '(') {
536: continue;
537: }
538:
539: int[] parens = findRightParen(filter, filtOffset, end);
540:
541: // add enclosing parens
542: int len = parens[1] - parens[0];
543: byte[] newfilter = new byte[len + 2];
544: System.arraycopy(filter, parens[0], newfilter, 1, len);
545: newfilter[0] = (byte) '(';
546: newfilter[len + 1] = (byte) ')';
547: encodeFilter(ber, newfilter, 0, newfilter.length);
548: }
549:
550: if (dbg) {
551: dbgIndent--;
552: }
553:
554: }
555:
556: //
557: // Encode extensible match
558: //
559: private static void encodeExtensibleMatch(BerEncoder ber,
560: byte[] filter, int matchStart, int matchEnd,
561: int valueStart, int valueEnd) throws IOException,
562: NamingException {
563:
564: boolean matchDN = false;
565: int colon;
566: int colon2;
567: int i;
568:
569: ber.beginSeq(LDAP_FILTER_EXT);
570:
571: // test for colon separator
572: if ((colon = indexOf(filter, ':', matchStart, matchEnd)) >= 0) {
573:
574: // test for match DN
575: if ((i = indexOf(filter, ":dn", colon, matchEnd)) >= 0) {
576: matchDN = true;
577: }
578:
579: // test for matching rule
580: if (((colon2 = indexOf(filter, ':', colon + 1, matchEnd)) >= 0)
581: || (i == -1)) {
582:
583: if (i == colon) {
584: ber.encodeOctetString(filter, LDAP_FILTER_EXT_RULE,
585: colon2 + 1, matchEnd - (colon2 + 1));
586:
587: } else if ((i == colon2) && (i >= 0)) {
588: ber.encodeOctetString(filter, LDAP_FILTER_EXT_RULE,
589: colon + 1, colon2 - (colon + 1));
590:
591: } else {
592: ber.encodeOctetString(filter, LDAP_FILTER_EXT_RULE,
593: colon + 1, matchEnd - (colon + 1));
594: }
595: }
596:
597: // test for attribute type
598: if (colon > matchStart) {
599: ber.encodeOctetString(filter, LDAP_FILTER_EXT_TYPE,
600: matchStart, colon - matchStart);
601: }
602: } else {
603: ber.encodeOctetString(filter, LDAP_FILTER_EXT_TYPE,
604: matchStart, matchEnd - matchStart);
605: }
606:
607: ber.encodeOctetString(unescapeFilterValue(filter, valueStart,
608: valueEnd), LDAP_FILTER_EXT_VAL);
609:
610: /*
611: * This element is defined in RFC-2251 with an ASN.1 DEFAULT tag.
612: * However, for Active Directory interoperability it is transmitted
613: * even when FALSE.
614: */
615: ber.encodeBoolean(matchDN, LDAP_FILTER_EXT_DN);
616:
617: ber.endSeq();
618: }
619:
620: ////////////////////////////////////////////////////////////////////////////
621: //
622: // some debug print code that does indenting. Useful for debugging
623: // the filter generation code
624: //
625: ////////////////////////////////////////////////////////////////////////////
626:
627: private static final boolean dbg = false;
628: private static int dbgIndent = 0;
629:
630: private static void dprint(String msg) {
631: dprint(msg, new byte[0], 0, 0);
632: }
633:
634: private static void dprint(String msg, byte[] str) {
635: dprint(msg, str, 0, str.length);
636: }
637:
638: private static void dprint(String msg, byte[] str, int start,
639: int end) {
640: String dstr = " ";
641: int i = dbgIndent;
642: while (i-- > 0) {
643: dstr += " ";
644: }
645: dstr += msg;
646:
647: System.err.print(dstr);
648: for (int j = start; j < end; j++) {
649: System.err.print((char) str[j]);
650: }
651: System.err.println();
652: }
653:
654: /////////////// Constants used for encoding filter //////////////
655:
656: static final int LDAP_FILTER_AND = 0xa0;
657: static final int LDAP_FILTER_OR = 0xa1;
658: static final int LDAP_FILTER_NOT = 0xa2;
659: static final int LDAP_FILTER_EQUALITY = 0xa3;
660: static final int LDAP_FILTER_SUBSTRINGS = 0xa4;
661: static final int LDAP_FILTER_GE = 0xa5;
662: static final int LDAP_FILTER_LE = 0xa6;
663: static final int LDAP_FILTER_PRESENT = 0x87;
664: static final int LDAP_FILTER_APPROX = 0xa8;
665: static final int LDAP_FILTER_EXT = 0xa9; // LDAPv3
666:
667: static final int LDAP_FILTER_EXT_RULE = 0x81; // LDAPv3
668: static final int LDAP_FILTER_EXT_TYPE = 0x82; // LDAPv3
669: static final int LDAP_FILTER_EXT_VAL = 0x83; // LDAPv3
670: static final int LDAP_FILTER_EXT_DN = 0x84; // LDAPv3
671:
672: static final int LDAP_SUBSTRING_INITIAL = 0x80;
673: static final int LDAP_SUBSTRING_ANY = 0x81;
674: static final int LDAP_SUBSTRING_FINAL = 0x82;
675: }
|