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