Completed
Push — master ( 988510...51ff77 )
by Joni
02:47
created

Name::firstValueOf()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 13
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 10
cts 10
cp 1
rs 9.2
c 0
b 0
f 0
cc 4
eloc 9
nc 4
nop 1
crap 4
1
<?php
2
3
namespace X501\ASN1;
4
5
use ASN1\Element;
6
use ASN1\Type\Constructed\Sequence;
7
use ASN1\Type\UnspecifiedType;
8
use X501\ASN1\AttributeValue\AttributeValue;
9
use X501\DN\DNParser;
10
11
12
/**
13
 * Implements <i>Name</i> ASN.1 type.
14
 *
15
 * Since <i>Name</i> is a CHOICE only supporting <i>RDNSequence</i> type,
16
 * this class implements <i>RDNSequence</i> semantics as well.
17
 *
18
 * @link
19
 *       https://www.itu.int/ITU-T/formal-language/itu-t/x/x501/2012/InformationFramework.html#InformationFramework.Name
20
 */
21
class Name implements \Countable, \IteratorAggregate
22
{
23
	/**
24
	 * Relative distinguished name components.
25
	 *
26
	 * @var RDN[] $_rdns
27
	 */
28
	protected $_rdns;
29
	
30
	/**
31
	 * Constructor
32
	 *
33
	 * @param RDN ...$rdns RDN components
34
	 */
35 37
	public function __construct(RDN ...$rdns) {
36 37
		$this->_rdns = $rdns;
37 37
	}
38
	
39
	/**
40
	 * Initialize from ASN.1.
41
	 *
42
	 * @param Sequence $seq
43
	 * @return self
44
	 */
45 1
	public static function fromASN1(Sequence $seq) {
46 1
		$rdns = array_map(
47
			function (UnspecifiedType $el) {
48 1
				return RDN::fromASN1($el->asSet());
49 1
			}, $seq->elements());
50 1
		return new self(...$rdns);
51
	}
52
	
53
	/**
54
	 * Initialize from distinguished name string.
55
	 *
56
	 * @link https://tools.ietf.org/html/rfc1779
57
	 * @param string $str
58
	 * @return self
59
	 */
60 36
	public static function fromString($str) {
61 36
		$rdns = array();
62 36
		foreach (DNParser::parseString($str) as $nameComponent) {
63 36
			$attribs = array();
64 36
			foreach ($nameComponent as list($name, $val)) {
65 36
				$type = AttributeType::fromName($name);
66
				// hexstrings are parsed to ASN.1 elements
67 36
				if ($val instanceof Element) {
68 4
					$el = $val;
69 4
				} else {
70 32
					$el = AttributeType::asn1StringForType($type->oid(), $val);
71
				}
72 36
				$value = AttributeValue::fromASN1ByOID($type->oid(), 
73 36
					$el->asUnspecified());
74 36
				$attribs[] = new AttributeTypeAndValue($type, $value);
75 36
			}
76 36
			$rdns[] = new RDN(...$attribs);
77 36
		}
78 36
		return new self(...$rdns);
79
	}
80
	
81
	/**
82
	 * Generate ASN.1 structure.
83
	 *
84
	 * @return Sequence
85
	 */
86 1
	public function toASN1() {
87 1
		$elements = array_map(
88
			function (RDN $rdn) {
89 1
				return $rdn->toASN1();
90 1
			}, $this->_rdns);
91 1
		return new Sequence(...$elements);
92
	}
93
	
94
	/**
95
	 * Get distinguised name string conforming to RFC 2253.
96
	 *
97
	 * @link https://tools.ietf.org/html/rfc2253#section-2.1
98
	 * @return string
99
	 */
100 13
	public function toString() {
101 13
		$parts = array_map(
102
			function (RDN $rdn) {
103 13
				return $rdn->toString();
104 13
			}, array_reverse($this->_rdns));
105 13
		return implode(",", $parts);
106
	}
107
	
108
	/**
109
	 * Whether name is semantically equal to other.
110
	 * Comparison conforms to RFC 4518 string preparation algorithm.
111
	 *
112
	 * @link https://tools.ietf.org/html/rfc4518
113
	 * @param Name $other Object to compare to
114
	 * @return bool
115
	 */
116 23
	public function equals(Name $other) {
117
		// if RDN count doesn't match
118 23
		if (count($this) != count($other)) {
119 1
			return false;
120
		}
121 22
		for ($i = count($this) - 1; $i >= 0; --$i) {
122 22
			$rdn1 = $this->_rdns[$i];
123 22
			$rdn2 = $other->_rdns[$i];
124 22
			if (!$rdn1->equals($rdn2)) {
125 9
				return false;
126
			}
127 16
		}
128 13
		return true;
129
	}
130
	
131
	/**
132
	 * Get all RDN objects.
133
	 *
134
	 * @return RDN[]
135
	 */
136 1
	public function all() {
137 1
		return $this->_rdns;
138
	}
139
	
140
	/**
141
	 * Get the first AttributeValue of given type.
142
	 *
143
	 * Relative name components shall be traversed in encoding order, which is
144
	 * reversed in regards to the string representation.
145
	 * Multi-valued RDN with multiple attributes of the requested type is
146
	 * ambiguous and shall throw an exception.
147
	 *
148
	 * @param string $name Attribute OID or name
149
	 * @throws \RuntimeException If attribute cannot be resolved
150
	 * @return AttributeValue
151
	 */
152 3
	public function firstValueOf($name) {
153 3
		$oid = AttributeType::attrNameToOID($name);
154 3
		foreach ($this->_rdns as $rdn) {
155 3
			$tvs = $rdn->allOf($oid);
156 3
			if (count($tvs) > 1) {
157 1
				throw new \RangeException("RDN with multiple $name attributes.");
158
			}
159 2
			if (1 == count($tvs)) {
160 1
				return $tvs[0]->value();
161
			}
162 1
		}
163 1
		throw new \RangeException("Attribute $name not found.");
164
	}
165
	
166
	/**
167
	 *
168
	 * @see Countable::count()
169
	 * @return int
170
	 */
171 24
	public function count() {
172 24
		return count($this->_rdns);
173
	}
174
	
175
	/**
176
	 * Get the number of attributes of given type.
177
	 *
178
	 * @param string $name Attribute OID or name
179
	 * @return int
180
	 */
181 2
	public function countOfType($name) {
182 2
		$oid = AttributeType::attrNameToOID($name);
183 2
		return array_sum(
184 2
			array_map(
185 2
				function (RDN $rdn) use ($oid) {
186 2
					return count($rdn->allOf($oid));
187 2
				}, $this->_rdns));
188
	}
189
	
190
	/**
191
	 *
192
	 * @see IteratorAggregate::getIterator()
193
	 * @return \ArrayIterator
194
	 */
195 1
	public function getIterator() {
196 1
		return new \ArrayIterator($this->_rdns);
197
	}
198
	
199
	/**
200
	 *
201
	 * @return string
202
	 */
203 1
	public function __toString() {
204 1
		return $this->toString();
205
	}
206
}
207