001 /*
002 * CDDL HEADER START
003 *
004 * The contents of this file are subject to the terms of the
005 * Common Development and Distribution License, Version 1.0 only
006 * (the "License"). You may not use this file except in compliance
007 * with the License.
008 *
009 * You can obtain a copy of the license at
010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012 * See the License for the specific language governing permissions
013 * and limitations under the License.
014 *
015 * When distributing Covered Code, include this CDDL HEADER in each
016 * file and include the License file at
017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
018 * add the following below this CDDL HEADER, with the fields enclosed
019 * by brackets "[]" replaced with your own identifying information:
020 * Portions Copyright [yyyy] [name of copyright owner]
021 *
022 * CDDL HEADER END
023 *
024 *
025 * Copyright 2006-2008 Sun Microsystems, Inc.
026 */
027 package org.opends.server.schema;
028 import org.opends.messages.Message;
029
030
031
032 import java.util.LinkedHashMap;
033 import java.util.LinkedHashSet;
034 import java.util.LinkedList;
035 import java.util.List;
036
037 import org.opends.server.admin.std.server.AttributeSyntaxCfg;
038 import org.opends.server.api.ApproximateMatchingRule;
039 import org.opends.server.api.AttributeSyntax;
040 import org.opends.server.api.EqualityMatchingRule;
041 import org.opends.server.api.OrderingMatchingRule;
042 import org.opends.server.api.SubstringMatchingRule;
043 import org.opends.server.config.ConfigException;
044 import org.opends.server.core.DirectoryServer;
045 import org.opends.server.types.AttributeType;
046 import org.opends.server.types.ByteString;
047 import org.opends.server.types.DirectoryException;
048 import org.opends.server.types.InitializationException;
049 import org.opends.server.types.NameForm;
050 import org.opends.server.types.ObjectClass;
051 import org.opends.server.types.ObjectClassType;
052 import org.opends.server.types.ResultCode;
053 import org.opends.server.types.Schema;
054
055 import static org.opends.server.loggers.debug.DebugLogger.*;
056 import org.opends.server.loggers.debug.DebugTracer;
057 import org.opends.server.types.DebugLogLevel;
058 import static org.opends.messages.SchemaMessages.*;
059 import org.opends.messages.MessageBuilder;
060 import static org.opends.server.schema.SchemaConstants.*;
061 import static org.opends.server.util.StaticUtils.*;
062
063
064
065 /**
066 * This class implements the name form description syntax, which is used to
067 * hold name form definitions in the server schema. The format of this syntax
068 * is defined in RFC 2252.
069 */
070 public class NameFormSyntax
071 extends AttributeSyntax<AttributeSyntaxCfg>
072 {
073 /**
074 * The tracer object for the debug logger.
075 */
076 private static final DebugTracer TRACER = getTracer();
077
078
079
080 // The default equality matching rule for this syntax.
081 private EqualityMatchingRule defaultEqualityMatchingRule;
082
083 // The default ordering matching rule for this syntax.
084 private OrderingMatchingRule defaultOrderingMatchingRule;
085
086 // The default substring matching rule for this syntax.
087 private SubstringMatchingRule defaultSubstringMatchingRule;
088
089
090
091 /**
092 * Creates a new instance of this syntax. Note that the only thing that
093 * should be done here is to invoke the default constructor for the
094 * superclass. All initialization should be performed in the
095 * <CODE>initializeSyntax</CODE> method.
096 */
097 public NameFormSyntax()
098 {
099 super();
100 }
101
102
103
104 /**
105 * {@inheritDoc}
106 */
107 public void initializeSyntax(AttributeSyntaxCfg configuration)
108 throws ConfigException, InitializationException
109 {
110 defaultEqualityMatchingRule =
111 DirectoryServer.getEqualityMatchingRule(EMR_CASE_IGNORE_OID);
112 if (defaultEqualityMatchingRule == null)
113 {
114 Message message = ERR_ATTR_SYNTAX_UNKNOWN_EQUALITY_MATCHING_RULE.get(
115 EMR_CASE_IGNORE_OID, SYNTAX_NAME_FORM_NAME);
116 throw new InitializationException(message);
117 }
118
119 defaultOrderingMatchingRule =
120 DirectoryServer.getOrderingMatchingRule(OMR_CASE_IGNORE_OID);
121 if (defaultOrderingMatchingRule == null)
122 {
123 Message message = ERR_ATTR_SYNTAX_UNKNOWN_ORDERING_MATCHING_RULE.get(
124 OMR_CASE_IGNORE_OID, SYNTAX_NAME_FORM_NAME);
125 throw new InitializationException(message);
126 }
127
128 defaultSubstringMatchingRule =
129 DirectoryServer.getSubstringMatchingRule(SMR_CASE_IGNORE_OID);
130 if (defaultSubstringMatchingRule == null)
131 {
132 Message message = ERR_ATTR_SYNTAX_UNKNOWN_SUBSTRING_MATCHING_RULE.get(
133 SMR_CASE_IGNORE_OID, SYNTAX_NAME_FORM_NAME);
134 throw new InitializationException(message);
135 }
136 }
137
138
139
140 /**
141 * {@inheritDoc}
142 */
143 public String getSyntaxName()
144 {
145 return SYNTAX_NAME_FORM_NAME;
146 }
147
148
149
150 /**
151 * {@inheritDoc}
152 */
153 public String getOID()
154 {
155 return SYNTAX_NAME_FORM_OID;
156 }
157
158
159
160 /**
161 * {@inheritDoc}
162 */
163 public String getDescription()
164 {
165 return SYNTAX_NAME_FORM_DESCRIPTION;
166 }
167
168
169
170 /**
171 * {@inheritDoc}
172 */
173 public EqualityMatchingRule getEqualityMatchingRule()
174 {
175 return defaultEqualityMatchingRule;
176 }
177
178
179
180 /**
181 * {@inheritDoc}
182 */
183 public OrderingMatchingRule getOrderingMatchingRule()
184 {
185 return defaultOrderingMatchingRule;
186 }
187
188
189
190 /**
191 * {@inheritDoc}
192 */
193 public SubstringMatchingRule getSubstringMatchingRule()
194 {
195 return defaultSubstringMatchingRule;
196 }
197
198
199
200 /**
201 * {@inheritDoc}
202 */
203 public ApproximateMatchingRule getApproximateMatchingRule()
204 {
205 // There is no approximate matching rule by default.
206 return null;
207 }
208
209
210
211 /**
212 * {@inheritDoc}
213 */
214 public boolean valueIsAcceptable(ByteString value,
215 MessageBuilder invalidReason)
216 {
217 // We'll use the decodeNameForm method to determine if the value is
218 // acceptable.
219 try
220 {
221 decodeNameForm(value, DirectoryServer.getSchema(), true);
222 return true;
223 }
224 catch (DirectoryException de)
225 {
226 if (debugEnabled())
227 {
228 TRACER.debugCaught(DebugLogLevel.ERROR, de);
229 }
230
231 invalidReason.append(de.getMessageObject());
232 return false;
233 }
234 }
235
236
237
238 /**
239 * Decodes the contents of the provided ASN.1 octet string as a name form
240 * definition according to the rules of this syntax. Note that the provided
241 * octet string value does not need to be normalized (and in fact, it should
242 * not be in order to allow the desired capitalization to be preserved).
243 *
244 * @param value The ASN.1 octet string containing the value
245 * to decode (it does not need to be
246 * normalized).
247 * @param schema The schema to use to resolve references to
248 * other schema elements.
249 * @param allowUnknownElements Indicates whether to allow values that
250 * reference a structural objectclass and/or
251 * required or optional attribute types which
252 * are not defined in the server schema. This
253 * should only be true when called by
254 * {@code valueIsAcceptable}.
255 *
256 * @return The decoded name form definition.
257 *
258 * @throws DirectoryException If the provided value cannot be decoded as an
259 * name form definition.
260 */
261 public static NameForm decodeNameForm(ByteString value, Schema schema,
262 boolean allowUnknownElements)
263 throws DirectoryException
264 {
265 // Get string representations of the provided value using the provided form
266 // and with all lowercase characters.
267 String valueStr = value.stringValue();
268 String lowerStr = toLowerCase(valueStr);
269
270
271 // We'll do this a character at a time. First, skip over any leading
272 // whitespace.
273 int pos = 0;
274 int length = valueStr.length();
275 while ((pos < length) && (valueStr.charAt(pos) == ' '))
276 {
277 pos++;
278 }
279
280 if (pos >= length)
281 {
282 // This means that the value was empty or contained only whitespace. That
283 // is illegal.
284 Message message = ERR_ATTR_SYNTAX_NAME_FORM_EMPTY_VALUE.get();
285 throw new DirectoryException(
286 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
287 }
288
289
290 // The next character must be an open parenthesis. If it is not, then that
291 // is an error.
292 char c = valueStr.charAt(pos++);
293 if (c != '(')
294 {
295 Message message = ERR_ATTR_SYNTAX_NAME_FORM_EXPECTED_OPEN_PARENTHESIS.get(
296 valueStr, (pos-1), c);
297 throw new DirectoryException(
298 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
299 }
300
301
302 // Skip over any spaces immediately following the opening parenthesis.
303 while ((pos < length) && ((c = valueStr.charAt(pos)) == ' '))
304 {
305 pos++;
306 }
307
308 if (pos >= length)
309 {
310 // This means that the end of the value was reached before we could find
311 // the OID. Ths is illegal.
312 Message message = ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(valueStr);
313 throw new DirectoryException(
314 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
315 }
316
317
318 // The next set of characters must be the OID. Strictly speaking, this
319 // should only be a numeric OID, but we'll also allow for the
320 // "ocname-oid" case as well. Look at the first character to figure out
321 // which we will be using.
322 int oidStartPos = pos;
323 if (isDigit(c))
324 {
325 // This must be a numeric OID. In that case, we will accept only digits
326 // and periods, but not consecutive periods.
327 boolean lastWasPeriod = false;
328 while ((pos < length) && ((c = valueStr.charAt(pos++)) != ' '))
329 {
330 if (c == '.')
331 {
332 if (lastWasPeriod)
333 {
334 Message message =
335 ERR_ATTR_SYNTAX_NAME_FORM_DOUBLE_PERIOD_IN_NUMERIC_OID.
336 get(valueStr, (pos-1));
337 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
338 message);
339 }
340 else
341 {
342 lastWasPeriod = true;
343 }
344 }
345 else if (! isDigit(c))
346 {
347 // This must have been an illegal character.
348 Message message =
349 ERR_ATTR_SYNTAX_NAME_FORM_ILLEGAL_CHAR_IN_NUMERIC_OID.
350 get(valueStr, c, (pos-1));
351 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
352 message);
353 }
354 else
355 {
356 lastWasPeriod = false;
357 }
358 }
359 }
360 else
361 {
362 // This must be a "fake" OID. In this case, we will only accept
363 // alphabetic characters, numeric digits, and the hyphen.
364 while ((pos < length) && ((c = valueStr.charAt(pos++)) != ' '))
365 {
366 if (isAlpha(c) || isDigit(c) || (c == '-') ||
367 ((c == '_') && DirectoryServer.allowAttributeNameExceptions()))
368 {
369 // This is fine. It is an acceptable character.
370 }
371 else
372 {
373 // This must have been an illegal character.
374 Message message =
375 ERR_ATTR_SYNTAX_NAME_FORM_ILLEGAL_CHAR_IN_STRING_OID.
376 get(valueStr, c, (pos-1));
377 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
378 message);
379 }
380 }
381 }
382
383
384 // If we're at the end of the value, then it isn't a valid name form
385 // description. Otherwise, parse out the OID.
386 String oid;
387 if (pos >= length)
388 {
389 Message message = ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(valueStr);
390 throw new DirectoryException(
391 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
392 }
393 else
394 {
395 oid = lowerStr.substring(oidStartPos, (pos-1));
396 }
397
398
399 // Skip over the space(s) after the OID.
400 while ((pos < length) && ((c = valueStr.charAt(pos)) == ' '))
401 {
402 pos++;
403 }
404
405 if (pos >= length)
406 {
407 // This means that the end of the value was reached before we could find
408 // the OID. Ths is illegal.
409 Message message = ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(valueStr);
410 throw new DirectoryException(
411 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
412 }
413
414
415 // At this point, we should have a pretty specific syntax that describes
416 // what may come next, but some of the components are optional and it would
417 // be pretty easy to put something in the wrong order, so we will be very
418 // flexible about what we can accept. Just look at the next token, figure
419 // out what it is and how to treat what comes after it, then repeat until
420 // we get to the end of the value. But before we start, set default values
421 // for everything else we might need to know.
422 LinkedHashMap<String,String> names = new LinkedHashMap<String,String>();
423 String description = null;
424 boolean isObsolete = false;
425 ObjectClass structuralClass = null;
426 LinkedHashSet<AttributeType> requiredAttributes =
427 new LinkedHashSet<AttributeType>();
428 LinkedHashSet<AttributeType> optionalAttributes =
429 new LinkedHashSet<AttributeType>();
430 LinkedHashMap<String,List<String>> extraProperties =
431 new LinkedHashMap<String,List<String>>();
432
433
434 while (true)
435 {
436 StringBuilder tokenNameBuffer = new StringBuilder();
437 pos = readTokenName(valueStr, tokenNameBuffer, pos);
438 String tokenName = tokenNameBuffer.toString();
439 String lowerTokenName = toLowerCase(tokenName);
440 if (tokenName.equals(")"))
441 {
442 // We must be at the end of the value. If not, then that's a problem.
443 if (pos < length)
444 {
445 Message message =
446 ERR_ATTR_SYNTAX_NAME_FORM_UNEXPECTED_CLOSE_PARENTHESIS.
447 get(valueStr, (pos-1));
448 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
449 message);
450 }
451
452 break;
453 }
454 else if (lowerTokenName.equals("name"))
455 {
456 // This specifies the set of names for the name form. It may be a
457 // single name in single quotes, or it may be an open parenthesis
458 // followed by one or more names in single quotes separated by spaces.
459 c = valueStr.charAt(pos++);
460 if (c == '\'')
461 {
462 StringBuilder userBuffer = new StringBuilder();
463 StringBuilder lowerBuffer = new StringBuilder();
464 pos = readQuotedString(valueStr, lowerStr, userBuffer, lowerBuffer,
465 (pos-1));
466 names.put(lowerBuffer.toString(), userBuffer.toString());
467 }
468 else if (c == '(')
469 {
470 StringBuilder userBuffer = new StringBuilder();
471 StringBuilder lowerBuffer = new StringBuilder();
472 pos = readQuotedString(valueStr, lowerStr, userBuffer, lowerBuffer,
473 pos);
474 names.put(lowerBuffer.toString(), userBuffer.toString());
475
476
477 while (true)
478 {
479 if (valueStr.charAt(pos) == ')')
480 {
481 // Skip over any spaces after the parenthesis.
482 pos++;
483 while ((pos < length) && ((c = valueStr.charAt(pos)) == ' '))
484 {
485 pos++;
486 }
487
488 break;
489 }
490 else
491 {
492 userBuffer = new StringBuilder();
493 lowerBuffer = new StringBuilder();
494
495 pos = readQuotedString(valueStr, lowerStr, userBuffer,
496 lowerBuffer, pos);
497 names.put(lowerBuffer.toString(), userBuffer.toString());
498 }
499 }
500 }
501 else
502 {
503 // This is an illegal character.
504 Message message =
505 ERR_ATTR_SYNTAX_NAME_FORM_ILLEGAL_CHAR.get(valueStr, c, (pos-1));
506 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
507 message);
508 }
509 }
510 else if (lowerTokenName.equals("desc"))
511 {
512 // This specifies the description for the name form. It is an
513 // arbitrary string of characters enclosed in single quotes.
514 StringBuilder descriptionBuffer = new StringBuilder();
515 pos = readQuotedString(valueStr, descriptionBuffer, pos);
516 description = descriptionBuffer.toString();
517 }
518 else if (lowerTokenName.equals("obsolete"))
519 {
520 // This indicates whether the name form should be considered obsolete.
521 // We do not need to do any more parsing for this token.
522 isObsolete = true;
523 }
524 else if (lowerTokenName.equals("oc"))
525 {
526 // This specifies the name or OID of the structural objectclass for this
527 // name form.
528 StringBuilder woidBuffer = new StringBuilder();
529 pos = readWOID(lowerStr, woidBuffer, pos);
530 structuralClass = schema.getObjectClass(woidBuffer.toString());
531 if (structuralClass == null)
532 {
533 // This is bad because we don't know what the structural objectclass
534 // is.
535 if (allowUnknownElements)
536 {
537 structuralClass = DirectoryServer.getDefaultObjectClass(
538 woidBuffer.toString());
539 }
540 else
541 {
542 Message message =
543 ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_STRUCTURAL_CLASS.
544 get(String.valueOf(oid), String.valueOf(woidBuffer));
545 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
546 message);
547 }
548 }
549 else if (structuralClass.getObjectClassType() !=
550 ObjectClassType.STRUCTURAL)
551 {
552 // This is bad because the associated structural class type is not
553 // structural.
554 Message message =
555 ERR_ATTR_SYNTAX_NAME_FORM_STRUCTURAL_CLASS_NOT_STRUCTURAL.
556 get(String.valueOf(oid), String.valueOf(woidBuffer),
557 structuralClass.getNameOrOID(),
558 String.valueOf(structuralClass.getObjectClassType()));
559 throw new DirectoryException(
560 ResultCode.CONSTRAINT_VIOLATION, message);
561 }
562 }
563 else if (lowerTokenName.equals("must"))
564 {
565 LinkedList<AttributeType> attrs = new LinkedList<AttributeType>();
566
567 // This specifies the set of required attributes for the name from.
568 // It may be a single name or OID (not in quotes), or it may be an
569 // open parenthesis followed by one or more names separated by spaces
570 // and the dollar sign character, followed by a closing parenthesis.
571 c = valueStr.charAt(pos++);
572 if (c == '(')
573 {
574 while (true)
575 {
576 StringBuilder woidBuffer = new StringBuilder();
577 pos = readWOID(lowerStr, woidBuffer, (pos));
578
579 AttributeType attr = schema.getAttributeType(woidBuffer.toString());
580 if (attr == null)
581 {
582 // This isn't good because it means that the name form requires
583 // an attribute type that we don't know anything about.
584 if (allowUnknownElements)
585 {
586 attr = DirectoryServer.getDefaultAttributeType(
587 woidBuffer.toString());
588 }
589 else
590 {
591 Message message =
592 ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_REQUIRED_ATTR.
593 get(oid, woidBuffer.toString());
594 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
595 message);
596 }
597 }
598
599 attrs.add(attr);
600
601
602 // The next character must be either a dollar sign or a closing
603 // parenthesis.
604 c = valueStr.charAt(pos++);
605 if (c == ')')
606 {
607 // This denotes the end of the list.
608 break;
609 }
610 else if (c != '$')
611 {
612 Message message = ERR_ATTR_SYNTAX_NAME_FORM_ILLEGAL_CHAR.get(
613 valueStr, c, (pos-1));
614 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
615 message);
616 }
617 }
618 }
619 else
620 {
621 StringBuilder woidBuffer = new StringBuilder();
622 pos = readWOID(lowerStr, woidBuffer, (pos-1));
623
624 AttributeType attr = schema.getAttributeType(woidBuffer.toString());
625 if (attr == null)
626 {
627 // This isn't good because it means that the name form requires an
628 // attribute type that we don't know anything about.
629 if (allowUnknownElements)
630 {
631 attr = DirectoryServer.getDefaultAttributeType(
632 woidBuffer.toString());
633 }
634 else
635 {
636 Message message = ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_REQUIRED_ATTR.
637 get(oid, woidBuffer.toString());
638 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
639 message);
640 }
641 }
642
643 attrs.add(attr);
644 }
645
646 requiredAttributes.addAll(attrs);
647 }
648 else if (lowerTokenName.equals("may"))
649 {
650 LinkedList<AttributeType> attrs = new LinkedList<AttributeType>();
651
652 // This specifies the set of optional attributes for the name form. It
653 // may be a single name or OID (not in quotes), or it may be an open
654 // parenthesis followed by one or more names separated by spaces and the
655 // dollar sign character, followed by a closing parenthesis.
656 c = valueStr.charAt(pos++);
657 if (c == '(')
658 {
659 while (true)
660 {
661 StringBuilder woidBuffer = new StringBuilder();
662 pos = readWOID(lowerStr, woidBuffer, (pos));
663
664 AttributeType attr = schema.getAttributeType(woidBuffer.toString());
665 if (attr == null)
666 {
667 // This isn't good because it means that the name form allows an
668 // attribute type that we don't know anything about.
669 if (allowUnknownElements)
670 {
671 attr = DirectoryServer.getDefaultAttributeType(
672 woidBuffer.toString());
673 }
674 else
675 {
676 Message message =
677 ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_OPTIONAL_ATTR.
678 get(oid, woidBuffer.toString());
679 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
680 message);
681 }
682 }
683
684 attrs.add(attr);
685
686
687 // The next character must be either a dollar sign or a closing
688 // parenthesis.
689 c = valueStr.charAt(pos++);
690 if (c == ')')
691 {
692 // This denotes the end of the list.
693 break;
694 }
695 else if (c != '$')
696 {
697 Message message = ERR_ATTR_SYNTAX_NAME_FORM_ILLEGAL_CHAR.get(
698 valueStr, c, (pos-1));
699 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
700 message);
701 }
702 }
703 }
704 else
705 {
706 StringBuilder woidBuffer = new StringBuilder();
707 pos = readWOID(lowerStr, woidBuffer, (pos-1));
708
709 AttributeType attr = schema.getAttributeType(woidBuffer.toString());
710 if (attr == null)
711 {
712 // This isn't good because it means that the name form allows an
713 // attribute type that we don't know anything about.
714 if (allowUnknownElements)
715 {
716 attr = DirectoryServer.getDefaultAttributeType(
717 woidBuffer.toString());
718 }
719 else
720 {
721 Message message = ERR_ATTR_SYNTAX_NAME_FORM_UNKNOWN_OPTIONAL_ATTR.
722 get(oid, woidBuffer.toString());
723 throw new DirectoryException(ResultCode.CONSTRAINT_VIOLATION,
724 message);
725 }
726 }
727
728 attrs.add(attr);
729 }
730
731 optionalAttributes.addAll(attrs);
732 }
733 else
734 {
735 // This must be a non-standard property and it must be followed by
736 // either a single value in single quotes or an open parenthesis
737 // followed by one or more values in single quotes separated by spaces
738 // followed by a close parenthesis.
739 LinkedList<String> valueList = new LinkedList<String>();
740 pos = readExtraParameterValues(valueStr, valueList, pos);
741 extraProperties.put(tokenName, valueList);
742 }
743 }
744
745
746 // Make sure that a structural class was specified. If not, then it cannot
747 // be valid.
748 if (structuralClass == null)
749 {
750 Message message =
751 ERR_ATTR_SYNTAX_NAME_FORM_NO_STRUCTURAL_CLASS.get(valueStr);
752 throw new DirectoryException(
753 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
754 }
755
756
757 return new NameForm(value.stringValue(), names, oid, description,
758 isObsolete, structuralClass, requiredAttributes,
759 optionalAttributes, extraProperties);
760 }
761
762
763
764 /**
765 * Reads the next token name from the name form definition, skipping over any
766 * leading or trailing spaces, and appends it to the provided buffer.
767 *
768 * @param valueStr The string representation of the name form definition.
769 * @param tokenName The buffer into which the token name will be written.
770 * @param startPos The position in the provided string at which to start
771 * reading the token name.
772 *
773 * @return The position of the first character that is not part of the token
774 * name or one of the trailing spaces after it.
775 *
776 * @throws DirectoryException If a problem is encountered while reading the
777 * token name.
778 */
779 private static int readTokenName(String valueStr, StringBuilder tokenName,
780 int startPos)
781 throws DirectoryException
782 {
783 // Skip over any spaces at the beginning of the value.
784 char c = '\u0000';
785 int length = valueStr.length();
786 while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' '))
787 {
788 startPos++;
789 }
790
791 if (startPos >= length)
792 {
793 Message message = ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(valueStr);
794 throw new DirectoryException(
795 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
796 }
797
798
799 // Read until we find the next space.
800 while ((startPos < length) && ((c = valueStr.charAt(startPos++)) != ' '))
801 {
802 tokenName.append(c);
803 }
804
805
806 // Skip over any trailing spaces after the value.
807 while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' '))
808 {
809 startPos++;
810 }
811
812
813 // Return the position of the first non-space character after the token.
814 return startPos;
815 }
816
817
818
819 /**
820 * Reads the value of a string enclosed in single quotes, skipping over the
821 * quotes and any leading or trailing spaces, and appending the string to the
822 * provided buffer.
823 *
824 * @param valueStr The user-provided representation of the name form
825 * definition.
826 * @param valueBuffer The buffer into which the user-provided representation
827 * of the value will be placed.
828 * @param startPos The position in the provided string at which to start
829 * reading the quoted string.
830 *
831 * @return The position of the first character that is not part of the quoted
832 * string or one of the trailing spaces after it.
833 *
834 * @throws DirectoryException If a problem is encountered while reading the
835 * quoted string.
836 */
837 private static int readQuotedString(String valueStr,
838 StringBuilder valueBuffer, int startPos)
839 throws DirectoryException
840 {
841 // Skip over any spaces at the beginning of the value.
842 char c = '\u0000';
843 int length = valueStr.length();
844 while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' '))
845 {
846 startPos++;
847 }
848
849 if (startPos >= length)
850 {
851 Message message = ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(valueStr);
852 throw new DirectoryException(
853 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
854 }
855
856
857 // The next character must be a single quote.
858 if (c != '\'')
859 {
860 Message message = ERR_ATTR_SYNTAX_NAME_FORM_EXPECTED_QUOTE_AT_POS.get(
861 valueStr, startPos, c);
862 throw new DirectoryException(
863 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
864 }
865
866
867 // Read until we find the closing quote.
868 startPos++;
869 while ((startPos < length) && ((c = valueStr.charAt(startPos)) != '\''))
870 {
871 valueBuffer.append(c);
872 startPos++;
873 }
874
875
876 // Skip over any trailing spaces after the value.
877 startPos++;
878 while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' '))
879 {
880 startPos++;
881 }
882
883
884 // If we're at the end of the value, then that's illegal.
885 if (startPos >= length)
886 {
887 Message message = ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(valueStr);
888 throw new DirectoryException(
889 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
890 }
891
892
893 // Return the position of the first non-space character after the token.
894 return startPos;
895 }
896
897
898
899 /**
900 * Reads the value of a string enclosed in single quotes, skipping over the
901 * quotes and any leading or trailing spaces, and appending the string to the
902 * provided buffer.
903 *
904 * @param valueStr The user-provided representation of the name form
905 * definition.
906 * @param lowerStr The all-lowercase representation of the name form
907 * definition.
908 * @param userBuffer The buffer into which the user-provided representation
909 * of the value will be placed.
910 * @param lowerBuffer The buffer into which the all-lowercase representation
911 * of the value will be placed.
912 * @param startPos The position in the provided string at which to start
913 * reading the quoted string.
914 *
915 * @return The position of the first character that is not part of the quoted
916 * string or one of the trailing spaces after it.
917 *
918 * @throws DirectoryException If a problem is encountered while reading the
919 * quoted string.
920 */
921 private static int readQuotedString(String valueStr, String lowerStr,
922 StringBuilder userBuffer,
923 StringBuilder lowerBuffer, int startPos)
924 throws DirectoryException
925 {
926 // Skip over any spaces at the beginning of the value.
927 char c = '\u0000';
928 int length = lowerStr.length();
929 while ((startPos < length) && ((c = lowerStr.charAt(startPos)) == ' '))
930 {
931 startPos++;
932 }
933
934 if (startPos >= length)
935 {
936 Message message = ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(lowerStr);
937 throw new DirectoryException(
938 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
939 }
940
941
942 // The next character must be a single quote.
943 if (c != '\'')
944 {
945 Message message = ERR_ATTR_SYNTAX_NAME_FORM_EXPECTED_QUOTE_AT_POS.get(
946 valueStr, startPos, c);
947 throw new DirectoryException(
948 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
949 }
950
951
952 // Read until we find the closing quote.
953 startPos++;
954 while ((startPos < length) && ((c = lowerStr.charAt(startPos)) != '\''))
955 {
956 lowerBuffer.append(c);
957 userBuffer.append(valueStr.charAt(startPos));
958 startPos++;
959 }
960
961
962 // Skip over any trailing spaces after the value.
963 startPos++;
964 while ((startPos < length) && ((c = lowerStr.charAt(startPos)) == ' '))
965 {
966 startPos++;
967 }
968
969
970 // If we're at the end of the value, then that's illegal.
971 if (startPos >= length)
972 {
973 Message message = ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(lowerStr);
974 throw new DirectoryException(
975 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
976 }
977
978
979 // Return the position of the first non-space character after the token.
980 return startPos;
981 }
982
983
984
985 /**
986 * Reads the attribute type description or numeric OID from the provided
987 * string, skipping over any leading or trailing spaces, and appending the
988 * value to the provided buffer.
989 *
990 * @param lowerStr The string from which the name or OID is to be read.
991 * @param woidBuffer The buffer into which the name or OID should be
992 * appended.
993 * @param startPos The position at which to start reading.
994 *
995 * @return The position of the first character after the name or OID that is
996 * not a space.
997 *
998 * @throws DirectoryException If a problem is encountered while reading the
999 * name or OID.
1000 */
1001 private static int readWOID(String lowerStr, StringBuilder woidBuffer,
1002 int startPos)
1003 throws DirectoryException
1004 {
1005 // Skip over any spaces at the beginning of the value.
1006 char c = '\u0000';
1007 int length = lowerStr.length();
1008 while ((startPos < length) && ((c = lowerStr.charAt(startPos)) == ' '))
1009 {
1010 startPos++;
1011 }
1012
1013 if (startPos >= length)
1014 {
1015 Message message = ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(lowerStr);
1016 throw new DirectoryException(
1017 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1018 }
1019
1020
1021 // The next character must be either numeric (for an OID) or alphabetic (for
1022 // an attribute type description).
1023 if (isDigit(c))
1024 {
1025 // This must be a numeric OID. In that case, we will accept only digits
1026 // and periods, but not consecutive periods.
1027 boolean lastWasPeriod = false;
1028 while ((startPos < length) && ((c = lowerStr.charAt(startPos++)) != ' '))
1029 {
1030 if (c == '.')
1031 {
1032 if (lastWasPeriod)
1033 {
1034 Message message =
1035 ERR_ATTR_SYNTAX_NAME_FORM_DOUBLE_PERIOD_IN_NUMERIC_OID.
1036 get(lowerStr, (startPos-1));
1037 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1038 message);
1039 }
1040 else
1041 {
1042 woidBuffer.append(c);
1043 lastWasPeriod = true;
1044 }
1045 }
1046 else if (! isDigit(c))
1047 {
1048 // Technically, this must be an illegal character. However, it is
1049 // possible that someone just got sloppy and did not include a space
1050 // between the name/OID and a closing parenthesis. In that case,
1051 // we'll assume it's the end of the value. What's more, we'll have
1052 // to prematurely return to nasty side effects from stripping off
1053 // additional characters.
1054 if (c == ')')
1055 {
1056 return (startPos-1);
1057 }
1058
1059 // This must have been an illegal character.
1060 Message message =
1061 ERR_ATTR_SYNTAX_NAME_FORM_ILLEGAL_CHAR_IN_NUMERIC_OID.
1062 get(lowerStr, c, (startPos-1));
1063 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1064 message);
1065 }
1066 else
1067 {
1068 woidBuffer.append(c);
1069 lastWasPeriod = false;
1070 }
1071 }
1072 }
1073 else if (isAlpha(c))
1074 {
1075 // This must be an attribute type description. In this case, we will only
1076 // accept alphabetic characters, numeric digits, and the hyphen.
1077 while ((startPos < length) && ((c = lowerStr.charAt(startPos++)) != ' '))
1078 {
1079 if (isAlpha(c) || isDigit(c) || (c == '-') ||
1080 ((c == '_') && DirectoryServer.allowAttributeNameExceptions()))
1081 {
1082 woidBuffer.append(c);
1083 }
1084 else
1085 {
1086 // Technically, this must be an illegal character. However, it is
1087 // possible that someone just got sloppy and did not include a space
1088 // between the name/OID and a closing parenthesis. In that case,
1089 // we'll assume it's the end of the value. What's more, we'll have
1090 // to prematurely return to nasty side effects from stripping off
1091 // additional characters.
1092 if (c == ')')
1093 {
1094 return (startPos-1);
1095 }
1096
1097 // This must have been an illegal character.
1098 Message message =
1099 ERR_ATTR_SYNTAX_NAME_FORM_ILLEGAL_CHAR_IN_STRING_OID.
1100 get(lowerStr, c, (startPos-1));
1101 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1102 message);
1103 }
1104 }
1105 }
1106 else
1107 {
1108 Message message =
1109 ERR_ATTR_SYNTAX_NAME_FORM_ILLEGAL_CHAR.get(lowerStr, c, startPos);
1110 throw new DirectoryException(
1111 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1112 }
1113
1114
1115 // Skip over any trailing spaces after the value.
1116 while ((startPos < length) && ((c = lowerStr.charAt(startPos)) == ' '))
1117 {
1118 startPos++;
1119 }
1120
1121
1122 // If we're at the end of the value, then that's illegal.
1123 if (startPos >= length)
1124 {
1125 Message message = ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(lowerStr);
1126 throw new DirectoryException(
1127 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1128 }
1129
1130
1131 // Return the position of the first non-space character after the token.
1132 return startPos;
1133 }
1134
1135
1136
1137 /**
1138 * Reads the value for an "extra" parameter. It will handle a single unquoted
1139 * word (which is technically illegal, but we'll allow it), a single quoted
1140 * string, or an open parenthesis followed by a space-delimited set of quoted
1141 * strings or unquoted words followed by a close parenthesis.
1142 *
1143 * @param valueStr The string containing the information to be read.
1144 * @param valueList The list of "extra" parameter values read so far.
1145 * @param startPos The position in the value string at which to start
1146 * reading.
1147 *
1148 * @return The "extra" parameter value that was read.
1149 *
1150 * @throws DirectoryException If a problem occurs while attempting to read
1151 * the value.
1152 */
1153 private static int readExtraParameterValues(String valueStr,
1154 List<String> valueList, int startPos)
1155 throws DirectoryException
1156 {
1157 // Skip over any leading spaces.
1158 int length = valueStr.length();
1159 char c = valueStr.charAt(startPos++);
1160 while ((startPos < length) && (c == ' '))
1161 {
1162 c = valueStr.charAt(startPos++);
1163 }
1164
1165 if (startPos >= length)
1166 {
1167 Message message = ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(valueStr);
1168 throw new DirectoryException(
1169 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1170 }
1171
1172
1173 // Look at the next character. If it is a quote, then parse until the next
1174 // quote and end. If it is an open parenthesis, then parse individual
1175 // values until the close parenthesis and end. Otherwise, parse until the
1176 // next space and end.
1177 if (c == '\'')
1178 {
1179 // Parse until the closing quote.
1180 StringBuilder valueBuffer = new StringBuilder();
1181 while ((startPos < length) && ((c = valueStr.charAt(startPos++)) != '\''))
1182 {
1183 valueBuffer.append(c);
1184 }
1185
1186 valueList.add(valueBuffer.toString());
1187 }
1188 else if (c == '(')
1189 {
1190 while (true)
1191 {
1192 // Skip over any leading spaces;
1193 startPos++;
1194 while ((startPos < length) && ((c = valueStr.charAt(startPos)) == ' '))
1195 {
1196 startPos++;
1197 }
1198
1199 if (startPos >= length)
1200 {
1201 Message message =
1202 ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(valueStr);
1203 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1204 message);
1205 }
1206
1207
1208 if (c == ')')
1209 {
1210 // This is the end of the list.
1211 break;
1212 }
1213 else if (c == '(')
1214 {
1215 // This is an illegal character.
1216 Message message =
1217 ERR_ATTR_SYNTAX_NAME_FORM_ILLEGAL_CHAR.get(valueStr, c, startPos);
1218 throw new DirectoryException(ResultCode.INVALID_ATTRIBUTE_SYNTAX,
1219 message);
1220 }
1221 else
1222 {
1223 // We'll recursively call this method to deal with this.
1224 startPos = readExtraParameterValues(valueStr, valueList, startPos);
1225 }
1226 }
1227 }
1228 else
1229 {
1230 // Parse until the next space.
1231 StringBuilder valueBuffer = new StringBuilder();
1232 while ((startPos < length) && ((c = valueStr.charAt(startPos++)) != ' '))
1233 {
1234 valueBuffer.append(c);
1235 }
1236
1237 valueList.add(valueBuffer.toString());
1238 }
1239
1240
1241
1242 // Skip over any trailing spaces.
1243 while ((startPos < length) && (valueStr.charAt(startPos) == ' '))
1244 {
1245 startPos++;
1246 }
1247
1248 if (startPos >= length)
1249 {
1250 Message message = ERR_ATTR_SYNTAX_NAME_FORM_TRUNCATED_VALUE.get(valueStr);
1251 throw new DirectoryException(
1252 ResultCode.INVALID_ATTRIBUTE_SYNTAX, message);
1253 }
1254
1255
1256 return startPos;
1257 }
1258 }
1259