001/* 002 * Copyright 2007-2022 Ping Identity Corporation 003 * All Rights Reserved. 004 */ 005/* 006 * Copyright 2007-2022 Ping Identity Corporation 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020/* 021 * Copyright (C) 2007-2022 Ping Identity Corporation 022 * 023 * This program is free software; you can redistribute it and/or modify 024 * it under the terms of the GNU General Public License (GPLv2 only) 025 * or the terms of the GNU Lesser General Public License (LGPLv2.1 only) 026 * as published by the Free Software Foundation. 027 * 028 * This program is distributed in the hope that it will be useful, 029 * but WITHOUT ANY WARRANTY; without even the implied warranty of 030 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 031 * GNU General Public License for more details. 032 * 033 * You should have received a copy of the GNU General Public License 034 * along with this program; if not, see <http://www.gnu.org/licenses>. 035 */ 036package com.unboundid.ldap.sdk; 037 038 039 040import java.io.Serializable; 041import java.lang.reflect.Constructor; 042import java.util.ArrayList; 043import java.util.concurrent.ConcurrentHashMap; 044 045import com.unboundid.asn1.ASN1Boolean; 046import com.unboundid.asn1.ASN1Buffer; 047import com.unboundid.asn1.ASN1BufferSequence; 048import com.unboundid.asn1.ASN1Constants; 049import com.unboundid.asn1.ASN1Element; 050import com.unboundid.asn1.ASN1Exception; 051import com.unboundid.asn1.ASN1OctetString; 052import com.unboundid.asn1.ASN1Sequence; 053import com.unboundid.asn1.ASN1StreamReader; 054import com.unboundid.asn1.ASN1StreamReaderSequence; 055import com.unboundid.util.Debug; 056import com.unboundid.util.Extensible; 057import com.unboundid.util.NotMutable; 058import com.unboundid.util.NotNull; 059import com.unboundid.util.Nullable; 060import com.unboundid.util.StaticUtils; 061import com.unboundid.util.ThreadSafety; 062import com.unboundid.util.ThreadSafetyLevel; 063import com.unboundid.util.Validator; 064 065import static com.unboundid.ldap.sdk.LDAPMessages.*; 066 067 068 069/** 070 * This class provides a data structure that represents an LDAP control. A 071 * control is an element that may be attached to an LDAP request or response 072 * to provide additional information about the processing that should be (or has 073 * been) performed. This class may be overridden to provide additional 074 * processing for specific types of controls. 075 * <BR><BR> 076 * A control includes the following elements: 077 * <UL> 078 * <LI>An object identifier (OID), which identifies the type of control.</LI> 079 * <LI>A criticality flag, which indicates whether the control should be 080 * considered critical to the processing of the operation. If a control 081 * is marked critical but the server either does not support that control 082 * or it is not appropriate for the associated request, then the server 083 * will reject the request. If a control is not marked critical and the 084 * server either does not support it or it is not appropriate for the 085 * associated request, then the server will simply ignore that 086 * control and process the request as if it were not present.</LI> 087 * <LI>An optional value, which provides additional information for the 088 * control. Some controls do not take values, and the value encoding for 089 * controls which do take values varies based on the type of control.</LI> 090 * </UL> 091 * Controls may be included in a request from the client to the server, as well 092 * as responses from the server to the client (including intermediate response, 093 * search result entry, and search result references, in addition to the final 094 * response message for an operation). When using request controls, they may be 095 * included in the request object at the time it is created, or may be added 096 * after the fact for {@link UpdatableLDAPRequest} objects. When using 097 * response controls, each response control class includes a {@code get} method 098 * that can be used to extract the appropriate control from an appropriate 099 * result (e.g., {@link LDAPResult}, {@link SearchResultEntry}, or 100 * {@link SearchResultReference}). 101 */ 102@Extensible() 103@NotMutable() 104@ThreadSafety(level=ThreadSafetyLevel.COMPLETELY_THREADSAFE) 105public class Control 106 implements Serializable 107{ 108 /** 109 * The BER type to use for the encoded set of controls in an LDAP message. 110 */ 111 private static final byte CONTROLS_TYPE = (byte) 0xA0; 112 113 114 115 /** 116 * A map of the decodable control classes that have been registered with the 117 * LDAP SDK, mapped from OID to fully-qualified class name. 118 */ 119 @NotNull static final ConcurrentHashMap<String,String> 120 DECODEABLE_CONTROL_CLASS_NAMES = new ConcurrentHashMap<>(); 121 122 123 124 /** 125 * A map of the instantiated decodeable control classes registered with the 126 * LDAP SDK, mapped from OID to class instance. 127 */ 128 @NotNull private static final ConcurrentHashMap<String,DecodeableControl> 129 DECODEABLE_CONTROL_INSTANCES = new ConcurrentHashMap<>(); 130 131 132 133 /** 134 * The serial version UID for this serializable class. 135 */ 136 private static final long serialVersionUID = 4440956109070220054L; 137 138 139 140 // The encoded value for this control, if there is one. 141 @Nullable private final ASN1OctetString value; 142 143 // Indicates whether this control should be considered critical. 144 private final boolean isCritical; 145 146 // The OID for this control 147 @NotNull private final String oid; 148 149 150 151 static 152 { 153 com.unboundid.ldap.sdk.controls.ControlHelper. 154 registerDefaultResponseControls(); 155 com.unboundid.ldap.sdk.experimental.ControlHelper. 156 registerDefaultResponseControls(); 157 com.unboundid.ldap.sdk.unboundidds.controls.ControlHelper. 158 registerDefaultResponseControls(); 159 } 160 161 162 163 /** 164 * Creates a new empty control instance that is intended to be used only for 165 * decoding controls via the {@code DecodeableControl} interface. All 166 * {@code DecodeableControl} objects must provide a default constructor that 167 * can be used to create an instance suitable for invoking the 168 * {@code decodeControl} method. 169 */ 170 protected Control() 171 { 172 oid = null; 173 isCritical = true; 174 value = null; 175 } 176 177 178 179 /** 180 * Creates a new control whose fields are initialized from the contents of the 181 * provided control. 182 * 183 * @param control The control whose information should be used to create 184 * this new control. 185 */ 186 protected Control(@NotNull final Control control) 187 { 188 oid = control.oid; 189 isCritical = control.isCritical; 190 value = control.value; 191 } 192 193 194 195 /** 196 * Creates a new control with the provided OID. It will not be critical, and 197 * it will not have a value. 198 * 199 * @param oid The OID for this control. It must not be {@code null}. 200 */ 201 public Control(@NotNull final String oid) 202 { 203 Validator.ensureNotNull(oid); 204 205 this.oid = oid; 206 isCritical = false; 207 value = null; 208 } 209 210 211 212 /** 213 * Creates a new control with the provided OID and criticality. It will not 214 * have a value. 215 * 216 * @param oid The OID for this control. It must not be {@code null}. 217 * @param isCritical Indicates whether this control should be considered 218 * critical. 219 */ 220 public Control(@NotNull final String oid, final boolean isCritical) 221 { 222 Validator.ensureNotNull(oid); 223 224 this.oid = oid; 225 this.isCritical = isCritical; 226 value = null; 227 } 228 229 230 231 /** 232 * Creates a new control with the provided information. 233 * 234 * @param oid The OID for this control. It must not be {@code null}. 235 * @param isCritical Indicates whether this control should be considered 236 * critical. 237 * @param value The value for this control. It may be {@code null} if 238 * there is no value. 239 */ 240 public Control(@NotNull final String oid, final boolean isCritical, 241 @Nullable final ASN1OctetString value) 242 { 243 Validator.ensureNotNull(oid); 244 245 this.oid = oid; 246 this.isCritical = isCritical; 247 this.value = value; 248 } 249 250 251 252 /** 253 * Retrieves the OID for this control. 254 * 255 * @return The OID for this control. 256 */ 257 @NotNull() 258 public final String getOID() 259 { 260 return oid; 261 } 262 263 264 265 /** 266 * Indicates whether this control should be considered critical. 267 * 268 * @return {@code true} if this control should be considered critical, or 269 * {@code false} if not. 270 */ 271 public final boolean isCritical() 272 { 273 return isCritical; 274 } 275 276 277 278 /** 279 * Indicates whether this control has a value. 280 * 281 * @return {@code true} if this control has a value, or {@code false} if not. 282 */ 283 public final boolean hasValue() 284 { 285 return (value != null); 286 } 287 288 289 290 /** 291 * Retrieves the encoded value for this control. 292 * 293 * @return The encoded value for this control, or {@code null} if there is no 294 * value. 295 */ 296 @Nullable() 297 public final ASN1OctetString getValue() 298 { 299 return value; 300 } 301 302 303 304 /** 305 * Writes an ASN.1-encoded representation of this control to the provided 306 * ASN.1 stream writer. 307 * 308 * @param writer The ASN.1 stream writer to which the encoded representation 309 * should be written. 310 */ 311 public final void writeTo(@NotNull final ASN1Buffer writer) 312 { 313 final ASN1BufferSequence controlSequence = writer.beginSequence(); 314 writer.addOctetString(oid); 315 316 if (isCritical) 317 { 318 writer.addBoolean(true); 319 } 320 321 if (value != null) 322 { 323 writer.addOctetString(value.getValue()); 324 } 325 326 controlSequence.end(); 327 } 328 329 330 331 /** 332 * Encodes this control to an ASN.1 sequence suitable for use in an LDAP 333 * message. 334 * 335 * @return The encoded representation of this control. 336 */ 337 @NotNull() 338 public final ASN1Sequence encode() 339 { 340 final ArrayList<ASN1Element> elementList = new ArrayList<>(3); 341 elementList.add(new ASN1OctetString(oid)); 342 343 if (isCritical) 344 { 345 elementList.add(new ASN1Boolean(isCritical)); 346 } 347 348 if (value != null) 349 { 350 elementList.add(new ASN1OctetString(value.getValue())); 351 } 352 353 return new ASN1Sequence(elementList); 354 } 355 356 357 358 /** 359 * Reads an LDAP control from the provided ASN.1 stream reader. 360 * 361 * @param reader The ASN.1 stream reader from which to read the control. 362 * 363 * @return The decoded control. 364 * 365 * @throws LDAPException If a problem occurs while attempting to read or 366 * parse the control. 367 */ 368 @NotNull() 369 public static Control readFrom(@NotNull final ASN1StreamReader reader) 370 throws LDAPException 371 { 372 try 373 { 374 final ASN1StreamReaderSequence controlSequence = reader.beginSequence(); 375 final String oid = reader.readString(); 376 377 boolean isCritical = false; 378 ASN1OctetString value = null; 379 while (controlSequence.hasMoreElements()) 380 { 381 final byte type = (byte) reader.peek(); 382 switch (type) 383 { 384 case ASN1Constants.UNIVERSAL_BOOLEAN_TYPE: 385 isCritical = reader.readBoolean(); 386 break; 387 case ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE: 388 value = new ASN1OctetString(reader.readBytes()); 389 break; 390 default: 391 throw new LDAPException(ResultCode.DECODING_ERROR, 392 ERR_CONTROL_INVALID_TYPE.get(StaticUtils.toHex(type))); 393 } 394 } 395 396 return decode(oid, isCritical, value); 397 } 398 catch (final LDAPException le) 399 { 400 Debug.debugException(le); 401 throw le; 402 } 403 catch (final Exception e) 404 { 405 Debug.debugException(e); 406 throw new LDAPException(ResultCode.DECODING_ERROR, 407 ERR_CONTROL_CANNOT_DECODE.get(StaticUtils.getExceptionMessage(e)), 408 e); 409 } 410 } 411 412 413 414 /** 415 * Decodes the provided ASN.1 sequence as an LDAP control. 416 * 417 * @param controlSequence The ASN.1 sequence to be decoded. 418 * 419 * @return The decoded control. 420 * 421 * @throws LDAPException If a problem occurs while attempting to decode the 422 * provided ASN.1 sequence as an LDAP control. 423 */ 424 @NotNull() 425 public static Control decode(@NotNull final ASN1Sequence controlSequence) 426 throws LDAPException 427 { 428 final ASN1Element[] elements = controlSequence.elements(); 429 430 if ((elements.length < 1) || (elements.length > 3)) 431 { 432 throw new LDAPException(ResultCode.DECODING_ERROR, 433 ERR_CONTROL_DECODE_INVALID_ELEMENT_COUNT.get( 434 elements.length)); 435 } 436 437 final String oid = 438 ASN1OctetString.decodeAsOctetString(elements[0]).stringValue(); 439 440 boolean isCritical = false; 441 ASN1OctetString value = null; 442 if (elements.length == 2) 443 { 444 switch (elements[1].getType()) 445 { 446 case ASN1Constants.UNIVERSAL_BOOLEAN_TYPE: 447 try 448 { 449 isCritical = 450 ASN1Boolean.decodeAsBoolean(elements[1]).booleanValue(); 451 } 452 catch (final ASN1Exception ae) 453 { 454 Debug.debugException(ae); 455 throw new LDAPException(ResultCode.DECODING_ERROR, 456 ERR_CONTROL_DECODE_CRITICALITY.get( 457 StaticUtils.getExceptionMessage(ae)), 458 ae); 459 } 460 break; 461 462 case ASN1Constants.UNIVERSAL_OCTET_STRING_TYPE: 463 value = ASN1OctetString.decodeAsOctetString(elements[1]); 464 break; 465 466 default: 467 throw new LDAPException(ResultCode.DECODING_ERROR, 468 ERR_CONTROL_INVALID_TYPE.get( 469 StaticUtils.toHex(elements[1].getType()))); 470 } 471 } 472 else if (elements.length == 3) 473 { 474 try 475 { 476 isCritical = ASN1Boolean.decodeAsBoolean(elements[1]).booleanValue(); 477 } 478 catch (final ASN1Exception ae) 479 { 480 Debug.debugException(ae); 481 throw new LDAPException(ResultCode.DECODING_ERROR, 482 ERR_CONTROL_DECODE_CRITICALITY.get( 483 StaticUtils.getExceptionMessage(ae)), 484 ae); 485 } 486 487 value = ASN1OctetString.decodeAsOctetString(elements[2]); 488 } 489 490 return decode(oid, isCritical, value); 491 } 492 493 494 495 /** 496 * Attempts to create the most appropriate control instance from the provided 497 * information. If a {@link DecodeableControl} instance has been registered 498 * for the specified OID, then this method will attempt to use that instance 499 * to construct a control. If that fails, or if no appropriate 500 * {@code DecodeableControl} is registered, then a generic control will be 501 * returned. 502 * 503 * @param oid The OID for the control. It must not be {@code null}. 504 * @param isCritical Indicates whether the control should be considered 505 * critical. 506 * @param value The value for the control. It may be {@code null} if 507 * there is no value. 508 * 509 * @return The decoded control. 510 * 511 * @throws LDAPException If a problem occurs while attempting to decode the 512 * provided ASN.1 sequence as an LDAP control. 513 */ 514 @NotNull() 515 public static Control decode(@NotNull final String oid, 516 final boolean isCritical, 517 @Nullable final ASN1OctetString value) 518 throws LDAPException 519 { 520 DecodeableControl decodeableControl = DECODEABLE_CONTROL_INSTANCES.get(oid); 521 if (decodeableControl == null) 522 { 523 final String controlClassName = DECODEABLE_CONTROL_CLASS_NAMES.get(oid); 524 if (controlClassName == null) 525 { 526 return new Control(oid, isCritical, value); 527 } 528 529 try 530 { 531 final Class<?> controlClass = Class.forName(controlClassName); 532 final Constructor<?> noArgumentConstructor = 533 controlClass.getDeclaredConstructor(); 534 noArgumentConstructor.setAccessible(true); 535 decodeableControl = 536 (DecodeableControl) noArgumentConstructor.newInstance(); 537 } 538 catch (final Exception e) 539 { 540 Debug.debugException(e); 541 return new Control(oid, isCritical, value); 542 } 543 } 544 545 try 546 { 547 return decodeableControl.decodeControl(oid, isCritical, value); 548 } 549 catch (final Exception e) 550 { 551 Debug.debugException(e); 552 return new Control(oid, isCritical, value); 553 } 554 } 555 556 557 558 /** 559 * Encodes the provided set of controls to an ASN.1 sequence suitable for 560 * inclusion in an LDAP message. 561 * 562 * @param controls The set of controls to be encoded. 563 * 564 * @return An ASN.1 sequence containing the encoded set of controls. 565 */ 566 @NotNull() 567 public static ASN1Sequence encodeControls(@NotNull final Control[] controls) 568 { 569 final ASN1Sequence[] controlElements = new ASN1Sequence[controls.length]; 570 for (int i=0; i < controls.length; i++) 571 { 572 controlElements[i] = controls[i].encode(); 573 } 574 575 return new ASN1Sequence(CONTROLS_TYPE, controlElements); 576 } 577 578 579 580 /** 581 * Decodes the contents of the provided sequence as a set of controls. 582 * 583 * @param controlSequence The ASN.1 sequence containing the encoded set of 584 * controls. 585 * 586 * @return The decoded set of controls. 587 * 588 * @throws LDAPException If a problem occurs while attempting to decode any 589 * of the controls. 590 */ 591 @NotNull() 592 public static Control[] decodeControls( 593 @NotNull final ASN1Sequence controlSequence) 594 throws LDAPException 595 { 596 final ASN1Element[] controlElements = controlSequence.elements(); 597 final Control[] controls = new Control[controlElements.length]; 598 599 for (int i=0; i < controlElements.length; i++) 600 { 601 try 602 { 603 controls[i] = decode(ASN1Sequence.decodeAsSequence(controlElements[i])); 604 } 605 catch (final ASN1Exception ae) 606 { 607 Debug.debugException(ae); 608 throw new LDAPException(ResultCode.DECODING_ERROR, 609 ERR_CONTROLS_DECODE_ELEMENT_NOT_SEQUENCE.get( 610 StaticUtils.getExceptionMessage(ae)), 611 ae); 612 } 613 } 614 615 return controls; 616 } 617 618 619 620 /** 621 * Registers the specified class to be used in an attempt to decode controls 622 * with the specified OID. 623 * 624 * @param oid The response control OID for which the provided class 625 * will be registered. 626 * @param className The fully-qualified name for the Java class that 627 * provides the decodeable control implementation to use 628 * for the provided OID. 629 */ 630 public static void registerDecodeableControl(@NotNull final String oid, 631 @NotNull final String className) 632 { 633 DECODEABLE_CONTROL_CLASS_NAMES.put(oid, className); 634 DECODEABLE_CONTROL_INSTANCES.remove(oid); 635 } 636 637 638 639 /** 640 * Registers the provided class to be used in an attempt to decode controls 641 * with the specified OID. 642 * 643 * @param oid The response control OID for which the provided 644 * class will be registered. 645 * @param controlInstance The control instance that should be used to decode 646 * controls with the provided OID. 647 */ 648 public static void registerDecodeableControl(@NotNull final String oid, 649 @NotNull final DecodeableControl controlInstance) 650 { 651 DECODEABLE_CONTROL_CLASS_NAMES.put(oid, 652 controlInstance.getClass().getName()); 653 DECODEABLE_CONTROL_INSTANCES.put(oid, controlInstance); 654 } 655 656 657 658 /** 659 * Deregisters the decodeable control class associated with the provided OID. 660 * 661 * @param oid The response control OID for which to deregister the 662 * decodeable control class. 663 */ 664 public static void deregisterDecodeableControl(@NotNull final String oid) 665 { 666 DECODEABLE_CONTROL_CLASS_NAMES.remove(oid); 667 DECODEABLE_CONTROL_INSTANCES.remove(oid); 668 } 669 670 671 672 /** 673 * Retrieves a hash code for this control. 674 * 675 * @return A hash code for this control. 676 */ 677 @Override() 678 public final int hashCode() 679 { 680 int hashCode = oid.hashCode(); 681 682 if (isCritical) 683 { 684 hashCode++; 685 } 686 687 if (value != null) 688 { 689 hashCode += value.hashCode(); 690 } 691 692 return hashCode; 693 } 694 695 696 697 /** 698 * Indicates whether the provided object may be considered equal to this 699 * control. 700 * 701 * @param o The object for which to make the determination. 702 * 703 * @return {@code true} if the provided object may be considered equal to 704 * this control, or {@code false} if not. 705 */ 706 @Override() 707 public final boolean equals(@Nullable final Object o) 708 { 709 if (o == null) 710 { 711 return false; 712 } 713 714 if (o == this) 715 { 716 return true; 717 } 718 719 if (! (o instanceof Control)) 720 { 721 return false; 722 } 723 724 final Control c = (Control) o; 725 if (! oid.equals(c.oid)) 726 { 727 return false; 728 } 729 730 if (isCritical != c.isCritical) 731 { 732 return false; 733 } 734 735 if (value == null) 736 { 737 if (c.value != null) 738 { 739 return false; 740 } 741 } 742 else 743 { 744 if (c.value == null) 745 { 746 return false; 747 } 748 749 if (! value.equals(c.value)) 750 { 751 return false; 752 } 753 } 754 755 756 return true; 757 } 758 759 760 761 /** 762 * Retrieves the user-friendly name for this control, if available. If no 763 * user-friendly name has been defined, then the OID will be returned. 764 * 765 * @return The user-friendly name for this control, or the OID if no 766 * user-friendly name is available. 767 */ 768 @NotNull() 769 public String getControlName() 770 { 771 // By default, we will return the OID. Subclasses should override this to 772 // provide the user-friendly name. 773 return oid; 774 } 775 776 777 778 /** 779 * Retrieves a string representation of this LDAP control. 780 * 781 * @return A string representation of this LDAP control. 782 */ 783 @Override() 784 @NotNull() 785 public String toString() 786 { 787 final StringBuilder buffer = new StringBuilder(); 788 toString(buffer); 789 return buffer.toString(); 790 } 791 792 793 794 /** 795 * Appends a string representation of this LDAP control to the provided 796 * buffer. 797 * 798 * @param buffer The buffer to which to append the string representation of 799 * this buffer. 800 */ 801 public void toString(@NotNull final StringBuilder buffer) 802 { 803 buffer.append("Control(oid="); 804 buffer.append(oid); 805 buffer.append(", isCritical="); 806 buffer.append(isCritical); 807 buffer.append(", value="); 808 809 if (value == null) 810 { 811 buffer.append("{null}"); 812 } 813 else 814 { 815 buffer.append("{byte["); 816 buffer.append(value.getValue().length); 817 buffer.append("]}"); 818 } 819 820 buffer.append(')'); 821 } 822}