001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.directory.api.ldap.model.schema.parsers; 021 022 023import java.io.BufferedReader; 024import java.io.File; 025import java.io.IOException; 026import java.io.InputStream; 027import java.io.InputStreamReader; 028import java.io.Reader; 029import java.io.StringReader; 030import java.nio.charset.Charset; 031import java.nio.file.Files; 032import java.nio.file.Paths; 033import java.text.ParseException; 034import java.util.ArrayList; 035import java.util.HashMap; 036import java.util.List; 037import java.util.Map; 038 039import org.apache.directory.api.asn1.util.Oid; 040import org.apache.directory.api.i18n.I18n; 041import org.apache.directory.api.ldap.model.exception.LdapSchemaException; 042import org.apache.directory.api.ldap.model.schema.AttributeType; 043import org.apache.directory.api.ldap.model.schema.DitContentRule; 044import org.apache.directory.api.ldap.model.schema.DitStructureRule; 045import org.apache.directory.api.ldap.model.schema.LdapSyntax; 046import org.apache.directory.api.ldap.model.schema.MatchingRule; 047import org.apache.directory.api.ldap.model.schema.MatchingRuleUse; 048import org.apache.directory.api.ldap.model.schema.ObjectClass; 049import org.apache.directory.api.ldap.model.schema.NameForm; 050import org.apache.directory.api.ldap.model.schema.ObjectClassTypeEnum; 051import org.apache.directory.api.ldap.model.schema.SchemaObject; 052import org.apache.directory.api.ldap.model.schema.UsageEnum; 053import org.apache.directory.api.ldap.model.schema.syntaxCheckers.OpenLdapObjectIdentifierMacro; 054import org.apache.directory.api.util.Strings; 055import org.slf4j.Logger; 056import org.slf4j.LoggerFactory; 057 058/** 059 * A reusable wrapper for hand parser OpenLDAP schema. 060 * 061 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 062 */ 063public class OpenLdapSchemaParser 064{ 065 /** The LoggerFactory used by this class */ 066 protected static final Logger LOG = LoggerFactory.getLogger( OpenLdapSchemaParser.class ); 067 068 /** A flag used to tell the parser if it should be strict or not */ 069 private boolean isQuirksModeEnabled = false; 070 071 /** the number of the current line being parsed by the reader */ 072 protected int lineNumber; 073 074 /** The list of parsed schema descriptions */ 075 private List<Object> schemaDescriptions = new ArrayList<>(); 076 077 /** The list of attribute type, initialized by splitParsedSchemaDescriptions() */ 078 private List<AttributeType> attributeTypes; 079 080 /** The list of object classes, initialized by splitParsedSchemaDescriptions()*/ 081 private List<ObjectClass> objectClasses; 082 083 /** The map of object identifier macros, initialized by splitParsedSchemaDescriptions()*/ 084 private Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros = new HashMap<>(); 085 086 /** Some contant strings used in descriptions */ 087 private static final String APPLIES_STR = "APPLIES"; 088 private static final String ABSTRACT_STR = "ABSTRACT"; 089 private static final String AUX_STR = "AUX"; 090 private static final String AUXILIARY_STR = "AUXILIARY"; 091 private static final String BYTECODE_STR = "BYTECODE"; 092 private static final String COLLECTIVE_STR = "COLLECTIVE"; 093 private static final String DESC_STR = "DESC"; 094 private static final String EQUALITY_STR = "EQUALITY"; 095 private static final String FORM_STR = "FORM"; 096 private static final String FQCN_STR = "FQCN"; 097 private static final String MAY_STR = "MAY"; 098 private static final String MUST_STR = "MUST"; 099 private static final String NAME_STR = "NAME"; 100 private static final String NO_USER_MODIFICATION_STR = "NO-USER-MODIFICATION"; 101 private static final String NOT_STR = "NOT"; 102 private static final String OBSOLETE_STR = "OBSOLETE"; 103 private static final String OC_STR = "OC"; 104 private static final String ORDERING_STR = "ORDERING"; 105 private static final String SINGLE_VALUE_STR = "SINGLE-VALUE"; 106 private static final String STRUCTURAL_STR = "STRUCTURAL"; 107 private static final String SUBSTR_STR = "SUBSTR"; 108 private static final String SUP_STR = "SUP"; 109 private static final String SYNTAX_STR = "SYNTAX"; 110 private static final String USAGE_STR = "USAGE"; 111 private static final String EXTENSION_PREFIX = "X-"; 112 113 /** Usage */ 114 private static final String DIRECTORY_OPERATION_STR = "directoryOperation"; 115 private static final String DISTRIBUTED_OPERATION_STR = "distributedOperation"; 116 private static final String DSA_OPERATION_STR = "dSAOperation"; 117 private static final String USER_APPLICATIONS_STR = "userApplications"; 118 119 /** Tokens */ 120 private static final char COLON = ':'; 121 private static final char DOLLAR = '$'; 122 private static final char DOT = '.'; 123 private static final char EQUAL = '='; 124 private static final char ESCAPE = '\\'; 125 private static final char HYPHEN = '-'; 126 private static final char LBRACE = '{'; 127 private static final char LPAREN = '('; 128 private static final char PLUS = '+'; 129 private static final char RBRACE = '}'; 130 private static final char RPAREN = ')'; 131 private static final char SEMI_COLON = ';'; 132 private static final char SHARP = '#'; 133 private static final char SLASH = '/'; 134 private static final char SQUOTE = '\''; 135 private static final char UNDERSCORE = '_'; 136 private static final char DQUOTE = '"'; 137 138 139 /** Flag whether object identifier macros should be resolved. */ 140 private boolean isResolveObjectIdentifierMacros; 141 142 private static final boolean UN_QUOTED = false; 143 144 /** Flag for strict or relaxed mode */ 145 private static final boolean STRICT = false; 146 private static final boolean RELAXED = true; 147 148 private class PosSchema 149 { 150 /** The line number in the file */ 151 int lineNumber; 152 153 /** The position in the current line */ 154 int start; 155 156 /** The line being processed */ 157 String line; 158 159 /** 160 * {@inheritDoc} 161 */ 162 @Override 163 public String toString() 164 { 165 if ( line == null ) 166 { 167 return "null"; 168 } 169 else if ( line.length() < start ) 170 { 171 return "EOL"; 172 } 173 else 174 { 175 return line.substring( start ); 176 } 177 } 178 } 179 180 181 private interface SchemaObjectElements 182 { 183 int getValue(); 184 } 185 186 187 /** 188 * The list of AttributeTypeDescription elements that can be seen 189 */ 190 private enum AttributeTypeElements implements SchemaObjectElements 191 { 192 NAME(1), 193 DESC(2), 194 OBSOLETE(4), 195 SUP(8), 196 EQUALITY(16), 197 ORDERING(32), 198 SUBSTR(64), 199 SYNTAX(128), 200 SINGLE_VALUE(256), 201 COLLECTIVE(512), 202 NO_USER_MODIFICATION(1024), 203 USAGE(2048); 204 205 private int value; 206 207 AttributeTypeElements( int value ) 208 { 209 this.value = value; 210 } 211 212 213 public int getValue() 214 { 215 return value; 216 } 217 } 218 219 220 /** 221 * The list of DitContentRuleDescription elements that can be seen 222 */ 223 private enum DitContentRuleElements implements SchemaObjectElements 224 { 225 NAME(1), 226 DESC(2), 227 OBSOLETE(4), 228 AUX(8), 229 MUST(16), 230 MAY(32), 231 NOT(64); 232 233 private int value; 234 235 DitContentRuleElements( int value ) 236 { 237 this.value = value; 238 } 239 240 241 public int getValue() 242 { 243 return value; 244 } 245 } 246 247 248 /** 249 * The list of DitStructureRuleDescription elements that can be seen 250 */ 251 private enum DitStructureRuleElements implements SchemaObjectElements 252 { 253 NAME(1), 254 DESC(2), 255 OBSOLETE(4), 256 FORM(8), 257 SUP(16); 258 259 private int value; 260 261 DitStructureRuleElements( int value ) 262 { 263 this.value = value; 264 } 265 266 267 public int getValue() 268 { 269 return value; 270 } 271 } 272 273 274 /** 275 * The list of LdapComparatorDescription elements that can be seen 276 */ 277 private enum LdapComparatorElements implements SchemaObjectElements 278 { 279 DESC(1), 280 FQCN(2), 281 BYTECODE(4); 282 283 private int value; 284 285 LdapComparatorElements( int value ) 286 { 287 this.value = value; 288 } 289 290 291 public int getValue() 292 { 293 return value; 294 } 295 } 296 297 298 /** 299 * The list of LdapSyntaxDescription elements that can be seen 300 */ 301 private enum LdapSyntaxElements implements SchemaObjectElements 302 { 303 DESC(1); 304 305 private int value; 306 307 LdapSyntaxElements( int value ) 308 { 309 this.value = value; 310 } 311 312 313 public int getValue() 314 { 315 return value; 316 } 317 } 318 319 320 /** 321 * The list of MatchingRuleDescription elements that can be seen 322 */ 323 private enum MatchingRuleElements implements SchemaObjectElements 324 { 325 NAME(1), 326 DESC(2), 327 OBSOLETE(4), 328 SYNTAX(8); 329 330 private int value; 331 332 MatchingRuleElements( int value ) 333 { 334 this.value = value; 335 } 336 337 338 public int getValue() 339 { 340 return value; 341 } 342 } 343 344 345 /** 346 * The list of MatchingRuleUseDescription elements that can be seen 347 */ 348 private enum MatchingRuleUseElements implements SchemaObjectElements 349 { 350 NAME(1), 351 DESC(2), 352 OBSOLETE(4), 353 APPLIES(8); 354 355 private int value; 356 357 MatchingRuleUseElements( int value ) 358 { 359 this.value = value; 360 } 361 362 363 public int getValue() 364 { 365 return value; 366 } 367 } 368 369 370 /** 371 * The list of NameFormDescription elements that can be seen 372 */ 373 private enum NameFormElements implements SchemaObjectElements 374 { 375 NAME(1), 376 DESC(2), 377 OBSOLETE(4), 378 OC(8), 379 MUST(16), 380 MAY(32); 381 382 private int value; 383 384 NameFormElements( int value ) 385 { 386 this.value = value; 387 } 388 389 390 public int getValue() 391 { 392 return value; 393 } 394 } 395 396 397 /** 398 * The list of NormalizerDescription elements that can be seen 399 */ 400 private enum NormalizerElements implements SchemaObjectElements 401 { 402 DESC(1), 403 FQCN(2), 404 BYTECODE(4); 405 406 private int value; 407 408 NormalizerElements( int value ) 409 { 410 this.value = value; 411 } 412 413 414 public int getValue() 415 { 416 return value; 417 } 418 } 419 420 421 /** 422 * The list of ObjectClassDescription elements that can be seen 423 */ 424 private enum ObjectClassElements implements SchemaObjectElements 425 { 426 NAME(1), 427 DESC(2), 428 OBSOLETE(4), 429 SUP(8), 430 MUST(16), 431 MAY(32), 432 ABSTRACT(64), 433 STRUCTURAL(64), 434 AUXILIARY(64); 435 436 private int value; 437 438 ObjectClassElements( int value ) 439 { 440 this.value = value; 441 } 442 443 444 public int getValue() 445 { 446 return value; 447 } 448 } 449 450 451 /** 452 * The list of SyntaxCheckerDescription elements that can be seen 453 */ 454 private enum SyntaxCheckerElements implements SchemaObjectElements 455 { 456 DESC(1), 457 FQCN(2), 458 BYTECODE(4); 459 460 private int value; 461 462 SyntaxCheckerElements( int value ) 463 { 464 this.value = value; 465 } 466 467 468 public int getValue() 469 { 470 return value; 471 } 472 } 473 474 475 /** 476 * Creates a reusable instance of an OpenLdapSchemaParser. 477 */ 478 public OpenLdapSchemaParser() 479 { 480 isResolveObjectIdentifierMacros = true; 481 isQuirksModeEnabled = false; 482 } 483 484 485 /** 486 * Reset the parser 487 */ 488 public void clear() 489 { 490 if ( attributeTypes != null ) 491 { 492 attributeTypes.clear(); 493 } 494 495 if ( objectClasses != null ) 496 { 497 objectClasses.clear(); 498 } 499 500 if ( schemaDescriptions != null ) 501 { 502 schemaDescriptions.clear(); 503 } 504 505 if ( objectIdentifierMacros != null ) 506 { 507 objectIdentifierMacros.clear(); 508 } 509 } 510 511 512 /** 513 * Gets the attribute types. 514 * 515 * @return the attribute types 516 */ 517 public List<AttributeType> getAttributeTypes() 518 { 519 return attributeTypes; 520 } 521 522 523 /** 524 * Gets the object class types. 525 * 526 * @return the object class types 527 */ 528 public List<ObjectClass> getObjectClasses() 529 { 530 return objectClasses; 531 } 532 533 534 /** 535 * Gets the object identifier macros. 536 * 537 * @return the object identifier macros 538 */ 539 public Map<String, OpenLdapObjectIdentifierMacro> getObjectIdentifierMacros() 540 { 541 return objectIdentifierMacros; 542 } 543 544 545 /** 546 * Splits parsed schema descriptions and resolved 547 * object identifier macros. 548 * 549 * @throws ParseException the parse exception 550 */ 551 private void afterParse() throws ParseException 552 { 553 objectClasses = new ArrayList<>(); 554 attributeTypes = new ArrayList<>(); 555 556 // split parsed schema descriptions 557 for ( Object obj : schemaDescriptions ) 558 { 559 if ( obj instanceof OpenLdapObjectIdentifierMacro ) 560 { 561 OpenLdapObjectIdentifierMacro oid = ( OpenLdapObjectIdentifierMacro ) obj; 562 objectIdentifierMacros.put( oid.getName(), oid ); 563 } 564 else if ( obj instanceof AttributeType ) 565 { 566 AttributeType attributeType = ( AttributeType ) obj; 567 568 attributeTypes.add( attributeType ); 569 } 570 else if ( obj instanceof ObjectClass ) 571 { 572 ObjectClass objectClass = ( ObjectClass ) obj; 573 574 objectClasses.add( objectClass ); 575 } 576 } 577 578 if ( isResolveObjectIdentifierMacros() ) 579 { 580 // resolve object identifier macros 581 for ( OpenLdapObjectIdentifierMacro oid : objectIdentifierMacros.values() ) 582 { 583 resolveObjectIdentifierMacro( oid ); 584 } 585 586 // apply object identifier macros to object classes 587 for ( ObjectClass objectClass : objectClasses ) 588 { 589 objectClass.setOid( getResolveOid( objectClass.getOid() ) ); 590 } 591 592 // apply object identifier macros to attribute types 593 for ( AttributeType attributeType : attributeTypes ) 594 { 595 attributeType.setOid( getResolveOid( attributeType.getOid() ) ); 596 attributeType.setSyntaxOid( getResolveOid( attributeType.getSyntaxOid() ) ); 597 } 598 599 } 600 } 601 602 603 /** 604 * Return a complete OID from a macro followed by an OID. 605 * 606 * @param oid The OID to find 607 * @return The extended OID 608 */ 609 private String getResolveOid( String oid ) 610 { 611 if ( oid != null && oid.indexOf( COLON ) != -1 ) 612 { 613 // resolve OID 614 String[] nameAndSuffix = oid.split( ":" ); 615 616 if ( objectIdentifierMacros.containsKey( nameAndSuffix[0] ) ) 617 { 618 OpenLdapObjectIdentifierMacro macro = objectIdentifierMacros.get( nameAndSuffix[0] ); 619 620 return macro.getResolvedOid() + "." + nameAndSuffix[1]; 621 } 622 } 623 624 return oid; 625 } 626 627 628 /** 629 * Find the proper OID from a OID which may contain a macro 630 * 631 * @param macro The element to resolve 632 * @throws ParseException If teh OID is invalid 633 */ 634 private void resolveObjectIdentifierMacro( OpenLdapObjectIdentifierMacro macro ) throws ParseException 635 { 636 String rawOidOrNameSuffix = macro.getRawOidOrNameSuffix(); 637 638 if ( !macro.isResolved() ) 639 { 640 if ( rawOidOrNameSuffix.indexOf( COLON ) != -1 ) 641 { 642 // resolve OID 643 String[] nameAndSuffix = rawOidOrNameSuffix.split( ":" ); 644 645 if ( objectIdentifierMacros.containsKey( nameAndSuffix[0] ) ) 646 { 647 OpenLdapObjectIdentifierMacro parentMacro = objectIdentifierMacros.get( nameAndSuffix[0] ); 648 resolveObjectIdentifierMacro( parentMacro ); 649 macro.setResolvedOid( parentMacro.getResolvedOid() + "." + nameAndSuffix[1] ); 650 } 651 else 652 { 653 throw new ParseException( I18n.err( I18n.ERR_13726_NO_OBJECT_IDENTIFIER_MACRO, nameAndSuffix[0] ), 0 ); 654 } 655 } 656 else 657 { 658 // no :suffix, 659 if ( objectIdentifierMacros.containsKey( rawOidOrNameSuffix ) ) 660 { 661 OpenLdapObjectIdentifierMacro parentMacro = objectIdentifierMacros.get( rawOidOrNameSuffix ); 662 resolveObjectIdentifierMacro( parentMacro ); 663 macro.setResolvedOid( parentMacro.getResolvedOid() ); 664 } 665 else 666 { 667 macro.setResolvedOid( rawOidOrNameSuffix ); 668 } 669 } 670 } 671 } 672 673 674 /** 675 * Parses an OpenLDAP schemaObject element/object. 676 * 677 * @param schemaObject the String image of a complete schema object 678 * @return the schema object 679 * @throws ParseException If the schemaObject can't be parsed 680 */ 681 public SchemaObject parse( String schemaObject ) throws ParseException 682 { 683 if ( ( schemaObject == null ) || Strings.isEmpty( schemaObject.trim() ) ) 684 { 685 throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 ); 686 } 687 688 try ( Reader reader = new BufferedReader( new StringReader( schemaObject ) ) ) 689 { 690 parse( reader ); 691 afterParse(); 692 } 693 catch ( IOException | LdapSchemaException e ) 694 { 695 throw new ParseException( e.getMessage(), 0 ); 696 } 697 698 if ( !schemaDescriptions.isEmpty() ) 699 { 700 for ( Object obj : schemaDescriptions ) 701 { 702 if ( obj instanceof SchemaObject ) 703 { 704 return ( SchemaObject ) obj; 705 } 706 } 707 } 708 709 return null; 710 } 711 712 713 /** 714 * Parses a stream of OpenLDAP schemaObject elements/objects. Default charset is used. 715 * 716 * @param schemaIn a stream of schema objects 717 * @throws ParseException If the schema can't be parsed 718 * @throws LdapSchemaException If there is an error in the schema 719 * @throws IOException If the stream can't be read 720 */ 721 public void parse( InputStream schemaIn ) throws ParseException, LdapSchemaException, IOException 722 { 723 try ( InputStreamReader in = new InputStreamReader( schemaIn, Charset.defaultCharset() ) ) 724 { 725 try ( Reader reader = new BufferedReader( in ) ) 726 { 727 parse( reader ); 728 afterParse(); 729 } 730 } 731 } 732 733 734 /** 735 * 736 * @param reader The stream reader 737 * @param pos The position in the Schema 738 * @param mandatory If the spaces are mandatory 739 * @throws IOException If the stream can't be read 740 * @throws LdapSchemaException If the schema is wrong 741 */ 742 private static void skipWhites( Reader reader, PosSchema pos, boolean mandatory ) throws IOException, LdapSchemaException 743 { 744 boolean hasSpace = false; 745 746 while ( true ) 747 { 748 if ( isEmpty( pos ) ) 749 { 750 getLine( reader, pos ); 751 752 if ( pos.line == null ) 753 { 754 return; 755 } 756 757 hasSpace = true; 758 continue; 759 } 760 761 if ( pos.line == null ) 762 { 763 throw new LdapSchemaException( I18n.err( I18n.ERR_13782_END_OF_FILE, pos.lineNumber, pos.start ) ); 764 } 765 766 while ( Character.isWhitespace( pos.line.charAt( pos.start ) ) ) 767 { 768 hasSpace = true; 769 pos.start++; 770 771 if ( isEmpty( pos ) ) 772 { 773 getLine( reader, pos ); 774 775 if ( pos.line == null ) 776 { 777 return; 778 } 779 } 780 } 781 782 if ( pos.line.charAt( pos.start ) == SHARP ) 783 { 784 getLine( reader, pos ); 785 786 if ( pos.line == null ) 787 { 788 return; 789 } 790 791 hasSpace = true; 792 } 793 else 794 { 795 if ( mandatory && !hasSpace ) 796 { 797 throw new LdapSchemaException( I18n.err( I18n.ERR_13783_SPACE_EXPECTED, pos.lineNumber, pos.start ) ); 798 } 799 else 800 { 801 return; 802 } 803 } 804 } 805 } 806 807 808 /** 809 * @param pos The position in the Schema 810 * @return <tt>true</tt> if this is a comment 811 */ 812 private static boolean isComment( PosSchema pos ) 813 { 814 if ( isEmpty( pos ) ) 815 { 816 return true; 817 } 818 819 return pos.line.charAt( pos.start ) == SHARP; 820 } 821 822 823 /** 824 * @param pos The position in the Schema 825 * @return <tt>true</tt> of the line is empty 826 */ 827 private static boolean isEmpty( PosSchema pos ) 828 { 829 return ( pos.line == null ) || ( pos.start >= pos.line.length() ); 830 } 831 832 833 /** 834 * @param pos The position in the Schema 835 * @param text The text to find at the beginning of the line 836 * @return <tt>true</tt> if teh line starts with the given text 837 */ 838 private static boolean startsWith( PosSchema pos, String text ) 839 { 840 if ( ( pos.line == null ) || ( pos.line.length() - pos.start < text.length() ) ) 841 { 842 return false; 843 } 844 845 return text.equalsIgnoreCase( pos.line.substring( pos.start, pos.start + text.length() ) ); 846 } 847 848 849 /** 850 * Check if the stream starts with a given char at a given position 851 * 852 * @param reader The stream reader 853 * @param pos The position in the Schema 854 * @param c The char to check 855 * @return <tt>true</tT> if the stream starts with the given char at the given position 856 * @throws IOException If we can't read the stream 857 * @throws LdapSchemaException If we have no char to read 858 */ 859 private static boolean startsWith( Reader reader, PosSchema pos, char c ) throws IOException, LdapSchemaException 860 { 861 return startsWith( reader, pos, c, UN_QUOTED ); 862 } 863 864 865 /** 866 * Check if the stream at the given position starts with a given char 867 * 868 * @param reader The stream reader 869 * @param pos The position in the Schema 870 * @param c The char to check 871 * @param quoted <tt>true</tt> if the char is quoted 872 * @return <tt>true</tt> if the stream starts with the given char at the given position 873 * @throws IOException If we can't read the stream 874 * @throws LdapSchemaException If we have no char to read 875 */ 876 private static boolean startsWith( Reader reader, PosSchema pos, char c, boolean quoted ) throws IOException, LdapSchemaException 877 { 878 if ( ( pos.line == null ) || ( pos.line.length() - pos.start < 1 ) ) 879 { 880 return false; 881 } 882 883 if ( quoted ) 884 { 885 // Don't read a new line when we are within quotes 886 return pos.line.charAt( pos.start ) == c; 887 } 888 889 while ( isEmpty( pos ) || ( isComment( pos ) ) ) 890 { 891 getLine( reader, pos ); 892 893 if ( pos.line == null ) 894 { 895 return false; 896 } 897 898 skipWhites( reader, pos, false ); 899 900 if ( isComment( pos ) ) 901 { 902 continue; 903 } 904 } 905 906 return pos.line.charAt( pos.start ) == c; 907 } 908 909 910 /** 911 * @param pos The position in the Schema 912 * @param c The char to find at the beginning of the line 913 * @return <tt>true</tt> if the char is found at the beginning of the line 914 */ 915 private static boolean startsWith( PosSchema pos, char c ) 916 { 917 if ( ( pos.line == null ) || ( pos.line.length() - pos.start < 1 ) ) 918 { 919 return false; 920 } 921 922 return pos.line.charAt( pos.start ) == c; 923 } 924 925 926 /** 927 * @param pos The position in the Schema 928 * @return <tt>true</tt> if the first char is alphabetic 929 */ 930 private static boolean isAlpha( PosSchema pos ) 931 { 932 return Character.isAlphabetic( pos.line.charAt( pos.start ) ); 933 } 934 935 936 /** 937 * @param pos The position in the Schema 938 * @return <tt>true</tt> if the first char is a digit 939 */ 940 private static boolean isDigit( PosSchema pos ) 941 { 942 return Character.isDigit( pos.line.charAt( pos.start ) ); 943 } 944 945 946 /** 947 * 948 * @param reader The stream reader 949 * @param pos The position in the Schema 950 * @throws IOException If the stream can't be read 951 */ 952 private static void getLine( Reader reader, PosSchema pos ) throws IOException 953 { 954 pos.line = ( ( BufferedReader ) reader ).readLine(); 955 pos.start = 0; 956 957 if ( pos.line != null ) 958 { 959 pos.lineNumber++; 960 } 961 } 962 963 964 /** 965 * <pre> 966 * numericoid ::= number ( DOT number )+ 967 * number ::= DIGIT | LDIGIT DIGIT+ 968 * DIGIT ::= %x30 | LDIGIT ; "0"-"9" 969 * LDIGIT ::= %x31-39 ; "1"-"9" 970 * DOT ::= %x2E ; period (".") 971 * </pre> 972 * 973 * @param pos The position in the Schema 974 * @return The numeric OID 975 * @throws LdapSchemaException If the schema is wrong 976 */ 977 private static String getNumericOid( PosSchema pos ) throws LdapSchemaException 978 { 979 int start = pos.start; 980 boolean isDot = false; 981 boolean isFirstZero = false; 982 boolean isFirstDigit = true; 983 984 while ( !isEmpty( pos ) ) 985 { 986 char c = pos.line.charAt( pos.start ); 987 988 if ( Character.isDigit( c ) ) 989 { 990 if ( isFirstZero ) 991 { 992 throw new LdapSchemaException( I18n.err( I18n.ERR_13784_BAD_OID_TWO_ZEROES, pos.lineNumber, pos.start ) ); 993 } 994 995 if ( ( pos.line.charAt( pos.start ) == '0' ) && isFirstDigit ) 996 { 997 isFirstZero = true; 998 } 999 1000 isDot = false; 1001 pos.start++; 1002 isFirstDigit = false; 1003 } 1004 else if ( c == DOT ) 1005 { 1006 if ( isDot ) 1007 { 1008 // We can't have two consecutive dots or a dot at the beginning 1009 throw new LdapSchemaException( I18n.err( I18n.ERR_13785_BAD_OID_CONSECUTIVE_DOTS, pos.lineNumber, pos.start ) ); 1010 } 1011 1012 isFirstZero = false; 1013 isFirstDigit = true; 1014 pos.start++; 1015 isDot = true; 1016 } 1017 else 1018 { 1019 break; 1020 } 1021 } 1022 1023 if ( isDot ) 1024 { 1025 // We can't have two consecutive dots or a dot at the beginning 1026 throw new LdapSchemaException( I18n.err( I18n.ERR_13786_BAD_OID_DOT_AT_THE_END, pos.lineNumber, pos.start ) ); 1027 } 1028 1029 String oidStr = pos.line.substring( start, pos.start ); 1030 1031 if ( Oid.isOid( oidStr ) ) 1032 { 1033 return oidStr; 1034 } 1035 else 1036 { 1037 throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.line, pos.start ) ); 1038 } 1039 } 1040 1041 1042 /** 1043 * <pre> 1044 * partialNumericoid ::= number ( DOT number )* 1045 * number ::= DIGIT | LDIGIT DIGIT+ 1046 * DIGIT ::= %x30 | LDIGIT ; "0"-"9" 1047 * LDIGIT ::= %x31-39 ; "1"-"9" 1048 * DOT ::= %x2E ; period (".") 1049 * </pre> 1050 * 1051 * @param pos The position in the Schema 1052 * @return The found OID 1053 * @throws LdapSchemaException If the schema is wrong 1054 */ 1055 private static String getPartialNumericOid( PosSchema pos ) throws LdapSchemaException 1056 { 1057 int start = pos.start; 1058 boolean isDot = false; 1059 boolean isFirstZero = false; 1060 boolean isFirstDigit = true; 1061 1062 while ( !isEmpty( pos ) ) 1063 { 1064 if ( isDigit( pos ) ) 1065 { 1066 if ( isFirstZero ) 1067 { 1068 throw new LdapSchemaException( I18n.err( I18n.ERR_13784_BAD_OID_TWO_ZEROES, pos.lineNumber, pos.start ) ); 1069 } 1070 1071 if ( ( pos.line.charAt( pos.start ) == '0' ) && isFirstDigit ) 1072 { 1073 isFirstZero = true; 1074 } 1075 1076 isDot = false; 1077 pos.start++; 1078 isFirstDigit = false; 1079 } 1080 else if ( startsWith( pos, DOT ) ) 1081 { 1082 if ( isDot ) 1083 { 1084 // We can't have two consecutive dots or a dot at the beginning 1085 throw new LdapSchemaException( I18n.err( I18n.ERR_13785_BAD_OID_CONSECUTIVE_DOTS, pos.lineNumber, pos.start ) ); 1086 } 1087 1088 isFirstZero = false; 1089 isFirstDigit = true; 1090 pos.start++; 1091 isDot = true; 1092 } 1093 else 1094 { 1095 break; 1096 } 1097 } 1098 1099 if ( isDot ) 1100 { 1101 // We can't have two consecutive dots or a dot at the beginning 1102 throw new LdapSchemaException( I18n.err( I18n.ERR_13786_BAD_OID_DOT_AT_THE_END, pos.lineNumber, pos.start ) ); 1103 } 1104 1105 return pos.line.substring( start, pos.start ); 1106 } 1107 1108 1109 1110 1111 /** 1112 * In relaxed mode : 1113 * <pre> 1114 * oid ::= descr | numericoid 1115 * descr ::= descrQ (COLON numericoid) 1116 * descrQ ::= keystringQ 1117 * keystringQ ::= LkeycharQ keycharQ* 1118 * LkeycharQ ::= ALPHA | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP 1119 * keycharQ ::= ALPHA | DIGIT | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP 1120 * numericoid ::= number ( DOT number )+ 1121 * number ::= DIGIT | LDIGIT DIGIT+ 1122 * ALPHA ::= %x41-5A | %x61-7A ; "A"-"Z" / "a"-"z" 1123 * DIGIT ::= %x30 | LDIGIT ; "0"-"9" 1124 * LDIGIT ::= %x31-39 ; "1"-"9" 1125 * HYPHEN ::= %x2D ; hyphen ("-") 1126 * UNDERSCORE ::= %x5F ; underscore ("_") 1127 * DOT ::= %x2E ; period (".") 1128 * COLON ::= %x3A ; colon (":") 1129 * SEMI_COLON ::= %x3B ; semi-colon(";") 1130 * SHARP ::= %x23 ; octothorpe (or sharp sign) ("#") 1131 * </pre> 1132 * 1133 * @param pos The position in the Schema 1134 * @param objectIdentifierMacros The set of existing Macros 1135 * @return The found OID 1136 * @throws LdapSchemaException If the schema is wrong 1137 */ 1138 private static String getOidAndMacroRelaxed( PosSchema pos, 1139 Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws LdapSchemaException 1140 { 1141 if ( isEmpty( pos ) ) 1142 { 1143 return ""; 1144 } 1145 1146 // This is a OID name 1147 int start = pos.start; 1148 char c = pos.line.charAt( pos.start ); 1149 boolean isDigit = Character.isDigit( c ); 1150 1151 while ( isDigit || Character.isAlphabetic( c ) || ( c == HYPHEN ) || ( c == UNDERSCORE ) 1152 || ( c == SEMI_COLON ) || ( c == DOT ) || ( c == SHARP ) ) 1153 { 1154 pos.start++; 1155 1156 if ( isEmpty( pos ) ) 1157 { 1158 break; 1159 } 1160 1161 c = pos.line.charAt( pos.start ); 1162 isDigit = Character.isDigit( c ); 1163 } 1164 1165 String oidName = pos.line.substring( start, pos.start ); 1166 1167 if ( Strings.isEmpty( oidName ) ) 1168 { 1169 throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) ); 1170 } 1171 1172 // We may have a ':' followed by an OID 1173 if ( startsWith( pos, COLON ) ) 1174 { 1175 pos.start++; 1176 String oid = getPartialNumericOid( pos ); 1177 1178 return objectIdentifierMacros.get( oidName ).getRawOidOrNameSuffix() + DOT + oid; 1179 } 1180 else 1181 { 1182 // Ok, we may just have an oidName 1183 OpenLdapObjectIdentifierMacro macro = objectIdentifierMacros.get( oidName ); 1184 1185 if ( macro == null ) 1186 { 1187 return oidName; 1188 } 1189 else 1190 { 1191 return macro.getRawOidOrNameSuffix(); 1192 } 1193 } 1194 } 1195 1196 1197 /** 1198 * In normal mode : 1199 * <pre> 1200 * oid ::= descr | numericoid 1201 * descr ::= keystring 1202 * keystring ::= leadkeychar keychar* 1203 * leadkeychar ::= ALPHA 1204 * keychar ::= ALPHA | DIGIT | HYPHEN 1205 * numericoid ::= number ( DOT number )+ | 1206 * number ::= DIGIT | LDIGIT DIGIT+ 1207 * ALPHA ::= %x41-5A | %x61-7A ; "A"-"Z" / "a"-"z" 1208 * DIGIT ::= %x30 | LDIGIT ; "0"-"9" 1209 * LDIGIT ::= %x31-39 ; "1"-"9" 1210 * DOT ::= %x2E ; period (".") 1211 * HYPHEN ::= %x2D ; hyphen ("-") 1212 * </pre> 1213 * 1214 * @param pos The position in the Schema 1215 * @return The found OID 1216 * @throws LdapSchemaException If the schema is wrong 1217 */ 1218 private static String getOidStrict( PosSchema pos ) throws LdapSchemaException 1219 { 1220 if ( isEmpty( pos ) ) 1221 { 1222 return ""; 1223 } 1224 1225 if ( isAlpha( pos ) ) 1226 { 1227 // A descr 1228 return getDescrStrict( pos ); 1229 } 1230 else if ( isDigit( pos ) ) 1231 { 1232 // This is a numeric oid 1233 return getNumericOid( pos ); 1234 } 1235 else 1236 { 1237 // This is an error 1238 throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) ); 1239 } 1240 } 1241 1242 1243 /** 1244 * In quirks mode : 1245 * <pre> 1246 * oid ::= descr-relaxed | numericoid | SQUOTE descr-relaxed SQUOTE | 1247 * DQUOTE descr-relaxed DQUOTE | SQUOTE numericoid SQUOTE | 1248 * DQUOTE numericoid DQUOTE 1249 * descr-relaxed::= macro (COLON numericoid) 1250 * macro ::= keystring 1251 * keystring ::= Lkeychar keychar* 1252 * Lkeychar ::= ALPHA | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP 1253 * keychar ::= ALPHA | DIGIT | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP 1254 * numericoid ::= number ( DOT number )+ 1255 * number ::= DIGIT | LDIGIT DIGIT+ 1256 * ALPHA ::= %x41-5A | %x61-7A ; "A"-"Z" / "a"-"z" 1257 * DIGIT ::= %x30 | LDIGIT ; "0"-"9" 1258 * LDIGIT ::= %x31-39 ; "1"-"9" 1259 * HYPHEN ::= %x2D ; hyphen ("-") 1260 * UNDERSCORE ::= %x5F ; underscore ("_") 1261 * DOT ::= %x2E ; period (".") 1262 * COLON ::= %x3A ; colon (":") 1263 * SEMI_COLON ::= %x3B ; semi-colon(";") 1264 * SHARP ::= %x23 ; octothorpe (or sharp sign) ("#") 1265 * </pre> 1266 * 1267 * @param pos The position in the Schema 1268 * @param hadQuote If we have had a quote 1269 * @return the found OID 1270 * @throws LdapSchemaException If the schema is wrong 1271 */ 1272 private static String getOidRelaxed( PosSchema pos, boolean hadQuote ) throws LdapSchemaException 1273 { 1274 if ( isEmpty( pos ) ) 1275 { 1276 return ""; 1277 } 1278 1279 boolean hasQuote = false; 1280 1281 char c = pos.line.charAt( pos.start ); 1282 1283 if ( c == SQUOTE ) 1284 { 1285 if ( hadQuote ) 1286 { 1287 return ""; 1288 } 1289 1290 hasQuote = true; 1291 pos.start++; 1292 1293 if ( isEmpty( pos ) ) 1294 { 1295 return ""; 1296 } 1297 1298 c = pos.line.charAt( pos.start ); 1299 } 1300 1301 String oid; 1302 1303 if ( Character.isAlphabetic( c ) ) 1304 { 1305 // This is a OID name 1306 oid = getDescrRelaxed( pos ); 1307 } 1308 else if ( Character.isDigit( c ) ) 1309 { 1310 // This is a numeric oid 1311 oid = getNumericOid( pos ); 1312 } 1313 else 1314 { 1315 // This is an error 1316 throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, 1317 pos.lineNumber, pos.start ) ); 1318 } 1319 1320 if ( isEmpty( pos ) ) 1321 { 1322 if ( hasQuote || hadQuote ) 1323 { 1324 throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END, 1325 pos.lineNumber, pos.start ) ); 1326 } 1327 else 1328 { 1329 return oid; 1330 } 1331 } 1332 1333 c = pos.line.charAt( pos.start ); 1334 1335 if ( ( c == SQUOTE ) && !hadQuote ) 1336 { 1337 if ( hasQuote ) 1338 { 1339 pos.start++; 1340 } 1341 else 1342 { 1343 throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END, 1344 pos.lineNumber, pos.start ) ); 1345 } 1346 } 1347 1348 return oid; 1349 } 1350 1351 1352 /** 1353 * In strict mode : 1354 * 1355 * <pre> 1356 * descr ::= keystring 1357 * keystring ::= leadkeychar keychar* 1358 * leadkeychar ::= ALPHA 1359 * keychar ::= ALPHA | DIGIT | HYPHEN 1360 * numericoid ::= number ( DOT number )+ | 1361 * number ::= DIGIT | LDIGIT DIGIT+ 1362 * ALPHA ::= %x41-5A | %x61-7A ; "A"-"Z" / "a"-"z" 1363 * DIGIT ::= %x30 | LDIGIT ; "0"-"9" 1364 * LDIGIT ::= %x31-39 ; "1"-"9" 1365 * DOT ::= %x2E ; period (".") 1366 * HYPHEN ::= %x2D ; hyphen ("-") 1367 * </pre> 1368 * 1369 * @param pos The position in the Schema 1370 * @return The descr 1371 * @throws LdapSchemaException If the schema is wrong 1372 */ 1373 private static String getDescrStrict( PosSchema pos ) throws LdapSchemaException 1374 { 1375 int start = pos.start; 1376 boolean isFirst = true; 1377 1378 while ( !isEmpty( pos ) ) 1379 { 1380 if ( isFirst ) 1381 { 1382 isFirst = false; 1383 1384 if ( isAlpha( pos ) ) 1385 { 1386 // leadkeychar 1387 pos.start++; 1388 } 1389 else 1390 { 1391 // Error, we are expecting a leadKeychar 1392 throw new LdapSchemaException( I18n.err( I18n.ERR_13788_LEAD_KEY_CHAR_EXPECTED, 1393 pos.lineNumber, pos.start ) ); 1394 } 1395 } 1396 else 1397 { 1398 char c = pos.line.charAt( pos.start ); 1399 1400 if ( Character.isAlphabetic( c ) || Character.isDigit( c ) || ( c == HYPHEN ) ) 1401 { 1402 pos.start++; 1403 } 1404 else 1405 { 1406 // We are done 1407 return pos.line.substring( start, pos.start ); 1408 } 1409 } 1410 } 1411 1412 return pos.line.substring( start, pos.start ); 1413 } 1414 1415 1416 1417 /** 1418 * In quirksMode : 1419 * 1420 * <pre> 1421 * descr ::= descrQ (COLON numericoid) 1422 * descrQ ::= keystringQ 1423 * keystringQ ::= LkeycharQ keycharQ* 1424 * LkeycharQ ::= ALPHA | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP 1425 * keycharQ ::= ALPHA | DIGIT | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP 1426 * numericoid ::= number ( DOT number )+ 1427 * number ::= DIGIT | LDIGIT DIGIT+ 1428 * ALPHA ::= %x41-5A | %x61-7A ; "A"-"Z" / "a"-"z" 1429 * DIGIT ::= %x30 | LDIGIT ; "0"-"9" 1430 * LDIGIT ::= %x31-39 ; "1"-"9" 1431 * HYPHEN ::= %x2D ; hyphen ("-") 1432 * UNDERSCORE ::= %x5F ; underscore ("_") 1433 * DOT ::= %x2E ; period (".") 1434 * COLON ::= %x3A ; colon (":") 1435 * SEMI_COLON ::= %x3B ; semi-colon(";") 1436 * SHARP ::= %x23 ; octothorpe (or sharp sign) ("#") 1437 * </pre> 1438 * 1439 * @param pos The position in the Schema 1440 * @return The descr 1441 * @throws LdapSchemaException If the schema is wrong 1442 */ 1443 private static String getDescrRelaxed( PosSchema pos ) throws LdapSchemaException 1444 { 1445 int start = pos.start; 1446 boolean isFirst = true; 1447 1448 while ( !isEmpty( pos ) ) 1449 { 1450 if ( isFirst ) 1451 { 1452 isFirst = false; 1453 1454 char c = pos.line.charAt( pos.start ); 1455 1456 if ( Character.isAlphabetic( c ) || ( c == HYPHEN ) || ( c == UNDERSCORE ) 1457 || ( c == SEMI_COLON ) || ( c == DOT ) || ( c == COLON ) || ( c == SHARP ) ) 1458 { 1459 // leadkeycharQ 1460 pos.start++; 1461 } 1462 else 1463 { 1464 // Error, we are expecting a leadKeychar 1465 throw new LdapSchemaException( I18n.err( I18n.ERR_13788_LEAD_KEY_CHAR_EXPECTED, 1466 pos.lineNumber, pos.start ) ); 1467 } 1468 } 1469 else 1470 { 1471 char c = pos.line.charAt( pos.start ); 1472 1473 if ( Character.isAlphabetic( c ) || Character.isDigit( c ) || ( c == HYPHEN ) 1474 || ( c == UNDERSCORE ) || ( c == SEMI_COLON ) || ( c == DOT ) || ( c == COLON ) || ( c == SHARP ) ) 1475 { 1476 pos.start++; 1477 } 1478 else 1479 { 1480 // We are done 1481 return pos.line.substring( start, pos.start ); 1482 } 1483 } 1484 } 1485 1486 return pos.line.substring( start, pos.start ); 1487 } 1488 1489 1490 /** 1491 * 1492 * @param pos The position in the Schema 1493 * @return The found macro, if any 1494 * @throws LdapSchemaException If the schema is wrong 1495 */ 1496 private String getMacro( PosSchema pos ) throws LdapSchemaException 1497 { 1498 if ( isQuirksModeEnabled ) 1499 { 1500 int start = pos.start; 1501 boolean isFirst = true; 1502 1503 while ( !isEmpty( pos ) ) 1504 { 1505 if ( isFirst ) 1506 { 1507 isFirst = false; 1508 1509 char c = pos.line.charAt( pos.start ); 1510 1511 if ( Character.isAlphabetic( c ) || ( c == HYPHEN ) || ( c == UNDERSCORE ) 1512 || ( c == SEMI_COLON ) || ( c == DOT ) || ( c == SHARP ) ) 1513 { 1514 // leadkeycharQ 1515 pos.start++; 1516 } 1517 else 1518 { 1519 // Error, we are expecting a leadKeychar 1520 throw new LdapSchemaException( I18n.err( I18n.ERR_13788_LEAD_KEY_CHAR_EXPECTED, 1521 pos.lineNumber, pos.start ) ); 1522 } 1523 } 1524 else 1525 { 1526 char c = pos.line.charAt( pos.start ); 1527 1528 if ( Character.isAlphabetic( c ) || Character.isDigit( c ) || ( c == HYPHEN ) 1529 || ( c == UNDERSCORE ) || ( c == SEMI_COLON ) || ( c == DOT ) || ( c == SHARP ) ) 1530 { 1531 pos.start++; 1532 } 1533 else 1534 { 1535 // We are done 1536 return pos.line.substring( start, pos.start ); 1537 } 1538 } 1539 } 1540 1541 return pos.line.substring( start, pos.start ); 1542 } 1543 else 1544 { 1545 int start = pos.start; 1546 boolean isFirst = true; 1547 1548 while ( !isEmpty( pos ) ) 1549 { 1550 if ( isFirst ) 1551 { 1552 isFirst = false; 1553 1554 if ( isAlpha( pos ) ) 1555 { 1556 // leadkeychar 1557 pos.start++; 1558 } 1559 else 1560 { 1561 // Error, we are expecting a leadKeychar 1562 throw new LdapSchemaException( I18n.err( I18n.ERR_13788_LEAD_KEY_CHAR_EXPECTED, 1563 pos.lineNumber, pos.start ) ); 1564 } 1565 } 1566 else 1567 { 1568 char c = pos.line.charAt( pos.start ); 1569 1570 if ( Character.isAlphabetic( c ) || Character.isDigit( c ) || ( c == HYPHEN ) ) 1571 { 1572 pos.start++; 1573 } 1574 else 1575 { 1576 // We are done 1577 return pos.line.substring( start, pos.start ); 1578 } 1579 } 1580 } 1581 1582 return pos.line.substring( start, pos.start ); 1583 } 1584 } 1585 1586 1587 /** 1588 * <pre> 1589 * qdescr ::== SQUOTE descr SQUOTE 1590 * descr ::= keystring 1591 * keystring ::= leadkeychar *keychar 1592 * leadkeychar ::= ALPHA 1593 * keychar ::= ALPHA | DIGIT | HYPHEN 1594 * </pre> 1595 * 1596 * In quirksMode : 1597 * 1598 * <pre> 1599 * qdescr ::== SQUOTE descr SQUOTE | descr | SQUOTE numericoid SQUOTE 1600 * descr ::= keystring 1601 * keystring ::= keychar+ 1602 * keychar ::= ALPHA | DIGIT | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP 1603 * </pre> 1604 * 1605 * @param reader The stream reader 1606 * @param pos The position in the Schema 1607 * @return The QDescr 1608 * @throws LdapSchemaException If the schema is wrong 1609 * @throws IOException If the stream can't be read 1610 */ 1611 private static String getQDescrStrict( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException 1612 { 1613 // The first quote 1614 if ( !startsWith( reader, pos, SQUOTE ) ) 1615 { 1616 throw new LdapSchemaException( I18n.err( I18n.ERR_13789_SIMPLE_QUOTE_EXPECTED_AT_START, 1617 pos.lineNumber, pos.start ) ); 1618 } 1619 1620 pos.start++; 1621 int start = pos.start; 1622 boolean isFirst = true; 1623 1624 while ( !startsWith( pos, SQUOTE ) ) 1625 { 1626 if ( isFirst ) 1627 { 1628 isFirst = false; 1629 1630 if ( !isEmpty( pos ) && isAlpha( pos ) ) 1631 { 1632 // leadkeychar 1633 pos.start++; 1634 } 1635 else 1636 { 1637 // Error, we are expecting a leadKeychar 1638 throw new LdapSchemaException( I18n.err( I18n.ERR_13788_LEAD_KEY_CHAR_EXPECTED, 1639 pos.lineNumber, pos.start ) ); 1640 } 1641 } 1642 else 1643 { 1644 if ( isEmpty( pos ) ) 1645 { 1646 // This is an error 1647 throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END, 1648 pos.lineNumber, pos.start ) ); 1649 } 1650 1651 char c = pos.line.charAt( pos.start ); 1652 1653 if ( Character.isAlphabetic( c ) || Character.isDigit( c ) || ( c == HYPHEN ) ) 1654 { 1655 pos.start++; 1656 } 1657 else 1658 { 1659 // This is an error 1660 throw new LdapSchemaException( I18n.err( I18n.ERR_13791_KEYCHAR_EXPECTED, c, 1661 pos.lineNumber, pos.start ) ); 1662 } 1663 } 1664 } 1665 1666 if ( startsWith( pos, SQUOTE ) ) 1667 { 1668 // We are done, move one char forward to eliminate the simple quote 1669 pos.start++; 1670 1671 return pos.line.substring( start, pos.start - 1 ); 1672 } 1673 else 1674 { 1675 // No closing simple quote, this is an error 1676 throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END, 1677 pos.lineNumber, pos.start ) ); 1678 } 1679 } 1680 1681 1682 /** 1683 * <pre> 1684 * qdescr ::== SQUOTE descr SQUOTE 1685 * descr ::= keystring 1686 * keystring ::= leadkeychar *keychar 1687 * leadkeychar ::= ALPHA 1688 * keychar ::= ALPHA | DIGIT | HYPHEN 1689 * </pre> 1690 * 1691 * In quirksMode : 1692 * 1693 * <pre> 1694 * qdescr ::== SQUOTE descr SQUOTE | descr | SQUOTE numericoid SQUOTE 1695 * descr ::= keystring 1696 * keystring ::= keychar+ 1697 * keychar ::= ALPHA | DIGIT | HYPHEN | UNDERSCORE | SEMI_COLON | DOT | COLON | SHARP 1698 * </pre> 1699 * 1700 * @param reader The stream reader 1701 * @param pos The position in the Schema 1702 * @return the QDescr 1703 * @throws IOException If the stream can't be read 1704 * @throws LdapSchemaException If the schema is wrong 1705 */ 1706 private static String getQDescrRelaxed( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException 1707 { 1708 if ( startsWith( reader, pos, SQUOTE ) ) 1709 { 1710 pos.start++; 1711 int start = pos.start; 1712 1713 while ( !startsWith( pos, SQUOTE ) ) 1714 { 1715 if ( isEmpty( pos ) ) 1716 { 1717 throw new LdapSchemaException( I18n.err( I18n.ERR_13789_SIMPLE_QUOTE_EXPECTED_AT_START, 1718 pos.lineNumber, pos.start ) ); 1719 } 1720 1721 char c = pos.line.charAt( pos.start ); 1722 1723 if ( Character.isDigit( c ) || Character.isAlphabetic( c ) || ( c == HYPHEN ) || ( c == UNDERSCORE ) 1724 || ( c == SEMI_COLON ) || ( c == DOT ) || ( c == COLON ) || ( c == SHARP ) ) 1725 { 1726 pos.start++; 1727 } 1728 else if ( c != SQUOTE ) 1729 { 1730 throw new LdapSchemaException( I18n.err( I18n.ERR_13790_NOT_A_KEYSTRING, pos.lineNumber, pos.start ) ); 1731 } 1732 } 1733 1734 pos.start++; 1735 1736 return pos.line.substring( start, pos.start - 1 ); 1737 } 1738 else 1739 { 1740 int start = pos.start; 1741 1742 while ( !isEmpty( pos ) ) 1743 { 1744 char c = pos.line.charAt( pos.start ); 1745 1746 if ( Character.isDigit( c ) || Character.isAlphabetic( c ) || ( c == HYPHEN ) || ( c == UNDERSCORE ) 1747 || ( c == SEMI_COLON ) || ( c == DOT ) || ( c == COLON ) || ( c == SHARP ) ) 1748 { 1749 pos.start++; 1750 } 1751 else 1752 { 1753 break; 1754 } 1755 } 1756 1757 return pos.line.substring( start, pos.start ); 1758 } 1759 } 1760 1761 1762 /** 1763 * No relaxed version. 1764 * <pre> 1765 * qdstring ::== SQUOTE dstring SQUOTE 1766 * dstring ::= ( QS | QQ | QUTF8 )+ ; escaped UTF-8 string 1767 * QS ::= ESC %x35 ( %x43 | %x63 ) ; "\5C" | "\5c", escape char 1768 * QQ ::= ESC %x32 %x37 ; "\27", simple quote char 1769 * QUTF8 ::= QUTF1 | UTFMB 1770 * QUTF1 ::= %x00-26 | %x28-5B | %x5D-7F ; All ascii but ' and \ 1771 * UTFMB ::= UTF2 | UTF3 | UTF4 1772 * UTF0 ::= %x80-BF 1773 * UTF2 ::= %xC2-DF UTF0 1774 * UTF3 ::= %xE0 %xA0-BF UTF0 | %xE1-EC UTF0 UTF0 | %xED %x80-9F UTF0 | %xEE-EF UTF0 UTF0 1775 * UTF4 ::= %xF0 %x90-BF UTF0 UTF0 | %xF1-F3 UTF0 UTF0 UTF0 | %xF4 %x80-8F UTF0 UTF0 1776 * ESC ::= %x5C ; backslash ("\") 1777 * </pre> 1778 * 1779 * @param reader The stream reader 1780 * @param pos The position in the Schema 1781 * @return The QDString 1782 * @throws LdapSchemaException If the schema is wrong 1783 * @throws IOException If the stream can't be read 1784 */ 1785 private static String getQDString( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException 1786 { 1787 // The first quote 1788 if ( !startsWith( reader, pos, SQUOTE ) ) 1789 { 1790 throw new LdapSchemaException( I18n.err( I18n.ERR_13789_SIMPLE_QUOTE_EXPECTED_AT_START, 1791 pos.lineNumber, pos.start ) ); 1792 } 1793 1794 pos.start++; 1795 int start = pos.start; 1796 int nbEscapes = 0; 1797 1798 while ( !isEmpty( pos ) && !startsWith( pos, SQUOTE ) ) 1799 { 1800 // At the moment, just swallow anything 1801 if ( startsWith( pos, ESCAPE ) ) 1802 { 1803 nbEscapes++; 1804 } 1805 1806 pos.start++; 1807 1808 } 1809 1810 if ( startsWith( pos, SQUOTE ) ) 1811 { 1812 // We are done, move one char forward to eliminate the simple quote 1813 pos.start++; 1814 1815 // Now, un-escape the escaped chars 1816 char[] unescaped = new char[pos.start - 1 - start - nbEscapes * 2]; 1817 int newPos = 0; 1818 1819 for ( int i = start; i < pos.start - 1; i++ ) 1820 { 1821 char c = pos.line.charAt( i ); 1822 1823 if ( c == ESCAPE ) 1824 { 1825 if ( i + 2 > pos.start ) 1826 { 1827 // Error : not enough hex value 1828 throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END, 1829 pos.lineNumber, pos.start ) ); 1830 } 1831 1832 int u = Character.digit( pos.line.charAt( i + 1 ), 16 ); 1833 int l = Character.digit( pos.line.charAt( i + 2 ), 16 ); 1834 1835 unescaped[newPos] = ( char ) ( ( u << 4 ) + l ); 1836 i += 2; 1837 } 1838 else 1839 { 1840 unescaped[newPos] = c; 1841 } 1842 1843 newPos++; 1844 } 1845 1846 return new String( unescaped ); 1847 } 1848 else 1849 { 1850 // No closing simple quote, this is an error 1851 throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END, 1852 pos.lineNumber, pos.start ) ); 1853 } 1854 } 1855 1856 1857 /** 1858 * <pre> 1859 * qdescrs ::= qdescr | LPAREN WSP qdescrlist WSP RPAREN 1860 * qdescrlist ::= [ qdescr *( SP qdescr ) ] 1861 * qdescr ::== SQUOTE descr SQUOTE 1862 * descr ::= keystring 1863 * keystring ::= leadkeychar *keychar 1864 * leadkeychar ::= ALPHA 1865 * keychar ::= ALPHA / DIGIT / HYPHEN 1866 * </pre> 1867 * 1868 * @param reader The stream reader 1869 * @param pos The position in the Schema 1870 * @param relaxed If the schema is to be processed in relaxed mode 1871 * @return The list of QDescr 1872 * @throws LdapSchemaException If the schema is wrong 1873 * @throws IOException If the stream can't be read 1874 */ 1875 private static List<String> getQDescrs( Reader reader, PosSchema pos, boolean relaxed ) 1876 throws LdapSchemaException, IOException 1877 { 1878 List<String> qdescrs = new ArrayList<>(); 1879 1880 // It may start with a '(' 1881 if ( startsWith( reader, pos, LPAREN ) ) 1882 { 1883 pos.start++; 1884 1885 // We have more than a name 1886 skipWhites( reader, pos, false ); 1887 1888 while ( !startsWith( reader, pos, RPAREN ) ) 1889 { 1890 String qdescr; 1891 1892 if ( relaxed ) 1893 { 1894 qdescr = getQDescrRelaxed( reader, pos ); 1895 } 1896 else 1897 { 1898 qdescr = getQDescrStrict( reader, pos ); 1899 } 1900 1901 qdescrs.add( qdescr ); 1902 1903 if ( startsWith( reader, pos, RPAREN ) ) 1904 { 1905 break; 1906 } 1907 1908 skipWhites( reader, pos, true ); 1909 } 1910 1911 if ( !startsWith( reader, pos, RPAREN ) ) 1912 { 1913 throw new LdapSchemaException( I18n.err( I18n.ERR_13793_NO_CLOSING_PAREN, 1914 pos.lineNumber, pos.start ) ); 1915 } 1916 1917 pos.start++; 1918 } 1919 else 1920 { 1921 // Only one name, read it 1922 String qDescr; 1923 1924 if ( relaxed ) 1925 { 1926 qDescr = getQDescrRelaxed( reader, pos ); 1927 } 1928 else 1929 { 1930 qDescr = getQDescrStrict( reader, pos ); 1931 } 1932 1933 if ( Strings.isEmpty( qDescr ) ) 1934 { 1935 throw new LdapSchemaException( I18n.err( I18n.ERR_13732_NAME_CANNOT_BE_NULL, pos.lineNumber, pos.start ) ); 1936 } 1937 1938 qdescrs.add( qDescr ); 1939 } 1940 1941 return qdescrs; 1942 } 1943 1944 1945 /** 1946 * <pre> 1947 * qdstrings ::= qdstring | ( LPAREN WSP qdstringlist WSP RPAREN ) 1948 * qdstringlist ::= qdstring *( SP qdstring )* 1949 * qdstring ::= SQUOTE dstring SQUOTE 1950 * dstring ::= 1*( QS / QQ / QUTF8 ) ; escaped UTF-8 string 1951 * </pre> 1952 * 1953 * @param reader The stream reader 1954 * @param pos The position in the Schema 1955 * @return The list of QDString 1956 * @throws LdapSchemaException If the schema is wrong 1957 * @throws IOException If the stream can't be read 1958 */ 1959 private static List<String> getQDStrings( Reader reader, PosSchema pos ) 1960 throws LdapSchemaException, IOException 1961 { 1962 List<String> qdStrings = new ArrayList<>(); 1963 1964 // It may start with a '(' 1965 if ( startsWith( reader, pos, LPAREN ) ) 1966 { 1967 pos.start++; 1968 1969 // We have more than a name 1970 skipWhites( reader, pos, false ); 1971 1972 while ( !startsWith( reader, pos, RPAREN ) ) 1973 { 1974 qdStrings.add( getQDString( reader, pos ) ); 1975 1976 if ( startsWith( reader, pos, RPAREN ) ) 1977 { 1978 break; 1979 } 1980 1981 skipWhites( reader, pos, true ); 1982 } 1983 1984 if ( !startsWith( reader, pos, RPAREN ) ) 1985 { 1986 throw new LdapSchemaException( I18n.err( I18n.ERR_13793_NO_CLOSING_PAREN, 1987 pos.lineNumber, pos.start ) ); 1988 } 1989 1990 pos.start++; 1991 } 1992 else 1993 { 1994 // Only one name, read it 1995 qdStrings.add( getQDString( reader, pos ) ); 1996 } 1997 1998 return qdStrings; 1999 } 2000 2001 2002 /** 2003 * <pre> 2004 * oids ::= oid | ( LPAREN WSP oidlist WSP RPAREN ) 2005 * oidlist ::= oid *( WSP DOLLAR WSP oid ) 2006 * </pre> 2007 * 2008 * @param reader The stream reader 2009 * @param pos The position in the Schema 2010 * @return The list of OIDs 2011 * @throws LdapSchemaException If the schema is wrong 2012 * @throws IOException If the stream can't be read 2013 */ 2014 private static List<String> getOidsStrict( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException 2015 { 2016 List<String> oids = new ArrayList<>(); 2017 2018 // It may start with a '(' 2019 if ( startsWith( reader, pos, LPAREN ) ) 2020 { 2021 pos.start++; 2022 2023 // We have more than a name 2024 skipWhites( reader, pos, false ); 2025 boolean moreExpected = false; 2026 2027 while ( !startsWith( reader, pos, RPAREN ) ) 2028 { 2029 moreExpected = false; 2030 2031 oids.add( getOidStrict( pos ) ); 2032 2033 if ( startsWith( reader, pos, RPAREN ) ) 2034 { 2035 break; 2036 } 2037 2038 skipWhites( reader, pos, false ); 2039 2040 if ( startsWith( reader, pos, DOLLAR ) ) 2041 { 2042 pos.start++; 2043 moreExpected = true; 2044 } 2045 2046 skipWhites( reader, pos, false ); 2047 } 2048 2049 if ( !startsWith( reader, pos, RPAREN ) ) 2050 { 2051 throw new LdapSchemaException( I18n.err( I18n.ERR_13793_NO_CLOSING_PAREN, 2052 pos.lineNumber, pos.start ) ); 2053 } 2054 2055 if ( moreExpected ) 2056 { 2057 throw new LdapSchemaException( I18n.err( I18n.ERR_13794_MORE_OIDS_EXPECTED, 2058 pos.lineNumber, pos.start ) ); 2059 } 2060 2061 pos.start++; 2062 } 2063 else 2064 { 2065 // Only one name, read it 2066 oids.add( getOidStrict( pos ) ); 2067 } 2068 2069 return oids; 2070 } 2071 2072 2073 /** 2074 * <pre> 2075 * oids ::= oid | ( LPAREN WSP oidlist WSP RPAREN ) 2076 * oidlist ::= oid *( WSP DOLLAR WSP oid ) 2077 * </pre> 2078 * 2079 * @param reader The stream reader 2080 * @param pos The position in the Schema 2081 * @return The list of OIDs 2082 * @throws LdapSchemaException If the schema is wrong 2083 * @throws IOException If the stream can't be read 2084 */ 2085 private static List<String> getOidsRelaxed( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException 2086 { 2087 List<String> oids = new ArrayList<>(); 2088 2089 // It may start with a '(' 2090 if ( startsWith( reader, pos, LPAREN ) ) 2091 { 2092 pos.start++; 2093 2094 // We have more than a name 2095 skipWhites( reader, pos, false ); 2096 boolean moreExpected = false; 2097 2098 while ( !startsWith( reader, pos, RPAREN ) ) 2099 { 2100 moreExpected = false; 2101 2102 oids.add( getOidRelaxed( pos, UN_QUOTED ) ); 2103 2104 if ( startsWith( reader, pos, RPAREN ) ) 2105 { 2106 break; 2107 } 2108 2109 skipWhites( reader, pos, false ); 2110 2111 if ( startsWith( reader, pos, DOLLAR ) ) 2112 { 2113 pos.start++; 2114 moreExpected = true; 2115 } 2116 2117 skipWhites( reader, pos, false ); 2118 } 2119 2120 if ( !startsWith( reader, pos, RPAREN ) ) 2121 { 2122 throw new LdapSchemaException( I18n.err( I18n.ERR_13793_NO_CLOSING_PAREN, 2123 pos.lineNumber, pos.start ) ); 2124 } 2125 2126 if ( moreExpected ) 2127 { 2128 throw new LdapSchemaException( I18n.err( I18n.ERR_13794_MORE_OIDS_EXPECTED, 2129 pos.lineNumber, pos.start ) ); 2130 } 2131 2132 pos.start++; 2133 } 2134 else 2135 { 2136 // Only one name, read it 2137 oids.add( getOidRelaxed( pos, UN_QUOTED ) ); 2138 } 2139 2140 return oids; 2141 } 2142 2143 2144 /** 2145 * <pre> 2146 * noidlen = oidStrict [ LCURLY len RCURLY ] 2147 * </pre> 2148 * 2149 * @param attributeType The AttributeType 2150 * @param pos The position in the Schema 2151 * @throws LdapSchemaException If the schema is wrong 2152 */ 2153 private static void getNoidLenStrict( AttributeType attributeType, PosSchema pos ) throws LdapSchemaException 2154 { 2155 // Get the oid 2156 String oid = getOidStrict( pos ); 2157 2158 if ( oid.length() == 0 ) 2159 { 2160 throw new LdapSchemaException( I18n.err( I18n.ERR_13828_MISSING_SYNTAX_OID, pos.line, pos.start ) ); 2161 } 2162 2163 attributeType.setSyntaxOid( oid ); 2164 2165 // Then the len, if any 2166 if ( startsWith( pos, LBRACE ) ) 2167 { 2168 pos.start++; 2169 int start = pos.start; 2170 2171 while ( !isEmpty( pos ) && isDigit( pos ) ) 2172 { 2173 pos.start++; 2174 } 2175 2176 if ( startsWith( pos, RBRACE ) ) 2177 { 2178 String lenStr = pos.line.substring( start, pos.start ); 2179 2180 if ( lenStr.length() == 0 ) 2181 { 2182 throw new LdapSchemaException( I18n.err( I18n.ERR_13827_EMPTY_SYNTAX_LEN, pos.line, pos.start ) ); 2183 } 2184 2185 pos.start++; 2186 2187 if ( Strings.isEmpty( lenStr ) ) 2188 { 2189 attributeType.setSyntaxLength( -1L ); 2190 } 2191 else 2192 { 2193 attributeType.setSyntaxLength( Long.parseLong( lenStr ) ); 2194 } 2195 } 2196 else 2197 { 2198 // The opening curly hasn't been closed 2199 throw new LdapSchemaException( I18n.err( I18n.ERR_13795_OPENED_BRACKET_NOT_CLOSED, 2200 pos.lineNumber, pos.start ) ); 2201 } 2202 } 2203 } 2204 2205 2206 /** 2207 * <pre> 2208 * noidlen = oidRelaxed [ LCURLY len RCURLY ] 2209 * </pre> 2210 * 2211 * @param attributeType The AttributeType 2212 * @param pos The position in the Schema 2213 * @throws LdapSchemaException If the schema is wrong 2214 */ 2215 private static void getNoidLenRelaxed( AttributeType attributeType, PosSchema pos ) throws LdapSchemaException 2216 { 2217 // Check for quotes 2218 boolean hasQuote = false; 2219 2220 char c = pos.line.charAt( pos.start ); 2221 2222 if ( c == SQUOTE ) 2223 { 2224 hasQuote = true; 2225 pos.start++; 2226 2227 if ( isEmpty( pos ) ) 2228 { 2229 throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END, 2230 pos.lineNumber, pos.start ) ); 2231 } 2232 } 2233 2234 // Get the oid 2235 String oid = getOidRelaxed( pos, hasQuote ); 2236 2237 if ( oid.length() == 0 ) 2238 { 2239 throw new LdapSchemaException( I18n.err( I18n.ERR_13828_MISSING_SYNTAX_OID, pos.line, pos.start ) ); 2240 } 2241 2242 attributeType.setSyntaxOid( oid ); 2243 2244 // Then the len, if any 2245 if ( startsWith( pos, LBRACE ) ) 2246 { 2247 pos.start++; 2248 int start = pos.start; 2249 2250 while ( !isEmpty( pos ) && isDigit( pos ) ) 2251 { 2252 pos.start++; 2253 } 2254 2255 if ( startsWith( pos, RBRACE ) ) 2256 { 2257 String lenStr = pos.line.substring( start, pos.start ); 2258 2259 pos.start++; 2260 2261 if ( Strings.isEmpty( lenStr ) ) 2262 { 2263 attributeType.setSyntaxLength( -1L ); 2264 } 2265 else 2266 { 2267 attributeType.setSyntaxLength( Long.parseLong( lenStr ) ); 2268 } 2269 } 2270 else 2271 { 2272 // The opening curly hasn't been closed 2273 throw new LdapSchemaException( I18n.err( I18n.ERR_13795_OPENED_BRACKET_NOT_CLOSED, 2274 pos.lineNumber, pos.start ) ); 2275 } 2276 } 2277 2278 if ( hasQuote ) 2279 { 2280 if ( isEmpty( pos ) ) 2281 { 2282 throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END, 2283 pos.lineNumber, pos.start ) ); 2284 } 2285 2286 c = pos.line.charAt( pos.start ); 2287 2288 if ( c == SQUOTE ) 2289 { 2290 pos.start++; 2291 } 2292 else 2293 { 2294 throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END, 2295 pos.lineNumber, pos.start ) ); 2296 } 2297 } 2298 } 2299 2300 2301 2302 /** 2303 * <pre> 2304 * ruleid ::= number 2305 * number ::= DIGIT | LDIGIT DIGIT+ 2306 * DIGIT ::= [0-9] 2307 * LDIGIT ::= [1-9] 2308 * </pre> 2309 * 2310 * @param pos The position in the Schema 2311 * @return The RuleID 2312 * @throws LdapSchemaException If the schema is wrong 2313 */ 2314 private static int getRuleId( PosSchema pos ) throws LdapSchemaException 2315 { 2316 int start = pos.start; 2317 2318 while ( !isEmpty( pos ) && isDigit( pos ) ) 2319 { 2320 pos.start++; 2321 } 2322 2323 if ( start == pos.start ) 2324 { 2325 // No ruleID 2326 throw new LdapSchemaException( I18n.err( I18n.ERR_13811_INVALID_RULE_ID, 2327 pos.lineNumber, pos.start ) ); 2328 } 2329 2330 String lenStr = pos.line.substring( start, pos.start ); 2331 2332 return Integer.parseInt( lenStr ); 2333 } 2334 2335 2336 /** 2337 * <pre> 2338 * ruleids ::= ruleid | ( LPAREN WSP ruleidlist WSP RPAREN ) 2339 * ruleidlist ::= ruleid ( SP ruleid )* 2340 * </pre> 2341 * 2342 * @param reader The stream reader 2343 * @param pos The position in the Schema 2344 * @return The list of RuleIDs 2345 * @throws LdapSchemaException If the schema is wrong 2346 * @throws IOException If the stream can't be read 2347 */ 2348 private static List<Integer> getRuleIds( Reader reader, PosSchema pos ) throws LdapSchemaException, IOException 2349 { 2350 List<Integer> ruleIds = new ArrayList<>(); 2351 2352 // It may start with a '(' 2353 if ( startsWith( reader, pos, LPAREN ) ) 2354 { 2355 pos.start++; 2356 2357 // We may have more than a ruleid 2358 skipWhites( reader, pos, false ); 2359 boolean moreExpected = false; 2360 2361 while ( !startsWith( reader, pos, RPAREN ) ) 2362 { 2363 moreExpected = false; 2364 2365 ruleIds.add( getRuleId( pos ) ); 2366 2367 if ( startsWith( reader, pos, RPAREN ) ) 2368 { 2369 break; 2370 } 2371 2372 skipWhites( reader, pos, false ); 2373 2374 if ( startsWith( reader, pos, DOLLAR ) ) 2375 { 2376 pos.start++; 2377 moreExpected = true; 2378 } 2379 2380 skipWhites( reader, pos, false ); 2381 } 2382 2383 if ( !startsWith( reader, pos, RPAREN ) ) 2384 { 2385 throw new LdapSchemaException( I18n.err( I18n.ERR_13793_NO_CLOSING_PAREN, 2386 pos.lineNumber, pos.start ) ); 2387 } 2388 2389 if ( moreExpected ) 2390 { 2391 throw new LdapSchemaException( I18n.err( I18n.ERR_13813_MORE_RULE_IDS_EXPECTED, 2392 pos.lineNumber, pos.start ) ); 2393 } 2394 2395 pos.start++; 2396 } 2397 else 2398 { 2399 // Only one ruleId, read it 2400 ruleIds.add( getRuleId( pos ) ); 2401 } 2402 2403 return ruleIds; 2404 } 2405 2406 2407 /** 2408 * 2409 * @param pos The position in the Schema 2410 * @return The USAGE 2411 * @throws LdapSchemaException If the schema is wrong 2412 */ 2413 private static UsageEnum getUsageStrict( PosSchema pos ) throws LdapSchemaException 2414 { 2415 if ( isEmpty( pos ) ) 2416 { 2417 throw new LdapSchemaException( I18n.err( I18n.ERR_13796_USAGE_EXPECTED, 2418 pos.lineNumber, pos.start ) ); 2419 } 2420 2421 if ( startsWith( pos, USER_APPLICATIONS_STR ) ) 2422 { 2423 pos.start += USER_APPLICATIONS_STR.length(); 2424 2425 return UsageEnum.USER_APPLICATIONS; 2426 } 2427 else if ( startsWith( pos, DIRECTORY_OPERATION_STR ) ) 2428 { 2429 pos.start += DIRECTORY_OPERATION_STR.length(); 2430 2431 return UsageEnum.DIRECTORY_OPERATION; 2432 } 2433 else if ( startsWith( pos, DISTRIBUTED_OPERATION_STR ) ) 2434 { 2435 pos.start += DISTRIBUTED_OPERATION_STR.length(); 2436 2437 return UsageEnum.DISTRIBUTED_OPERATION; 2438 } 2439 else if ( startsWith( pos, DSA_OPERATION_STR ) ) 2440 { 2441 pos.start += DSA_OPERATION_STR.length(); 2442 2443 return UsageEnum.DSA_OPERATION; 2444 } 2445 else 2446 { 2447 throw new LdapSchemaException( I18n.err( I18n.ERR_13797_USAGE_UNKNOWN, 2448 pos.lineNumber, pos.start ) ); 2449 } 2450 } 2451 2452 2453 /** 2454 * 2455 * @param pos The position in the Schema 2456 * @return The USAGE 2457 * @throws LdapSchemaException If the schema is wrong 2458 */ 2459 private static UsageEnum getUsageRelaxed( PosSchema pos ) throws LdapSchemaException 2460 { 2461 if ( isEmpty( pos ) ) 2462 { 2463 throw new LdapSchemaException( I18n.err( I18n.ERR_13796_USAGE_EXPECTED, 2464 pos.lineNumber, pos.start ) ); 2465 } 2466 2467 boolean isSQuoted = false; 2468 boolean isDQuoted = false; 2469 2470 if ( pos.line.charAt( pos.start ) == SQUOTE ) 2471 { 2472 isSQuoted = true; 2473 pos.start++; 2474 2475 if ( isEmpty( pos ) ) 2476 { 2477 throw new LdapSchemaException( I18n.err( I18n.ERR_13796_USAGE_EXPECTED, 2478 pos.lineNumber, pos.start ) ); 2479 } 2480 } 2481 else if ( pos.line.charAt( pos.start ) == DQUOTE ) 2482 { 2483 isDQuoted = true; 2484 pos.start++; 2485 2486 if ( isEmpty( pos ) ) 2487 { 2488 throw new LdapSchemaException( I18n.err( I18n.ERR_13796_USAGE_EXPECTED, 2489 pos.lineNumber, pos.start ) ); 2490 } 2491 } 2492 2493 UsageEnum usage = UsageEnum.USER_APPLICATIONS; 2494 2495 if ( startsWith( pos, USER_APPLICATIONS_STR ) ) 2496 { 2497 pos.start += USER_APPLICATIONS_STR.length(); 2498 2499 usage = UsageEnum.USER_APPLICATIONS; 2500 } 2501 else if ( startsWith( pos, DIRECTORY_OPERATION_STR ) ) 2502 { 2503 pos.start += DIRECTORY_OPERATION_STR.length(); 2504 2505 usage = UsageEnum.DIRECTORY_OPERATION; 2506 } 2507 else if ( startsWith( pos, DISTRIBUTED_OPERATION_STR ) ) 2508 { 2509 pos.start += DISTRIBUTED_OPERATION_STR.length(); 2510 2511 usage = UsageEnum.DISTRIBUTED_OPERATION; 2512 } 2513 else if ( startsWith( pos, DSA_OPERATION_STR ) ) 2514 { 2515 pos.start += DSA_OPERATION_STR.length(); 2516 2517 usage = UsageEnum.DSA_OPERATION; 2518 } 2519 else 2520 { 2521 throw new LdapSchemaException( I18n.err( I18n.ERR_13797_USAGE_UNKNOWN, 2522 pos.lineNumber, pos.start ) ); 2523 } 2524 2525 if ( isSQuoted ) 2526 { 2527 if ( isEmpty( pos ) ) 2528 { 2529 throw new LdapSchemaException( I18n.err( I18n.ERR_13796_USAGE_EXPECTED, 2530 pos.lineNumber, pos.start ) ); 2531 } 2532 2533 if ( pos.line.charAt( pos.start ) != SQUOTE ) 2534 { 2535 throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END, 2536 pos.lineNumber, pos.start ) ); 2537 } 2538 2539 pos.start++; 2540 } 2541 else if ( isDQuoted ) 2542 { 2543 if ( isEmpty( pos ) ) 2544 { 2545 throw new LdapSchemaException( I18n.err( I18n.ERR_13796_USAGE_EXPECTED, 2546 pos.lineNumber, pos.start ) ); 2547 } 2548 2549 if ( pos.line.charAt( pos.start ) != DQUOTE ) 2550 { 2551 throw new LdapSchemaException( I18n.err( I18n.ERR_13792_SIMPLE_QUOTE_EXPECTED_AT_END, 2552 pos.lineNumber, pos.start ) ); 2553 } 2554 2555 pos.start++; 2556 } 2557 2558 return usage; 2559 } 2560 2561 2562 /** 2563 * <pre> 2564 * extension ::= xstring SP qdstrings 2565 * xstring ::= "X" HYPHEN ( ALPHA | HYPHEN | USCORE )+ 2566 * qdstrings ::= qdstring | ( LPAREN WSP qdstringlist WSP RPAREN ) 2567 * qdstringlist ::= qdstring *( SP qdstring )* 2568 * qdstring ::= SQUOTE dstring SQUOTE 2569 * dstring ::= 1*( QS / QQ / QUTF8 ) ; escaped UTF-8 string 2570 * </pre> 2571 * 2572 * @param reader The stream reader 2573 * @param pos The position in the Schema 2574 * @param schemaObject The SchemaObject 2575 * @throws IOException If the stream can't be read 2576 * @throws LdapSchemaException If the schema is wrong 2577 */ 2578 private static void processExtension( Reader reader, PosSchema pos, SchemaObject schemaObject ) 2579 throws LdapSchemaException, IOException 2580 { 2581 // The xstring first 2582 String extensionKey = getXString( pos ); 2583 2584 skipWhites( reader, pos, true ); 2585 2586 List<String> extensionValues = getQDStrings( reader, pos ); 2587 2588 if ( schemaObject.hasExtension( extensionKey ) ) 2589 { 2590 throw new LdapSchemaException( 2591 I18n.err( I18n.ERR_13780_SCHEMA_OBJECT_DESCRIPTION_HAS_ELEMENT_TWICE, extensionKey, 2592 pos.lineNumber, pos.start ) ); 2593 } 2594 2595 schemaObject.addExtension( extensionKey, extensionValues ); 2596 } 2597 2598 2599 /** 2600 * <pre> 2601 * xstring ::= "X" HYPHEN ( ALPHA | HYPHEN | USCORE )+ 2602 * </pre> 2603 * 2604 * @param pos The position in the Schema 2605 * @return the X-String 2606 * @throws LdapSchemaException If the schema is wrong 2607 */ 2608 private static String getXString( PosSchema pos ) throws LdapSchemaException 2609 { 2610 int start = pos.start; 2611 2612 if ( startsWith( pos, EXTENSION_PREFIX ) ) 2613 { 2614 pos.start += 2; 2615 2616 // Now parse the remaining string 2617 while ( !isEmpty( pos ) && ( isAlpha( pos ) || startsWith( pos, HYPHEN ) || startsWith( pos, UNDERSCORE ) ) ) 2618 { 2619 pos.start++; 2620 } 2621 2622 return pos.line.substring( start, pos.start ); 2623 } 2624 else 2625 { 2626 throw new LdapSchemaException( I18n.err( I18n.ERR_13802_EXTENSION_SHOULD_START_WITH_X, 2627 pos.lineNumber, pos.start ) ); 2628 } 2629 } 2630 2631 2632 /** 2633 * A FQCN 2634 * <pre> 2635 * FQCN ::= FQCN_IDENTIFIER ( '.' FQCN_IDENTIFIER )* 2636 * FQCN_IDENTIFIER ::= ( JavaLetter ( JavaLetterOrDigit )* 2637 * </pre> 2638 * 2639 * @param pos The position in the Schema 2640 * @return The FQCN 2641 * @throws LdapSchemaException If the schema is wrong 2642 */ 2643 private static String getFqcn( PosSchema pos ) throws LdapSchemaException 2644 { 2645 if ( ( pos.line == null ) || ( pos.line.length() - pos.start < 1 ) ) 2646 { 2647 return ""; 2648 } 2649 2650 int start = pos.start; 2651 boolean isFirst = true; 2652 boolean dotSeen = false; 2653 2654 while ( true ) 2655 { 2656 char c = pos.line.charAt( pos.start ); 2657 2658 if ( isFirst ) 2659 { 2660 if ( !Character.isJavaIdentifierStart( c ) ) 2661 { 2662 throw new LdapSchemaException( I18n.err( I18n.ERR_13822_INVALID_FQCN_BAD_IDENTIFIER_START, 2663 pos.lineNumber, pos.start ) ); 2664 } 2665 2666 isFirst = false; 2667 dotSeen = false; 2668 pos.start++; 2669 } 2670 else 2671 { 2672 if ( c == DOT ) 2673 { 2674 if ( dotSeen ) 2675 { 2676 throw new LdapSchemaException( I18n.err( I18n.ERR_13823_INVALID_FQCN_DOUBLE_DOT, 2677 pos.lineNumber, pos.start ) ); 2678 } 2679 else 2680 { 2681 isFirst = true; 2682 dotSeen = true; 2683 pos.start++; 2684 } 2685 } 2686 else 2687 { 2688 if ( Character.isJavaIdentifierPart( c ) ) 2689 { 2690 pos.start++; 2691 dotSeen = false; 2692 } 2693 else 2694 { 2695 return pos.line.substring( start, pos.start ); 2696 } 2697 } 2698 } 2699 2700 if ( pos.line.length() - pos.start < 1 ) 2701 { 2702 return pos.line.substring( start, pos.start ); 2703 } 2704 } 2705 } 2706 2707 2708 /** 2709 * A base64 string 2710 * <pre> 2711 * byteCode ::= ( [a-z] | [A-Z] | [0-9] | '+' | '/' | '=' )* 2712 * </pre> 2713 * 2714 * @param pos The position in the Schema 2715 * @return The ByteCode 2716 */ 2717 private static String getByteCode( PosSchema pos ) 2718 { 2719 if ( ( pos.line == null ) || ( pos.line.length() - pos.start < 1 ) ) 2720 { 2721 return ""; 2722 } 2723 2724 int start = pos.start; 2725 2726 2727 while ( !isEmpty( pos ) && ( isAlpha( pos ) || isDigit( pos ) || startsWith( pos, PLUS ) 2728 || startsWith( pos, SLASH ) || startsWith( pos, EQUAL ) ) ) 2729 { 2730 pos.start++; 2731 2732 if ( ( pos.line == null ) || ( pos.line.length() - pos.start < 1 ) ) 2733 { 2734 return pos.line.substring( start, pos.start ); 2735 } 2736 } 2737 2738 return pos.line.substring( start, pos.start ); 2739 } 2740 2741 2742 /** 2743 * 2744 * @param elementsSeen The elements that have been processed already 2745 * @param element The current element 2746 * @param pos T he position in the Schema 2747 * @return The elements we have just processed 2748 * @throws LdapSchemaException If the schema is wrong 2749 */ 2750 private static int checkElement( int elementsSeen, SchemaObjectElements element, PosSchema pos ) throws LdapSchemaException 2751 { 2752 if ( ( elementsSeen & element.getValue() ) != 0 ) 2753 { 2754 throw new LdapSchemaException( I18n.err( I18n.ERR_13780_SCHEMA_OBJECT_DESCRIPTION_HAS_ELEMENT_TWICE, 2755 element, pos.lineNumber, pos.start ) ); 2756 } 2757 2758 elementsSeen |= element.getValue(); 2759 2760 return elementsSeen; 2761 } 2762 2763 2764 /** 2765 * Production for matching attribute type descriptions. It is fault-tolerant 2766 * against element ordering. 2767 * 2768 * <pre> 2769 * AttributeTypeDescription = LPAREN WSP 2770 * numericoid ; object identifier 2771 * [ SP "NAME" SP qdescrs ] ; short names (descriptors) 2772 * [ SP "DESC" SP qdstring ] ; description 2773 * [ SP "OBSOLETE" ] ; not active 2774 * [ SP "SUP" SP oid ] ; supertype 2775 * [ SP "EQUALITY" SP oid ] ; equality matching rule 2776 * [ SP "ORDERING" SP oid ] ; ordering matching rule 2777 * [ SP "SUBSTR" SP oid ] ; substrings matching rule 2778 * [ SP "SYNTAX" SP noidlen ] ; value syntax 2779 * [ SP "SINGLE-VALUE" ] ; single-value 2780 * [ SP "COLLECTIVE" ] ; collective 2781 * [ SP "NO-USER-MODIFICATION" ] ; not user modifiable 2782 * [ SP "USAGE" SP usage ] ; usage 2783 * extensions WSP RPAREN ; extensions 2784 * 2785 * usage = "userApplications" / ; user 2786 * "directoryOperation" / ; directory operational 2787 * "distributedOperation" / ; DSA-shared operational 2788 * "dSAOperation" ; DSA-specific operational 2789 * 2790 * extensions = *( SP xstring SP qdstrings ) 2791 * xstring = "X" HYPHEN 1*( ALPHA / HYPHEN / USCORE ) 2792 * </pre> 2793 * 2794 * @param attributeTypeDescription The String containing the AttributeTypeDescription 2795 * @return An instance of AttributeType 2796 * @throws ParseException If the element was invalid 2797 */ 2798 public AttributeType parseAttributeType( String attributeTypeDescription ) throws ParseException 2799 { 2800 if ( ( attributeTypeDescription == null ) || Strings.isEmpty( attributeTypeDescription.trim() ) ) 2801 { 2802 throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 ); 2803 } 2804 2805 try ( Reader reader = new BufferedReader( new StringReader( attributeTypeDescription ) ) ) 2806 { 2807 PosSchema pos = new PosSchema(); 2808 2809 if ( isQuirksModeEnabled ) 2810 { 2811 return parseAttributeTypeRelaxed( reader, pos, objectIdentifierMacros ); 2812 } 2813 else 2814 { 2815 return parseAttributeTypeStrict( reader, pos, objectIdentifierMacros ); 2816 } 2817 } 2818 catch ( IOException | LdapSchemaException e ) 2819 { 2820 // This exception is not passed as a cause in ParseException. Therefore at least log in, so it won't be lost. 2821 LOG.trace( I18n.err( I18n.ERR_13865_ERROR_PARSING_AT, attributeTypeDescription, e.getMessage() ), e ); 2822 throw new ParseException( e.getMessage(), 0 ); 2823 } 2824 } 2825 2826 2827 /** 2828 * Production for matching attribute type descriptions. It is fault-tolerant 2829 * against element ordering. It's strict. 2830 * 2831 * <pre> 2832 * AttributeTypeDescription = LPAREN WSP 2833 * numericoid ; object identifier 2834 * [ SP "NAME" SP qdescrs ] ; short names (descriptors) 2835 * [ SP "DESC" SP qdstring ] ; description 2836 * [ SP "OBSOLETE" ] ; not active 2837 * [ SP "SUP" SP oid ] ; supertype 2838 * [ SP "EQUALITY" SP oid ] ; equality matching rule 2839 * [ SP "ORDERING" SP oid ] ; ordering matching rule 2840 * [ SP "SUBSTR" SP oid ] ; substrings matching rule 2841 * [ SP "SYNTAX" SP noidlen ] ; value syntax 2842 * [ SP "SINGLE-VALUE" ] ; single-value 2843 * [ SP "COLLECTIVE" ] ; collective 2844 * [ SP "NO-USER-MODIFICATION" ] ; not user modifiable 2845 * [ SP "USAGE" SP usage ] ; usage 2846 * extensions WSP RPAREN ; extensions 2847 * 2848 * usage = "userApplications" / ; user 2849 * "directoryOperation" / ; directory operational 2850 * "distributedOperation" / ; DSA-shared operational 2851 * "dSAOperation" ; DSA-specific operational 2852 * 2853 * extensions = *( SP xstring SP qdstrings ) 2854 * xstring = "X" HYPHEN 1*( ALPHA / HYPHEN / USCORE ) 2855 * </pre> 2856 * 2857 * @param reader The stream reader 2858 * @param pos The position in the Schema 2859 * @param objectIdentifierMacros The set of existing Macros 2860 * @return An instance of AttributeType 2861 * @throws IOException If the stream can't be read 2862 * @throws LdapSchemaException If the schema is wrong 2863 */ 2864 private static AttributeType parseAttributeTypeStrict( Reader reader, PosSchema pos, 2865 Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException 2866 { 2867 // Get rid of whites, comments end empty lines 2868 skipWhites( reader, pos, false ); 2869 2870 // we must have a '(' 2871 if ( pos.line.charAt( pos.start ) != LPAREN ) 2872 { 2873 throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 2874 pos.lineNumber, pos.start ) ); 2875 } 2876 else 2877 { 2878 pos.start++; 2879 } 2880 2881 // Get rid of whites, comments end empty lines 2882 skipWhites( reader, pos, false ); 2883 2884 // Now, the OID. 2885 String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros ); 2886 2887 // Check that the OID is valid 2888 if ( !Oid.isOid( oid ) ) 2889 { 2890 throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) ); 2891 } 2892 2893 AttributeType attributeType = new AttributeType( oid ); 2894 boolean hasSup = false; 2895 boolean hasSyntax = false; 2896 int elementsSeen = 0; 2897 2898 while ( true ) 2899 { 2900 if ( startsWith( reader, pos, RPAREN ) ) 2901 { 2902 pos.start++; 2903 break; 2904 } 2905 2906 skipWhites( reader, pos, true ); 2907 2908 if ( startsWith( pos, NAME_STR ) ) 2909 { 2910 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.NAME, pos ); 2911 2912 pos.start += NAME_STR.length(); 2913 2914 skipWhites( reader, pos, true ); 2915 2916 attributeType.setNames( getQDescrs( reader, pos, STRICT ) ); 2917 } 2918 else if ( startsWith( pos, DESC_STR ) ) 2919 { 2920 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.DESC, pos ); 2921 2922 pos.start += DESC_STR.length(); 2923 2924 skipWhites( reader, pos, true ); 2925 2926 attributeType.setDescription( getQDString( reader, pos ) ); 2927 } 2928 else if ( startsWith( pos, OBSOLETE_STR ) ) 2929 { 2930 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.OBSOLETE, pos ); 2931 2932 pos.start += OBSOLETE_STR.length(); 2933 2934 attributeType.setObsolete( true ); 2935 } 2936 else if ( startsWith( pos, SUP_STR ) ) 2937 { 2938 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SUP, pos ); 2939 2940 pos.start += SUP_STR.length(); 2941 2942 skipWhites( reader, pos, true ); 2943 2944 String superiorOid = getOidStrict( pos ); 2945 2946 attributeType.setSuperiorOid( superiorOid ); 2947 hasSup = true; 2948 } 2949 else if ( startsWith( pos, EQUALITY_STR ) ) 2950 { 2951 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.EQUALITY, pos ); 2952 2953 pos.start += EQUALITY_STR.length(); 2954 2955 skipWhites( reader, pos, true ); 2956 2957 String equalityOid = getOidStrict( pos ); 2958 2959 attributeType.setEqualityOid( equalityOid ); 2960 } 2961 else if ( startsWith( pos, ORDERING_STR ) ) 2962 { 2963 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.ORDERING, pos ); 2964 2965 pos.start += ORDERING_STR.length(); 2966 2967 skipWhites( reader, pos, true ); 2968 2969 String orderingOid = getOidStrict( pos ); 2970 2971 attributeType.setOrderingOid( orderingOid ); 2972 } 2973 else if ( startsWith( pos, SUBSTR_STR ) ) 2974 { 2975 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SUBSTR, pos ); 2976 2977 pos.start += SUBSTR_STR.length(); 2978 2979 skipWhites( reader, pos, true ); 2980 2981 String substrOid = getOidStrict( pos ); 2982 2983 attributeType.setSubstringOid( substrOid ); 2984 } 2985 else if ( startsWith( pos, SYNTAX_STR ) ) 2986 { 2987 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SYNTAX, pos ); 2988 2989 pos.start += SYNTAX_STR.length(); 2990 2991 skipWhites( reader, pos, true ); 2992 2993 getNoidLenStrict( attributeType, pos ); 2994 2995 hasSyntax = true; 2996 } 2997 else if ( startsWith( pos, SINGLE_VALUE_STR ) ) 2998 { 2999 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SINGLE_VALUE, pos ); 3000 3001 pos.start += SINGLE_VALUE_STR.length(); 3002 3003 attributeType.setSingleValued( true ); 3004 } 3005 else if ( startsWith( pos, COLLECTIVE_STR ) ) 3006 { 3007 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.COLLECTIVE, pos ); 3008 3009 pos.start += COLLECTIVE_STR.length(); 3010 3011 attributeType.setCollective( true ); 3012 } 3013 else if ( startsWith( pos, NO_USER_MODIFICATION_STR ) ) 3014 { 3015 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.NO_USER_MODIFICATION, pos ); 3016 3017 pos.start += NO_USER_MODIFICATION_STR.length(); 3018 3019 attributeType.setUserModifiable( false ); 3020 } 3021 else if ( startsWith( pos, USAGE_STR ) ) 3022 { 3023 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.USAGE, pos ); 3024 3025 pos.start += USAGE_STR.length(); 3026 3027 skipWhites( reader, pos, true ); 3028 3029 UsageEnum usage = getUsageStrict( pos ); 3030 3031 attributeType.setUsage( usage ); 3032 } 3033 else if ( startsWith( pos, EXTENSION_PREFIX ) ) 3034 { 3035 processExtension( reader, pos, attributeType ); 3036 } 3037 else if ( startsWith( reader, pos, RPAREN ) ) 3038 { 3039 pos.start++; 3040 break; 3041 } 3042 else 3043 { 3044 // This is an error 3045 throw new LdapSchemaException( I18n.err( I18n.ERR_13798_AT_DESCRIPTION_INVALID, 3046 pos.lineNumber, pos.start ) ); 3047 } 3048 } 3049 3050 // Semantic checks 3051 if ( !hasSup && !hasSyntax ) 3052 { 3053 throw new LdapSchemaException( I18n.err( I18n.ERR_13799_SYNTAX_OR_SUP_REQUIRED, 3054 pos.lineNumber, pos.start ) ); 3055 } 3056 3057 if ( attributeType.isCollective() && ( attributeType.getUsage() != UsageEnum.USER_APPLICATIONS ) ) 3058 { 3059 throw new LdapSchemaException( I18n.err( I18n.ERR_13800_COLLECTIVE_REQUIRES_USER_APPLICATION, 3060 pos.lineNumber, pos.start ) ); 3061 } 3062 3063 // NO-USER-MODIFICATION requires an operational USAGE. 3064 if ( !attributeType.isUserModifiable() && ( attributeType.getUsage() == UsageEnum.USER_APPLICATIONS ) ) 3065 { 3066 throw new LdapSchemaException( I18n.err( I18n.ERR_13801_NO_USER_MOD_REQUIRE_OPERATIONAL, 3067 pos.lineNumber, pos.start ) ); 3068 } 3069 3070 return attributeType; 3071 } 3072 3073 3074 /** 3075 * Production for matching attribute type descriptions. It is fault-tolerant 3076 * against element ordering. It's relaxed. 3077 * 3078 * <pre> 3079 * AttributeTypeDescription = LPAREN WSP 3080 * numericoid ; object identifier 3081 * [ SP "NAME" SP qdescrs ] ; short names (descriptors) 3082 * [ SP "DESC" SP qdstring ] ; description 3083 * [ SP "OBSOLETE" ] ; not active 3084 * [ SP "SUP" SP oid ] ; supertype 3085 * [ SP "EQUALITY" SP oid ] ; equality matching rule 3086 * [ SP "ORDERING" SP oid ] ; ordering matching rule 3087 * [ SP "SUBSTR" SP oid ] ; substrings matching rule 3088 * [ SP "SYNTAX" SP noidlen ] ; value syntax 3089 * [ SP "SINGLE-VALUE" ] ; single-value 3090 * [ SP "COLLECTIVE" ] ; collective 3091 * [ SP "NO-USER-MODIFICATION" ] ; not user modifiable 3092 * [ SP "USAGE" SP usage ] ; usage 3093 * extensions WSP RPAREN ; extensions 3094 * 3095 * usage = "userApplications" / ; user 3096 * "directoryOperation" / ; directory operational 3097 * "distributedOperation" / ; DSA-shared operational 3098 * "dSAOperation" ; DSA-specific operational 3099 * 3100 * extensions = *( SP xstring SP qdstrings ) 3101 * xstring = "X" HYPHEN 1*( ALPHA / HYPHEN / USCORE ) 3102 * </pre> 3103 * 3104 * @param reader The stream reader 3105 * @param pos The position in the Schema 3106 * @param objectIdentifierMacros The set of existing Macros 3107 * @return An instance of AttributeType 3108 * @throws IOException If the stream can't be read 3109 * @throws LdapSchemaException If the schema is wrong 3110 */ 3111 private static AttributeType parseAttributeTypeRelaxed( Reader reader, PosSchema pos, 3112 Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException 3113 { 3114 // Get rid of whites, comments end empty lines 3115 skipWhites( reader, pos, false ); 3116 3117 // we must have a '(' 3118 if ( pos.line.charAt( pos.start ) != LPAREN ) 3119 { 3120 throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 3121 pos.lineNumber, pos.start ) ); 3122 } 3123 else 3124 { 3125 pos.start++; 3126 } 3127 3128 // Get rid of whites, comments end empty lines 3129 skipWhites( reader, pos, false ); 3130 3131 // Now, the OID. 3132 String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros ); 3133 3134 AttributeType attributeType = new AttributeType( oid ); 3135 int elementsSeen = 0; 3136 3137 while ( true ) 3138 { 3139 if ( startsWith( reader, pos, RPAREN ) ) 3140 { 3141 pos.start++; 3142 break; 3143 } 3144 3145 // Make whitespace non-mandatory here. 3146 // E.g. OpenDJ is missing the the space in some schema definitions. 3147 skipWhites( reader, pos, false ); 3148 3149 if ( startsWith( pos, NAME_STR ) ) 3150 { 3151 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.NAME, pos ); 3152 3153 pos.start += NAME_STR.length(); 3154 3155 skipWhites( reader, pos, true ); 3156 3157 attributeType.setNames( getQDescrs( reader, pos, RELAXED ) ); 3158 } 3159 else if ( startsWith( pos, DESC_STR ) ) 3160 { 3161 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.DESC, pos ); 3162 3163 pos.start += DESC_STR.length(); 3164 3165 skipWhites( reader, pos, true ); 3166 3167 attributeType.setDescription( getQDString( reader, pos ) ); 3168 } 3169 else if ( startsWith( pos, OBSOLETE_STR ) ) 3170 { 3171 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.OBSOLETE, pos ); 3172 3173 pos.start += OBSOLETE_STR.length(); 3174 3175 attributeType.setObsolete( true ); 3176 } 3177 else if ( startsWith( pos, SUP_STR ) ) 3178 { 3179 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SUP, pos ); 3180 3181 pos.start += SUP_STR.length(); 3182 3183 skipWhites( reader, pos, true ); 3184 3185 String superiorOid = getOidRelaxed( pos, false ); 3186 3187 attributeType.setSuperiorOid( superiorOid ); 3188 } 3189 else if ( startsWith( pos, EQUALITY_STR ) ) 3190 { 3191 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.EQUALITY, pos ); 3192 3193 pos.start += EQUALITY_STR.length(); 3194 3195 skipWhites( reader, pos, true ); 3196 3197 String equalityOid = getOidRelaxed( pos, false ); 3198 3199 attributeType.setEqualityOid( equalityOid ); 3200 } 3201 else if ( startsWith( pos, ORDERING_STR ) ) 3202 { 3203 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.ORDERING, pos ); 3204 3205 pos.start += ORDERING_STR.length(); 3206 3207 skipWhites( reader, pos, true ); 3208 3209 String orderingOid = getOidRelaxed( pos, false ); 3210 3211 attributeType.setOrderingOid( orderingOid ); 3212 } 3213 else if ( startsWith( pos, SUBSTR_STR ) ) 3214 { 3215 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SUBSTR, pos ); 3216 3217 pos.start += SUBSTR_STR.length(); 3218 3219 skipWhites( reader, pos, true ); 3220 3221 String substrOid = getOidRelaxed( pos, false ); 3222 3223 attributeType.setSubstringOid( substrOid ); 3224 } 3225 else if ( startsWith( pos, SYNTAX_STR ) ) 3226 { 3227 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SYNTAX, pos ); 3228 3229 pos.start += SYNTAX_STR.length(); 3230 3231 skipWhites( reader, pos, true ); 3232 3233 getNoidLenRelaxed( attributeType, pos ); 3234 } 3235 else if ( startsWith( pos, SINGLE_VALUE_STR ) ) 3236 { 3237 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.SINGLE_VALUE, pos ); 3238 3239 pos.start += SINGLE_VALUE_STR.length(); 3240 3241 attributeType.setSingleValued( true ); 3242 } 3243 else if ( startsWith( pos, COLLECTIVE_STR ) ) 3244 { 3245 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.COLLECTIVE, pos ); 3246 3247 pos.start += COLLECTIVE_STR.length(); 3248 3249 attributeType.setCollective( true ); 3250 } 3251 else if ( startsWith( pos, NO_USER_MODIFICATION_STR ) ) 3252 { 3253 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.NO_USER_MODIFICATION, pos ); 3254 3255 pos.start += NO_USER_MODIFICATION_STR.length(); 3256 3257 attributeType.setUserModifiable( false ); 3258 } 3259 else if ( startsWith( pos, USAGE_STR ) ) 3260 { 3261 elementsSeen = checkElement( elementsSeen, AttributeTypeElements.USAGE, pos ); 3262 3263 pos.start += USAGE_STR.length(); 3264 3265 skipWhites( reader, pos, true ); 3266 3267 UsageEnum usage = getUsageRelaxed( pos ); 3268 3269 attributeType.setUsage( usage ); 3270 } 3271 else if ( startsWith( pos, EXTENSION_PREFIX ) ) 3272 { 3273 processExtension( reader, pos, attributeType ); 3274 } 3275 else if ( startsWith( reader, pos, RPAREN ) ) 3276 { 3277 pos.start++; 3278 break; 3279 } 3280 else 3281 { 3282 // This is an error 3283 throw new LdapSchemaException( I18n.err( I18n.ERR_13798_AT_DESCRIPTION_INVALID, 3284 pos.lineNumber, pos.start ) ); 3285 } 3286 } 3287 3288 return attributeType; 3289 } 3290 3291 3292 /** 3293 * Production for matching DitContentRule descriptions. It is fault-tolerant 3294 * against element ordering. 3295 * 3296 * <pre> 3297 * DITContentRuleDescription = LPAREN WSP 3298 * numericoid ; object identifier 3299 * [ SP "NAME" SP qdescrs ] ; short names (descriptors) 3300 * [ SP "DESC" SP qdstring ] ; description 3301 * [ SP "OBSOLETE" ] ; not active 3302 * [ SP "AUX" SP oids ] ; auxiliary object classes 3303 * [ SP "MUST" SP oids ] ; attribute types 3304 * [ SP "MAY" SP oids ] ; attribute types 3305 * [ SP "NOT" SP oids ] ; attribute types 3306 * extensions WSP RPAREN ; extensions 3307 * </pre> 3308 * 3309 * @param ditContentRuleDescription The String containing the DitContentRuleDescription 3310 * @return An instance of ditContentRule 3311 * @throws ParseException If the element was invalid 3312 */ 3313 public DitContentRule parseDitContentRule( String ditContentRuleDescription ) throws ParseException 3314 { 3315 if ( ( ditContentRuleDescription == null ) || Strings.isEmpty( ditContentRuleDescription.trim() ) ) 3316 { 3317 throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 ); 3318 } 3319 3320 try ( Reader reader = new BufferedReader( new StringReader( ditContentRuleDescription ) ) ) 3321 { 3322 PosSchema pos = new PosSchema(); 3323 3324 if ( isQuirksModeEnabled ) 3325 { 3326 return parseDitContentRuleRelaxed( reader, pos, objectIdentifierMacros ); 3327 } 3328 else 3329 { 3330 return parseDitContentRuleStrict( reader, pos, objectIdentifierMacros ); 3331 } 3332 } 3333 catch ( IOException | LdapSchemaException e ) 3334 { 3335 throw new ParseException( e.getMessage(), 0 ); 3336 } 3337 } 3338 3339 3340 /** 3341 * Production for DitContentRule descriptions. It is fault-tolerant 3342 * against element ordering. 3343 * 3344 * <pre> 3345 * DITContentRuleDescription = LPAREN WSP 3346 * numericoid ; object identifier 3347 * [ SP "NAME" SP qdescrs ] ; short names (descriptors) 3348 * [ SP "DESC" SP qdstring ] ; description 3349 * [ SP "OBSOLETE" ] ; not active 3350 * [ SP "AUX" SP oids ] ; auxiliary object classes 3351 * [ SP "MUST" SP oids ] ; attribute types 3352 * [ SP "MAY" SP oids ] ; attribute types 3353 * [ SP "NOT" SP oids ] ; attribute types 3354 * extensions WSP RPAREN ; extensions 3355 * </pre> 3356 * 3357 * @param reader The stream reader 3358 * @param pos The position in the Schema 3359 * @param objectIdentifierMacros The set of existing Macros 3360 * @return An instance of DitContentRule 3361 * @throws LdapSchemaException If the schema is wrong 3362 * @throws IOException If the stream can't be read 3363 */ 3364 private static DitContentRule parseDitContentRuleStrict( Reader reader, PosSchema pos, 3365 Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException 3366 { 3367 // Get rid of whites, comments end empty lines 3368 skipWhites( reader, pos, false ); 3369 3370 // we must have a '(' 3371 if ( pos.line.charAt( pos.start ) != LPAREN ) 3372 { 3373 throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 3374 pos.lineNumber, pos.start ) ); 3375 } 3376 else 3377 { 3378 pos.start++; 3379 } 3380 3381 // Get rid of whites, comments end empty lines 3382 skipWhites( reader, pos, false ); 3383 3384 // Now, the OID. 3385 String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros ); 3386 3387 // Check that the OID is valid 3388 if ( !Oid.isOid( oid ) ) 3389 { 3390 throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) ); 3391 } 3392 3393 DitContentRule ditContentRule = new DitContentRule( oid ); 3394 int elementsSeen = 0; 3395 3396 while ( true ) 3397 { 3398 if ( startsWith( reader, pos, RPAREN ) ) 3399 { 3400 pos.start++; 3401 break; 3402 } 3403 3404 skipWhites( reader, pos, true ); 3405 3406 if ( startsWith( pos, NAME_STR ) ) 3407 { 3408 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.NAME, pos ); 3409 3410 pos.start += NAME_STR.length(); 3411 3412 skipWhites( reader, pos, true ); 3413 3414 ditContentRule.setNames( getQDescrs( reader, pos, STRICT ) ); 3415 } 3416 else if ( startsWith( pos, DESC_STR ) ) 3417 { 3418 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.DESC, pos ); 3419 3420 pos.start += DESC_STR.length(); 3421 3422 skipWhites( reader, pos, true ); 3423 3424 ditContentRule.setDescription( getQDString( reader, pos ) ); 3425 } 3426 else if ( startsWith( pos, OBSOLETE_STR ) ) 3427 { 3428 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.OBSOLETE, pos ); 3429 3430 pos.start += OBSOLETE_STR.length(); 3431 3432 ditContentRule.setObsolete( true ); 3433 } 3434 else if ( startsWith( pos, AUX_STR ) ) 3435 { 3436 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.AUX, pos ); 3437 3438 pos.start += AUX_STR.length(); 3439 3440 skipWhites( reader, pos, true ); 3441 3442 List<String> aux = getOidsStrict( reader, pos ); 3443 3444 ditContentRule.setAuxObjectClassOids( aux ); 3445 } 3446 else if ( startsWith( pos, MUST_STR ) ) 3447 { 3448 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.MUST, pos ); 3449 3450 pos.start += MUST_STR.length(); 3451 3452 skipWhites( reader, pos, true ); 3453 3454 List<String> must = getOidsStrict( reader, pos ); 3455 3456 ditContentRule.setMustAttributeTypeOids( must ); 3457 } 3458 else if ( startsWith( pos, MAY_STR ) ) 3459 { 3460 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.MAY, pos ); 3461 3462 pos.start += MAY_STR.length(); 3463 3464 skipWhites( reader, pos, true ); 3465 3466 List<String> may = getOidsStrict( reader, pos ); 3467 3468 ditContentRule.setMayAttributeTypeOids( may ); 3469 } 3470 else if ( startsWith( pos, NOT_STR ) ) 3471 { 3472 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.NOT, pos ); 3473 3474 pos.start += NOT_STR.length(); 3475 3476 skipWhites( reader, pos, true ); 3477 3478 List<String> not = getOidsStrict( reader, pos ); 3479 3480 ditContentRule.setNotAttributeTypeOids( not ); 3481 } 3482 else if ( startsWith( pos, EXTENSION_PREFIX ) ) 3483 { 3484 processExtension( reader, pos, ditContentRule ); 3485 } 3486 else if ( startsWith( reader, pos, RPAREN ) ) 3487 { 3488 pos.start++; 3489 3490 break; 3491 } 3492 else 3493 { 3494 // This is an error 3495 throw new LdapSchemaException( I18n.err( I18n.ERR_13809_DCR_DESCRIPTION_INVALID, 3496 pos.lineNumber, pos.start ) ); 3497 } 3498 } 3499 3500 return ditContentRule; 3501 } 3502 3503 3504 /** 3505 * Production for DitContentRule descriptions. It is fault-tolerant 3506 * against element ordering. 3507 * 3508 * <pre> 3509 * DITContentRuleDescription = LPAREN WSP 3510 * numericoid ; object identifier 3511 * [ SP "NAME" SP qdescrs ] ; short names (descriptors) 3512 * [ SP "DESC" SP qdstring ] ; description 3513 * [ SP "OBSOLETE" ] ; not active 3514 * [ SP "AUX" SP oids ] ; auxiliary object classes 3515 * [ SP "MUST" SP oids ] ; attribute types 3516 * [ SP "MAY" SP oids ] ; attribute types 3517 * [ SP "NOT" SP oids ] ; attribute types 3518 * extensions WSP RPAREN ; extensions 3519 * </pre> 3520 * 3521 * @param reader The stream reader 3522 * @param pos The position in the Schema 3523 * @param objectIdentifierMacros The set of existing Macros 3524 * @return An instance of DitContentRule 3525 * @throws LdapSchemaException If the schema is wrong 3526 * @throws IOException If the stream can't be read 3527 */ 3528 private static DitContentRule parseDitContentRuleRelaxed( Reader reader, PosSchema pos, 3529 Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException 3530 { 3531 // Get rid of whites, comments end empty lines 3532 skipWhites( reader, pos, false ); 3533 3534 // we must have a '(' 3535 if ( pos.line.charAt( pos.start ) != LPAREN ) 3536 { 3537 throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 3538 pos.lineNumber, pos.start ) ); 3539 } 3540 else 3541 { 3542 pos.start++; 3543 } 3544 3545 // Get rid of whites, comments end empty lines 3546 skipWhites( reader, pos, false ); 3547 3548 // Now, the OID. 3549 String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros ); 3550 // Now, the OID. 3551 3552 DitContentRule ditContentRule = new DitContentRule( oid ); 3553 int elementsSeen = 0; 3554 3555 while ( true ) 3556 { 3557 if ( startsWith( reader, pos, RPAREN ) ) 3558 { 3559 pos.start++; 3560 break; 3561 } 3562 3563 skipWhites( reader, pos, true ); 3564 3565 if ( startsWith( pos, NAME_STR ) ) 3566 { 3567 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.NAME, pos ); 3568 3569 pos.start += NAME_STR.length(); 3570 3571 skipWhites( reader, pos, true ); 3572 3573 ditContentRule.setNames( getQDescrs( reader, pos, RELAXED ) ); 3574 } 3575 else if ( startsWith( pos, DESC_STR ) ) 3576 { 3577 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.DESC, pos ); 3578 3579 pos.start += DESC_STR.length(); 3580 3581 skipWhites( reader, pos, true ); 3582 3583 ditContentRule.setDescription( getQDString( reader, pos ) ); 3584 } 3585 else if ( startsWith( pos, OBSOLETE_STR ) ) 3586 { 3587 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.OBSOLETE, pos ); 3588 3589 pos.start += OBSOLETE_STR.length(); 3590 3591 ditContentRule.setObsolete( true ); 3592 } 3593 else if ( startsWith( pos, AUX_STR ) ) 3594 { 3595 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.AUX, pos ); 3596 3597 pos.start += AUX_STR.length(); 3598 3599 skipWhites( reader, pos, true ); 3600 3601 List<String> aux = getOidsRelaxed( reader, pos ); 3602 3603 ditContentRule.setAuxObjectClassOids( aux ); 3604 } 3605 else if ( startsWith( pos, MUST_STR ) ) 3606 { 3607 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.MUST, pos ); 3608 3609 pos.start += MUST_STR.length(); 3610 3611 skipWhites( reader, pos, true ); 3612 3613 List<String> must = getOidsRelaxed( reader, pos ); 3614 3615 ditContentRule.setMustAttributeTypeOids( must ); 3616 } 3617 else if ( startsWith( pos, MAY_STR ) ) 3618 { 3619 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.MAY, pos ); 3620 3621 pos.start += MAY_STR.length(); 3622 3623 skipWhites( reader, pos, true ); 3624 3625 List<String> may = getOidsRelaxed( reader, pos ); 3626 3627 ditContentRule.setMayAttributeTypeOids( may ); 3628 } 3629 else if ( startsWith( pos, NOT_STR ) ) 3630 { 3631 elementsSeen = checkElement( elementsSeen, DitContentRuleElements.NOT, pos ); 3632 3633 pos.start += NOT_STR.length(); 3634 3635 skipWhites( reader, pos, true ); 3636 3637 List<String> not = getOidsRelaxed( reader, pos ); 3638 3639 ditContentRule.setNotAttributeTypeOids( not ); 3640 } 3641 else if ( startsWith( pos, EXTENSION_PREFIX ) ) 3642 { 3643 processExtension( reader, pos, ditContentRule ); 3644 } 3645 else if ( startsWith( reader, pos, RPAREN ) ) 3646 { 3647 pos.start++; 3648 3649 break; 3650 } 3651 else 3652 { 3653 // This is an error 3654 throw new LdapSchemaException( I18n.err( I18n.ERR_13809_DCR_DESCRIPTION_INVALID, 3655 pos.lineNumber, pos.start ) ); 3656 } 3657 } 3658 3659 return ditContentRule; 3660 } 3661 3662 3663 /** 3664 * Production for matching DitStructureRule descriptions. It is fault-tolerant 3665 * against element ordering. 3666 * 3667 * <pre> 3668 * DITStructureRuleDescription = LPAREN WSP 3669 * ruleid ; rule identifier 3670 * [ SP "NAME" SP qdescrs ] ; short names (descriptors) 3671 * [ SP "DESC" SP qdstring ] ; description 3672 * [ SP "OBSOLETE" ] ; not active 3673 * SP "FORM" SP oid ; NameForm 3674 * [ SP "SUP" ruleids ] ; superior rules 3675 * extensions WSP RPAREN ; extensions 3676 * 3677 * ruleids = ruleid / ( LPAREN WSP ruleidlist WSP RPAREN ) 3678 * ruleidlist = ruleid *( SP ruleid ) 3679 * ruleid = number 3680 * </pre> 3681 * 3682 * @param ditStructureRuleDescription The String containing the DitStructureRuleDescription 3683 * @return An instance of DitStructureRule 3684 * @throws ParseException If the element was invalid 3685 */ 3686 public DitStructureRule parseDitStructureRule( String ditStructureRuleDescription ) throws ParseException 3687 { 3688 if ( ( ditStructureRuleDescription == null ) || Strings.isEmpty( ditStructureRuleDescription.trim() ) ) 3689 { 3690 throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 ); 3691 } 3692 3693 try ( Reader reader = new BufferedReader( new StringReader( ditStructureRuleDescription ) ) ) 3694 { 3695 PosSchema pos = new PosSchema(); 3696 3697 if ( isQuirksModeEnabled ) 3698 { 3699 return parseDitStructureRuleRelaxed( reader, pos, objectIdentifierMacros ); 3700 } 3701 else 3702 { 3703 return parseDitStructureRuleStrict( reader, pos ); 3704 } 3705 } 3706 catch ( IOException | LdapSchemaException e ) 3707 { 3708 throw new ParseException( e.getMessage(), 0 ); 3709 } 3710 } 3711 3712 3713 /** 3714 * Production for DitStructureRule descriptions. It is fault-tolerant 3715 * against element ordering. 3716 * 3717 * <pre> 3718 * DITStructureRuleDescription = LPAREN WSP 3719 * ruleid ; rule identifier 3720 * [ SP "NAME" SP qdescrs ] ; short names (descriptors) 3721 * [ SP "DESC" SP qdstring ] ; description 3722 * [ SP "OBSOLETE" ] ; not active 3723 * SP "FORM" SP oid ; NameForm 3724 * [ SP "SUP" ruleids ] ; superior rules 3725 * extensions WSP RPAREN ; extensions 3726 * 3727 * ruleids = ruleid / ( LPAREN WSP ruleidlist WSP RPAREN ) 3728 * ruleidlist = ruleid *( SP ruleid ) 3729 * ruleid = number 3730 * </pre> 3731 * 3732 * @param reader The stream reader 3733 * @param pos The position in the Schema 3734 * @return An instance of DitStructureRule 3735 * @throws LdapSchemaException If the schema is wrong 3736 * @throws IOException If the stream can't be read 3737 */ 3738 private static DitStructureRule parseDitStructureRuleStrict( Reader reader, PosSchema pos ) 3739 throws IOException, LdapSchemaException 3740 { 3741 // Get rid of whites, comments end empty lines 3742 skipWhites( reader, pos, false ); 3743 3744 // we must have a '(' 3745 if ( pos.line.charAt( pos.start ) != LPAREN ) 3746 { 3747 throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 3748 pos.lineNumber, pos.start ) ); 3749 } 3750 else 3751 { 3752 pos.start++; 3753 } 3754 3755 // Get rid of whites, comments end empty lines 3756 skipWhites( reader, pos, false ); 3757 3758 // Now, the ruleID. 3759 int ruleId = getRuleId( pos ); 3760 3761 DitStructureRule ditStructureRule = new DitStructureRule( ruleId ); 3762 int elementsSeen = 0; 3763 boolean hasForm = false; 3764 3765 while ( true ) 3766 { 3767 if ( startsWith( reader, pos, RPAREN ) ) 3768 { 3769 pos.start++; 3770 break; 3771 } 3772 3773 skipWhites( reader, pos, true ); 3774 3775 if ( startsWith( pos, NAME_STR ) ) 3776 { 3777 elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.NAME, pos ); 3778 3779 pos.start += NAME_STR.length(); 3780 3781 skipWhites( reader, pos, true ); 3782 3783 ditStructureRule.setNames( getQDescrs( reader, pos, STRICT ) ); 3784 } 3785 else if ( startsWith( pos, DESC_STR ) ) 3786 { 3787 elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.DESC, pos ); 3788 3789 pos.start += DESC_STR.length(); 3790 3791 skipWhites( reader, pos, true ); 3792 3793 ditStructureRule.setDescription( getQDString( reader, pos ) ); 3794 } 3795 else if ( startsWith( pos, OBSOLETE_STR ) ) 3796 { 3797 elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.OBSOLETE, pos ); 3798 3799 pos.start += OBSOLETE_STR.length(); 3800 3801 ditStructureRule.setObsolete( true ); 3802 } 3803 else if ( startsWith( pos, FORM_STR ) ) 3804 { 3805 elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.FORM, pos ); 3806 3807 pos.start += FORM_STR.length(); 3808 3809 skipWhites( reader, pos, true ); 3810 3811 String form = getOidStrict( pos ); 3812 3813 ditStructureRule.setForm( form ); 3814 hasForm = true; 3815 } 3816 else if ( startsWith( pos, SUP_STR ) ) 3817 { 3818 elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.SUP, pos ); 3819 3820 pos.start += SUP_STR.length(); 3821 3822 skipWhites( reader, pos, true ); 3823 3824 List<Integer> superRules = getRuleIds( reader, pos ); 3825 3826 ditStructureRule.setSuperRules( superRules ); 3827 } 3828 else if ( startsWith( pos, EXTENSION_PREFIX ) ) 3829 { 3830 processExtension( reader, pos, ditStructureRule ); 3831 } 3832 else if ( startsWith( reader, pos, RPAREN ) ) 3833 { 3834 pos.start++; 3835 break; 3836 } 3837 else 3838 { 3839 // This is an error 3840 throw new LdapSchemaException( I18n.err( I18n.ERR_13809_DCR_DESCRIPTION_INVALID, 3841 pos.lineNumber, pos.start ) ); 3842 } 3843 } 3844 3845 // Semantic checks 3846 if ( !hasForm ) 3847 { 3848 throw new LdapSchemaException( I18n.err( I18n.ERR_13812_FORM_REQUIRED, 3849 pos.lineNumber, pos.start ) ); 3850 } 3851 3852 return ditStructureRule; 3853 } 3854 3855 3856 /** 3857 * Production for DitStructureRule descriptions. It is fault-tolerant 3858 * against element ordering. 3859 * 3860 * <pre> 3861 * DITStructureRuleDescription = LPAREN WSP 3862 * ruleid ; rule identifier 3863 * [ SP "NAME" SP qdescrs ] ; short names (descriptors) 3864 * [ SP "DESC" SP qdstring ] ; description 3865 * [ SP "OBSOLETE" ] ; not active 3866 * SP "FORM" SP oid ; NameForm 3867 * [ SP "SUP" ruleids ] ; superior rules 3868 * extensions WSP RPAREN ; extensions 3869 * 3870 * ruleids = ruleid / ( LPAREN WSP ruleidlist WSP RPAREN ) 3871 * ruleidlist = ruleid *( SP ruleid ) 3872 * ruleid = number 3873 * </pre> 3874 * 3875 * @param reader The stream reader 3876 * @param pos The position in the Schema 3877 * @param objectIdentifierMacros The set of existing Macros 3878 * @return An instance of DitStructureRule 3879 * @throws LdapSchemaException If the schema is wrong 3880 * @throws IOException If the stream can't be read 3881 */ 3882 private static DitStructureRule parseDitStructureRuleRelaxed( Reader reader, PosSchema pos, 3883 Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 3884 throws IOException, LdapSchemaException 3885 { 3886 // Get rid of whites, comments end empty lines 3887 skipWhites( reader, pos, false ); 3888 3889 // we must have a '(' 3890 if ( pos.line.charAt( pos.start ) != LPAREN ) 3891 { 3892 throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 3893 pos.lineNumber, pos.start ) ); 3894 } 3895 else 3896 { 3897 pos.start++; 3898 } 3899 3900 // Get rid of whites, comments end empty lines 3901 skipWhites( reader, pos, false ); 3902 3903 // Now, the ruleID. 3904 int ruleId = getRuleId( pos ); 3905 3906 DitStructureRule ditStructureRule = new DitStructureRule( ruleId ); 3907 int elementsSeen = 0; 3908 boolean hasForm = false; 3909 3910 while ( true ) 3911 { 3912 if ( startsWith( reader, pos, RPAREN ) ) 3913 { 3914 pos.start++; 3915 break; 3916 } 3917 3918 skipWhites( reader, pos, true ); 3919 3920 if ( startsWith( pos, NAME_STR ) ) 3921 { 3922 elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.NAME, pos ); 3923 3924 pos.start += NAME_STR.length(); 3925 3926 skipWhites( reader, pos, true ); 3927 3928 ditStructureRule.setNames( getQDescrs( reader, pos, RELAXED ) ); 3929 } 3930 else if ( startsWith( pos, DESC_STR ) ) 3931 { 3932 elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.DESC, pos ); 3933 3934 pos.start += DESC_STR.length(); 3935 3936 skipWhites( reader, pos, true ); 3937 3938 ditStructureRule.setDescription( getQDString( reader, pos ) ); 3939 } 3940 else if ( startsWith( pos, OBSOLETE_STR ) ) 3941 { 3942 elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.OBSOLETE, pos ); 3943 3944 pos.start += OBSOLETE_STR.length(); 3945 3946 ditStructureRule.setObsolete( true ); 3947 } 3948 else if ( startsWith( pos, FORM_STR ) ) 3949 { 3950 elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.FORM, pos ); 3951 3952 pos.start += FORM_STR.length(); 3953 3954 skipWhites( reader, pos, true ); 3955 3956 String form = getOidRelaxed( pos, UN_QUOTED ); 3957 3958 ditStructureRule.setForm( form ); 3959 hasForm = true; 3960 } 3961 else if ( startsWith( pos, SUP_STR ) ) 3962 { 3963 elementsSeen = checkElement( elementsSeen, DitStructureRuleElements.SUP, pos ); 3964 3965 pos.start += SUP_STR.length(); 3966 3967 skipWhites( reader, pos, true ); 3968 3969 List<Integer> superRules = getRuleIds( reader, pos ); 3970 3971 ditStructureRule.setSuperRules( superRules ); 3972 } 3973 else if ( startsWith( pos, EXTENSION_PREFIX ) ) 3974 { 3975 processExtension( reader, pos, ditStructureRule ); 3976 } 3977 else if ( startsWith( reader, pos, RPAREN ) ) 3978 { 3979 pos.start++; 3980 break; 3981 } 3982 else 3983 { 3984 // This is an error 3985 throw new LdapSchemaException( I18n.err( I18n.ERR_13809_DCR_DESCRIPTION_INVALID, 3986 pos.lineNumber, pos.start ) ); 3987 } 3988 } 3989 3990 if ( !hasForm ) 3991 { 3992 throw new LdapSchemaException( I18n.err( I18n.ERR_13812_FORM_REQUIRED, 3993 pos.lineNumber, pos.start ) ); 3994 } 3995 3996 return ditStructureRule; 3997 } 3998 3999 4000 /** 4001 * Production for LdapComparator descriptions. It is fault-tolerant 4002 * against element ordering. 4003 * 4004 * <pre> 4005 * LdapComparatorDescription = LPAREN WSP 4006 * numericoid ; object identifier 4007 * [ SP "DESC" SP qdstring ] ; description 4008 * SP "FQCN" SP fqcn ; fully qualified class name 4009 * [ SP "BYTECODE" SP base64 ] ; optional base64 encoded bytecode 4010 * extensions WSP RPAREN ; extensions 4011 * 4012 * base64 = *(4base64-char) 4013 * base64-char = ALPHA / DIGIT / "+" / "/" 4014 * fqcn = fqcnComponent 1*( DOT fqcnComponent ) 4015 * fqcnComponent = ??? 4016 * </pre> 4017 * 4018 * @param ldapComparatorDescription The String containing the LdapComparatorDescription 4019 * @return An instance of LdapComparatorDescription 4020 * @throws ParseException If the element was invalid 4021 */ 4022 public LdapComparatorDescription parseLdapComparator( String ldapComparatorDescription ) throws ParseException 4023 { 4024 if ( ( ldapComparatorDescription == null ) || Strings.isEmpty( ldapComparatorDescription.trim() ) ) 4025 { 4026 throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 ); 4027 } 4028 4029 try ( Reader reader = new BufferedReader( new StringReader( ldapComparatorDescription ) ) ) 4030 { 4031 PosSchema pos = new PosSchema(); 4032 4033 if ( isQuirksModeEnabled ) 4034 { 4035 return parseLdapComparatorRelaxed( reader, pos, objectIdentifierMacros ); 4036 } 4037 else 4038 { 4039 return parseLdapComparatorStrict( reader, pos, objectIdentifierMacros ); 4040 } 4041 } 4042 catch ( IOException | LdapSchemaException e ) 4043 { 4044 throw new ParseException( e.getMessage(), 0 ); 4045 } 4046 } 4047 4048 4049 /** 4050 * Production for LdapComparator descriptions. It is fault-tolerant 4051 * against element ordering. 4052 * 4053 * <pre> 4054 * LdapComparatorDescription = LPAREN WSP 4055 * numericoid ; object identifier 4056 * [ SP "DESC" SP qdstring ] ; description 4057 * SP "FQCN" SP fqcn ; fully qualified class name 4058 * [ SP "BYTECODE" SP base64 ] ; optional base64 encoded bytecode 4059 * extensions WSP RPAREN ; extensions 4060 * 4061 * base64 = *(4base64-char) 4062 * base64-char = ALPHA / DIGIT / "+" / "/" 4063 * fqcn = fqcnComponent 1*( DOT fqcnComponent ) 4064 * fqcnComponent = ??? 4065 * </pre> 4066 * 4067 * @param reader The stream reader 4068 * @param pos The position in the Schema 4069 * @param objectIdentifierMacros The set of existing Macros 4070 * @return An instance of LdapComparatorDescription 4071 * @throws LdapSchemaException If the schema is wrong 4072 * @throws IOException If the stream can't be read 4073 */ 4074 private static LdapComparatorDescription parseLdapComparatorStrict( Reader reader, PosSchema pos, 4075 Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException 4076 { 4077 // Get rid of whites, comments end empty lines 4078 skipWhites( reader, pos, false ); 4079 4080 // we must have a '(' 4081 if ( pos.line.charAt( pos.start ) != LPAREN ) 4082 { 4083 throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 4084 pos.lineNumber, pos.start ) ); 4085 } 4086 else 4087 { 4088 pos.start++; 4089 } 4090 4091 // Get rid of whites, comments end empty lines 4092 skipWhites( reader, pos, false ); 4093 4094 // Now, the OID. 4095 String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros ); 4096 4097 // Check that the OID is valid 4098 if ( !Oid.isOid( oid ) ) 4099 { 4100 throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) ); 4101 } 4102 4103 LdapComparatorDescription ldapComparator = new LdapComparatorDescription( oid ); 4104 int elementsSeen = 0; 4105 boolean hasFqcn = false; 4106 boolean hasByteCode = false; 4107 4108 while ( true ) 4109 { 4110 if ( startsWith( reader, pos, RPAREN ) ) 4111 { 4112 pos.start++; 4113 break; 4114 } 4115 4116 skipWhites( reader, pos, true ); 4117 4118 if ( startsWith( pos, DESC_STR ) ) 4119 { 4120 elementsSeen = checkElement( elementsSeen, LdapComparatorElements.DESC, pos ); 4121 4122 pos.start += DESC_STR.length(); 4123 4124 skipWhites( reader, pos, true ); 4125 4126 ldapComparator.setDescription( getQDString( reader, pos ) ); 4127 } 4128 else if ( startsWith( pos, FQCN_STR ) ) 4129 { 4130 elementsSeen = checkElement( elementsSeen, LdapComparatorElements.FQCN, pos ); 4131 4132 pos.start += FQCN_STR.length(); 4133 4134 skipWhites( reader, pos, true ); 4135 4136 String fqcn = getFqcn( pos ); 4137 ldapComparator.setFqcn( fqcn ); 4138 hasFqcn = true; 4139 } 4140 else if ( startsWith( pos, BYTECODE_STR ) ) 4141 { 4142 elementsSeen = checkElement( elementsSeen, LdapComparatorElements.BYTECODE, pos ); 4143 4144 pos.start += BYTECODE_STR.length(); 4145 4146 skipWhites( reader, pos, true ); 4147 4148 String byteCode = getByteCode( pos ); 4149 ldapComparator.setBytecode( byteCode ); 4150 hasByteCode = true; 4151 } 4152 else if ( startsWith( pos, EXTENSION_PREFIX ) ) 4153 { 4154 processExtension( reader, pos, ldapComparator ); 4155 } 4156 else if ( startsWith( reader, pos, RPAREN ) ) 4157 { 4158 pos.start++; 4159 break; 4160 } 4161 else 4162 { 4163 // This is an error 4164 throw new LdapSchemaException( I18n.err( I18n.ERR_13825_COMP_DESCRIPTION_INVALID, 4165 pos.lineNumber, pos.start ) ); 4166 } 4167 } 4168 4169 // Semantic checks 4170 if ( !hasFqcn ) 4171 { 4172 throw new LdapSchemaException( I18n.err( I18n.ERR_13819_FQCN_REQUIRED, 4173 pos.lineNumber, pos.start ) ); 4174 } 4175 4176 if ( ( hasByteCode ) && ( ldapComparator.getBytecode().length() % 4 != 0 ) ) 4177 { 4178 throw new LdapSchemaException( I18n.err( I18n.ERR_13820_BYTE_CODE_REQUIRED, 4179 pos.lineNumber, pos.start ) ); 4180 } 4181 4182 return ldapComparator; 4183 } 4184 4185 4186 /** 4187 * Production for LdapComparator descriptions. It is fault-tolerant 4188 * against element ordering. 4189 * 4190 * <pre> 4191 * LdapComparatorDescription = LPAREN WSP 4192 * numericoid ; object identifier 4193 * [ SP "DESC" SP qdstring ] ; description 4194 * SP "FQCN" SP fqcn ; fully qualified class name 4195 * [ SP "BYTECODE" SP base64 ] ; optional base64 encoded bytecode 4196 * extensions WSP RPAREN ; extensions 4197 * 4198 * base64 = *(4base64-char) 4199 * base64-char = ALPHA / DIGIT / "+" / "/" 4200 * fqcn = fqcnComponent 1*( DOT fqcnComponent ) 4201 * fqcnComponent = ??? 4202 * </pre> 4203 * 4204 * @param reader The stream reader 4205 * @param pos The position in the Schema 4206 * @param objectIdentifierMacros The set of existing Macros 4207 * @return An instance of LdapComparatorDescription 4208 * @throws LdapSchemaException If the schema is wrong 4209 * @throws IOException If the stream can't be read 4210 */ 4211 private static LdapComparatorDescription parseLdapComparatorRelaxed( Reader reader, PosSchema pos, 4212 Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 4213 throws IOException, LdapSchemaException 4214 { 4215 // Get rid of whites, comments end empty lines 4216 skipWhites( reader, pos, false ); 4217 4218 // we must have a '(' 4219 if ( pos.line.charAt( pos.start ) != LPAREN ) 4220 { 4221 throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 4222 pos.lineNumber, pos.start ) ); 4223 } 4224 else 4225 { 4226 pos.start++; 4227 } 4228 4229 // Get rid of whites, comments end empty lines 4230 skipWhites( reader, pos, false ); 4231 4232 // Now, the OID. 4233 String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros ); 4234 4235 LdapComparatorDescription ldapComparator = new LdapComparatorDescription( oid ); 4236 int elementsSeen = 0; 4237 4238 while ( true ) 4239 { 4240 if ( startsWith( reader, pos, RPAREN ) ) 4241 { 4242 pos.start++; 4243 break; 4244 } 4245 4246 skipWhites( reader, pos, true ); 4247 4248 if ( startsWith( pos, DESC_STR ) ) 4249 { 4250 elementsSeen = checkElement( elementsSeen, LdapComparatorElements.DESC, pos ); 4251 4252 pos.start += DESC_STR.length(); 4253 4254 skipWhites( reader, pos, true ); 4255 4256 ldapComparator.setDescription( getQDString( reader, pos ) ); 4257 } 4258 else if ( startsWith( pos, FQCN_STR ) ) 4259 { 4260 elementsSeen = checkElement( elementsSeen, LdapComparatorElements.FQCN, pos ); 4261 4262 pos.start += FQCN_STR.length(); 4263 4264 skipWhites( reader, pos, true ); 4265 4266 String fqcn = getFqcn( pos ); 4267 ldapComparator.setFqcn( fqcn ); 4268 } 4269 else if ( startsWith( pos, BYTECODE_STR ) ) 4270 { 4271 elementsSeen = checkElement( elementsSeen, LdapComparatorElements.BYTECODE, pos ); 4272 4273 pos.start += BYTECODE_STR.length(); 4274 4275 skipWhites( reader, pos, true ); 4276 4277 String byteCode = getByteCode( pos ); 4278 ldapComparator.setBytecode( byteCode ); 4279 } 4280 else if ( startsWith( pos, EXTENSION_PREFIX ) ) 4281 { 4282 processExtension( reader, pos, ldapComparator ); 4283 } 4284 else if ( startsWith( reader, pos, RPAREN ) ) 4285 { 4286 pos.start++; 4287 break; 4288 } 4289 else 4290 { 4291 // This is an error 4292 throw new LdapSchemaException( I18n.err( I18n.ERR_13825_COMP_DESCRIPTION_INVALID, 4293 pos.lineNumber, pos.start ) ); 4294 } 4295 } 4296 4297 return ldapComparator; 4298 } 4299 4300 4301 /** 4302 * Production for matching ldap syntax descriptions. It is fault-tolerant 4303 * against element ordering. 4304 * 4305 * <pre> 4306 * SyntaxDescription = LPAREN WSP 4307 * numericoid ; object identifier 4308 * [ SP "DESC" SP qdstring ] ; description 4309 * extensions WSP RPAREN ; extensions 4310 * </pre> 4311 * 4312 * @param ldapSyntaxDescription The String containing the Ldap Syntax description 4313 * @return An instance of LdapSyntax 4314 * @throws ParseException If the element was invalid 4315 */ 4316 public LdapSyntax parseLdapSyntax( String ldapSyntaxDescription ) throws ParseException 4317 { 4318 if ( ( ldapSyntaxDescription == null ) || Strings.isEmpty( ldapSyntaxDescription.trim() ) ) 4319 { 4320 throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 ); 4321 } 4322 4323 try ( Reader reader = new BufferedReader( new StringReader( ldapSyntaxDescription ) ) ) 4324 { 4325 PosSchema pos = new PosSchema(); 4326 4327 if ( isQuirksModeEnabled ) 4328 { 4329 return parseLdapSyntaxRelaxed( reader, pos, objectIdentifierMacros ); 4330 } 4331 else 4332 { 4333 return parseLdapSyntaxStrict( reader, pos, objectIdentifierMacros ); 4334 } 4335 } 4336 catch ( IOException | LdapSchemaException e ) 4337 { 4338 throw new ParseException( e.getMessage(), 0 ); 4339 } 4340 } 4341 4342 4343 /** 4344 * Production for matching ldap syntax descriptions. It is fault-tolerant 4345 * against element ordering. 4346 * 4347 * <pre> 4348 * SyntaxDescription = LPAREN WSP 4349 * numericoid ; object identifier 4350 * [ SP "DESC" SP qdstring ] ; description 4351 * extensions WSP RPAREN ; extensions 4352 * </pre> 4353 * 4354 * @param reader The stream reader 4355 * @param pos The position in the Schema 4356 * @param objectIdentifierMacros The set of existing Macros 4357 * @return An instance of LdapSyntax 4358 * @throws LdapSchemaException If the schema is wrong 4359 * @throws IOException If the stream can't be read 4360 */ 4361 private static LdapSyntax parseLdapSyntaxStrict( Reader reader, PosSchema pos, 4362 Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException 4363 { 4364 // Get rid of whites, comments end empty lines 4365 skipWhites( reader, pos, false ); 4366 4367 // we must have a '(' 4368 if ( pos.line.charAt( pos.start ) != LPAREN ) 4369 { 4370 throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 4371 pos.lineNumber, pos.start ) ); 4372 } 4373 else 4374 { 4375 pos.start++; 4376 } 4377 4378 // Get rid of whites, comments end empty lines 4379 skipWhites( reader, pos, false ); 4380 4381 // Now, the OID. 4382 String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros ); 4383 4384 // Check that the OID is valid 4385 if ( !Oid.isOid( oid ) ) 4386 { 4387 throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) ); 4388 } 4389 4390 LdapSyntax ldapSyntax = new LdapSyntax( oid ); 4391 int elementsSeen = 0; 4392 4393 while ( true ) 4394 { 4395 if ( startsWith( reader, pos, RPAREN ) ) 4396 { 4397 pos.start++; 4398 break; 4399 } 4400 4401 skipWhites( reader, pos, true ); 4402 4403 if ( startsWith( pos, DESC_STR ) ) 4404 { 4405 elementsSeen = checkElement( elementsSeen, LdapSyntaxElements.DESC, pos ); 4406 4407 pos.start += DESC_STR.length(); 4408 4409 skipWhites( reader, pos, true ); 4410 4411 ldapSyntax.setDescription( getQDString( reader, pos ) ); 4412 } 4413 else if ( startsWith( pos, EXTENSION_PREFIX ) ) 4414 { 4415 processExtension( reader, pos, ldapSyntax ); 4416 } 4417 else if ( startsWith( reader, pos, RPAREN ) ) 4418 { 4419 pos.start++; 4420 break; 4421 } 4422 else 4423 { 4424 // This is an error 4425 throw new LdapSchemaException( I18n.err( I18n.ERR_13807_SYN_DESCRIPTION_INVALID, 4426 pos.lineNumber, pos.start ) ); 4427 } 4428 } 4429 4430 return ldapSyntax; 4431 } 4432 4433 4434 /** 4435 * Production for matching ldap syntax descriptions. It is fault-tolerant 4436 * against element ordering. 4437 * 4438 * <pre> 4439 * SyntaxDescription = LPAREN WSP 4440 * numericoid ; object identifier 4441 * [ SP "DESC" SP qdstring ] ; description 4442 * extensions WSP RPAREN ; extensions 4443 * </pre> 4444 * 4445 * @param reader The stream reader 4446 * @param pos The position in the Schema 4447 * @param objectIdentifierMacros The set of existing Macros 4448 * @return An instance of LdapSyntax 4449 * @throws LdapSchemaException If the schema is wrong 4450 * @throws IOException If the stream can't be read 4451 */ 4452 private static LdapSyntax parseLdapSyntaxRelaxed( Reader reader, PosSchema pos, 4453 Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException 4454 { 4455 // Get rid of whites, comments end empty lines 4456 skipWhites( reader, pos, false ); 4457 4458 // we must have a '(' 4459 if ( pos.line.charAt( pos.start ) != LPAREN ) 4460 { 4461 throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 4462 pos.lineNumber, pos.start ) ); 4463 } 4464 else 4465 { 4466 pos.start++; 4467 } 4468 4469 // Get rid of whites, comments end empty lines 4470 skipWhites( reader, pos, false ); 4471 4472 // Now, the OID. 4473 String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros ); 4474 4475 LdapSyntax ldapSyntax = new LdapSyntax( oid ); 4476 int elementsSeen = 0; 4477 4478 while ( true ) 4479 { 4480 if ( startsWith( reader, pos, RPAREN ) ) 4481 { 4482 pos.start++; 4483 break; 4484 } 4485 4486 skipWhites( reader, pos, true ); 4487 4488 if ( startsWith( pos, DESC_STR ) ) 4489 { 4490 elementsSeen = checkElement( elementsSeen, LdapSyntaxElements.DESC, pos ); 4491 4492 pos.start += DESC_STR.length(); 4493 4494 skipWhites( reader, pos, true ); 4495 4496 ldapSyntax.setDescription( getQDString( reader, pos ) ); 4497 } 4498 else if ( startsWith( pos, EXTENSION_PREFIX ) ) 4499 { 4500 processExtension( reader, pos, ldapSyntax ); 4501 } 4502 else if ( startsWith( reader, pos, RPAREN ) ) 4503 { 4504 pos.start++; 4505 break; 4506 } 4507 else 4508 { 4509 // This is an error 4510 throw new LdapSchemaException( I18n.err( I18n.ERR_13807_SYN_DESCRIPTION_INVALID, 4511 pos.lineNumber, pos.start ) ); 4512 } 4513 } 4514 4515 return ldapSyntax; 4516 } 4517 4518 4519 /** 4520 * Production for matching MatchingRule descriptions. It is fault-tolerant 4521 * against element ordering. 4522 * 4523 * <pre> 4524 * MatchingRuleDescription = LPAREN WSP 4525 * numericoid ; object identifier 4526 * [ SP "NAME" SP qdescrs ] ; short names (descriptors) 4527 * [ SP "DESC" SP qdstring ] ; description 4528 * [ SP "OBSOLETE" ] ; not active 4529 * SP "SYNTAX" SP numericoid ; assertion syntax 4530 * extensions WSP RPAREN ; extensions 4531 * </pre> 4532 * 4533 * @param matchingRuleDescription The String containing the MatchingRuledescription 4534 * @return An instance of MatchingRule 4535 * @throws ParseException If the element was invalid 4536 */ 4537 public MatchingRule parseMatchingRule( String matchingRuleDescription ) throws ParseException 4538 { 4539 if ( ( matchingRuleDescription == null ) || Strings.isEmpty( matchingRuleDescription.trim() ) ) 4540 { 4541 throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 ); 4542 } 4543 4544 try ( Reader reader = new BufferedReader( new StringReader( matchingRuleDescription ) ) ) 4545 { 4546 PosSchema pos = new PosSchema(); 4547 4548 if ( isQuirksModeEnabled ) 4549 { 4550 return parseMatchingRuleRelaxed( reader, pos, objectIdentifierMacros ); 4551 } 4552 else 4553 { 4554 return parseMatchingRuleStrict( reader, pos, objectIdentifierMacros ); 4555 } 4556 } 4557 catch ( IOException | LdapSchemaException e ) 4558 { 4559 throw new ParseException( e.getMessage(), 0 ); 4560 } 4561 } 4562 4563 4564 /** 4565 * Production for matching rule descriptions. It is fault-tolerant 4566 * against element ordering. 4567 * 4568 * <pre> 4569 * MatchingRuleDescription = LPAREN WSP 4570 * numericoid ; object identifier 4571 * [ SP "NAME" SP qdescrs ] ; short names (descriptors) 4572 * [ SP "DESC" SP qdstring ] ; description 4573 * [ SP "OBSOLETE" ] ; not active 4574 * SP "SYNTAX" SP numericoid ; assertion syntax 4575 * extensions WSP RPAREN ; extensions 4576 * </pre> 4577 * 4578 * @param reader The stream reader 4579 * @param pos The position in the Schema 4580 * @param objectIdentifierMacros The set of existing Macros 4581 * @return An instance of MatchingRule 4582 * @throws LdapSchemaException If the schema is wrong 4583 * @throws IOException If the stream can't be read 4584 */ 4585 private static MatchingRule parseMatchingRuleStrict( Reader reader, PosSchema pos, 4586 Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException 4587 { 4588 // Get rid of whites, comments end empty lines 4589 skipWhites( reader, pos, false ); 4590 4591 // we must have a '(' 4592 if ( pos.line.charAt( pos.start ) != LPAREN ) 4593 { 4594 throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 4595 pos.lineNumber, pos.start ) ); 4596 } 4597 else 4598 { 4599 pos.start++; 4600 } 4601 4602 // Get rid of whites, comments end empty lines 4603 skipWhites( reader, pos, false ); 4604 4605 // Now, the OID. 4606 String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros ); 4607 4608 // Check that the OID is valid 4609 if ( !Oid.isOid( oid ) ) 4610 { 4611 throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) ); 4612 } 4613 4614 MatchingRule matchingRule = new MatchingRule( oid ); 4615 int elementsSeen = 0; 4616 boolean hasSyntax = false; 4617 4618 while ( true ) 4619 { 4620 if ( startsWith( reader, pos, RPAREN ) ) 4621 { 4622 pos.start++; 4623 break; 4624 } 4625 4626 skipWhites( reader, pos, true ); 4627 4628 if ( startsWith( pos, NAME_STR ) ) 4629 { 4630 elementsSeen = checkElement( elementsSeen, MatchingRuleElements.NAME, pos ); 4631 4632 pos.start += NAME_STR.length(); 4633 4634 skipWhites( reader, pos, true ); 4635 4636 matchingRule.setNames( getQDescrs( reader, pos, STRICT ) ); 4637 } 4638 else if ( startsWith( pos, DESC_STR ) ) 4639 { 4640 elementsSeen = checkElement( elementsSeen, MatchingRuleElements.DESC, pos ); 4641 4642 pos.start += DESC_STR.length(); 4643 4644 skipWhites( reader, pos, true ); 4645 4646 matchingRule.setDescription( getQDString( reader, pos ) ); 4647 } 4648 else if ( startsWith( pos, OBSOLETE_STR ) ) 4649 { 4650 elementsSeen = checkElement( elementsSeen, MatchingRuleElements.OBSOLETE, pos ); 4651 4652 pos.start += OBSOLETE_STR.length(); 4653 4654 matchingRule.setObsolete( true ); 4655 } 4656 else if ( startsWith( pos, SYNTAX_STR ) ) 4657 { 4658 elementsSeen = checkElement( elementsSeen, MatchingRuleElements.SYNTAX, pos ); 4659 4660 pos.start += SYNTAX_STR.length(); 4661 4662 skipWhites( reader, pos, true ); 4663 4664 String syntaxOid = getNumericOid( pos ); 4665 4666 matchingRule.setSyntaxOid( syntaxOid ); 4667 hasSyntax = true; 4668 } 4669 else if ( startsWith( pos, EXTENSION_PREFIX ) ) 4670 { 4671 processExtension( reader, pos, matchingRule ); 4672 } 4673 else if ( startsWith( reader, pos, RPAREN ) ) 4674 { 4675 pos.start++; 4676 break; 4677 } 4678 else 4679 { 4680 // This is an error 4681 throw new LdapSchemaException( I18n.err( I18n.ERR_13781_MR_DESCRIPTION_INVALID, 4682 pos.lineNumber, pos.start ) ); 4683 } 4684 } 4685 4686 // Semantic checks 4687 if ( !hasSyntax ) 4688 { 4689 throw new LdapSchemaException( I18n.err( I18n.ERR_13808_SYNTAX_REQUIRED, 4690 pos.lineNumber, pos.start ) ); 4691 } 4692 4693 return matchingRule; 4694 } 4695 4696 4697 /** 4698 * Production for matching rule descriptions. It is fault-tolerant 4699 * against element ordering. 4700 * 4701 * <pre> 4702 * MatchingRuleDescription = LPAREN WSP 4703 * numericoid ; object identifier 4704 * [ SP "NAME" SP qdescrs ] ; short names (descriptors) 4705 * [ SP "DESC" SP qdstring ] ; description 4706 * [ SP "OBSOLETE" ] ; not active 4707 * SP "SYNTAX" SP numericoid ; assertion syntax 4708 * extensions WSP RPAREN ; extensions 4709 * </pre> 4710 * 4711 * @param reader The stream reader 4712 * @param pos The position in the Schema 4713 * @param objectIdentifierMacros The set of existing Macros 4714 * @return An instance of MatchingRule 4715 * @throws LdapSchemaException If the schema is wrong 4716 * @throws IOException If the stream can't be read 4717 */ 4718 private static MatchingRule parseMatchingRuleRelaxed( Reader reader, PosSchema pos, 4719 Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 4720 throws IOException, LdapSchemaException 4721 { 4722 // Get rid of whites, comments end empty lines 4723 skipWhites( reader, pos, false ); 4724 4725 // we must have a '(' 4726 if ( pos.line.charAt( pos.start ) != LPAREN ) 4727 { 4728 throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 4729 pos.lineNumber, pos.start ) ); 4730 } 4731 else 4732 { 4733 pos.start++; 4734 } 4735 4736 // Get rid of whites, comments end empty lines 4737 skipWhites( reader, pos, false ); 4738 4739 // Now, the OID. 4740 String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros ); 4741 4742 MatchingRule matchingRule = new MatchingRule( oid ); 4743 int elementsSeen = 0; 4744 4745 while ( true ) 4746 { 4747 if ( startsWith( reader, pos, RPAREN ) ) 4748 { 4749 pos.start++; 4750 break; 4751 } 4752 4753 skipWhites( reader, pos, true ); 4754 4755 if ( startsWith( pos, NAME_STR ) ) 4756 { 4757 elementsSeen = checkElement( elementsSeen, MatchingRuleElements.NAME, pos ); 4758 4759 pos.start += NAME_STR.length(); 4760 4761 skipWhites( reader, pos, true ); 4762 4763 matchingRule.setNames( getQDescrs( reader, pos, RELAXED ) ); 4764 } 4765 else if ( startsWith( pos, DESC_STR ) ) 4766 { 4767 elementsSeen = checkElement( elementsSeen, MatchingRuleElements.DESC, pos ); 4768 4769 pos.start += DESC_STR.length(); 4770 4771 skipWhites( reader, pos, true ); 4772 4773 matchingRule.setDescription( getQDString( reader, pos ) ); 4774 } 4775 else if ( startsWith( pos, OBSOLETE_STR ) ) 4776 { 4777 elementsSeen = checkElement( elementsSeen, MatchingRuleElements.OBSOLETE, pos ); 4778 4779 pos.start += OBSOLETE_STR.length(); 4780 4781 matchingRule.setObsolete( true ); 4782 } 4783 else if ( startsWith( pos, SYNTAX_STR ) ) 4784 { 4785 elementsSeen = checkElement( elementsSeen, MatchingRuleElements.SYNTAX, pos ); 4786 4787 pos.start += SYNTAX_STR.length(); 4788 4789 skipWhites( reader, pos, true ); 4790 4791 String syntaxOid = getNumericOid( pos ); 4792 4793 matchingRule.setSyntaxOid( syntaxOid ); 4794 } 4795 else if ( startsWith( pos, EXTENSION_PREFIX ) ) 4796 { 4797 processExtension( reader, pos, matchingRule ); 4798 } 4799 else if ( startsWith( reader, pos, RPAREN ) ) 4800 { 4801 pos.start++; 4802 break; 4803 } 4804 else 4805 { 4806 // This is an error 4807 throw new LdapSchemaException( I18n.err( I18n.ERR_13781_MR_DESCRIPTION_INVALID, 4808 pos.lineNumber, pos.start ) ); 4809 } 4810 } 4811 4812 return matchingRule; 4813 } 4814 4815 4816 /** 4817 * Production for matching MatchingRuleUse descriptions. It is fault-tolerant 4818 * against element ordering. 4819 * 4820 * <pre> 4821 * MatchingRuleUseDescription = LPAREN WSP 4822 * numericoid ; object identifier 4823 * [ SP "NAME" SP qdescrs ] ; short names (descriptors) 4824 * [ SP "DESC" SP qdstring ] ; description 4825 * [ SP "OBSOLETE" ] ; not active 4826 * SP "APPLIES" SP oids ; attribute types 4827 * extensions WSP RPAREN ; extensions 4828 * </pre> 4829 * 4830 * @param matchingRuleUseDescription The String containing the MatchingRuleUsedescription 4831 * @return An instance of MatchingRuleUse 4832 * @throws ParseException If the element was invalid 4833 */ 4834 public MatchingRuleUse parseMatchingRuleUse( String matchingRuleUseDescription ) throws ParseException 4835 { 4836 if ( ( matchingRuleUseDescription == null ) || Strings.isEmpty( matchingRuleUseDescription.trim() ) ) 4837 { 4838 throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 ); 4839 } 4840 4841 try ( Reader reader = new BufferedReader( new StringReader( matchingRuleUseDescription ) ) ) 4842 { 4843 PosSchema pos = new PosSchema(); 4844 4845 if ( isQuirksModeEnabled ) 4846 { 4847 return parseMatchingRuleUseRelaxed( reader, pos, objectIdentifierMacros ); 4848 } 4849 else 4850 { 4851 return parseMatchingRuleUseStrict( reader, pos, objectIdentifierMacros ); 4852 } 4853 } 4854 catch ( IOException | LdapSchemaException e ) 4855 { 4856 throw new ParseException( e.getMessage(), 0 ); 4857 } 4858 } 4859 4860 4861 /** 4862 * Production for MatchingRuleUse descriptions. It is fault-tolerant 4863 * against element ordering. 4864 * 4865 * <pre> 4866 * MatchingRuleUseDescription = LPAREN WSP 4867 * numericoid ; object identifier 4868 * [ SP "NAME" SP qdescrs ] ; short names (descriptors) 4869 * [ SP "DESC" SP qdstring ] ; description 4870 * [ SP "OBSOLETE" ] ; not active 4871 * SP "APPLIES" SP oids ; attribute types 4872 * extensions WSP RPAREN ; extensions 4873 * </pre> 4874 * 4875 * @param reader The stream reader 4876 * @param pos The position in the Schema 4877 * @param objectIdentifierMacros The set of existing Macros 4878 * @return An instance of MatchingRuleUse 4879 * @throws LdapSchemaException If the schema is wrong 4880 * @throws IOException If the stream can't be read 4881 */ 4882 private static MatchingRuleUse parseMatchingRuleUseStrict( Reader reader, PosSchema pos, 4883 Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException 4884 { 4885 // Get rid of whites, comments end empty lines 4886 skipWhites( reader, pos, false ); 4887 4888 // we must have a '(' 4889 if ( pos.line.charAt( pos.start ) != LPAREN ) 4890 { 4891 throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 4892 pos.lineNumber, pos.start ) ); 4893 } 4894 else 4895 { 4896 pos.start++; 4897 } 4898 4899 // Get rid of whites, comments end empty lines 4900 skipWhites( reader, pos, false ); 4901 4902 // Now, the OID. 4903 String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros ); 4904 4905 // Check that the OID is valid 4906 if ( !Oid.isOid( oid ) ) 4907 { 4908 throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) ); 4909 } 4910 4911 MatchingRuleUse matchingRuleUse = new MatchingRuleUse( oid ); 4912 int elementsSeen = 0; 4913 boolean hasApplies = false; 4914 4915 while ( true ) 4916 { 4917 if ( startsWith( reader, pos, RPAREN ) ) 4918 { 4919 pos.start++; 4920 break; 4921 } 4922 4923 skipWhites( reader, pos, false ); 4924 4925 if ( startsWith( pos, NAME_STR ) ) 4926 { 4927 elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.NAME, pos ); 4928 4929 pos.start += NAME_STR.length(); 4930 4931 skipWhites( reader, pos, true ); 4932 4933 matchingRuleUse.setNames( getQDescrs( reader, pos, STRICT ) ); 4934 } 4935 else if ( startsWith( pos, DESC_STR ) ) 4936 { 4937 elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.DESC, pos ); 4938 4939 pos.start += DESC_STR.length(); 4940 4941 skipWhites( reader, pos, true ); 4942 4943 matchingRuleUse.setDescription( getQDString( reader, pos ) ); 4944 } 4945 else if ( startsWith( pos, OBSOLETE_STR ) ) 4946 { 4947 elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.OBSOLETE, pos ); 4948 4949 pos.start += OBSOLETE_STR.length(); 4950 4951 matchingRuleUse.setObsolete( true ); 4952 } 4953 else if ( startsWith( pos, APPLIES_STR ) ) 4954 { 4955 elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.APPLIES, pos ); 4956 4957 pos.start += APPLIES_STR.length(); 4958 4959 skipWhites( reader, pos, true ); 4960 4961 List<String> oids = getOidsStrict( reader, pos ); 4962 4963 matchingRuleUse.setApplicableAttributeOids( oids ); 4964 hasApplies = true; 4965 } 4966 else if ( startsWith( pos, EXTENSION_PREFIX ) ) 4967 { 4968 processExtension( reader, pos, matchingRuleUse ); 4969 } 4970 else if ( startsWith( reader, pos, RPAREN ) ) 4971 { 4972 pos.start++; 4973 break; 4974 } 4975 else 4976 { 4977 // This is an error 4978 throw new LdapSchemaException( I18n.err( I18n.ERR_13815_MRU_DESCRIPTION_INVALID, 4979 pos.lineNumber, pos.start ) ); 4980 } 4981 } 4982 4983 // Semantic checks 4984 if ( !hasApplies ) 4985 { 4986 throw new LdapSchemaException( I18n.err( I18n.ERR_13814_APPLIES_REQUIRED, 4987 pos.lineNumber, pos.start ) ); 4988 } 4989 4990 return matchingRuleUse; 4991 } 4992 4993 4994 /** 4995 * Production for MatchingRuleUse descriptions. It is fault-tolerant 4996 * against element ordering. 4997 * 4998 * <pre> 4999 * MatchingRuleUseDescription = LPAREN WSP 5000 * numericoid ; object identifier 5001 * [ SP "NAME" SP qdescrs ] ; short names (descriptors) 5002 * [ SP "DESC" SP qdstring ] ; description 5003 * [ SP "OBSOLETE" ] ; not active 5004 * SP "APPLIES" SP oids ; attribute types 5005 * extensions WSP RPAREN ; extensions 5006 * </pre> 5007 * 5008 * @param reader The stream reader 5009 * @param pos The position in the Schema 5010 * @param objectIdentifierMacros The set of existing Macros 5011 * @return An instance of MatchingRuleUse 5012 * @throws LdapSchemaException If the schema is wrong 5013 * @throws IOException If the stream can't be read 5014 */ 5015 private static MatchingRuleUse parseMatchingRuleUseRelaxed( Reader reader, PosSchema pos, 5016 Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 5017 throws IOException, LdapSchemaException 5018 { 5019 // Get rid of whites, comments end empty lines 5020 skipWhites( reader, pos, false ); 5021 5022 // we must have a '(' 5023 if ( pos.line.charAt( pos.start ) != LPAREN ) 5024 { 5025 throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 5026 pos.lineNumber, pos.start ) ); 5027 } 5028 else 5029 { 5030 pos.start++; 5031 } 5032 5033 // Get rid of whites, comments end empty lines 5034 skipWhites( reader, pos, false ); 5035 5036 // Now, the OID. 5037 String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros ); 5038 5039 MatchingRuleUse matchingRuleUse = new MatchingRuleUse( oid ); 5040 int elementsSeen = 0; 5041 5042 while ( true ) 5043 { 5044 if ( startsWith( reader, pos, RPAREN ) ) 5045 { 5046 pos.start++; 5047 break; 5048 } 5049 5050 skipWhites( reader, pos, true ); 5051 5052 if ( startsWith( pos, NAME_STR ) ) 5053 { 5054 elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.NAME, pos ); 5055 5056 pos.start += NAME_STR.length(); 5057 5058 skipWhites( reader, pos, true ); 5059 5060 matchingRuleUse.setNames( getQDescrs( reader, pos, RELAXED ) ); 5061 } 5062 else if ( startsWith( pos, DESC_STR ) ) 5063 { 5064 elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.DESC, pos ); 5065 5066 pos.start += DESC_STR.length(); 5067 5068 skipWhites( reader, pos, true ); 5069 5070 matchingRuleUse.setDescription( getQDString( reader, pos ) ); 5071 } 5072 else if ( startsWith( pos, OBSOLETE_STR ) ) 5073 { 5074 elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.OBSOLETE, pos ); 5075 5076 pos.start += OBSOLETE_STR.length(); 5077 5078 matchingRuleUse.setObsolete( true ); 5079 } 5080 else if ( startsWith( pos, APPLIES_STR ) ) 5081 { 5082 elementsSeen = checkElement( elementsSeen, MatchingRuleUseElements.APPLIES, pos ); 5083 5084 pos.start += APPLIES_STR.length(); 5085 5086 skipWhites( reader, pos, true ); 5087 5088 List<String> oids = getOidsRelaxed( reader, pos ); 5089 5090 matchingRuleUse.setApplicableAttributeOids( oids ); 5091 } 5092 else if ( startsWith( pos, EXTENSION_PREFIX ) ) 5093 { 5094 processExtension( reader, pos, matchingRuleUse ); 5095 } 5096 else if ( startsWith( reader, pos, RPAREN ) ) 5097 { 5098 pos.start++; 5099 break; 5100 } 5101 else 5102 { 5103 // This is an error 5104 throw new LdapSchemaException( I18n.err( I18n.ERR_13815_MRU_DESCRIPTION_INVALID, 5105 pos.lineNumber, pos.start ) ); 5106 } 5107 } 5108 5109 return matchingRuleUse; 5110 } 5111 5112 5113 /** 5114 * Production for NameForm descriptions. It is fault-tolerant 5115 * against element ordering. 5116 * 5117 * <pre> 5118 * NameFormDescription = LPAREN WSP 5119 * numericoid ; object identifier 5120 * [ SP "NAME" SP qdescrs ] ; short names (descriptors) 5121 * [ SP "DESC" SP qdstring ] ; description 5122 * [ SP "OBSOLETE" ] ; not active 5123 * SP "OC" SP oid ; structural object class 5124 * SP "MUST" SP oids ; attribute types 5125 * [ SP "MAY" SP oids ] ; attribute types 5126 * extensions WSP RPAREN ; extensions 5127 * </pre> 5128 * 5129 * @param nameFormDescription The String containing the NameFormdescription 5130 * @return An instance of NameForm 5131 * @throws ParseException If the element was invalid 5132 */ 5133 public NameForm parseNameForm( String nameFormDescription ) throws ParseException 5134 { 5135 if ( ( nameFormDescription == null ) || Strings.isEmpty( nameFormDescription.trim() ) ) 5136 { 5137 throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 ); 5138 } 5139 5140 try ( Reader reader = new BufferedReader( new StringReader( nameFormDescription ) ) ) 5141 { 5142 PosSchema pos = new PosSchema(); 5143 5144 if ( isQuirksModeEnabled ) 5145 { 5146 return parseNameFormRelaxed( reader, pos, objectIdentifierMacros ); 5147 } 5148 else 5149 { 5150 return parseNameFormStrict( reader, pos, objectIdentifierMacros ); 5151 } 5152 } 5153 catch ( IOException | LdapSchemaException e ) 5154 { 5155 throw new ParseException( e.getMessage(), 0 ); 5156 } 5157 } 5158 5159 5160 /** 5161 * Production for NameForm descriptions. It is fault-tolerant 5162 * against element ordering. 5163 * 5164 * <pre> 5165 * NameFormDescription = LPAREN WSP 5166 * numericoid ; object identifier 5167 * [ SP "NAME" SP qdescrs ] ; short names (descriptors) 5168 * [ SP "DESC" SP qdstring ] ; description 5169 * [ SP "OBSOLETE" ] ; not active 5170 * SP "OC" SP oid ; structural object class 5171 * SP "MUST" SP oids ; attribute types 5172 * [ SP "MAY" SP oids ] ; attribute types 5173 * extensions WSP RPAREN ; extensions 5174 * </pre> 5175 * 5176 * @param reader The stream reader 5177 * @param pos The position in the Schema 5178 * @return An instance of NameForm 5179 * @param objectIdentifierMacros The set of existing Macros 5180 * @throws LdapSchemaException If the schema is wrong 5181 * @throws IOException If the stream can't be read 5182 */ 5183 private static NameForm parseNameFormStrict( Reader reader, PosSchema pos, 5184 Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException 5185 { 5186 // Get rid of whites, comments end empty lines 5187 skipWhites( reader, pos, false ); 5188 5189 // we must have a '(' 5190 if ( pos.line.charAt( pos.start ) != LPAREN ) 5191 { 5192 throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 5193 pos.lineNumber, pos.start ) ); 5194 } 5195 else 5196 { 5197 pos.start++; 5198 } 5199 5200 // Get rid of whites, comments end empty lines 5201 skipWhites( reader, pos, false ); 5202 5203 // Now, the OID. 5204 String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros ); 5205 5206 // Check that the OID is valid 5207 if ( !Oid.isOid( oid ) ) 5208 { 5209 throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) ); 5210 } 5211 5212 NameForm nameForm = new NameForm( oid ); 5213 int elementsSeen = 0; 5214 boolean hasOc = false; 5215 boolean hasMust = false; 5216 5217 while ( true ) 5218 { 5219 if ( startsWith( reader, pos, RPAREN ) ) 5220 { 5221 pos.start++; 5222 break; 5223 } 5224 5225 skipWhites( reader, pos, true ); 5226 5227 if ( startsWith( pos, NAME_STR ) ) 5228 { 5229 elementsSeen = checkElement( elementsSeen, NameFormElements.NAME, pos ); 5230 5231 pos.start += NAME_STR.length(); 5232 5233 skipWhites( reader, pos, true ); 5234 5235 nameForm.setNames( getQDescrs( reader, pos, STRICT ) ); 5236 } 5237 else if ( startsWith( pos, DESC_STR ) ) 5238 { 5239 elementsSeen = checkElement( elementsSeen, NameFormElements.DESC, pos ); 5240 5241 pos.start += DESC_STR.length(); 5242 5243 skipWhites( reader, pos, true ); 5244 5245 nameForm.setDescription( getQDString( reader, pos ) ); 5246 } 5247 else if ( startsWith( pos, OBSOLETE_STR ) ) 5248 { 5249 elementsSeen = checkElement( elementsSeen, NameFormElements.OBSOLETE, pos ); 5250 5251 pos.start += OBSOLETE_STR.length(); 5252 5253 nameForm.setObsolete( true ); 5254 } 5255 else if ( startsWith( pos, OC_STR ) ) 5256 { 5257 elementsSeen = checkElement( elementsSeen, NameFormElements.OC, pos ); 5258 5259 pos.start += OC_STR.length(); 5260 5261 skipWhites( reader, pos, true ); 5262 5263 String oc = getOidStrict( pos ); 5264 5265 nameForm.setStructuralObjectClassOid( oc ); 5266 hasOc = true; 5267 } 5268 else if ( startsWith( pos, MUST_STR ) ) 5269 { 5270 elementsSeen = checkElement( elementsSeen, NameFormElements.MUST, pos ); 5271 5272 pos.start += MUST_STR.length(); 5273 5274 skipWhites( reader, pos, true ); 5275 5276 List<String> must = getOidsStrict( reader, pos ); 5277 5278 nameForm.setMustAttributeTypeOids( must ); 5279 hasMust = true; 5280 } 5281 else if ( startsWith( pos, MAY_STR ) ) 5282 { 5283 elementsSeen = checkElement( elementsSeen, NameFormElements.MAY, pos ); 5284 5285 pos.start += MAY_STR.length(); 5286 5287 skipWhites( reader, pos, true ); 5288 5289 List<String> may = getOidsStrict( reader, pos ); 5290 5291 nameForm.setMayAttributeTypeOids( may ); 5292 } 5293 else if ( startsWith( pos, EXTENSION_PREFIX ) ) 5294 { 5295 processExtension( reader, pos, nameForm ); 5296 } 5297 else if ( startsWith( reader, pos, RPAREN ) ) 5298 { 5299 pos.start++; 5300 break; 5301 } 5302 else 5303 { 5304 // This is an error 5305 throw new LdapSchemaException( I18n.err( I18n.ERR_13816_NF_DESCRIPTION_INVALID, 5306 pos.lineNumber, pos.start ) ); 5307 } 5308 } 5309 5310 // Semantic checks 5311 if ( !hasOc ) 5312 { 5313 throw new LdapSchemaException( I18n.err( I18n.ERR_13817_STRUCTURAL_OBJECT_CLASS_REQUIRED, 5314 pos.lineNumber, pos.start ) ); 5315 } 5316 5317 if ( !hasMust ) 5318 { 5319 throw new LdapSchemaException( I18n.err( I18n.ERR_13818_MUST_REQUIRED, 5320 pos.lineNumber, pos.start ) ); 5321 } 5322 5323 return nameForm; 5324 } 5325 5326 5327 /** 5328 * Production for NameForm descriptions. It is fault-tolerant 5329 * against element ordering. 5330 * 5331 * <pre> 5332 * NameFormDescription = LPAREN WSP 5333 * numericoid ; object identifier 5334 * [ SP "NAME" SP qdescrs ] ; short names (descriptors) 5335 * [ SP "DESC" SP qdstring ] ; description 5336 * [ SP "OBSOLETE" ] ; not active 5337 * SP "OC" SP oid ; structural object class 5338 * SP "MUST" SP oids ; attribute types 5339 * [ SP "MAY" SP oids ] ; attribute types 5340 * extensions WSP RPAREN ; extensions 5341 * </pre> 5342 * 5343 * @param reader The stream reader 5344 * @param pos The position in the Schema 5345 * @param objectIdentifierMacros The set of existing Macros 5346 * @return An instance of NameForm 5347 * @throws LdapSchemaException If the schema is wrong 5348 * @throws IOException If the stream can't be read 5349 */ 5350 private static NameForm parseNameFormRelaxed( Reader reader, PosSchema pos, 5351 Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 5352 throws IOException, LdapSchemaException 5353 { 5354 // Get rid of whites, comments end empty lines 5355 skipWhites( reader, pos, false ); 5356 5357 // we must have a '(' 5358 if ( pos.line.charAt( pos.start ) != LPAREN ) 5359 { 5360 throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 5361 pos.lineNumber, pos.start ) ); 5362 } 5363 else 5364 { 5365 pos.start++; 5366 } 5367 5368 // Get rid of whites, comments end empty lines 5369 skipWhites( reader, pos, false ); 5370 5371 // Now, the OID. 5372 String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros ); 5373 5374 NameForm nameForm = new NameForm( oid ); 5375 int elementsSeen = 0; 5376 5377 while ( true ) 5378 { 5379 if ( startsWith( reader, pos, RPAREN ) ) 5380 { 5381 pos.start++; 5382 break; 5383 } 5384 5385 skipWhites( reader, pos, true ); 5386 5387 if ( startsWith( pos, NAME_STR ) ) 5388 { 5389 elementsSeen = checkElement( elementsSeen, NameFormElements.NAME, pos ); 5390 5391 pos.start += NAME_STR.length(); 5392 5393 skipWhites( reader, pos, true ); 5394 5395 nameForm.setNames( getQDescrs( reader, pos, RELAXED ) ); 5396 } 5397 else if ( startsWith( pos, DESC_STR ) ) 5398 { 5399 elementsSeen = checkElement( elementsSeen, NameFormElements.DESC, pos ); 5400 5401 pos.start += DESC_STR.length(); 5402 5403 skipWhites( reader, pos, true ); 5404 5405 nameForm.setDescription( getQDString( reader, pos ) ); 5406 } 5407 else if ( startsWith( pos, OBSOLETE_STR ) ) 5408 { 5409 elementsSeen = checkElement( elementsSeen, NameFormElements.OBSOLETE, pos ); 5410 5411 pos.start += OBSOLETE_STR.length(); 5412 5413 nameForm.setObsolete( true ); 5414 } 5415 else if ( startsWith( pos, OC_STR ) ) 5416 { 5417 elementsSeen = checkElement( elementsSeen, NameFormElements.OC, pos ); 5418 5419 pos.start += OC_STR.length(); 5420 5421 skipWhites( reader, pos, true ); 5422 5423 String oc = getOidRelaxed( pos, UN_QUOTED ); 5424 5425 nameForm.setStructuralObjectClassOid( oc ); 5426 } 5427 else if ( startsWith( pos, MUST_STR ) ) 5428 { 5429 elementsSeen = checkElement( elementsSeen, NameFormElements.MUST, pos ); 5430 5431 pos.start += MUST_STR.length(); 5432 5433 skipWhites( reader, pos, true ); 5434 5435 List<String> must = getOidsRelaxed( reader, pos ); 5436 5437 nameForm.setMustAttributeTypeOids( must ); 5438 } 5439 else if ( startsWith( pos, MAY_STR ) ) 5440 { 5441 elementsSeen = checkElement( elementsSeen, NameFormElements.MAY, pos ); 5442 5443 pos.start += MAY_STR.length(); 5444 5445 skipWhites( reader, pos, true ); 5446 5447 List<String> may = getOidsRelaxed( reader, pos ); 5448 5449 nameForm.setMayAttributeTypeOids( may ); 5450 } 5451 else if ( startsWith( pos, EXTENSION_PREFIX ) ) 5452 { 5453 processExtension( reader, pos, nameForm ); 5454 } 5455 else if ( startsWith( reader, pos, RPAREN ) ) 5456 { 5457 pos.start++; 5458 break; 5459 } 5460 else 5461 { 5462 // This is an error 5463 throw new LdapSchemaException( I18n.err( I18n.ERR_13816_NF_DESCRIPTION_INVALID, 5464 pos.lineNumber, pos.start ) ); 5465 } 5466 } 5467 5468 return nameForm; 5469 } 5470 5471 5472 /** 5473 * Production for Normalizer descriptions. It is fault-tolerant 5474 * against element ordering. 5475 * 5476 * <pre> 5477 * NormalizerDescription = LPAREN WSP 5478 * numericoid ; object identifier 5479 * [ SP "DESC" SP qdstring ] ; description 5480 * SP "FQCN" SP fqcn ; fully qualified class name 5481 * [ SP "BYTECODE" SP base64 ] ; optional base64 encoded bytecode 5482 * extensions WSP RPAREN ; extensions 5483 * 5484 * base64 = *(4base64-char) 5485 * base64-char = ALPHA / DIGIT / "+" / "/" 5486 * fqcn = fqcnComponent 1*( DOT fqcnComponent ) 5487 * fqcnComponent = ??? 5488 * </pre> 5489 * 5490 * @param normalizerDescription The String containing the NormalizerDescription 5491 * @return An instance of NormalizerDescription 5492 * @throws ParseException If the element was invalid 5493 */ 5494 public NormalizerDescription parseNormalizer( String normalizerDescription ) throws ParseException 5495 { 5496 if ( ( normalizerDescription == null ) || Strings.isEmpty( normalizerDescription.trim() ) ) 5497 { 5498 throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 ); 5499 } 5500 5501 try ( Reader reader = new BufferedReader( new StringReader( normalizerDescription ) ) ) 5502 { 5503 PosSchema pos = new PosSchema(); 5504 5505 if ( isQuirksModeEnabled ) 5506 { 5507 return parseNormalizerRelaxed( reader, pos, objectIdentifierMacros ); 5508 } 5509 else 5510 { 5511 return parseNormalizerStrict( reader, pos, objectIdentifierMacros ); 5512 } 5513 } 5514 catch ( IOException | LdapSchemaException e ) 5515 { 5516 throw new ParseException( e.getMessage(), 0 ); 5517 } 5518 } 5519 5520 5521 /** 5522 * Production for Normalizer descriptions. It is fault-tolerant 5523 * against element ordering. 5524 * 5525 * <pre> 5526 * NormalizerDescription = LPAREN WSP 5527 * numericoid ; object identifier 5528 * [ SP "DESC" SP qdstring ] ; description 5529 * SP "FQCN" SP fqcn ; fully qualified class name 5530 * [ SP "BYTECODE" SP base64 ] ; optional base64 encoded bytecode 5531 * extensions WSP RPAREN ; extensions 5532 * 5533 * base64 = *(4base64-char) 5534 * base64-char = ALPHA / DIGIT / "+" / "/" 5535 * fqcn = fqcnComponent 1*( DOT fqcnComponent ) 5536 * fqcnComponent = ??? 5537 * </pre> 5538 * 5539 * @param reader The stream reader 5540 * @param pos The position in the Schema 5541 * @param objectIdentifierMacros The set of existing Macros 5542 * @return An instance of NormalizerDescription 5543 * @throws LdapSchemaException If the schema is wrong 5544 * @throws IOException If the stream can't be read 5545 */ 5546 private static NormalizerDescription parseNormalizerStrict( Reader reader, PosSchema pos, 5547 Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException 5548 { 5549 // Get rid of whites, comments end empty lines 5550 skipWhites( reader, pos, false ); 5551 5552 // we must have a '(' 5553 if ( pos.line.charAt( pos.start ) != LPAREN ) 5554 { 5555 throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 5556 pos.lineNumber, pos.start ) ); 5557 } 5558 else 5559 { 5560 pos.start++; 5561 } 5562 5563 // Get rid of whites, comments end empty lines 5564 skipWhites( reader, pos, false ); 5565 5566 // Now, the OID. 5567 String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros ); 5568 5569 // Check that the OID is valid 5570 if ( !Oid.isOid( oid ) ) 5571 { 5572 throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) ); 5573 } 5574 5575 NormalizerDescription normalizer = new NormalizerDescription( oid ); 5576 int elementsSeen = 0; 5577 boolean hasFqcn = false; 5578 boolean hasByteCode = false; 5579 5580 while ( true ) 5581 { 5582 if ( startsWith( reader, pos, RPAREN ) ) 5583 { 5584 pos.start++; 5585 break; 5586 } 5587 5588 skipWhites( reader, pos, true ); 5589 5590 if ( startsWith( pos, DESC_STR ) ) 5591 { 5592 elementsSeen = checkElement( elementsSeen, NormalizerElements.DESC, pos ); 5593 5594 pos.start += DESC_STR.length(); 5595 5596 skipWhites( reader, pos, true ); 5597 5598 normalizer.setDescription( getQDString( reader, pos ) ); 5599 } 5600 else if ( startsWith( pos, FQCN_STR ) ) 5601 { 5602 elementsSeen = checkElement( elementsSeen, NormalizerElements.FQCN, pos ); 5603 5604 pos.start += FQCN_STR.length(); 5605 5606 skipWhites( reader, pos, true ); 5607 5608 String fqcn = getFqcn( pos ); 5609 normalizer.setFqcn( fqcn ); 5610 hasFqcn = true; 5611 } 5612 else if ( startsWith( pos, BYTECODE_STR ) ) 5613 { 5614 elementsSeen = checkElement( elementsSeen, NormalizerElements.BYTECODE, pos ); 5615 5616 pos.start += BYTECODE_STR.length(); 5617 5618 skipWhites( reader, pos, true ); 5619 5620 String byteCode = getByteCode( pos ); 5621 normalizer.setBytecode( byteCode ); 5622 hasByteCode = true; 5623 } 5624 else if ( startsWith( pos, EXTENSION_PREFIX ) ) 5625 { 5626 processExtension( reader, pos, normalizer ); 5627 } 5628 else if ( startsWith( reader, pos, RPAREN ) ) 5629 { 5630 pos.start++; 5631 break; 5632 } 5633 else 5634 { 5635 // This is an error 5636 throw new LdapSchemaException( I18n.err( I18n.ERR_13821_NORM_DESCRIPTION_INVALID, 5637 pos.lineNumber, pos.start ) ); 5638 } 5639 } 5640 5641 // Semantic checks 5642 if ( !hasFqcn ) 5643 { 5644 throw new LdapSchemaException( I18n.err( I18n.ERR_13819_FQCN_REQUIRED, 5645 pos.lineNumber, pos.start ) ); 5646 } 5647 5648 if ( ( hasByteCode ) && ( normalizer.getBytecode().length() % 4 != 0 ) ) 5649 { 5650 throw new LdapSchemaException( I18n.err( I18n.ERR_13820_BYTE_CODE_REQUIRED, 5651 pos.lineNumber, pos.start ) ); 5652 } 5653 5654 return normalizer; 5655 } 5656 5657 5658 /** 5659 * Production for Normalizer descriptions. It is fault-tolerant 5660 * against element ordering. 5661 * 5662 * <pre> 5663 * NormalizerDescription = LPAREN WSP 5664 * numericoid ; object identifier 5665 * [ SP "DESC" SP qdstring ] ; description 5666 * SP "FQCN" SP fqcn ; fully qualified class name 5667 * [ SP "BYTECODE" SP base64 ] ; optional base64 encoded bytecode 5668 * extensions WSP RPAREN ; extensions 5669 * 5670 * base64 = *(4base64-char) 5671 * base64-char = ALPHA / DIGIT / "+" / "/" 5672 * fqcn = fqcnComponent 1*( DOT fqcnComponent ) 5673 * fqcnComponent = ??? 5674 * </pre> 5675 * 5676 * @param reader The stream reader 5677 * @param pos The position in the Schema 5678 * @param objectIdentifierMacros The set of existing Macros 5679 * @return An instance of NormalizerDescription 5680 * @throws LdapSchemaException If the schema is wrong 5681 * @throws IOException If the stream can't be read 5682 */ 5683 private static NormalizerDescription parseNormalizerRelaxed( Reader reader, PosSchema pos, 5684 Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 5685 throws IOException, LdapSchemaException 5686 { 5687 // Get rid of whites, comments end empty lines 5688 skipWhites( reader, pos, false ); 5689 5690 // we must have a '(' 5691 if ( pos.line.charAt( pos.start ) != LPAREN ) 5692 { 5693 throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 5694 pos.lineNumber, pos.start ) ); 5695 } 5696 else 5697 { 5698 pos.start++; 5699 } 5700 5701 // Get rid of whites, comments end empty lines 5702 skipWhites( reader, pos, false ); 5703 5704 // Now, the OID. 5705 String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros ); 5706 5707 NormalizerDescription normalizer = new NormalizerDescription( oid ); 5708 int elementsSeen = 0; 5709 5710 while ( true ) 5711 { 5712 if ( startsWith( reader, pos, RPAREN ) ) 5713 { 5714 pos.start++; 5715 break; 5716 } 5717 5718 skipWhites( reader, pos, true ); 5719 5720 if ( startsWith( pos, DESC_STR ) ) 5721 { 5722 elementsSeen = checkElement( elementsSeen, NormalizerElements.DESC, pos ); 5723 5724 pos.start += DESC_STR.length(); 5725 5726 skipWhites( reader, pos, true ); 5727 5728 normalizer.setDescription( getQDString( reader, pos ) ); 5729 } 5730 else if ( startsWith( pos, FQCN_STR ) ) 5731 { 5732 elementsSeen = checkElement( elementsSeen, NormalizerElements.FQCN, pos ); 5733 5734 pos.start += FQCN_STR.length(); 5735 5736 skipWhites( reader, pos, true ); 5737 5738 String fqcn = getFqcn( pos ); 5739 normalizer.setFqcn( fqcn ); 5740 } 5741 else if ( startsWith( pos, BYTECODE_STR ) ) 5742 { 5743 elementsSeen = checkElement( elementsSeen, NormalizerElements.BYTECODE, pos ); 5744 5745 pos.start += BYTECODE_STR.length(); 5746 5747 skipWhites( reader, pos, true ); 5748 5749 String byteCode = getByteCode( pos ); 5750 normalizer.setBytecode( byteCode ); 5751 } 5752 else if ( startsWith( pos, EXTENSION_PREFIX ) ) 5753 { 5754 processExtension( reader, pos, normalizer ); 5755 } 5756 else if ( startsWith( reader, pos, RPAREN ) ) 5757 { 5758 pos.start++; 5759 break; 5760 } 5761 else 5762 { 5763 // This is an error 5764 throw new LdapSchemaException( I18n.err( I18n.ERR_13821_NORM_DESCRIPTION_INVALID, 5765 pos.lineNumber, pos.start ) ); 5766 } 5767 } 5768 5769 return normalizer; 5770 } 5771 5772 5773 /** 5774 * Production for matching ObjectClass descriptions. It is fault-tolerant 5775 * against element ordering. 5776 * 5777 * <pre> 5778 * ObjectClassDescription = LPAREN WSP 5779 * numericoid ; object identifier 5780 * [ SP "NAME" SP qdescrs ] ; short names (descriptors) 5781 * [ SP "DESC" SP qdstring ] ; description 5782 * [ SP "OBSOLETE" ] ; not active 5783 * [ SP "SUP" SP oids ] ; superior object classes 5784 * [ SP kind ] ; kind of class 5785 * [ SP "MUST" SP oids ] ; attribute types 5786 * [ SP "MAY" SP oids ] ; attribute types 5787 * extensions WSP RPAREN 5788 * 5789 * kind = "ABSTRACT" / "STRUCTURAL" / "AUXILIARY" 5790 * </pre> 5791 * 5792 * @param objectClassDescription The String containing the ObjectClassDescription 5793 * @return An instance of objectClass 5794 * @throws ParseException If the element was invalid 5795 */ 5796 public ObjectClass parseObjectClass( String objectClassDescription ) throws ParseException 5797 { 5798 if ( ( objectClassDescription == null ) || Strings.isEmpty( objectClassDescription.trim() ) ) 5799 { 5800 throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 ); 5801 } 5802 5803 try ( Reader reader = new BufferedReader( new StringReader( objectClassDescription ) ) ) 5804 { 5805 PosSchema pos = new PosSchema(); 5806 5807 if ( isQuirksModeEnabled ) 5808 { 5809 return parseObjectClassRelaxed( reader, pos, objectIdentifierMacros ); 5810 } 5811 else 5812 { 5813 return parseObjectClassStrict( reader, pos, objectIdentifierMacros ); 5814 } 5815 } 5816 catch ( IOException | LdapSchemaException e ) 5817 { 5818 throw new ParseException( e.getMessage(), 0 ); 5819 } 5820 } 5821 5822 5823 /** 5824 * Production for matching ObjectClass descriptions. It is fault-tolerant 5825 * against element ordering. 5826 * <pre> 5827 * ObjectClassDescription = LPAREN WSP 5828 * numericoid ; object identifier 5829 * [ SP "NAME" SP qdescrs ] ; short names (descriptors) 5830 * [ SP "DESC" SP qdstring ] ; description 5831 * [ SP "OBSOLETE" ] ; not active 5832 * [ SP "SUP" SP oids ] ; superior object classes 5833 * [ SP kind ] ; kind of class 5834 * [ SP "MUST" SP oids ] ; attribute types 5835 * [ SP "MAY" SP oids ] ; attribute types 5836 * extensions WSP RPAREN 5837 * 5838 * kind = "ABSTRACT" / "STRUCTURAL" / "AUXILIARY" 5839 * </pre> 5840 * 5841 * @param reader The stream reader 5842 * @param pos The position in the Schema 5843 * @param objectIdentifierMacros The set of existing Macros 5844 * @return An instance of ObjectClass 5845 * @throws LdapSchemaException If the schema is wrong 5846 * @throws IOException If the stream can't be read 5847 */ 5848 private static ObjectClass parseObjectClassStrict( Reader reader, PosSchema pos, 5849 Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 5850 throws IOException, LdapSchemaException 5851 { 5852 // Get rid of whites, comments end empty lines 5853 skipWhites( reader, pos, false ); 5854 5855 // we must have a '(' 5856 if ( pos.line.charAt( pos.start ) != LPAREN ) 5857 { 5858 throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 5859 pos.lineNumber, pos.start ) ); 5860 } 5861 else 5862 { 5863 pos.start++; 5864 } 5865 5866 // Get rid of whites, comments end empty lines 5867 skipWhites( reader, pos, false ); 5868 5869 // Now, the numeric OID 5870 String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros ); 5871 5872 // Check that the OID is valid 5873 if ( !Oid.isOid( oid ) ) 5874 { 5875 throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) ); 5876 } 5877 5878 ObjectClass objectClass = new ObjectClass( oid ); 5879 int elementsSeen = 0; 5880 5881 while ( true ) 5882 { 5883 if ( startsWith( reader, pos, RPAREN ) ) 5884 { 5885 pos.start++; 5886 break; 5887 } 5888 5889 skipWhites( reader, pos, true ); 5890 5891 if ( startsWith( pos, NAME_STR ) ) 5892 { 5893 elementsSeen = checkElement( elementsSeen, ObjectClassElements.NAME, pos ); 5894 5895 pos.start += NAME_STR.length(); 5896 5897 skipWhites( reader, pos, true ); 5898 5899 List<String> names = getQDescrs( reader, pos, STRICT ); 5900 objectClass.setNames( names ); 5901 } 5902 else if ( startsWith( pos, DESC_STR ) ) 5903 { 5904 elementsSeen = checkElement( elementsSeen, ObjectClassElements.DESC, pos ); 5905 5906 pos.start += DESC_STR.length(); 5907 5908 skipWhites( reader, pos, true ); 5909 5910 objectClass.setDescription( getQDString( reader, pos ) ); 5911 } 5912 else if ( startsWith( pos, OBSOLETE_STR ) ) 5913 { 5914 elementsSeen = checkElement( elementsSeen, ObjectClassElements.OBSOLETE, pos ); 5915 5916 pos.start += OBSOLETE_STR.length(); 5917 5918 objectClass.setObsolete( true ); 5919 } 5920 else if ( startsWith( pos, SUP_STR ) ) 5921 { 5922 elementsSeen = checkElement( elementsSeen, ObjectClassElements.SUP, pos ); 5923 5924 pos.start += SUP_STR.length(); 5925 5926 skipWhites( reader, pos, true ); 5927 5928 List<String> superiorOids = getOidsStrict( reader, pos ); 5929 5930 objectClass.setSuperiorOids( superiorOids ); 5931 } 5932 else if ( startsWith( pos, ABSTRACT_STR ) ) 5933 { 5934 elementsSeen = checkElement( elementsSeen, ObjectClassElements.ABSTRACT, pos ); 5935 5936 pos.start += ABSTRACT_STR.length(); 5937 5938 objectClass.setType( ObjectClassTypeEnum.ABSTRACT ); 5939 } 5940 else if ( startsWith( pos, STRUCTURAL_STR ) ) 5941 { 5942 elementsSeen = checkElement( elementsSeen, ObjectClassElements.STRUCTURAL, pos ); 5943 5944 pos.start += STRUCTURAL_STR.length(); 5945 5946 objectClass.setType( ObjectClassTypeEnum.STRUCTURAL ); 5947 } 5948 else if ( startsWith( pos, AUXILIARY_STR ) ) 5949 { 5950 elementsSeen = checkElement( elementsSeen, ObjectClassElements.AUXILIARY, pos ); 5951 5952 pos.start += AUXILIARY_STR.length(); 5953 5954 objectClass.setType( ObjectClassTypeEnum.AUXILIARY ); 5955 } 5956 else if ( startsWith( pos, MUST_STR ) ) 5957 { 5958 elementsSeen = checkElement( elementsSeen, ObjectClassElements.MUST, pos ); 5959 5960 pos.start += MUST_STR.length(); 5961 5962 skipWhites( reader, pos, true ); 5963 5964 List<String> mustAttributeTypes = getOidsStrict( reader, pos ); 5965 objectClass.setMustAttributeTypeOids( mustAttributeTypes ); 5966 } 5967 else if ( startsWith( pos, MAY_STR ) ) 5968 { 5969 elementsSeen = checkElement( elementsSeen, ObjectClassElements.MAY, pos ); 5970 5971 pos.start += MAY_STR.length(); 5972 5973 skipWhites( reader, pos, true ); 5974 5975 List<String> mayAttributeTypes = getOidsStrict( reader, pos ); 5976 objectClass.setMayAttributeTypeOids( mayAttributeTypes ); 5977 } 5978 else if ( startsWith( pos, EXTENSION_PREFIX ) ) 5979 { 5980 processExtension( reader, pos, objectClass ); 5981 } 5982 else if ( startsWith( reader, pos, RPAREN ) ) 5983 { 5984 pos.start++; 5985 break; 5986 } 5987 else 5988 { 5989 // This is an error 5990 throw new LdapSchemaException( I18n.err( I18n.ERR_13803_OC_DESCRIPTION_INVALID, 5991 pos.lineNumber, pos.start ) ); 5992 } 5993 } 5994 5995 pos.start++; 5996 5997 return objectClass; 5998 } 5999 6000 6001 /** 6002 * Production for matching ObjectClass descriptions. It is fault-tolerant 6003 * against element ordering. 6004 * <pre> 6005 * ObjectClassDescription = LPAREN WSP 6006 * numericoid ; object identifier 6007 * [ SP "NAME" SP qdescrs ] ; short names (descriptors) 6008 * [ SP "DESC" SP qdstring ] ; description 6009 * [ SP "OBSOLETE" ] ; not active 6010 * [ SP "SUP" SP oids ] ; superior object classes 6011 * [ SP kind ] ; kind of class 6012 * [ SP "MUST" SP oids ] ; attribute types 6013 * [ SP "MAY" SP oids ] ; attribute types 6014 * extensions WSP RPAREN 6015 * 6016 * kind = "ABSTRACT" / "STRUCTURAL" / "AUXILIARY" 6017 * </pre> 6018 * 6019 * @param reader The stream reader 6020 * @param pos The position in the Schema 6021 * @param objectIdentifierMacros The set of existing Macros 6022 * @return An instance of ObjectClass 6023 * @throws LdapSchemaException If the schema is wrong 6024 * @throws IOException If the stream can't be read 6025 */ 6026 private static ObjectClass parseObjectClassRelaxed( Reader reader, PosSchema pos, 6027 Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 6028 throws IOException, LdapSchemaException 6029 { 6030 // Get rid of whites, comments end empty lines 6031 skipWhites( reader, pos, false ); 6032 6033 // we must have a '(' 6034 if ( pos.line.charAt( pos.start ) != LPAREN ) 6035 { 6036 throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 6037 pos.lineNumber, pos.start ) ); 6038 } 6039 else 6040 { 6041 pos.start++; 6042 } 6043 6044 // Get rid of whites, comments end empty lines 6045 skipWhites( reader, pos, false ); 6046 6047 // Now, the numeric OID 6048 String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros ); 6049 6050 ObjectClass objectClass = new ObjectClass( oid ); 6051 int elementsSeen = 0; 6052 6053 while ( true ) 6054 { 6055 if ( startsWith( reader, pos, RPAREN ) ) 6056 { 6057 pos.start++; 6058 break; 6059 } 6060 6061 skipWhites( reader, pos, true ); 6062 6063 if ( startsWith( pos, NAME_STR ) ) 6064 { 6065 elementsSeen = checkElement( elementsSeen, ObjectClassElements.NAME, pos ); 6066 6067 pos.start += NAME_STR.length(); 6068 6069 skipWhites( reader, pos, true ); 6070 6071 List<String> names = getQDescrs( reader, pos, RELAXED ); 6072 6073 objectClass.setNames( names ); 6074 } 6075 else if ( startsWith( pos, DESC_STR ) ) 6076 { 6077 elementsSeen = checkElement( elementsSeen, ObjectClassElements.DESC, pos ); 6078 6079 pos.start += DESC_STR.length(); 6080 6081 skipWhites( reader, pos, true ); 6082 6083 objectClass.setDescription( getQDString( reader, pos ) ); 6084 } 6085 else if ( startsWith( pos, OBSOLETE_STR ) ) 6086 { 6087 elementsSeen = checkElement( elementsSeen, ObjectClassElements.OBSOLETE, pos ); 6088 6089 pos.start += OBSOLETE_STR.length(); 6090 6091 objectClass.setObsolete( true ); 6092 } 6093 else if ( startsWith( pos, SUP_STR ) ) 6094 { 6095 elementsSeen = checkElement( elementsSeen, ObjectClassElements.SUP, pos ); 6096 6097 pos.start += SUP_STR.length(); 6098 6099 skipWhites( reader, pos, true ); 6100 6101 List<String> superiorOids = getOidsRelaxed( reader, pos ); 6102 6103 objectClass.setSuperiorOids( superiorOids ); 6104 } 6105 else if ( startsWith( pos, ABSTRACT_STR ) ) 6106 { 6107 elementsSeen = checkElement( elementsSeen, ObjectClassElements.ABSTRACT, pos ); 6108 6109 pos.start += ABSTRACT_STR.length(); 6110 6111 objectClass.setType( ObjectClassTypeEnum.ABSTRACT ); 6112 } 6113 else if ( startsWith( pos, STRUCTURAL_STR ) ) 6114 { 6115 elementsSeen = checkElement( elementsSeen, ObjectClassElements.STRUCTURAL, pos ); 6116 6117 pos.start += STRUCTURAL_STR.length(); 6118 6119 objectClass.setType( ObjectClassTypeEnum.STRUCTURAL ); 6120 } 6121 else if ( startsWith( pos, AUXILIARY_STR ) ) 6122 { 6123 elementsSeen = checkElement( elementsSeen, ObjectClassElements.AUXILIARY, pos ); 6124 6125 pos.start += AUXILIARY_STR.length(); 6126 6127 objectClass.setType( ObjectClassTypeEnum.AUXILIARY ); 6128 } 6129 else if ( startsWith( pos, MUST_STR ) ) 6130 { 6131 elementsSeen = checkElement( elementsSeen, ObjectClassElements.MUST, pos ); 6132 6133 pos.start += MUST_STR.length(); 6134 6135 skipWhites( reader, pos, true ); 6136 6137 List<String> mustAttributeTypes = getOidsRelaxed( reader, pos ); 6138 objectClass.setMustAttributeTypeOids( mustAttributeTypes ); 6139 } 6140 else if ( startsWith( pos, MAY_STR ) ) 6141 { 6142 elementsSeen = checkElement( elementsSeen, ObjectClassElements.MAY, pos ); 6143 6144 pos.start += MAY_STR.length(); 6145 6146 skipWhites( reader, pos, true ); 6147 6148 List<String> mayAttributeTypes = getOidsRelaxed( reader, pos ); 6149 objectClass.setMayAttributeTypeOids( mayAttributeTypes ); 6150 } 6151 else if ( startsWith( pos, EXTENSION_PREFIX ) ) 6152 { 6153 processExtension( reader, pos, objectClass ); 6154 } 6155 else if ( startsWith( reader, pos, RPAREN ) ) 6156 { 6157 pos.start++; 6158 break; 6159 } 6160 else 6161 { 6162 // This is an error 6163 throw new LdapSchemaException( I18n.err( I18n.ERR_13803_OC_DESCRIPTION_INVALID, 6164 pos.lineNumber, pos.start ) ); 6165 } 6166 } 6167 6168 pos.start++; 6169 6170 return objectClass; 6171 } 6172 6173 6174 /** 6175 * Production for SyntaxChecker descriptions. It is fault-tolerant 6176 * against element ordering. 6177 * 6178 * <pre> 6179 * SyntaxCheckerDescription = LPAREN WSP 6180 * numericoid ; object identifier 6181 * [ SP "DESC" SP qdstring ] ; description 6182 * SP "FQCN" SP fqcn ; fully qualified class name 6183 * [ SP "BYTECODE" SP base64 ] ; optional base64 encoded bytecode 6184 * extensions WSP RPAREN ; extensions 6185 * 6186 * base64 = *(4base64-char) 6187 * base64-char = ALPHA / DIGIT / "+" / "/" 6188 * fqcn = fqcnComponent 1*( DOT fqcnComponent ) 6189 * fqcnComponent = ??? 6190 * </pre> 6191 * 6192 * @param syntaxCheckerDescription The String containing the SyntaxCheckerDescription 6193 * @return An instance of SyntaxCheckerDescription 6194 * @throws ParseException If the element was invalid 6195 */ 6196 public SyntaxCheckerDescription parseSyntaxChecker( String syntaxCheckerDescription ) throws ParseException 6197 { 6198 if ( ( syntaxCheckerDescription == null ) || Strings.isEmpty( syntaxCheckerDescription.trim() ) ) 6199 { 6200 throw new ParseException( I18n.err( I18n.ERR_13716_NULL_OR_EMPTY_STRING_SCHEMA_OBJECT ), 0 ); 6201 } 6202 6203 try ( Reader reader = new BufferedReader( new StringReader( syntaxCheckerDescription ) ) ) 6204 { 6205 PosSchema pos = new PosSchema(); 6206 6207 if ( isQuirksModeEnabled ) 6208 { 6209 return parseSyntaxCheckerRelaxed( reader, pos, objectIdentifierMacros ); 6210 } 6211 else 6212 { 6213 return parseSyntaxCheckerStrict( reader, pos, objectIdentifierMacros ); 6214 } 6215 } 6216 catch ( IOException | LdapSchemaException e ) 6217 { 6218 throw new ParseException( e.getMessage(), 0 ); 6219 } 6220 } 6221 6222 6223 /** 6224 * Production for SyntaxChecker descriptions. It is fault-tolerant 6225 * against element ordering. 6226 * 6227 * <pre> 6228 * SyntaxCheckerDescription = LPAREN WSP 6229 * numericoid ; object identifier 6230 * [ SP "DESC" SP qdstring ] ; description 6231 * SP "FQCN" SP fqcn ; fully qualified class name 6232 * [ SP "BYTECODE" SP base64 ] ; optional base64 encoded bytecode 6233 * extensions WSP RPAREN ; extensions 6234 * 6235 * base64 = *(4base64-char) 6236 * base64-char = ALPHA / DIGIT / "+" / "/" 6237 * fqcn = fqcnComponent 1*( DOT fqcnComponent ) 6238 * fqcnComponent = ??? 6239 * </pre> 6240 * 6241 * @param reader The stream reader 6242 * @param pos The position in the Schema 6243 * @param objectIdentifierMacros The set of existing Macros 6244 * @return An instance of SyntaxCheckerDescription 6245 * @throws LdapSchemaException If the schema is wrong 6246 * @throws IOException If the stream can't be read 6247 */ 6248 private static SyntaxCheckerDescription parseSyntaxCheckerStrict( Reader reader, PosSchema pos, 6249 Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) throws IOException, LdapSchemaException 6250 { 6251 // Get rid of whites, comments end empty lines 6252 skipWhites( reader, pos, false ); 6253 6254 // we must have a '(' 6255 if ( pos.line.charAt( pos.start ) != LPAREN ) 6256 { 6257 throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 6258 pos.lineNumber, pos.start ) ); 6259 } 6260 else 6261 { 6262 pos.start++; 6263 } 6264 6265 // Get rid of whites, comments end empty lines 6266 skipWhites( reader, pos, false ); 6267 6268 // Now, the OID. 6269 String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros ); 6270 6271 // Check that the OID is valid 6272 if ( !Oid.isOid( oid ) ) 6273 { 6274 throw new LdapSchemaException( I18n.err( I18n.ERR_13787_OID_EXPECTED, pos.lineNumber, pos.start ) ); 6275 } 6276 6277 SyntaxCheckerDescription syntaxChecker = new SyntaxCheckerDescription( oid ); 6278 int elementsSeen = 0; 6279 boolean hasFqcn = false; 6280 boolean hasByteCode = false; 6281 6282 while ( true ) 6283 { 6284 if ( startsWith( reader, pos, RPAREN ) ) 6285 { 6286 pos.start++; 6287 break; 6288 } 6289 6290 skipWhites( reader, pos, true ); 6291 6292 if ( startsWith( pos, DESC_STR ) ) 6293 { 6294 elementsSeen = checkElement( elementsSeen, SyntaxCheckerElements.DESC, pos ); 6295 6296 pos.start += DESC_STR.length(); 6297 6298 skipWhites( reader, pos, true ); 6299 6300 syntaxChecker.setDescription( getQDString( reader, pos ) ); 6301 } 6302 else if ( startsWith( pos, FQCN_STR ) ) 6303 { 6304 elementsSeen = checkElement( elementsSeen, SyntaxCheckerElements.FQCN, pos ); 6305 6306 pos.start += FQCN_STR.length(); 6307 6308 skipWhites( reader, pos, true ); 6309 6310 String fqcn = getFqcn( pos ); 6311 syntaxChecker.setFqcn( fqcn ); 6312 hasFqcn = true; 6313 } 6314 else if ( startsWith( pos, BYTECODE_STR ) ) 6315 { 6316 elementsSeen = checkElement( elementsSeen, SyntaxCheckerElements.BYTECODE, pos ); 6317 6318 pos.start += BYTECODE_STR.length(); 6319 6320 skipWhites( reader, pos, true ); 6321 6322 String byteCode = getByteCode( pos ); 6323 syntaxChecker.setBytecode( byteCode ); 6324 hasByteCode = true; 6325 } 6326 else if ( startsWith( pos, EXTENSION_PREFIX ) ) 6327 { 6328 processExtension( reader, pos, syntaxChecker ); 6329 } 6330 else if ( startsWith( reader, pos, RPAREN ) ) 6331 { 6332 pos.start++; 6333 break; 6334 } 6335 else 6336 { 6337 // This is an error 6338 throw new LdapSchemaException( I18n.err( I18n.ERR_13826_SC_DESCRIPTION_INVALID, 6339 pos.lineNumber, pos.start ) ); 6340 } 6341 } 6342 6343 // Semantic checks 6344 if ( !hasFqcn ) 6345 { 6346 throw new LdapSchemaException( I18n.err( I18n.ERR_13819_FQCN_REQUIRED, 6347 pos.lineNumber, pos.start ) ); 6348 } 6349 6350 if ( ( hasByteCode ) && ( syntaxChecker.getBytecode().length() % 4 != 0 ) ) 6351 { 6352 throw new LdapSchemaException( I18n.err( I18n.ERR_13820_BYTE_CODE_REQUIRED, 6353 pos.lineNumber, pos.start ) ); 6354 } 6355 6356 return syntaxChecker; 6357 } 6358 6359 6360 /** 6361 * Production for SyntaxChecker descriptions. It is fault-tolerant 6362 * against element ordering. 6363 * 6364 * <pre> 6365 * SyntaxCheckerDescription = LPAREN WSP 6366 * numericoid ; object identifier 6367 * [ SP "DESC" SP qdstring ] ; description 6368 * SP "FQCN" SP fqcn ; fully qualified class name 6369 * [ SP "BYTECODE" SP base64 ] ; optional base64 encoded bytecode 6370 * extensions WSP RPAREN ; extensions 6371 * 6372 * base64 = *(4base64-char) 6373 * base64-char = ALPHA / DIGIT / "+" / "/" 6374 * fqcn = fqcnComponent 1*( DOT fqcnComponent ) 6375 * fqcnComponent = ??? 6376 * </pre> 6377 * 6378 * @param reader The stream reader 6379 * @param pos The position in the Schema 6380 * @param objectIdentifierMacros The set of existing Macros 6381 * @return An instance of SyntaxCheckerDescription 6382 * @throws LdapSchemaException If the schema is wrong 6383 * @throws IOException If the stream can't be read 6384 */ 6385 private static SyntaxCheckerDescription parseSyntaxCheckerRelaxed( Reader reader, PosSchema pos, 6386 Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros ) 6387 throws IOException, LdapSchemaException 6388 { 6389 // Get rid of whites, comments end empty lines 6390 skipWhites( reader, pos, false ); 6391 6392 // we must have a '(' 6393 if ( pos.line.charAt( pos.start ) != LPAREN ) 6394 { 6395 throw new LdapSchemaException( I18n.err( I18n.ERR_13829_NO_OPENING_PAREN, 6396 pos.lineNumber, pos.start ) ); 6397 } 6398 else 6399 { 6400 pos.start++; 6401 } 6402 6403 // Get rid of whites, comments end empty lines 6404 skipWhites( reader, pos, false ); 6405 6406 // Now, the OID. 6407 String oid = getOidAndMacroRelaxed( pos, objectIdentifierMacros ); 6408 6409 SyntaxCheckerDescription syntaxChecker = new SyntaxCheckerDescription( oid ); 6410 int elementsSeen = 0; 6411 6412 while ( true ) 6413 { 6414 if ( startsWith( reader, pos, RPAREN ) ) 6415 { 6416 pos.start++; 6417 break; 6418 } 6419 6420 skipWhites( reader, pos, true ); 6421 6422 if ( startsWith( pos, DESC_STR ) ) 6423 { 6424 elementsSeen = checkElement( elementsSeen, SyntaxCheckerElements.DESC, pos ); 6425 6426 pos.start += DESC_STR.length(); 6427 6428 skipWhites( reader, pos, true ); 6429 6430 syntaxChecker.setDescription( getQDString( reader, pos ) ); 6431 } 6432 else if ( startsWith( pos, FQCN_STR ) ) 6433 { 6434 elementsSeen = checkElement( elementsSeen, SyntaxCheckerElements.FQCN, pos ); 6435 6436 pos.start += FQCN_STR.length(); 6437 6438 skipWhites( reader, pos, true ); 6439 6440 String fqcn = getFqcn( pos ); 6441 syntaxChecker.setFqcn( fqcn ); 6442 } 6443 else if ( startsWith( pos, BYTECODE_STR ) ) 6444 { 6445 elementsSeen = checkElement( elementsSeen, SyntaxCheckerElements.BYTECODE, pos ); 6446 6447 pos.start += BYTECODE_STR.length(); 6448 6449 skipWhites( reader, pos, true ); 6450 6451 String byteCode = getByteCode( pos ); 6452 syntaxChecker.setBytecode( byteCode ); 6453 } 6454 else if ( startsWith( pos, EXTENSION_PREFIX ) ) 6455 { 6456 processExtension( reader, pos, syntaxChecker ); 6457 } 6458 else if ( startsWith( reader, pos, RPAREN ) ) 6459 { 6460 pos.start++; 6461 break; 6462 } 6463 else 6464 { 6465 // This is an error 6466 throw new LdapSchemaException( I18n.err( I18n.ERR_13826_SC_DESCRIPTION_INVALID, 6467 pos.lineNumber, pos.start ) ); 6468 } 6469 } 6470 6471 return syntaxChecker; 6472 } 6473 6474 6475 /** 6476 * Process OpenLDAP macros, like : objectidentifier DUAConfSchemaOID 1.3.6.1.4.1.11.1.3.1. 6477 * 6478 * <pre> 6479 * objectidentifier ::= 'objectidentifier' descr SP+ macroOid 6480 * descr ::= ALPHA ( ALPHA | DIGIT | HYPHEN )* 6481 * macroOid ::= (descr ':')? oid 6482 * </pre> 6483 * 6484 * @param reader The stream reader 6485 * @param pos The position in the Schema 6486 * @throws LdapSchemaException If something went wrong in the schema 6487 * @throws IOException If the stream can't be read 6488 */ 6489 private void processObjectIdentifier( Reader reader, PosSchema pos ) throws IOException, LdapSchemaException 6490 { 6491 // Get rid of whites, comments end empty lines 6492 skipWhites( reader, pos, false ); 6493 6494 // Now, the name 6495 String name = getDescrStrict( pos ); 6496 6497 OpenLdapObjectIdentifierMacro macro = new OpenLdapObjectIdentifierMacro(); 6498 6499 skipWhites( reader, pos, true ); 6500 6501 // Get the descr, if any 6502 if ( isEmpty( pos ) ) 6503 { 6504 throw new LdapSchemaException( I18n.err( I18n.ERR_13805_OBJECT_IDENTIFIER_INVALID_OID, 6505 pos.lineNumber, pos.start ) ); 6506 } 6507 6508 if ( isAlpha( pos ) ) 6509 { 6510 // A macro 6511 String descr = getMacro( pos ); 6512 6513 if ( isEmpty( pos ) ) 6514 { 6515 throw new LdapSchemaException( I18n.err( I18n.ERR_13804_OBJECT_IDENTIFIER_HAS_NO_OID, 6516 pos.lineNumber, pos.start ) ); 6517 } 6518 6519 if ( startsWith( reader, pos, COLON ) ) 6520 { 6521 pos.start++; 6522 6523 // Now, the OID 6524 String numericOid = getPartialNumericOid( pos ); 6525 String realOid = objectIdentifierMacros.get( descr ).getRawOidOrNameSuffix() + DOT + numericOid; 6526 macro.setName( name ); 6527 macro.setRawOidOrNameSuffix( realOid ); 6528 6529 objectIdentifierMacros.put( name, macro ); 6530 } 6531 } 6532 else if ( isDigit( pos ) ) 6533 { 6534 // An oid 6535 String numericOid = getNumericOid( pos ); 6536 macro.setRawOidOrNameSuffix( numericOid ); 6537 macro.setName( name ); 6538 6539 objectIdentifierMacros.put( name, macro ); 6540 } 6541 else 6542 { 6543 throw new LdapSchemaException( I18n.err( I18n.ERR_13805_OBJECT_IDENTIFIER_INVALID_OID, 6544 pos.lineNumber, pos.start ) ); 6545 } 6546 } 6547 6548 6549 /** 6550 * Reads an entry in a ldif buffer, and returns the resulting lines, without 6551 * comments, and unfolded. 6552 * 6553 * The lines represent *one* entry. 6554 * 6555 * @param reader The stream reader 6556 * @throws LdapSchemaException If something went wrong in the schema 6557 * @throws IOException If the stream can't be read 6558 */ 6559 public void parse( Reader reader ) throws LdapSchemaException, IOException 6560 { 6561 PosSchema pos = new PosSchema(); 6562 6563 while ( true ) 6564 { 6565 // Always move forward to the next element, skipping whites, NL and comments 6566 skipWhites( reader, pos, false ); 6567 6568 if ( pos.line == null ) 6569 { 6570 // The end, get out 6571 break; 6572 } 6573 6574 // Ok, we have something which must be one of openLdapObjectIdentifier( "objectidentifier" ), 6575 // openLdapAttributeType ( "attributetype" ) or openLdapObjectClass ( "objectclass" ) 6576 if ( startsWith( pos, "objectidentifier" ) ) 6577 { 6578 pos.start += "objectidentifier".length(); 6579 6580 processObjectIdentifier( reader, pos ); 6581 } 6582 else if ( startsWith( pos, "attributetype" ) ) 6583 { 6584 pos.start += "attributetype".length(); 6585 6586 AttributeType attributeType = parseAttributeTypeStrict( reader, pos, objectIdentifierMacros ); 6587 schemaDescriptions.add( attributeType ); 6588 } 6589 else if ( startsWith( pos, "objectclass" ) ) 6590 { 6591 pos.start += "objectclass".length(); 6592 6593 ObjectClass objectClass = parseObjectClassStrict( reader, pos, objectIdentifierMacros ); 6594 schemaDescriptions.add( objectClass ); 6595 } 6596 else 6597 { 6598 // This is an error 6599 throw new LdapSchemaException( I18n.err( I18n.ERR_13806_UNEXPECTED_ELEMENT_READ, 6600 pos.line.substring( pos.start ), pos.lineNumber, pos.start ) ); 6601 } 6602 } 6603 } 6604 6605 6606 /** 6607 * Parses a file of OpenLDAP schemaObject elements/objects. Default charset is used. 6608 * 6609 * @param schemaFile a file of schema objects 6610 * @throws ParseException If the schemaObject can't be parsed 6611 */ 6612 public void parse( File schemaFile ) throws ParseException 6613 { 6614 try ( InputStream is = Files.newInputStream( Paths.get( schemaFile.getPath() ) ) ) 6615 { 6616 try ( Reader reader = new BufferedReader( new InputStreamReader( is, Charset.defaultCharset() ) ) ) 6617 { 6618 parse( reader ); 6619 afterParse(); 6620 } 6621 catch ( LdapSchemaException | IOException e ) 6622 { 6623 throw new ParseException( e.getMessage(), 0 ); 6624 } 6625 } 6626 catch ( IOException e ) 6627 { 6628 String msg = I18n.err( I18n.ERR_13443_CANNOT_FIND_FILE, schemaFile.getAbsoluteFile() ); 6629 LOG.error( msg ); 6630 throw new ParseException( e.getMessage(), 0 ); 6631 } 6632 } 6633 6634 6635 /** 6636 * Checks if object identifier macros should be resolved. 6637 * 6638 * @return true, object identifier macros should be resolved. 6639 */ 6640 public boolean isResolveObjectIdentifierMacros() 6641 { 6642 return isResolveObjectIdentifierMacros; 6643 } 6644 6645 6646 /** 6647 * Sets if object identifier macros should be resolved. 6648 * 6649 * @param resolveObjectIdentifierMacros true if object identifier macros should be resolved 6650 */ 6651 public void setResolveObjectIdentifierMacros( boolean resolveObjectIdentifierMacros ) 6652 { 6653 this.isResolveObjectIdentifierMacros = resolveObjectIdentifierMacros; 6654 } 6655 6656 /** 6657 * Checks if quirks mode is enabled. 6658 * 6659 * @return true, if is quirks mode is enabled 6660 */ 6661 public boolean isQuirksMode() 6662 { 6663 return isQuirksModeEnabled; 6664 } 6665 6666 6667 /** 6668 * Sets the quirks mode. 6669 * 6670 * If enabled the parser accepts non-numeric OIDs and some 6671 * special characters in descriptions. 6672 * 6673 * @param enabled the new quirks mode 6674 */ 6675 public void setQuirksMode( boolean enabled ) 6676 { 6677 isQuirksModeEnabled = enabled; 6678 } 6679}