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}