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\Entry; 13 14use FreeDSx\Ldap\Exception\InvalidArgumentException; 15use function array_keys; 16use function array_values; 17use function count; 18use function explode; 19use function preg_split; 20use function str_replace; 21use function substr; 22use function substr_replace; 23 24/** 25 * Represents a Relative Distinguished Name. 26 * 27 * @author Chad Sikorra <Chad.Sikorra@gmail.com> 28 */ 29class Rdn 30{ 31 use EscapeTrait; 32 33 public const ESCAPE_MAP = [ 34 '\\' => '\\5c', 35 '"' => '\\22', 36 '+' => '\\2b', 37 ',' => '\\2c', 38 ';' => '\\3b', 39 '<' => '\\3c', 40 '>' => '\\3e', 41 ]; 42 43 /** 44 * @var string 45 */ 46 protected $name; 47 48 /** 49 * @var string 50 */ 51 protected $value; 52 53 /** 54 * @var Rdn[] 55 */ 56 protected $additional = []; 57 58 /** 59 * @param string $name 60 * @param string $value 61 */ 62 public function __construct(string $name, string $value) 63 { 64 $this->name = $name; 65 $this->value = $value; 66 } 67 68 /** 69 * @return string 70 */ 71 public function getName(): string 72 { 73 return $this->name; 74 } 75 76 /** 77 * @return string 78 */ 79 public function getValue(): string 80 { 81 return $this->value; 82 } 83 84 /** 85 * @return bool 86 */ 87 public function isMultivalued(): bool 88 { 89 return count($this->additional) !== 0; 90 } 91 92 /** 93 * @return string 94 */ 95 public function toString(): string 96 { 97 $rdn = $this->name . '=' . $this->value; 98 99 foreach ($this->additional as $additional) { 100 $rdn .= '+' . $additional->getName() . '=' . $additional->getValue(); 101 } 102 103 return $rdn; 104 } 105 106 /** 107 * @return string 108 */ 109 public function __toString() 110 { 111 return $this->toString(); 112 } 113 114 /** 115 * @param string $rdn 116 * @return Rdn 117 * @throws InvalidArgumentException 118 */ 119 public static function create(string $rdn): Rdn 120 { 121 $pieces = preg_split('/(?<!\\\\)\+/', $rdn); 122 if ($pieces === false) { 123 throw new InvalidArgumentException(sprintf('The RDN "%s" is invalid.', $rdn)); 124 } 125 126 // @todo Simplify this logic somehow? 127 $obj = null; 128 foreach ($pieces as $piece) { 129 $parts = explode('=', $piece, 2); 130 if (count($parts) !== 2) { 131 throw new InvalidArgumentException(sprintf('The RDN "%s" is invalid.', $piece)); 132 } 133 if ($obj === null) { 134 $obj = new self($parts[0], $parts[1]); 135 } else { 136 /** @var Rdn $obj */ 137 $obj->additional[] = new self($parts[0], $parts[1]); 138 } 139 } 140 141 if ($obj === null) { 142 throw new InvalidArgumentException(sprintf("The RDN '%s' is not valid.", $rdn)); 143 } 144 145 return $obj; 146 } 147 148 /** 149 * Escape an RDN value. 150 * 151 * @param string $value 152 * @return string 153 */ 154 public static function escape(string $value): string 155 { 156 if (self::shouldNotEscape($value)) { 157 return $value; 158 } 159 $value = str_replace(array_keys(self::ESCAPE_MAP), array_values(self::ESCAPE_MAP), $value); 160 161 if ($value[0] === '#' || $value[0] === ' ') { 162 $value = ($value[0] === '#' ? '\23' : '\20') . substr($value, 1); 163 } 164 if ($value[-1] === ' ') { 165 $value = substr_replace($value, '\20', -1, 1); 166 } 167 168 return self::escapeNonPrintable($value); 169 } 170} 171