1<?php
2
3/**
4 * This file is part of the FreeDSx LDAP package.
5 *
6 * (c) Chad Sikorra <Chad.Sikorra@gmail.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace FreeDSx\Ldap\Operation\Response;
13
14use FreeDSx\Asn1\Asn1;
15use FreeDSx\Asn1\Exception\EncoderException;
16use FreeDSx\Asn1\Exception\PartialPduException;
17use FreeDSx\Asn1\Type\AbstractType;
18use FreeDSx\Asn1\Type\SequenceType;
19use FreeDSx\Ldap\Exception\ProtocolException;
20use FreeDSx\Ldap\Operation\LdapResult;
21use FreeDSx\Ldap\Protocol\LdapEncoder;
22use FreeDSx\Ldap\Protocol\ProtocolElementInterface;
23
24/**
25 * RFC 4511, 4.12
26 *
27 * ExtendedResponse ::= [APPLICATION 24] SEQUENCE {
28 *     COMPONENTS OF LDAPResult,
29 *         responseName     [10] LDAPOID OPTIONAL,
30 *         responseValue    [11] OCTET STRING OPTIONAL }
31 *
32 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
33 */
34class ExtendedResponse extends LdapResult
35{
36    /**
37     * @var int
38     */
39    protected $tagNumber = 24;
40
41    /**
42     * RFC 4511, 4.4.1. Used by the server to notify the client it is terminating the LDAP session.
43     */
44    public const OID_NOTICE_OF_DISCONNECTION = '1.3.6.1.4.1.1466.20036';
45
46    /**
47     * @var null|string
48     */
49    protected $responseName;
50
51    /**
52     * @var null|string|AbstractType|ProtocolElementInterface
53     */
54    protected $responseValue;
55
56    /**
57     * @param LdapResult $result
58     * @param null|string $responseName
59     * @param null|string $responseValue
60     */
61    public function __construct(LdapResult $result, ?string $responseName = null, $responseValue = null)
62    {
63        $this->responseValue = $responseValue;
64        $this->responseName = $responseName;
65        parent::__construct($result->getResultCode(), $result->getDn(), $result->getDiagnosticMessage(), ...$result->getReferrals());
66    }
67
68    /**
69     * Get the OID name of the extended response.
70     *
71     * @return null|string
72     */
73    public function getName(): ?string
74    {
75        return $this->responseName;
76    }
77
78    /**
79     * Get the value of the extended response.
80     *
81     * @return null|string
82     */
83    public function getValue(): ?string
84    {
85        return is_string($this->responseValue) ? $this->responseValue : null;
86    }
87
88    /**
89     * {@inheritDoc}
90     * @return self
91     * @throws EncoderException
92     */
93    public static function fromAsn1(AbstractType $type)
94    {
95        return new self(
96            self::createLdapResult($type),
97            ...self::parseExtendedResponse($type)
98        );
99    }
100
101    /**
102     * @return AbstractType
103     * @throws ProtocolException
104     * @throws EncoderException
105     */
106    public function toAsn1(): AbstractType
107    {
108        /** @var SequenceType $asn1 */
109        $asn1 = parent::toAsn1();
110
111        if ($this->responseName !== null) {
112            $asn1->addChild(Asn1::context(10, Asn1::octetString($this->responseName)));
113        }
114        if ($this->responseValue !== null) {
115            $encoder = new LdapEncoder();
116            $value = $this->responseValue;
117            if ($value instanceof AbstractType) {
118                $value = $encoder->encode($value);
119            } elseif ($value instanceof ProtocolElementInterface) {
120                $value = $encoder->encode($value->toAsn1());
121            }
122            $asn1->addChild(Asn1::context(11, Asn1::octetString($value)));
123        }
124
125        return $asn1;
126    }
127
128    /**
129     * @param AbstractType $type
130     * @return array
131     */
132    protected static function parseExtendedResponse(AbstractType $type)
133    {
134        $info = [0 => null, 1 => null];
135
136        foreach ($type->getChildren() as $child) {
137            if ($child->getTagNumber() === 10) {
138                $info[0] = $child->getValue();
139            } elseif ($child->getTagNumber() === 11) {
140                $info[1] = $child->getValue();
141            }
142        }
143
144        return $info;
145    }
146
147    /**
148     * @param AbstractType $type
149     * @return LdapResult
150     * @throws ProtocolException
151     * @throws EncoderException
152     */
153    protected static function createLdapResult(AbstractType $type)
154    {
155        [$resultCode, $dn, $diagnosticMessage, $referrals] = self::parseResultData($type);
156
157        return new LdapResult($resultCode, $dn, $diagnosticMessage, ...$referrals);
158    }
159
160    /**
161     * @param AbstractType $type
162     * @return AbstractType|null
163     * @throws ProtocolException
164     * @throws EncoderException
165     * @throws PartialPduException
166     */
167    protected static function decodeEncodedValue(AbstractType $type)
168    {
169        if (!$type instanceof SequenceType) {
170            throw new ProtocolException('The received control is malformed. Unable to get the encoded value.');
171        }
172        [1 => $value] = self::parseExtendedResponse($type);
173
174        return $value === null ? null : (new LdapEncoder())->decode($value);
175    }
176}
177