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