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