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.ASN1Element; 033import com.unboundid.asn1.ASN1Exception; 034import com.unboundid.asn1.ASN1OctetString; 035import com.unboundid.asn1.ASN1Sequence; 036import com.unboundid.asn1.ASN1StreamReader; 037import com.unboundid.asn1.ASN1StreamReaderSequence; 038import com.unboundid.util.Extensible; 039import com.unboundid.util.NotMutable; 040import com.unboundid.util.ThreadSafety; 041import com.unboundid.util.ThreadSafetyLevel; 042 043import static com.unboundid.asn1.ASN1Constants.*; 044import static com.unboundid.ldap.sdk.LDAPMessages.*; 045import static com.unboundid.util.Debug.*; 046import static com.unboundid.util.StaticUtils.*; 047import static com.unboundid.util.Validator.*; 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<String,DecodeableControl>(); 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 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 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 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<ASN1Element>(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 UNIVERSAL_BOOLEAN_TYPE: 353 isCritical = reader.readBoolean(); 354 break; 355 case 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(toHex(type))); 361 } 362 } 363 364 return decode(oid, isCritical, value); 365 } 366 catch (final LDAPException le) 367 { 368 debugException(le); 369 throw le; 370 } 371 catch (final Exception e) 372 { 373 debugException(e); 374 throw new LDAPException(ResultCode.DECODING_ERROR, 375 ERR_CONTROL_CANNOT_DECODE.get(getExceptionMessage(e)), e); 376 } 377 } 378 379 380 381 /** 382 * Decodes the provided ASN.1 sequence as an LDAP control. 383 * 384 * @param controlSequence The ASN.1 sequence to be decoded. 385 * 386 * @return The decoded control. 387 * 388 * @throws LDAPException If a problem occurs while attempting to decode the 389 * provided ASN.1 sequence as an LDAP control. 390 */ 391 public static Control decode(final ASN1Sequence controlSequence) 392 throws LDAPException 393 { 394 final ASN1Element[] elements = controlSequence.elements(); 395 396 if ((elements.length < 1) || (elements.length > 3)) 397 { 398 throw new LDAPException(ResultCode.DECODING_ERROR, 399 ERR_CONTROL_DECODE_INVALID_ELEMENT_COUNT.get( 400 elements.length)); 401 } 402 403 final String oid = 404 ASN1OctetString.decodeAsOctetString(elements[0]).stringValue(); 405 406 boolean isCritical = false; 407 ASN1OctetString value = null; 408 if (elements.length == 2) 409 { 410 switch (elements[1].getType()) 411 { 412 case UNIVERSAL_BOOLEAN_TYPE: 413 try 414 { 415 isCritical = 416 ASN1Boolean.decodeAsBoolean(elements[1]).booleanValue(); 417 } 418 catch (final ASN1Exception ae) 419 { 420 debugException(ae); 421 throw new LDAPException(ResultCode.DECODING_ERROR, 422 ERR_CONTROL_DECODE_CRITICALITY.get(getExceptionMessage(ae)), 423 ae); 424 } 425 break; 426 427 case UNIVERSAL_OCTET_STRING_TYPE: 428 value = ASN1OctetString.decodeAsOctetString(elements[1]); 429 break; 430 431 default: 432 throw new LDAPException(ResultCode.DECODING_ERROR, 433 ERR_CONTROL_INVALID_TYPE.get( 434 toHex(elements[1].getType()))); 435 } 436 } 437 else if (elements.length == 3) 438 { 439 try 440 { 441 isCritical = ASN1Boolean.decodeAsBoolean(elements[1]).booleanValue(); 442 } 443 catch (final ASN1Exception ae) 444 { 445 debugException(ae); 446 throw new LDAPException(ResultCode.DECODING_ERROR, 447 ERR_CONTROL_DECODE_CRITICALITY.get(getExceptionMessage(ae)), ae); 448 } 449 450 value = ASN1OctetString.decodeAsOctetString(elements[2]); 451 } 452 453 return decode(oid, isCritical, value); 454 } 455 456 457 458 /** 459 * Attempts to create the most appropriate control instance from the provided 460 * information. If a {@link DecodeableControl} instance has been registered 461 * for the specified OID, then this method will attempt to use that instance 462 * to construct a control. If that fails, or if no appropriate 463 * {@code DecodeableControl} is registered, then a generic control will be 464 * returned. 465 * 466 * @param oid The OID for the control. It must not be {@code null}. 467 * @param isCritical Indicates whether the control should be considered 468 * critical. 469 * @param value The value for the control. It may be {@code null} if 470 * there is no value. 471 * 472 * @return The decoded control. 473 * 474 * @throws LDAPException If a problem occurs while attempting to decode the 475 * provided ASN.1 sequence as an LDAP control. 476 */ 477 public static Control decode(final String oid, final boolean isCritical, 478 final ASN1OctetString value) 479 throws LDAPException 480 { 481 final DecodeableControl decodeableControl = decodeableControlMap.get(oid); 482 if (decodeableControl == null) 483 { 484 return new Control(oid, isCritical, value); 485 } 486 else 487 { 488 try 489 { 490 return decodeableControl.decodeControl(oid, isCritical, value); 491 } 492 catch (final Exception e) 493 { 494 debugException(e); 495 return new Control(oid, isCritical, value); 496 } 497 } 498 } 499 500 501 502 /** 503 * Encodes the provided set of controls to an ASN.1 sequence suitable for 504 * inclusion in an LDAP message. 505 * 506 * @param controls The set of controls to be encoded. 507 * 508 * @return An ASN.1 sequence containing the encoded set of controls. 509 */ 510 public static ASN1Sequence encodeControls(final Control[] controls) 511 { 512 final ASN1Sequence[] controlElements = new ASN1Sequence[controls.length]; 513 for (int i=0; i < controls.length; i++) 514 { 515 controlElements[i] = controls[i].encode(); 516 } 517 518 return new ASN1Sequence(CONTROLS_TYPE, controlElements); 519 } 520 521 522 523 /** 524 * Decodes the contents of the provided sequence as a set of controls. 525 * 526 * @param controlSequence The ASN.1 sequence containing the encoded set of 527 * controls. 528 * 529 * @return The decoded set of controls. 530 * 531 * @throws LDAPException If a problem occurs while attempting to decode any 532 * of the controls. 533 */ 534 public static Control[] decodeControls(final ASN1Sequence controlSequence) 535 throws LDAPException 536 { 537 final ASN1Element[] controlElements = controlSequence.elements(); 538 final Control[] controls = new Control[controlElements.length]; 539 540 for (int i=0; i < controlElements.length; i++) 541 { 542 try 543 { 544 controls[i] = decode(ASN1Sequence.decodeAsSequence(controlElements[i])); 545 } 546 catch (final ASN1Exception ae) 547 { 548 debugException(ae); 549 throw new LDAPException(ResultCode.DECODING_ERROR, 550 ERR_CONTROLS_DECODE_ELEMENT_NOT_SEQUENCE.get( 551 getExceptionMessage(ae)), 552 ae); 553 } 554 } 555 556 return controls; 557 } 558 559 560 561 /** 562 * Registers the provided class to be used in an attempt to decode controls 563 * with the specified OID. 564 * 565 * @param oid The response control OID for which the provided 566 * class will be registered. 567 * @param controlInstance The control instance that should be used to decode 568 * controls with the provided OID. 569 */ 570 public static void registerDecodeableControl(final String oid, 571 final DecodeableControl controlInstance) 572 { 573 decodeableControlMap.put(oid, controlInstance); 574 } 575 576 577 578 /** 579 * Deregisters the decodeable control class associated with the provided OID. 580 * 581 * @param oid The response control OID for which to deregister the 582 * decodeable control class. 583 */ 584 public static void deregisterDecodeableControl(final String oid) 585 { 586 decodeableControlMap.remove(oid); 587 } 588 589 590 591 /** 592 * Retrieves a hash code for this control. 593 * 594 * @return A hash code for this control. 595 */ 596 @Override() 597 public final int hashCode() 598 { 599 int hashCode = oid.hashCode(); 600 601 if (isCritical) 602 { 603 hashCode++; 604 } 605 606 if (value != null) 607 { 608 hashCode += value.hashCode(); 609 } 610 611 return hashCode; 612 } 613 614 615 616 /** 617 * Indicates whether the provided object may be considered equal to this 618 * control. 619 * 620 * @param o The object for which to make the determination. 621 * 622 * @return {@code true} if the provided object may be considered equal to 623 * this control, or {@code false} if not. 624 */ 625 @Override() 626 public final boolean equals(final Object o) 627 { 628 if (o == null) 629 { 630 return false; 631 } 632 633 if (o == this) 634 { 635 return true; 636 } 637 638 if (! (o instanceof Control)) 639 { 640 return false; 641 } 642 643 final Control c = (Control) o; 644 if (! oid.equals(c.oid)) 645 { 646 return false; 647 } 648 649 if (isCritical != c.isCritical) 650 { 651 return false; 652 } 653 654 if (value == null) 655 { 656 if (c.value != null) 657 { 658 return false; 659 } 660 } 661 else 662 { 663 if (c.value == null) 664 { 665 return false; 666 } 667 668 if (! value.equals(c.value)) 669 { 670 return false; 671 } 672 } 673 674 675 return true; 676 } 677 678 679 680 /** 681 * Retrieves the user-friendly name for this control, if available. If no 682 * user-friendly name has been defined, then the OID will be returned. 683 * 684 * @return The user-friendly name for this control, or the OID if no 685 * user-friendly name is available. 686 */ 687 public String getControlName() 688 { 689 // By default, we will return the OID. Subclasses should override this to 690 // provide the user-friendly name. 691 return oid; 692 } 693 694 695 696 /** 697 * Retrieves a string representation of this LDAP control. 698 * 699 * @return A string representation of this LDAP control. 700 */ 701 @Override() 702 public String toString() 703 { 704 final StringBuilder buffer = new StringBuilder(); 705 toString(buffer); 706 return buffer.toString(); 707 } 708 709 710 711 /** 712 * Appends a string representation of this LDAP control to the provided 713 * buffer. 714 * 715 * @param buffer The buffer to which to append the string representation of 716 * this buffer. 717 */ 718 public void toString(final StringBuilder buffer) 719 { 720 buffer.append("Control(oid="); 721 buffer.append(oid); 722 buffer.append(", isCritical="); 723 buffer.append(isCritical); 724 buffer.append(", value="); 725 726 if (value == null) 727 { 728 buffer.append("{null}"); 729 } 730 else 731 { 732 buffer.append("{byte["); 733 buffer.append(value.getValue().length); 734 buffer.append("]}"); 735 } 736 737 buffer.append(')'); 738 } 739}