Source for gnu.inet.ldap.BEREncoder

   1: /*
   2:  * BEREncoder.java
   3:  * Copyright (C) 2004 The Free Software Foundation
   4:  * 
   5:  * This file is part of GNU inetlib, a library.
   6:  * 
   7:  * GNU inetlib is free software; you can redistribute it and/or modify
   8:  * it under the terms of the GNU General Public License as published by
   9:  * the Free Software Foundation; either version 2 of the License, or
  10:  * (at your option) any later version.
  11:  * 
  12:  * GNU inetlib is distributed in the hope that it will be useful,
  13:  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14:  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15:  * GNU General Public License for more details.
  16:  * 
  17:  * You should have received a copy of the GNU General Public License
  18:  * along with this library; if not, write to the Free Software
  19:  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  20:  * 
  21:  * Linking this library statically or dynamically with other modules is
  22:  * making a combined work based on this library.  Thus, the terms and
  23:  * conditions of the GNU General Public License cover the whole
  24:  * combination.
  25:  *
  26:  * As a special exception, the copyright holders of this library give you
  27:  * permission to link this library with independent modules to produce an
  28:  * executable, regardless of the license terms of these independent
  29:  * modules, and to copy and distribute the resulting executable under
  30:  * terms of your choice, provided that you also meet, for each linked
  31:  * independent module, the terms and conditions of the license of that
  32:  * module.  An independent module is a module which is not derived from
  33:  * or based on this library.  If you modify this library, you may extend
  34:  * this exception to your version of the library, but you are not
  35:  * obliged to do so.  If you do not wish to do so, delete this
  36:  * exception statement from your version.
  37:  */
  38: 
  39: package gnu.inet.ldap;
  40: 
  41: import java.io.UnsupportedEncodingException;
  42: 
  43: /**
  44:  * Utility class to construct BER-encoded data.
  45:  *
  46:  * @author <a href='mailto:dog@gnu.org'>Chris Burdess</a>
  47:  */
  48: public class BEREncoder
  49: {
  50: 
  51:   private byte[] buffer;
  52:   private int offset;
  53:   private int[] sequenceOffset;
  54:   private int sequenceIndex;
  55:   private boolean utf8;
  56: 
  57:   /**
  58:    * Constructor.
  59:    * @param utf whether to use UTF-8 for encoding strings
  60:    */
  61:   public BEREncoder(boolean utf8)
  62:   {
  63:     this(utf8, 1024);
  64:   }
  65: 
  66:   /**
  67:    * Constructor.
  68:    * @param utf whether to use UTF-8 for encoding strings
  69:    * @param initialSize the initial buffer size
  70:    */
  71:   public BEREncoder(boolean utf8, int initialSize)
  72:   {
  73:     this.utf8 = utf8;
  74:     buffer = new byte[initialSize];
  75:     offset = 0;
  76:     sequenceOffset = new int[16];
  77:     sequenceIndex = 0;
  78:   }
  79: 
  80:   /**
  81:    * Reset this encoder for reuse.
  82:    */
  83:   public void reset()
  84:   {
  85:     for (int i = 0; i < offset; i++)
  86:       {
  87:         buffer[i] = 0;
  88:       }
  89:     offset = 0;
  90:     for (int i = 0; i < sequenceIndex; i++)
  91:       {
  92:         sequenceOffset[i] = 0;
  93:       }
  94:     sequenceIndex = 0;
  95:   }
  96: 
  97:   /**
  98:    * Returns the current size of the encoded data.
  99:    */
 100:   public int size()
 101:   {
 102:     return offset;
 103:   }
 104: 
 105:   /**
 106:    * Returns the encoded data.
 107:    */
 108:   public byte[] toByteArray()
 109:   {
 110:     byte[] ret = new byte[offset];
 111:     System.arraycopy(buffer, 0, ret, 0, offset);
 112:     return ret;
 113:   }
 114: 
 115:   // -- 8.2 Encoding of a boolean value --
 116: 
 117:   /**
 118:    * Appends a boolean value.
 119:    * @param value the value
 120:    */
 121:   public void append(boolean value)
 122:   {
 123:     append(value, BERConstants.BOOLEAN);
 124:   }
 125:   
 126:   /**
 127:    * Appends a boolean value with the specified ASN.1 type code.
 128:    * @param value the value
 129:    * @param code the type code
 130:    */
 131:   public void append(boolean value, int code)
 132:   {
 133:     allocate(3);
 134:     buffer[offset++] = (byte) code;
 135:     buffer[offset++] = (byte) 1; /* length */
 136:     buffer[offset++] = value ? (byte) 0xff :(byte) 0;
 137:   }
 138: 
 139:   // -- 8.3 Encoding of an integer value --
 140: 
 141:   /**
 142:    * Appends an integer value.
 143:    * @param value the value
 144:    */
 145:   public void append(int value)
 146:   {
 147:     append(value, BERConstants.INTEGER);
 148:   }
 149:   
 150:   /**
 151:    * Appends an integer value with the specified ASN.1 type code.
 152:    * @param value the value
 153:    * @param code the type code
 154:    */
 155:   public void append(int value, int code)
 156:   {
 157:     final int mask = 0xff800000;
 158:     int len = 4;
 159:     while (((value & mask) == 0 || (value & mask) == mask) && (len > 1))
 160:       {
 161:         len--;
 162:         value <<= 8;
 163:       }
 164:     allocate(len + 2);
 165:     buffer[offset++] = (byte) code;
 166:     buffer[offset++] = (byte) len;
 167:     for (; len > 0; len--)
 168:       {
 169:         buffer[offset++] = (byte) ((value & 0xff000000) >> 24);
 170:       }
 171:   }
 172: 
 173:   // TODO -- 8.5 Encoding of a real value --
 174:   
 175:   // TODO -- 8.6 Encoding of a bitstring value --
 176:   
 177:   // -- 8.7 Encoding of an octetstring value --
 178: 
 179:   /**
 180:    * Appends an octetstring value.
 181:    * @param bytes the value
 182:    */
 183:   public void append(byte[] bytes)
 184:     throws BERException
 185:   {
 186:     append(bytes, BERConstants.OCTET_STRING);
 187:   }
 188:   
 189:   /**
 190:    * Appends an octetstring value with the specified ASN.1 type code.
 191:    * Sequences and sets can be appended by using the corresponding type
 192:    * codes from BERConstants.
 193:    * @param bytes the value
 194:    * @param code the type code
 195:    */
 196:   public void append(byte[] bytes, int code)
 197:     throws BERException
 198:   {
 199:     int len = (bytes == null) ? 0 : bytes.length;
 200:     append(bytes, 0, len, code);
 201:   }
 202: 
 203:   void append(byte[] bytes, int off, int len, int code)
 204:     throws BERException
 205:   {
 206:     allocate(len + 5);
 207:     buffer[offset++] = (byte) code;
 208:     appendLength(len);
 209:     if (len > 0)
 210:       {
 211:         System.arraycopy(bytes, off, buffer, offset, len);
 212:         offset += len;
 213:       }
 214:   }
 215: 
 216:   /**
 217:    * Appends a string value.
 218:    * @param value the value
 219:    */
 220:   public void append(String value)
 221:     throws BERException
 222:   {
 223:     append(value, BERConstants.UTF8_STRING);
 224:   }
 225:   
 226:   /**
 227:    * Appends a string value with the specified ASN.1 type code.
 228:    * @param value the value
 229:    * @param code the type code
 230:    */
 231:   public void append(String value, int code)
 232:     throws BERException
 233:   {
 234:     byte[] bytes = null;
 235:     if (value == null)
 236:       {
 237:         bytes = new byte[0];
 238:       }
 239:     else
 240:       {
 241:         String encoding = utf8 ? "UTF-8" : "ISO-8859-1";
 242:         try
 243:           {
 244:             bytes = value.getBytes(encoding);
 245:           }
 246:         catch (UnsupportedEncodingException e)
 247:           {
 248:             throw new BERException("JVM does not support " + encoding);
 249:           }
 250:       }
 251:     int len = bytes.length;
 252:     allocate(len + 5);
 253:     buffer[offset++] = (byte) code;
 254:     appendLength(len);
 255:     System.arraycopy(bytes, 0, buffer, offset, len);
 256:     offset += len;
 257:   }
 258: 
 259:   // -- 8.8 Encoding of a null value --
 260: 
 261:   /**
 262:    * Appends a BER NULL value.
 263:    */
 264:   public void appendNull()
 265:   {
 266:     allocate(2);
 267:     buffer[offset++] = BERConstants.NULL;
 268:     buffer[offset++] = (byte) 0; /* length */
 269:   }
 270: 
 271:   /**
 272:    * Allocate at least len bytes.
 273:    */
 274:   private void allocate(int len)
 275:   {
 276:     if (buffer.length - offset < len)
 277:       {
 278:         int size = buffer.length;
 279:         do
 280:           {
 281:             size *= 2;
 282:           }
 283:         while (size - offset < len);
 284:         byte[] ret = new byte[size];
 285:         System.arraycopy(buffer, 0, ret, 0, offset);
 286:         buffer = ret;
 287:       }
 288:   }
 289: 
 290:   /**
 291:    * Append the specified length for a string.
 292:    */
 293:   private void appendLength(int len)
 294:     throws BERException
 295:   {
 296:     if (len < 0x80)
 297:       {
 298:         buffer[offset++] = (byte) len;
 299:       }
 300:     else if (len < 0x100)
 301:       {
 302:         buffer[offset++] = (byte) 0x81;
 303:         buffer[offset++] = (byte) len;
 304:       }
 305:     else if (len < 0x10000)
 306:       {
 307:         buffer[offset++] = (byte) 0x82;
 308:         buffer[offset++] = (byte)(len >> 0x08);
 309:         buffer[offset++] = (byte)(len & 0xff);
 310:       }
 311:     else if (len < 0x1000000)
 312:       {
 313:         buffer[offset++] = (byte) 0x83;
 314:         buffer[offset++] = (byte)(len >> 0x10);
 315:         buffer[offset++] = (byte)(len >> 0x08);
 316:         buffer[offset++] = (byte)(len & 0xff);
 317:       }
 318:     else
 319:       {
 320:         throw new BERException("Data too long: " + len);
 321:       }
 322:   }
 323: 
 324:   /**
 325:    * Appends an RFC2254 search filter to this encoder.
 326:    * @param filter the filter expression
 327:    */
 328:   public void appendFilter(String filter)
 329:     throws BERException
 330:   {
 331:     if (filter == null || filter.length() == 0)
 332:       {
 333:         throw new BERException("Empty filter expression");
 334:       }
 335:     final byte[] bytes;
 336:     String charset = utf8 ? "UTF-8" : "ISO-8859-1";
 337:     try
 338:       {
 339:         bytes = filter.getBytes(charset);
 340:       }
 341:     catch (UnsupportedEncodingException e)
 342:       {
 343:         throw new BERException("JVM does not support " + charset);
 344:       }
 345:     appendFilter(bytes, 0);
 346:   }
 347: 
 348:   int appendFilter(final byte[] bytes, int off)
 349:     throws BERException
 350:   {
 351:     int depth = 0;
 352:     while(off < bytes.length)
 353:       {
 354:         switch (bytes[off])
 355:           {
 356:           case 0x20: // SP
 357:             off++;
 358:             break; /* NOOP */
 359:           case 0x28: //(
 360:             depth++;
 361:             off++;
 362:             break;
 363:           case 0x29: // )
 364:             depth--;
 365:             if (depth == 0)
 366:               {
 367:                 return off + 1;
 368:               }
 369:             break;
 370:           case 0x26: // &
 371:             off = appendFilterList(bytes, off + 1,
 372:                                    BERConstants.FILTER_AND);
 373:             break;
 374:           case 0x2c: // |
 375:             off = appendFilterList(bytes, off + 1,
 376:                                    BERConstants.FILTER_OR);
 377:             break;
 378:           case 0x21: // !
 379:             off = appendFilterList(bytes, off + 1,
 380:                                    BERConstants.FILTER_NOT);
 381:             break;
 382:           default:
 383:             off = appendFilterItem(bytes, off);
 384:           }
 385:       }
 386:     if (depth != 0)
 387:       {
 388:         //System.err.println("depth="+depth+", off="+off);
 389:         throw new BERException("Unbalanced parentheses");
 390:       }
 391:     return off;
 392:   }
 393: 
 394:   int appendFilterList(final byte[] bytes, int off, int code)
 395:     throws BERException
 396:   {
 397:     BEREncoder sequence = new BEREncoder(utf8);
 398:     while (off < bytes.length && bytes[off] == '(')
 399:       {
 400:         off = sequence.appendFilter(bytes, off);
 401:       }
 402:     append(sequence.toByteArray(), code);
 403:     return off;
 404:   }
 405: 
 406:   int appendFilterItem(final byte[] bytes, int off)
 407:     throws BERException
 408:   {
 409:     int ei = indexOf(bytes,(byte) 0x3d, off); // =
 410:     if (ei == -1)
 411:       {
 412:         throw new BERException("Missing '='");
 413:       }
 414:     int end = ei;
 415:     int code;
 416:     BEREncoder item = new BEREncoder(utf8);
 417:     switch (bytes[ei - 1])
 418:       {
 419:       case 0x7e: // ~ approx
 420:         code = BERConstants.FILTER_APPROX;
 421:         end--;
 422:         break;
 423:       case 0x3e: // > greater
 424:         code = BERConstants.FILTER_GREATER;
 425:         end--;
 426:         break;
 427:       case 0x3c: // < less
 428:         code = BERConstants.FILTER_LESS;
 429:         end--;
 430:         break;
 431:       case 0x3a: // : ext
 432:         code = BERConstants.FILTER_EXTENSIBLE;
 433:         // TODO return appendFilterExtensibleMatch(bytes, off, ei);
 434:         break;
 435:       default: // equal/substring
 436:         int si = indexOf(bytes,(byte) 0x2a, ei + 1); // *
 437:         if (si == -1)
 438:           {
 439:             code = BERConstants.FILTER_EQUAL;
 440:           }
 441:         else
 442:           {
 443:             if (ei + 1 == bytes.length || bytes[ei + 2] == 0x29) // * present
 444:               {
 445:                 code = BERConstants.FILTER_PRESENT;
 446:                 end--;
 447:               }
 448:             else
 449:               {
 450:                 // substring
 451:                 BEREncoder substring = new BEREncoder(utf8);
 452:                 substring.append(bytes, off, end, BERConstants.OCTET_STRING);
 453:                 end = indexOf(bytes,(byte) 0x29, ei + 1); // )
 454:                 if (end == -1)
 455:                   {
 456:                     throw new BERException("No terminating ')'");
 457:                   }
 458:                 BEREncoder value = new BEREncoder(utf8);
 459:                 value.append(unencode(bytes, ei + 1, end));
 460:                 substring.append(value.toByteArray(), 48);
 461:                 append(substring.toByteArray(),
 462:                        BERConstants.FILTER_SUBSTRING);
 463:                 off = end;
 464:                 return off;
 465:               }
 466:           }
 467:           
 468:       }
 469:     item.append(bytes, off,(end - off), BERConstants.OCTET_STRING);
 470:     end = indexOf(bytes,(byte) 0x29, ei + 1); // )
 471:     if (end == -1)
 472:       {
 473:         throw new BERException("No terminating ')'");
 474:       }
 475:     if (code != BERConstants.FILTER_PRESENT)
 476:       {
 477:         item.append(unencode(bytes, ei + 1, end));
 478:       }
 479:     append(item.toByteArray(), code);
 480:     off = end;
 481:     return off;
 482:   }
 483: 
 484:   /**
 485:    * Returns the index of the first matching byte in the specified
 486:    * octet-string, starting at the given index. The filterlist terminator
 487:    * ')' stops the search.
 488:    */
 489:   static int indexOf(final byte[] bytes, byte c, int off)
 490:   {
 491:     for (int i = off; i < bytes.length; i++)
 492:       {
 493:         if (bytes[i] == c)
 494:           {
 495:             return i;
 496:           }
 497:         else if (bytes[i] == 0x29) // )
 498:           {
 499:             return -1;
 500:           }
 501:       }
 502:     return -1;
 503:   }
 504: 
 505:   /**
 506:    * Returns the unencoded version of the specified octet-string. The
 507:    * filterlist terminator ')' delimits the end of the string.
 508:    * This routine converts each character encoded as "\xx" where xx is the ASCII
 509:    * character code to a single character.
 510:    */
 511:   static byte[] unencode(final byte[] bytes, int off, int end)
 512:     throws BERException
 513:   {
 514:     byte[] buf = new byte[end - off];
 515:     int pos = 0;
 516:     int bsi = indexOf(bytes,(byte) 0x5c, off); // \
 517:     while(bsi != -1)
 518:       {
 519:         if (bsi + 3 > end)
 520:           {
 521:             throw new BERException("Illegal filter value encoding");
 522:           }
 523:         int l = bsi - off;
 524:         System.arraycopy(bytes, off, buf, pos, l);
 525:         pos += l;
 526:         int c = Character.digit((char) bytes[bsi + 2], 0x10);
 527:         c += Character.digit((char) bytes[bsi + 1], 0x10) * 0x10;
 528:         buf[pos++] = (byte) c;
 529:         off += l + 3;
 530:         bsi = indexOf(bytes,(byte) 0x5c, off); // \
 531:       }
 532:     int l = end - off;
 533:     System.arraycopy(bytes, off, buf, pos, l);
 534:     pos += l;
 535:     off += l;
 536:     if (pos != buf.length)
 537:       {
 538:         byte[] swap = new byte[pos];
 539:         System.arraycopy(buf, 0, swap, 0, pos);
 540:         buf = swap;
 541:       }
 542:     return buf;
 543:   }
 544:   
 545: }