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\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\Protocol\LdapEncoder; 21use FreeDSx\Ldap\Protocol\ProtocolElementInterface; 22use function count; 23 24/** 25 * An Extended Request. RFC 4511, 4.12 26 * 27 * ExtendedRequest ::= [APPLICATION 23] SEQUENCE { 28 * requestName [0] LDAPOID, 29 * requestValue [1] OCTET STRING OPTIONAL } 30 * 31 * @author Chad Sikorra <Chad.Sikorra@gmail.com> 32 */ 33class ExtendedRequest implements RequestInterface 34{ 35 protected const APP_TAG = 23; 36 37 /** 38 * Represents a request to cancel an operation. RFC 3909. 39 */ 40 public const OID_CANCEL = '1.3.6.1.1.8'; 41 42 /** 43 * Represents a request to issue a StartTLS to encrypt the connection. 44 */ 45 public const OID_START_TLS = '1.3.6.1.4.1.1466.20037'; 46 47 /** 48 * Represents a "whoami" request. RFC 4532. 49 */ 50 public const OID_WHOAMI = '1.3.6.1.4.1.4203.1.11.3'; 51 52 /** 53 * Represents a Password Modify Extended Operation. RFC 3062. 54 */ 55 public const OID_PWD_MODIFY = '1.3.6.1.4.1.4203.1.11.1'; 56 57 /** 58 * @var string 59 */ 60 protected $requestName; 61 62 /** 63 * @var null|AbstractType|ProtocolElementInterface|string 64 */ 65 protected $requestValue; 66 67 /** 68 * @param string $requestName 69 * @param null|AbstractType|ProtocolElementInterface|string $requestValue 70 */ 71 public function __construct(string $requestName, $requestValue = null) 72 { 73 $this->requestName = $requestName; 74 $this->requestValue = $requestValue; 75 } 76 77 /** 78 * @param string $requestName 79 * @return $this 80 */ 81 public function setName(string $requestName) 82 { 83 $this->requestName = $requestName; 84 85 return $this; 86 } 87 88 /** 89 * @return string 90 */ 91 public function getName(): string 92 { 93 return $this->requestName; 94 } 95 96 /** 97 * @param AbstractType|ProtocolElementInterface|string|null $requestValue 98 * @return $this 99 */ 100 public function setValue($requestValue) 101 { 102 $this->requestValue = $requestValue; 103 104 return $this; 105 } 106 107 /** 108 * @return AbstractType|ProtocolElementInterface|string|null 109 */ 110 public function getValue() 111 { 112 return $this->requestValue; 113 } 114 115 /** 116 * @return AbstractType 117 * @throws EncoderException 118 */ 119 public function toAsn1(): AbstractType 120 { 121 $asn1 = Asn1::sequence(Asn1::context(0, Asn1::octetString($this->requestName))); 122 123 if ($this->requestValue !== null) { 124 $value = $this->requestValue; 125 $encoder = new LdapEncoder(); 126 if ($value instanceof AbstractType) { 127 $value = $encoder->encode($value); 128 } elseif ($value instanceof ProtocolElementInterface) { 129 $value = $encoder->encode($value->toAsn1()); 130 } 131 $asn1->addChild(Asn1::context(1, Asn1::octetString($value))); 132 } 133 134 return Asn1::application(self::APP_TAG, $asn1); 135 } 136 137 /** 138 * {@inheritDoc} 139 * @return self 140 */ 141 public static function fromAsn1(AbstractType $type) 142 { 143 return new self(...self::parseAsn1ExtendedRequest($type)); 144 } 145 146 /** 147 * @param AbstractType $type 148 * @return AbstractType 149 * @throws ProtocolException 150 * @throws EncoderException 151 * @throws PartialPduException 152 */ 153 protected static function decodeEncodedValue(AbstractType $type): ?AbstractType 154 { 155 [1 => $value] = self::parseAsn1ExtendedRequest($type); 156 157 return $value !== null ? (new LdapEncoder())->decode($value) : null; 158 } 159 160 /** 161 * @param AbstractType $type 162 * @return array 163 * @throws ProtocolException 164 * @psalm-return array{0: mixed, 1: mixed|null} 165 */ 166 protected static function parseAsn1ExtendedRequest(AbstractType $type) 167 { 168 if (!($type instanceof SequenceType && (count($type) === 1 || count($type) === 2))) { 169 throw new ProtocolException('The extended request is malformed'); 170 } 171 $oid = null; 172 $value = null; 173 174 foreach ($type->getChildren() as $child) { 175 if ($child->getTagClass() === AbstractType::TAG_CLASS_CONTEXT_SPECIFIC && $child->getTagNumber() === 0) { 176 $oid = $child; 177 } elseif ($child->getTagClass() === AbstractType::TAG_CLASS_CONTEXT_SPECIFIC && $child->getTagNumber() === 1) { 178 $value = $child; 179 } 180 } 181 if ($oid === null) { 182 throw new ProtocolException('The extended request is malformed'); 183 } 184 if ($value !== null) { 185 $value = $value->getValue(); 186 } 187 188 return [$oid->getValue(), $value]; 189 } 190} 191