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