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