1 /*
   2  * Copyright (c) 1999, 2004, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.jndi.ldap;
  27 
  28 import javax.naming.*;
  29 import javax.naming.directory.*;
  30 import java.util.Hashtable;
  31 import java.util.Vector;
  32 
  33 /**
  34  * Netscape's 3.1 servers have some schema bugs:
  35  * - It puts quotes around OIDs (such as those for SUP, SYNTAX).
  36  * - When you try to write out the MUST/MAY list (such as "MUST cn"),
  37  *   it wants ("MUST (cn)") instead
  38  */
  39 
  40 final class LdapSchemaParser {
  41 
  42     // do debugging
  43     private static final boolean debug = false;
  44 
  45 
  46     // names of attribute IDs in the LDAP schema entry
  47     static final String OBJECTCLASSDESC_ATTR_ID = "objectClasses";
  48     static final String ATTRIBUTEDESC_ATTR_ID = "attributeTypes";
  49     static final String SYNTAXDESC_ATTR_ID = "ldapSyntaxes";
  50     static final String MATCHRULEDESC_ATTR_ID = "matchingRules";
  51 
  52     // information for creating internal nodes in JNDI schema tree
  53     static final String OBJECTCLASS_DEFINITION_NAME =
  54                         "ClassDefinition";
  55     private static final String[] CLASS_DEF_ATTRS = {
  56                          "objectclass", "ClassDefinition"};
  57             static final String ATTRIBUTE_DEFINITION_NAME =
  58                         "AttributeDefinition";
  59     private static final String[] ATTR_DEF_ATTRS = {
  60                         "objectclass", "AttributeDefinition" };
  61             static final String SYNTAX_DEFINITION_NAME =
  62                         "SyntaxDefinition";
  63     private static final String[] SYNTAX_DEF_ATTRS = {
  64                         "objectclass", "SyntaxDefinition" };
  65             static final String MATCHRULE_DEFINITION_NAME =
  66                         "MatchingRule";
  67     private static final String[] MATCHRULE_DEF_ATTRS = {
  68                         "objectclass", "MatchingRule" };
  69 
  70     // special tokens used in LDAP schema descriptions
  71     private static final char   SINGLE_QUOTE = '\'';
  72     private static final char   WHSP = ' ';
  73     private static final char   OID_LIST_BEGIN = '(';
  74     private static final char   OID_LIST_END = ')';
  75     private static final char   OID_SEPARATOR = '$';
  76 
  77     // common IDs
  78     private static final String  NUMERICOID_ID = "NUMERICOID";
  79     private static final String        NAME_ID = "NAME";
  80     private static final String        DESC_ID = "DESC";
  81     private static final String    OBSOLETE_ID = "OBSOLETE";
  82     private static final String         SUP_ID = "SUP";
  83     private static final String     PRIVATE_ID = "X-";
  84 
  85     // Object Class specific IDs
  86     private static final String    ABSTRACT_ID = "ABSTRACT";
  87     private static final String  STRUCTURAL_ID = "STRUCTURAL";
  88     private static final String    AUXILARY_ID = "AUXILIARY";
  89     private static final String        MUST_ID = "MUST";
  90     private static final String         MAY_ID = "MAY";
  91 
  92     // Attribute Type specific IDs
  93     private static final String    EQUALITY_ID = "EQUALITY";
  94     private static final String    ORDERING_ID = "ORDERING";
  95     private static final String      SUBSTR_ID = "SUBSTR";
  96     private static final String      SYNTAX_ID = "SYNTAX";
  97     private static final String  SINGLE_VAL_ID = "SINGLE-VALUE";
  98     private static final String  COLLECTIVE_ID = "COLLECTIVE";
  99     private static final String NO_USER_MOD_ID = "NO-USER-MODIFICATION";
 100     private static final String       USAGE_ID = "USAGE";
 101 
 102     // The string value we give to boolean variables
 103     private static final String SCHEMA_TRUE_VALUE = "true";
 104 
 105     // To get around writing schemas that crash Netscape server
 106     private boolean netscapeBug;
 107 
 108     LdapSchemaParser(boolean netscapeBug) {
 109         this.netscapeBug = netscapeBug;
 110     }
 111 
 112     final static void LDAP2JNDISchema(Attributes schemaAttrs,
 113         LdapSchemaCtx schemaRoot) throws NamingException {
 114         Attribute               objectClassesAttr = null;
 115         Attribute               attributeDefAttr = null;
 116         Attribute               syntaxDefAttr = null;
 117         Attribute               matchRuleDefAttr = null;
 118 
 119         objectClassesAttr = schemaAttrs.get(OBJECTCLASSDESC_ATTR_ID);
 120         if(objectClassesAttr != null) {
 121             objectDescs2ClassDefs(objectClassesAttr,schemaRoot);
 122         }
 123 
 124         attributeDefAttr = schemaAttrs.get(ATTRIBUTEDESC_ATTR_ID);
 125         if(attributeDefAttr != null) {
 126             attrDescs2AttrDefs(attributeDefAttr, schemaRoot);
 127         }
 128 
 129         syntaxDefAttr = schemaAttrs.get(SYNTAXDESC_ATTR_ID);
 130         if(syntaxDefAttr != null) {
 131             syntaxDescs2SyntaxDefs(syntaxDefAttr, schemaRoot);
 132         }
 133 
 134         matchRuleDefAttr = schemaAttrs.get(MATCHRULEDESC_ATTR_ID);
 135         if(matchRuleDefAttr != null) {
 136             matchRuleDescs2MatchRuleDefs(matchRuleDefAttr, schemaRoot);
 137         }
 138     }
 139 
 140     final private static DirContext objectDescs2ClassDefs(Attribute objDescsAttr,
 141                                                    LdapSchemaCtx schemaRoot)
 142         throws NamingException {
 143 
 144         NamingEnumeration       objDescs;
 145         Attributes      objDef;
 146         LdapSchemaCtx   classDefTree;
 147 
 148         // create the class def subtree
 149         Attributes attrs = new BasicAttributes(LdapClient.caseIgnore);
 150         attrs.put(CLASS_DEF_ATTRS[0], CLASS_DEF_ATTRS[1]);
 151         classDefTree = schemaRoot.setup(LdapSchemaCtx.OBJECTCLASS_ROOT,
 152             OBJECTCLASS_DEFINITION_NAME, attrs);
 153 
 154         objDescs = objDescsAttr.getAll();
 155         String currentName;
 156         while(objDescs.hasMore()) {
 157             String objDesc = (String)objDescs.next();
 158             try {
 159                 Object[] def = desc2Def(objDesc);
 160                 currentName = (String) def[0];
 161                 objDef = (Attributes) def[1];
 162                 classDefTree.setup(LdapSchemaCtx.OBJECTCLASS,
 163                     currentName, objDef);
 164             } catch (NamingException ne) {
 165                 // error occurred while parsing, ignore current entry
 166             }
 167         }
 168 
 169         return classDefTree;
 170     }
 171 
 172     final private static DirContext attrDescs2AttrDefs(Attribute attributeDescAttr,
 173                                                 LdapSchemaCtx schemaRoot)
 174         throws NamingException {
 175 
 176         NamingEnumeration       attrDescs;
 177         Attributes      attrDef;
 178         LdapSchemaCtx   attrDefTree;
 179 
 180         // create the AttributeDef subtree
 181         Attributes attrs = new BasicAttributes(LdapClient.caseIgnore);
 182         attrs.put(ATTR_DEF_ATTRS[0], ATTR_DEF_ATTRS[1]);
 183         attrDefTree = schemaRoot.setup(LdapSchemaCtx.ATTRIBUTE_ROOT,
 184             ATTRIBUTE_DEFINITION_NAME, attrs);
 185 
 186         attrDescs = attributeDescAttr.getAll();
 187         String currentName;
 188         while(attrDescs.hasMore()) {
 189             String attrDesc = (String)attrDescs.next();
 190             try {
 191                 Object[] def = desc2Def(attrDesc);
 192                 currentName = (String) def[0];
 193                 attrDef = (Attributes) def[1];
 194                 attrDefTree.setup(LdapSchemaCtx.ATTRIBUTE,
 195                     currentName, attrDef);
 196             } catch (NamingException ne) {
 197                 // error occurred while parsing, ignore current entry
 198             }
 199         }
 200 
 201         return attrDefTree;
 202     }
 203 
 204     final private static DirContext syntaxDescs2SyntaxDefs(
 205                                                 Attribute syntaxDescAttr,
 206                                                 LdapSchemaCtx schemaRoot)
 207         throws NamingException {
 208 
 209         NamingEnumeration       syntaxDescs;
 210         Attributes      syntaxDef;
 211         LdapSchemaCtx   syntaxDefTree;
 212 
 213         // create the SyntaxDef subtree
 214         Attributes attrs = new BasicAttributes(LdapClient.caseIgnore);
 215         attrs.put(SYNTAX_DEF_ATTRS[0], SYNTAX_DEF_ATTRS[1]);
 216         syntaxDefTree = schemaRoot.setup(LdapSchemaCtx.SYNTAX_ROOT,
 217             SYNTAX_DEFINITION_NAME, attrs);
 218 
 219         syntaxDescs = syntaxDescAttr.getAll();
 220         String currentName;
 221         while(syntaxDescs.hasMore()) {
 222             String syntaxDesc = (String)syntaxDescs.next();
 223             try {
 224                 Object[] def = desc2Def(syntaxDesc);
 225                 currentName = (String) def[0];
 226                 syntaxDef = (Attributes) def[1];
 227                 syntaxDefTree.setup(LdapSchemaCtx.SYNTAX,
 228                     currentName, syntaxDef);
 229             } catch (NamingException ne) {
 230                 // error occurred while parsing, ignore current entry
 231             }
 232         }
 233 
 234         return syntaxDefTree;
 235     }
 236 
 237     final private static DirContext matchRuleDescs2MatchRuleDefs(
 238                                                 Attribute matchRuleDescAttr,
 239                                                 LdapSchemaCtx schemaRoot)
 240         throws NamingException {
 241 
 242         NamingEnumeration       matchRuleDescs;
 243         Attributes      matchRuleDef;
 244         LdapSchemaCtx   matchRuleDefTree;
 245 
 246         // create the MatchRuleDef subtree
 247         Attributes attrs = new BasicAttributes(LdapClient.caseIgnore);
 248         attrs.put(MATCHRULE_DEF_ATTRS[0], MATCHRULE_DEF_ATTRS[1]);
 249         matchRuleDefTree = schemaRoot.setup(LdapSchemaCtx.MATCHRULE_ROOT,
 250             MATCHRULE_DEFINITION_NAME, attrs);
 251 
 252         matchRuleDescs = matchRuleDescAttr.getAll();
 253         String currentName;
 254         while(matchRuleDescs.hasMore()) {
 255             String matchRuleDesc = (String)matchRuleDescs.next();
 256             try {
 257                 Object[] def = desc2Def(matchRuleDesc);
 258                 currentName = (String) def[0];
 259                 matchRuleDef = (Attributes) def[1];
 260                 matchRuleDefTree.setup(LdapSchemaCtx.MATCHRULE,
 261                     currentName, matchRuleDef);
 262             } catch (NamingException ne) {
 263                 // error occurred while parsing, ignore current entry
 264             }
 265         }
 266 
 267         return matchRuleDefTree;
 268     }
 269 
 270     final private static Object[] desc2Def(String desc)
 271         throws NamingException {
 272             //System.err.println(desc);
 273 
 274         Attributes      attrs = new BasicAttributes(LdapClient.caseIgnore);
 275         Attribute       attr = null;
 276         int[]           pos = new int[]{1}; // tolerate missing leading space
 277         boolean         moreTags = true;
 278 
 279         // Always begins with <whsp numericoid whsp>
 280         attr = readNumericOID(desc, pos);
 281         String currentName = (String) attr.get(0);  // name is OID by default
 282         attrs.put(attr);
 283 
 284         skipWhitespace(desc, pos);
 285 
 286         while (moreTags) {
 287             attr = readNextTag(desc, pos);
 288             attrs.put(attr);
 289 
 290             if (attr.getID().equals(NAME_ID)) {
 291                 currentName = (String) attr.get(0);  // use NAME attribute as name
 292             }
 293 
 294             skipWhitespace(desc, pos);
 295 
 296             if( pos[0] >= desc.length() -1 ) {
 297                 moreTags = false;
 298             }
 299         }
 300 
 301         return new Object[] {currentName, attrs};
 302     }
 303 
 304     // returns the index of the first whitespace char of a linear whitspace
 305     // sequince ending at the given position.
 306     final private static int findTrailingWhitespace(String string, int pos) {
 307         for(int i = pos; i > 0; i--) {
 308             if(string.charAt(i) != WHSP) {
 309                 return i + 1;
 310             }
 311         }
 312         return 0;
 313     }
 314 
 315     final private static void skipWhitespace(String string, int[] pos) {
 316         for(int i=pos[0]; i < string.length(); i++) {
 317             if(string.charAt(i) != WHSP) {
 318                 pos[0] = i;
 319                 if (debug) {
 320                     System.err.println("skipWhitespace: skipping to "+i);
 321                 }
 322                 return;
 323             }
 324         }
 325     }
 326 
 327     final private static Attribute readNumericOID(String string, int[] pos)
 328         throws NamingException {
 329 
 330         if (debug) {
 331             System.err.println("readNumericoid: pos="+pos[0]);
 332         }
 333 
 334         int begin, end;
 335         String value = null;
 336 
 337         skipWhitespace(string, pos);
 338 
 339         begin = pos[0];
 340         end = string.indexOf(WHSP, begin);
 341 
 342         if (end == -1 || end - begin < 1) {
 343             throw new InvalidAttributeValueException("no numericoid found: "
 344                                                      + string);
 345         }
 346 
 347         value = string.substring(begin, end);
 348 
 349         pos[0] += value.length();
 350 
 351         return new BasicAttribute(NUMERICOID_ID, value);
 352     }
 353 
 354     final private static Attribute readNextTag(String string, int[] pos)
 355         throws NamingException {
 356 
 357         Attribute       attr = null;
 358         String          tagName = null;
 359         String[]        values = null;
 360 
 361         skipWhitespace(string, pos);
 362 
 363         if (debug) {
 364             System.err.println("readNextTag: pos="+pos[0]);
 365         }
 366 
 367         // get the name and values of the attribute to return
 368         int trailingSpace = string.indexOf( WHSP, pos[0] );
 369 
 370         // tolerate a schema that omits the trailing space
 371         if (trailingSpace < 0) {
 372             tagName = string.substring( pos[0], string.length() - 1);
 373         } else {
 374             tagName = string.substring( pos[0], trailingSpace );
 375         }
 376 
 377         values = readTag(tagName, string, pos);
 378 
 379         // make sure at least one value was returned
 380         if(values.length < 0) {
 381             throw new InvalidAttributeValueException("no values for " +
 382                                                      "attribute \"" +
 383                                                      tagName + "\"");
 384         }
 385 
 386         // create the attribute, using the first value
 387         attr = new BasicAttribute(tagName, values[0]);
 388 
 389         // add other values if there are any
 390         for(int i = 1; i < values.length; i++) {
 391             attr.add(values[i]);
 392         }
 393 
 394         return attr;
 395     }
 396 
 397     final private static String[] readTag(String tag, String string, int[] pos)
 398         throws NamingException {
 399 
 400         if (debug) {
 401             System.err.println("ReadTag: " + tag + " pos="+pos[0]);
 402         }
 403 
 404         // move parser past tag name
 405         pos[0] += tag.length();
 406         skipWhitespace(string, pos);
 407 
 408         if (tag.equals(NAME_ID)) {
 409             return readQDescrs(string, pos);  // names[0] is NAME
 410         }
 411 
 412         if(tag.equals(DESC_ID)) {
 413            return readQDString(string, pos);
 414         }
 415 
 416         if (
 417            tag.equals(EQUALITY_ID) ||
 418            tag.equals(ORDERING_ID) ||
 419            tag.equals(SUBSTR_ID) ||
 420            tag.equals(SYNTAX_ID)) {
 421             return readWOID(string, pos);
 422         }
 423 
 424         if (tag.equals(OBSOLETE_ID) ||
 425             tag.equals(ABSTRACT_ID) ||
 426             tag.equals(STRUCTURAL_ID) ||
 427             tag.equals(AUXILARY_ID) ||
 428             tag.equals(SINGLE_VAL_ID) ||
 429             tag.equals(COLLECTIVE_ID) ||
 430             tag.equals(NO_USER_MOD_ID)) {
 431             return new String[] {SCHEMA_TRUE_VALUE};
 432         }
 433 
 434         if (tag.equals(SUP_ID) ||   // oid list for object class; WOID for attribute
 435             tag.equals(MUST_ID) ||
 436             tag.equals(MAY_ID) ||
 437             tag.equals(USAGE_ID)) {
 438             return readOIDs(string, pos);
 439         }
 440 
 441         // otherwise it's a schema element with a quoted string value
 442         return readQDStrings(string, pos);
 443     }
 444 
 445     final private static String[] readQDString(String string, int[] pos)
 446         throws NamingException {
 447 
 448         int begin, end;
 449 
 450         begin = string.indexOf(SINGLE_QUOTE, pos[0]) + 1;
 451         end = string.indexOf(SINGLE_QUOTE, begin);
 452 
 453         if (debug) {
 454             System.err.println("ReadQDString: pos=" + pos[0] +
 455                                " begin=" + begin + " end=" + end);
 456         }
 457 
 458         if(begin == -1 || end == -1 || begin == end) {
 459             throw new InvalidAttributeIdentifierException("malformed " +
 460                                                           "QDString: " +
 461                                                           string);
 462         }
 463 
 464         // make sure the qdstring end symbol is there
 465         if (string.charAt(begin - 1) != SINGLE_QUOTE) {
 466             throw new InvalidAttributeIdentifierException("qdstring has " +
 467                                                           "no end mark: " +
 468                                                           string);
 469         }
 470 
 471         pos[0] = end+1;
 472         return new String[] {string.substring(begin, end)};
 473     }
 474 
 475    /**
 476     * dstring         = 1*utf8
 477     * qdstring        = whsp "'" dstring "'" whsp
 478     * qdstringlist    = [ qdstring *( qdstring ) ]
 479     * qdstrings       = qdstring / ( whsp "(" qdstringlist ")" whsp )
 480     */
 481     private final static String[] readQDStrings(String string, int[] pos)
 482         throws NamingException {
 483 
 484         return readQDescrs(string, pos);
 485     }
 486 
 487     /**
 488      * ; object descriptors used as schema element names
 489      * qdescrs         = qdescr / ( whsp "(" qdescrlist ")" whsp )
 490      * qdescrlist      = [ qdescr *( qdescr ) ]
 491      * qdescr          = whsp "'" descr "'" whsp
 492      * descr           = keystring
 493      */
 494     final private static String[] readQDescrs(String string, int[] pos)
 495         throws NamingException {
 496 
 497         if (debug) {
 498             System.err.println("readQDescrs: pos="+pos[0]);
 499         }
 500 
 501         skipWhitespace(string, pos);
 502 
 503         switch( string.charAt(pos[0]) ) {
 504         case OID_LIST_BEGIN:
 505             return readQDescrList(string, pos);
 506         case SINGLE_QUOTE:
 507             return readQDString(string, pos);
 508         default:
 509             throw new InvalidAttributeValueException("unexpected oids " +
 510                                                      "string: " + string);
 511         }
 512     }
 513 
 514     /**
 515      * qdescrlist      = [ qdescr *( qdescr ) ]
 516      * qdescr          = whsp "'" descr "'" whsp
 517      * descr           = keystring
 518      */
 519     final private static String[] readQDescrList(String string, int[] pos)
 520         throws NamingException {
 521 
 522         int     begin, end;
 523         Vector  values = new Vector(5);
 524 
 525         if (debug) {
 526             System.err.println("ReadQDescrList: pos="+pos[0]);
 527         }
 528 
 529         pos[0]++; // skip '('
 530         skipWhitespace(string, pos);
 531         begin = pos[0];
 532         end = string.indexOf(OID_LIST_END, begin);
 533 
 534         if(end == -1) {
 535             throw new InvalidAttributeValueException ("oidlist has no end "+
 536                                                       "mark: " + string);
 537         }
 538 
 539         while(begin < end) {
 540             String[] one = readQDString(string,  pos);
 541 
 542             if (debug) {
 543                 System.err.println("ReadQDescrList: found '" + one[0] +
 544                                    "' at begin=" + begin + " end =" + end);
 545             }
 546 
 547             values.addElement(one[0]);
 548             skipWhitespace(string, pos);
 549             begin = pos[0];
 550         }
 551 
 552         pos[0] = end+1; // skip ')'
 553 
 554         String[] answer = new String[values.size()];
 555         for (int i = 0; i < answer.length; i++) {
 556             answer[i] = (String)values.elementAt(i);
 557         }
 558         return answer;
 559     }
 560 
 561     final private static String[] readWOID(String string, int[] pos)
 562         throws NamingException {
 563 
 564         if (debug) {
 565             System.err.println("readWOIDs: pos="+pos[0]);
 566         }
 567 
 568         skipWhitespace(string, pos);
 569 
 570         if (string.charAt(pos[0]) == SINGLE_QUOTE) {
 571             // %%% workaround for Netscape schema bug
 572             return readQDString(string, pos);
 573         }
 574 
 575         int begin, end;
 576 
 577         begin = pos[0];
 578         end = string.indexOf(WHSP, begin);
 579 
 580         if (debug) {
 581             System.err.println("ReadWOID: pos=" + pos[0] +
 582                                " begin=" + begin + " end=" + end);
 583         }
 584 
 585         if(end == -1 || begin == end) {
 586             throw new InvalidAttributeIdentifierException("malformed " +
 587                                                           "OID: " +
 588                                                           string);
 589         }
 590         pos[0] = end+1;
 591 
 592         return new String[] {string.substring(begin, end)};
 593     }
 594 
 595     /*
 596      * oids            = woid / ( "(" oidlist ")" )
 597      * oidlist         = woid *( "$" woid )
 598      */
 599     final private static String[] readOIDs(String string, int[] pos)
 600         throws NamingException {
 601 
 602         if (debug) {
 603             System.err.println("readOIDs: pos="+pos[0]);
 604         }
 605 
 606         skipWhitespace(string, pos);
 607 
 608         // Single OID
 609         if (string.charAt(pos[0]) != OID_LIST_BEGIN) {
 610             return readWOID(string, pos);
 611         }
 612 
 613         // Multiple OIDs
 614 
 615         int     begin, cur, end;
 616         String  oidName = null;
 617         Vector  values = new Vector(5);
 618 
 619         if (debug) {
 620             System.err.println("ReadOIDList: pos="+pos[0]);
 621         }
 622 
 623         pos[0]++;
 624         skipWhitespace(string, pos);
 625         begin = pos[0];
 626         end = string.indexOf(OID_LIST_END, begin);
 627         cur = string.indexOf(OID_SEPARATOR, begin);
 628 
 629         if(end == -1) {
 630             throw new InvalidAttributeValueException ("oidlist has no end "+
 631                                                       "mark: " + string);
 632         }
 633 
 634         if(cur == -1 || end < cur) {
 635             cur = end;
 636         }
 637 
 638         while(cur < end && cur > 0) {
 639             int wsBegin = findTrailingWhitespace(string, cur - 1);
 640             oidName = string.substring(begin, wsBegin);
 641             if (debug) {
 642                 System.err.println("ReadOIDList: found '" + oidName +
 643                                    "' at begin=" + begin + " end =" + end);
 644             }
 645             values.addElement(oidName);
 646             pos[0] = cur + 1;
 647             skipWhitespace(string, pos);
 648             begin = pos[0];
 649             cur = string.indexOf(OID_SEPARATOR, begin);
 650             if(debug) {System.err.println("ReadOIDList: begin = " + begin);}
 651         }
 652 
 653         if (debug) {
 654             System.err.println("ReadOIDList: found '" + oidName +
 655                                "' at begin=" + begin + " end =" + end);
 656         }
 657 
 658         int wsBegin = findTrailingWhitespace(string, end - 1);
 659         oidName = string.substring(begin, wsBegin);
 660         values.addElement(oidName);
 661 
 662         pos[0] = end+1;
 663 
 664         String[] answer = new String[values.size()];
 665         for (int i = 0; i < answer.length; i++) {
 666             answer[i] = (String)values.elementAt(i);
 667         }
 668         return answer;
 669     }
 670 
 671 // ----------------- "unparser" methods
 672 // Methods that are used for translating a node in the schema tree
 673 // into RFC2252 format for storage back into the LDAP directory
 674 /*
 675      static Attributes JNDI2LDAPSchema(DirContext schemaRoot)
 676         throws NamingException {
 677 
 678         Attribute objDescAttr = new BasicAttribute(OBJECTCLASSDESC_ATTR_ID);
 679         Attribute attrDescAttr = new BasicAttribute(ATTRIBUTEDESC_ATTR_ID);
 680         Attribute syntaxDescAttr = new BasicAttribute(SYNTAXDESC_ATTR_ID);
 681         Attributes attrs = new BasicAttributes(LdapClient.caseIgnore);
 682         DirContext classDefs, attributeDefs, syntaxDefs;
 683         Attributes classDefsAttrs, attributeDefsAttrs, syntaxDefsAttrs;
 684         NamingEnumeration defs;
 685         Object obj;
 686         int i = 0;
 687 
 688         try {
 689             obj = schemaRoot.lookup(OBJECTCLASS_DEFINITION_NAME);
 690             if(obj != null && obj instanceof DirContext) {
 691                 classDefs = (DirContext)obj;
 692                 defs = classDefs.listBindings("");
 693                 while(defs.hasMoreElements()) {
 694                     i++;
 695                     DirContext classDef = (DirContext)
 696                         ((Binding)(defs.next())).getObject();
 697                     classDefAttrs = classDef.getAttributes("");
 698                     objDescAttr.add(classDef2ObjectDesc(classDefAttrs));
 699                 }
 700                 if (debug)
 701                     System.err.println(i + " total object classes");
 702                 attrs.put(objDescAttr);
 703             } else {
 704                 throw new NamingException(
 705                     "Problem with Schema tree: the object named " +
 706                     OBJECTCLASS_DEFINITION_NAME + " is not a " +
 707                     "DirContext");
 708             }
 709         } catch (NameNotFoundException e) {} // ignore
 710 
 711         i=0;
 712         try {
 713             obj = schemaRoot.lookup(ATTRIBUTE_DEFINITION_NAME);
 714             if(obj instanceof DirContext) {
 715                 attributeDefs = (DirContext)obj;
 716                 defs = attributeDefs.listBindings("");
 717                 while(defs.hasMoreElements()) {
 718                     i++;
 719                     DirContext attrDef = (DirContext)
 720                         ((Binding)defs.next()).getObject();
 721                     attrDefAttrs = attrDef.getAttributes("");
 722                     attrDescAttr.add(attrDef2AttrDesc(attrDefAttrs));
 723                 }
 724                 if (debug)
 725                     System.err.println(i + " attribute definitions");
 726                 attrs.put(attrDescAttr);
 727             } else {
 728                 throw new NamingException(
 729                     "Problem with schema tree: the object named " +
 730                     ATTRIBUTE_DEFINITION_NAME + " is not a " +
 731                     "DirContext");
 732             }
 733         } catch (NameNotFoundException e) {} // ignore
 734 
 735         i=0;
 736         try {
 737             obj = schemaRoot.lookup(SYNTAX_DEFINITION_NAME);
 738             if(obj instanceof DirContext) {
 739                 syntaxDefs = (DirContext)obj;
 740                 defs =syntaxDefs.listBindings("");
 741                 while(defs.hasMoreElements()) {
 742                     i++;
 743                     DirContext syntaxDef = (DirContext)
 744                         ((Binding)defs.next()).getObject();
 745                     syntaxDefAttrs = syntaxDef.getAttributes("");
 746                     syntaxDescAttr.add(syntaxDef2SyntaxDesc(syntaxDefAttrs));
 747                 }
 748                 if (debug)
 749                     System.err.println(i + " total syntax definitions");
 750                 attrs.put(syntaxDescAttr);
 751             } else {
 752                 throw new NamingException(
 753                     "Problem with schema tree: the object named " +
 754                     SYNTAX_DEFINITION_NAME + " is not a " +
 755                     "DirContext");
 756             }
 757         } catch (NameNotFoundException e) {} // ignore
 758 
 759         return attrs;
 760     }
 761 
 762 */
 763 
 764     /**
 765       * Translate attributes that describe an object class into the
 766       * string description as defined in RFC 2252.
 767       */
 768     final private String classDef2ObjectDesc(Attributes attrs)
 769         throws NamingException {
 770 
 771         StringBuffer objectDesc = new StringBuffer("( ");
 772 
 773         Attribute attr = null;
 774         int count = 0;
 775 
 776         // extract attributes by ID to guarantee ordering
 777 
 778         attr = attrs.get(NUMERICOID_ID);
 779         if (attr != null) {
 780             objectDesc.append(writeNumericOID(attr));
 781             count++;
 782         } else {
 783             throw new ConfigurationException("Class definition doesn't" +
 784                                              "have a numeric OID");
 785         }
 786 
 787         attr = attrs.get(NAME_ID);
 788         if (attr != null) {
 789             objectDesc.append(writeQDescrs(attr));
 790             count++;
 791         }
 792 
 793         attr = attrs.get(DESC_ID);
 794         if (attr != null) {
 795             objectDesc.append(writeQDString(attr));
 796             count++;
 797         }
 798 
 799         attr = attrs.get(OBSOLETE_ID);
 800         if (attr != null) {
 801             objectDesc.append(writeBoolean(attr));
 802             count++;
 803         }
 804 
 805         attr = attrs.get(SUP_ID);
 806         if (attr != null) {
 807             objectDesc.append(writeOIDs(attr));
 808             count++;
 809         }
 810 
 811         attr = attrs.get(ABSTRACT_ID);
 812         if (attr != null) {
 813             objectDesc.append(writeBoolean(attr));
 814             count++;
 815         }
 816 
 817         attr = attrs.get(STRUCTURAL_ID);
 818         if (attr != null) {
 819             objectDesc.append(writeBoolean(attr));
 820             count++;
 821         }
 822 
 823         attr = attrs.get(AUXILARY_ID);
 824         if (attr != null) {
 825             objectDesc.append(writeBoolean(attr));
 826             count++;
 827         }
 828 
 829         attr = attrs.get(MUST_ID);
 830         if (attr != null) {
 831             objectDesc.append(writeOIDs(attr));
 832             count++;
 833         }
 834 
 835         attr = attrs.get(MAY_ID);
 836         if (attr != null) {
 837             objectDesc.append(writeOIDs(attr));
 838             count++;
 839         }
 840 
 841         // process any remaining attributes
 842         if (count < attrs.size()) {
 843             String attrId = null;
 844 
 845             // use enumeration because attribute ID is not known
 846             for (NamingEnumeration ae = attrs.getAll();
 847                 ae.hasMoreElements(); ) {
 848 
 849                 attr = (Attribute)ae.next();
 850                 attrId = attr.getID();
 851 
 852                 // skip those already processed
 853                 if (attrId.equals(NUMERICOID_ID) ||
 854                     attrId.equals(NAME_ID) ||
 855                     attrId.equals(SUP_ID) ||
 856                     attrId.equals(MAY_ID) ||
 857                     attrId.equals(MUST_ID) ||
 858                     attrId.equals(STRUCTURAL_ID) ||
 859                     attrId.equals(DESC_ID) ||
 860                     attrId.equals(AUXILARY_ID) ||
 861                     attrId.equals(ABSTRACT_ID) ||
 862                     attrId.equals(OBSOLETE_ID)) {
 863                     continue;
 864 
 865                 } else {
 866                     objectDesc.append(writeQDStrings(attr));
 867                 }
 868             }
 869         }
 870 
 871         objectDesc.append(")");
 872 
 873         return objectDesc.toString();
 874     }
 875 
 876     /**
 877       * Translate attributes that describe an attribute definition into the
 878       * string description as defined in RFC 2252.
 879       */
 880     final private String attrDef2AttrDesc(Attributes attrs)
 881         throws NamingException {
 882 
 883         StringBuffer attrDesc = new StringBuffer("( "); // opening parens
 884 
 885         Attribute attr = null;
 886         int count = 0;
 887 
 888         // extract attributes by ID to guarantee ordering
 889 
 890         attr = attrs.get(NUMERICOID_ID);
 891         if (attr != null) {
 892             attrDesc.append(writeNumericOID(attr));
 893             count++;
 894         } else {
 895             throw new ConfigurationException("Attribute type doesn't" +
 896                                              "have a numeric OID");
 897         }
 898 
 899         attr = attrs.get(NAME_ID);
 900         if (attr != null) {
 901             attrDesc.append(writeQDescrs(attr));
 902             count++;
 903         }
 904 
 905         attr = attrs.get(DESC_ID);
 906         if (attr != null) {
 907             attrDesc.append(writeQDString(attr));
 908             count++;
 909         }
 910 
 911         attr = attrs.get(OBSOLETE_ID);
 912         if (attr != null) {
 913             attrDesc.append(writeBoolean(attr));
 914             count++;
 915         }
 916 
 917         attr = attrs.get(SUP_ID);
 918         if (attr != null) {
 919             attrDesc.append(writeWOID(attr));
 920             count++;
 921         }
 922 
 923         attr = attrs.get(EQUALITY_ID);
 924         if (attr != null) {
 925             attrDesc.append(writeWOID(attr));
 926             count++;
 927         }
 928 
 929         attr = attrs.get(ORDERING_ID);
 930         if (attr != null) {
 931             attrDesc.append(writeWOID(attr));
 932             count++;
 933         }
 934 
 935         attr = attrs.get(SUBSTR_ID);
 936         if (attr != null) {
 937             attrDesc.append(writeWOID(attr));
 938             count++;
 939         }
 940 
 941         attr = attrs.get(SYNTAX_ID);
 942         if (attr != null) {
 943             attrDesc.append(writeWOID(attr));
 944             count++;
 945         }
 946 
 947         attr = attrs.get(SINGLE_VAL_ID);
 948         if (attr != null) {
 949             attrDesc.append(writeBoolean(attr));
 950             count++;
 951         }
 952 
 953         attr = attrs.get(COLLECTIVE_ID);
 954         if (attr != null) {
 955             attrDesc.append(writeBoolean(attr));
 956             count++;
 957         }
 958 
 959         attr = attrs.get(NO_USER_MOD_ID);
 960         if (attr != null) {
 961             attrDesc.append(writeBoolean(attr));
 962             count++;
 963         }
 964 
 965         attr = attrs.get(USAGE_ID);
 966         if (attr != null) {
 967             attrDesc.append(writeQDString(attr));
 968             count++;
 969         }
 970 
 971         // process any remaining attributes
 972         if (count < attrs.size()) {
 973             String attrId = null;
 974 
 975             // use enumeration because attribute ID is not known
 976             for (NamingEnumeration ae = attrs.getAll();
 977                 ae.hasMoreElements(); ) {
 978 
 979                 attr = (Attribute)ae.next();
 980                 attrId = attr.getID();
 981 
 982                 // skip those already processed
 983                 if (attrId.equals(NUMERICOID_ID) ||
 984                     attrId.equals(NAME_ID) ||
 985                     attrId.equals(SYNTAX_ID) ||
 986                     attrId.equals(DESC_ID) ||
 987                     attrId.equals(SINGLE_VAL_ID) ||
 988                     attrId.equals(EQUALITY_ID) ||
 989                     attrId.equals(ORDERING_ID) ||
 990                     attrId.equals(SUBSTR_ID) ||
 991                     attrId.equals(NO_USER_MOD_ID) ||
 992                     attrId.equals(USAGE_ID) ||
 993                     attrId.equals(SUP_ID) ||
 994                     attrId.equals(COLLECTIVE_ID) ||
 995                     attrId.equals(OBSOLETE_ID)) {
 996                     continue;
 997 
 998                 } else {
 999                     attrDesc.append(writeQDStrings(attr));
1000                 }
1001             }
1002         }
1003 
1004         attrDesc.append(")");  // add closing parens
1005 
1006         return attrDesc.toString();
1007     }
1008 
1009     /**
1010       * Translate attributes that describe an attribute syntax definition into the
1011       * string description as defined in RFC 2252.
1012       */
1013     final private String syntaxDef2SyntaxDesc(Attributes attrs)
1014         throws NamingException {
1015 
1016         StringBuffer syntaxDesc = new StringBuffer("( "); // opening parens
1017 
1018         Attribute attr = null;
1019         int count = 0;
1020 
1021         // extract attributes by ID to guarantee ordering
1022 
1023         attr = attrs.get(NUMERICOID_ID);
1024         if (attr != null) {
1025             syntaxDesc.append(writeNumericOID(attr));
1026             count++;
1027         } else {
1028             throw new ConfigurationException("Attribute type doesn't" +
1029                                              "have a numeric OID");
1030         }
1031 
1032         attr = attrs.get(DESC_ID);
1033         if (attr != null) {
1034             syntaxDesc.append(writeQDString(attr));
1035             count++;
1036         }
1037 
1038         // process any remaining attributes
1039         if (count < attrs.size()) {
1040             String attrId = null;
1041 
1042             // use enumeration because attribute ID is not known
1043             for (NamingEnumeration ae = attrs.getAll();
1044                 ae.hasMoreElements(); ) {
1045 
1046                 attr = (Attribute)ae.next();
1047                 attrId = attr.getID();
1048 
1049                 // skip those already processed
1050                 if (attrId.equals(NUMERICOID_ID) ||
1051                     attrId.equals(DESC_ID)) {
1052                     continue;
1053 
1054                 } else {
1055                     syntaxDesc.append(writeQDStrings(attr));
1056                 }
1057             }
1058         }
1059 
1060         syntaxDesc.append(")");
1061 
1062         return syntaxDesc.toString();
1063     }
1064 
1065     /**
1066       * Translate attributes that describe an attribute matching rule
1067       * definition into the string description as defined in RFC 2252.
1068       */
1069     final private String matchRuleDef2MatchRuleDesc(Attributes attrs)
1070         throws NamingException {
1071 
1072         StringBuffer matchRuleDesc = new StringBuffer("( "); // opening parens
1073 
1074         Attribute attr = null;
1075         int count = 0;
1076 
1077         // extract attributes by ID to guarantee ordering
1078 
1079         attr = attrs.get(NUMERICOID_ID);
1080         if (attr != null) {
1081             matchRuleDesc.append(writeNumericOID(attr));
1082             count++;
1083         } else {
1084             throw new ConfigurationException("Attribute type doesn't" +
1085                                              "have a numeric OID");
1086         }
1087 
1088         attr = attrs.get(NAME_ID);
1089         if (attr != null) {
1090             matchRuleDesc.append(writeQDescrs(attr));
1091             count++;
1092         }
1093 
1094         attr = attrs.get(DESC_ID);
1095         if (attr != null) {
1096             matchRuleDesc.append(writeQDString(attr));
1097             count++;
1098         }
1099 
1100         attr = attrs.get(OBSOLETE_ID);
1101         if (attr != null) {
1102             matchRuleDesc.append(writeBoolean(attr));
1103             count++;
1104         }
1105 
1106         attr = attrs.get(SYNTAX_ID);
1107         if (attr != null) {
1108             matchRuleDesc.append(writeWOID(attr));
1109             count++;
1110         } else {
1111             throw new ConfigurationException("Attribute type doesn't" +
1112                                              "have a syntax OID");
1113         }
1114 
1115         // process any remaining attributes
1116         if (count < attrs.size()) {
1117             String attrId = null;
1118 
1119             // use enumeration because attribute ID is not known
1120             for (NamingEnumeration ae = attrs.getAll();
1121                 ae.hasMoreElements(); ) {
1122 
1123                 attr = (Attribute)ae.next();
1124                 attrId = attr.getID();
1125 
1126                 // skip those already processed
1127                 if (attrId.equals(NUMERICOID_ID) ||
1128                     attrId.equals(NAME_ID) ||
1129                     attrId.equals(SYNTAX_ID) ||
1130                     attrId.equals(DESC_ID) ||
1131                     attrId.equals(OBSOLETE_ID)) {
1132                     continue;
1133 
1134                 } else {
1135                     matchRuleDesc.append(writeQDStrings(attr));
1136                 }
1137             }
1138         }
1139 
1140         matchRuleDesc.append(")");
1141 
1142         return matchRuleDesc.toString();
1143     }
1144 
1145     final private String writeNumericOID(Attribute nOIDAttr)
1146         throws NamingException {
1147         if(nOIDAttr.size() != 1) {
1148             throw new InvalidAttributeValueException(
1149                 "A class definition must have exactly one numeric OID");
1150         }
1151         return (String)(nOIDAttr.get()) + WHSP;
1152     }
1153 
1154     final private String writeWOID(Attribute attr) throws NamingException {
1155         if (netscapeBug)
1156             return writeQDString(attr);
1157         else
1158             return attr.getID() + WHSP + attr.get() + WHSP;
1159     }
1160 
1161     /*  qdescr          = whsp "'" descr "'" whsp */
1162     final private String writeQDString(Attribute qdStringAttr)
1163         throws NamingException {
1164         if(qdStringAttr.size() != 1) {
1165             throw new InvalidAttributeValueException(
1166                 qdStringAttr.getID() + " must have exactly one value");
1167         }
1168 
1169         return qdStringAttr.getID() + WHSP +
1170             SINGLE_QUOTE + qdStringAttr.get() + SINGLE_QUOTE + WHSP;
1171     }
1172 
1173    /**
1174     * dstring         = 1*utf8
1175     * qdstring        = whsp "'" dstring "'" whsp
1176     * qdstringlist    = [ qdstring *( qdstring ) ]
1177     * qdstrings       = qdstring / ( whsp "(" qdstringlist ")" whsp )
1178     */
1179     private final String writeQDStrings(Attribute attr) throws NamingException {
1180         return writeQDescrs(attr);
1181     }
1182 
1183     /**
1184      * qdescrs         = qdescr / ( whsp "(" qdescrlist ")" whsp )
1185      * qdescrlist      = [ qdescr *( qdescr ) ]
1186      * qdescr          = whsp "'" descr "'" whsp
1187      * descr           = keystring
1188      */
1189     private final String writeQDescrs(Attribute attr) throws NamingException {
1190         switch(attr.size()) {
1191         case 0:
1192             throw new InvalidAttributeValueException(
1193                 attr.getID() + "has no values");
1194         case 1:
1195             return writeQDString(attr);
1196         }
1197 
1198         // write QDList
1199 
1200         StringBuffer qdList = new StringBuffer(attr.getID());
1201         qdList.append(WHSP);
1202         qdList.append(OID_LIST_BEGIN);
1203 
1204         NamingEnumeration values = attr.getAll();
1205 
1206         while(values.hasMore()) {
1207             qdList.append(WHSP);
1208             qdList.append(SINGLE_QUOTE);
1209             qdList.append((String)values.next());
1210             qdList.append(SINGLE_QUOTE);
1211             qdList.append(WHSP);
1212         }
1213 
1214         qdList.append(OID_LIST_END);
1215         qdList.append(WHSP);
1216 
1217         return qdList.toString();
1218     }
1219 
1220     final private String writeOIDs(Attribute oidsAttr)
1221         throws NamingException {
1222 
1223         switch(oidsAttr.size()) {
1224         case 0:
1225             throw new InvalidAttributeValueException(
1226                 oidsAttr.getID() + "has no values");
1227 
1228         case 1:
1229             if (netscapeBug) {
1230                 break; // %%% write out as list to avoid crashing server
1231             }
1232             return writeWOID(oidsAttr);
1233         }
1234 
1235         // write OID List
1236 
1237         StringBuffer oidList = new StringBuffer(oidsAttr.getID());
1238         oidList.append(WHSP);
1239         oidList.append(OID_LIST_BEGIN);
1240 
1241         NamingEnumeration values = oidsAttr.getAll();
1242         oidList.append(WHSP);
1243         oidList.append(values.next());
1244 
1245         while(values.hasMore()) {
1246             oidList.append(WHSP);
1247             oidList.append(OID_SEPARATOR);
1248             oidList.append(WHSP);
1249             oidList.append((String)values.next());
1250         }
1251 
1252         oidList.append(WHSP);
1253         oidList.append(OID_LIST_END);
1254         oidList.append(WHSP);
1255 
1256         return oidList.toString();
1257     }
1258 
1259     private final String writeBoolean(Attribute booleanAttr)
1260         throws NamingException {
1261             return booleanAttr.getID() + WHSP;
1262     }
1263 
1264     /**
1265      * Returns an attribute for updating the Object Class Definition schema
1266      * attribute
1267      */
1268     final Attribute stringifyObjDesc(Attributes classDefAttrs)
1269         throws NamingException {
1270         Attribute objDescAttr = new BasicAttribute(OBJECTCLASSDESC_ATTR_ID);
1271         objDescAttr.add(classDef2ObjectDesc(classDefAttrs));
1272         return objDescAttr;
1273     }
1274 
1275     /**
1276      * Returns an attribute for updating the Attribute Definition schema attribute
1277      */
1278     final Attribute stringifyAttrDesc(Attributes attrDefAttrs)
1279         throws NamingException {
1280         Attribute attrDescAttr = new BasicAttribute(ATTRIBUTEDESC_ATTR_ID);
1281         attrDescAttr.add(attrDef2AttrDesc(attrDefAttrs));
1282         return attrDescAttr;
1283     }
1284 
1285     /**
1286      * Returns an attribute for updating the Syntax schema attribute
1287      */
1288     final Attribute stringifySyntaxDesc(Attributes syntaxDefAttrs)
1289     throws NamingException {
1290         Attribute syntaxDescAttr = new BasicAttribute(SYNTAXDESC_ATTR_ID);
1291         syntaxDescAttr.add(syntaxDef2SyntaxDesc(syntaxDefAttrs));
1292         return syntaxDescAttr;
1293     }
1294 
1295     /**
1296      * Returns an attribute for updating the Matching Rule schema attribute
1297      */
1298     final Attribute stringifyMatchRuleDesc(Attributes matchRuleDefAttrs)
1299     throws NamingException {
1300         Attribute matchRuleDescAttr = new BasicAttribute(MATCHRULEDESC_ATTR_ID);
1301         matchRuleDescAttr.add(matchRuleDef2MatchRuleDesc(matchRuleDefAttrs));
1302         return matchRuleDescAttr;
1303     }
1304 }