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