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\Request;
12
13use FreeDSx\Asn1\Asn1;
14use FreeDSx\Asn1\Type\AbstractType;
15use FreeDSx\Asn1\Type\OctetStringType;
16use FreeDSx\Asn1\Type\SequenceType;
17use FreeDSx\Asn1\Type\SetType;
18use FreeDSx\Ldap\Entry\Attribute;
19use FreeDSx\Ldap\Entry\Dn;
20use FreeDSx\Ldap\Entry\Entry;
21use FreeDSx\Ldap\Exception\ProtocolException;
22
23/**
24 * A request to add an entry to LDAP. RFC 4511, 4.7.
25 *
26 * AddRequest ::= [APPLICATION 8] SEQUENCE {
27 *     entry           LDAPDN,
28 *     attributes      AttributeList }
29 *
30 * AttributeList ::= SEQUENCE OF attribute Attribute
31 *
32 * PartialAttribute ::= SEQUENCE {
33 *     type       AttributeDescription,
34 *     vals       SET OF value AttributeValue }
35 *
36 * Attribute ::= PartialAttribute(WITH COMPONENTS {
37 *     ...,
38 *     vals (SIZE(1..MAX))})
39 *
40 * AttributeDescription ::= LDAPString
41 *
42 * AttributeValue ::= OCTET STRING
43 *
44 * @author Chad Sikorra <Chad.Sikorra@gmail.com>
45 */
46class AddRequest implements RequestInterface
47{
48    protected const APP_TAG = 8;
49
50    /**
51     * @var Entry
52     */
53    protected $entry;
54
55    /**
56     * @param Entry $entry
57     */
58    public function __construct(Entry $entry)
59    {
60        $this->entry = $entry;
61    }
62
63    /**
64     * @return Entry
65     */
66    public function getEntry(): Entry
67    {
68        return $this->entry;
69    }
70
71    /**
72     * @param Entry $entry
73     * @return $this
74     */
75    public function setEntry(Entry $entry)
76    {
77        $this->entry = $entry;
78
79        return $this;
80    }
81
82    /**
83     * {@inheritdoc}
84     */
85    public static function fromAsn1(AbstractType $type)
86    {
87        if (!($type instanceof SequenceType && count($type) === 2)) {
88            throw new ProtocolException('The add request is malformed: %s');
89        }
90
91        $dn = $type->getChild(0);
92        $attrList = $type->getChild(1);
93        if (!($dn instanceof OctetStringType && $attrList instanceof SequenceType)) {
94            throw new ProtocolException('The add request is malformed.');
95        }
96        $dn = new Dn($dn->getValue());
97
98        $attributes = [];
99        foreach ($attrList->getChildren() as $attrListing) {
100            if (!($attrListing instanceof SequenceType && \count($attrListing->getChildren()) == 2)) {
101                throw new ProtocolException(sprintf(
102                    'Expected a sequence type, but received: %s',
103                    get_class($attrListing)
104                ));
105            }
106
107            $attrType = $attrListing->getChild(0);
108            $vals = $attrListing->getChild(1);
109            if (!($attrType instanceof OctetStringType && $vals instanceof SetType)) {
110                throw new ProtocolException('The add request is malformed.');
111            }
112
113            $attrValues = [];
114            foreach ($vals->getChildren() as $val) {
115                if (!$val instanceof OctetStringType) {
116                    throw new ProtocolException('The add request is malformed.');
117                }
118                $attrValues[] = $val->getValue();
119            }
120
121            $attributes[] = new Attribute($attrType->getValue(), ...$attrValues);
122        }
123
124        return new self(new Entry($dn, ...$attributes));
125    }
126
127    /**
128     * {@inheritdoc}
129     */
130    public function toAsn1(): AbstractType
131    {
132        $attributeList = Asn1::sequenceOf();
133
134        /** @var Attribute $attribute */
135        foreach ($this->entry->getAttributes() as $attribute) {
136            $attr = Asn1::sequence(Asn1::octetString($attribute->getDescription()));
137
138            $attrValues = Asn1::setOf(...\array_map(function ($value) {
139                return Asn1::octetString($value);
140            }, $attribute->getValues()));
141
142            $attributeList->addChild($attr->addChild($attrValues));
143        }
144
145        return Asn1::application(self::APP_TAG, Asn1::sequence(
146            Asn1::octetString($this->entry->getDn()->toString()),
147            $attributeList
148        ));
149    }
150}
151