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