Submit
Path:
~
/
/
usr
/
local
/
psa
/
admin
/
plib
/
modules
/
platform360
/
vendor
/
spomky-labs
/
pki-framework
/
src
/
X501
/
DN
/
File Content:
DNParser.php
<?php declare (strict_types=1); namespace Platform360\SpomkyLabs\Pki\X501\DN; use Platform360\SpomkyLabs\Pki\ASN1\Element; use Platform360\SpomkyLabs\Pki\ASN1\Exception\DecodeException; use Platform360\SpomkyLabs\Pki\ASN1\Feature\ElementBase; use UnexpectedValueException; use function mb_strlen; use function sprintf; /** * Distinguished Name parsing conforming to RFC 2253 and RFC 1779. * * @see https://tools.ietf.org/html/rfc1779 * @see https://tools.ietf.org/html/rfc2253 */ final class DNParser { /** * RFC 2253 special characters. * * @var string */ public final const SPECIAL_CHARS = ',=+<>#;'; /** * DN string length. */ private readonly int $_len; /** * @param string $_dn Distinguised name */ private function __construct(private readonly string $_dn) { $this->_len = mb_strlen($_dn, '8bit'); } /** * Parse distinguished name string to name-components. * * @return array<array<string>> */ public static function parseString(string $dn) : array { $parser = new self($dn); return $parser->parse(); } /** * Escape a AttributeValue string conforming to RFC 2253. * * @see https://tools.ietf.org/html/rfc2253#section-2.4 */ public static function escapeString(string $str) : string { // one of the characters ",", "+", """, "\", "<", ">" or ";" $str = \preg_replace('/([,\\+"\\\\<\\>;])/u', '\\\\$1', $str); // a space character occurring at the end of the string $str = \preg_replace('/( )$/u', '\\\\$1', (string) $str); // a space or "#" character occurring at the beginning of the string $str = \preg_replace('/^([ #])/u', '\\\\$1', (string) $str); // implementation specific special characters $str = \preg_replace_callback('/([\\pC])/u', function ($m) { $octets = \mb_str_split(\bin2hex($m[1]), 2, '8bit'); return \implode('', \array_map(static fn($octet) => '\\' . \mb_strtoupper($octet, '8bit'), $octets)); }, (string) $str); return $str; } /** * Parse DN to name-components. * * @return array<array<string>> */ private function parse() : array { $offset = 0; $name = $this->_parseName($offset); if ($offset < $this->_len) { $remains = \mb_substr($this->_dn, $offset, null, '8bit'); throw new UnexpectedValueException(sprintf('Parser finished before the end of string, remaining: %s', $remains)); } return $name; } /** * Parse 'name'. * * name-component *("," name-component) * * @return array<array<string>> Array of name-components */ private function _parseName(int &$offset) : array { $idx = $offset; $names = []; while ($idx < $this->_len) { $names[] = $this->_parseNameComponent($idx); if ($idx >= $this->_len) { break; } $this->_skipWs($idx); if ($this->_dn[$idx] !== ',' && $this->_dn[$idx] !== ';') { break; } ++$idx; $this->_skipWs($idx); } $offset = $idx; return \array_reverse($names); } /** * Parse 'name-component'. * * attributeTypeAndValue *("+" attributeTypeAndValue) * * @return array<array<string, string|ElementBase>> Array of [type, value] tuples */ private function _parseNameComponent(int &$offset) : array { $idx = $offset; $tvpairs = []; while ($idx < $this->_len) { $tvpairs[] = $this->_parseAttrTypeAndValue($idx); $this->_skipWs($idx); if ($idx >= $this->_len || $this->_dn[$idx] !== '+') { break; } ++$idx; $this->_skipWs($idx); } $offset = $idx; return $tvpairs; } /** * Parse 'attributeTypeAndValue'. * * attributeType "=" attributeValue * * @return array<string, string|ElementBase> A tuple of [type, value]. Value may be either a string or * an Element, if it's encoded as hexstring. */ private function _parseAttrTypeAndValue(int &$offset) : array { $idx = $offset; $type = $this->_parseAttrType($idx); $this->_skipWs($idx); if ($idx >= $this->_len || $this->_dn[$idx++] !== '=') { throw new UnexpectedValueException('Invalid type and value pair.'); } $this->_skipWs($idx); // hexstring if ($idx < $this->_len && $this->_dn[$idx] === '#') { ++$idx; $data = $this->_parseAttrHexValue($idx); try { $value = Element::fromDER($data); } catch (DecodeException $e) { throw new UnexpectedValueException('Invalid DER encoding from hexstring.', 0, $e); } } else { $value = $this->_parseAttrStringValue($idx); } $offset = $idx; return [$type, $value]; } /** * Parse 'attributeType'. * * (ALPHA 1*keychar) / oid */ private function _parseAttrType(int &$offset) : string { $idx = $offset; // dotted OID $type = $this->_regexMatch('/^(?:oid\\.)?([0-9]+(?:\\.[0-9]+)*)/i', $idx); if ($type === null) { // name $type = $this->_regexMatch('/^[a-z][a-z0-9\\-]*/i', $idx); if ($type === null) { throw new UnexpectedValueException('Invalid attribute type.'); } } $offset = $idx; return $type; } /** * Parse 'attributeValue' of string type. */ private function _parseAttrStringValue(int &$offset) : string { $idx = $offset; if ($idx >= $this->_len) { return ''; } if ($this->_dn[$idx] === '"') { // quoted string $val = $this->_parseQuotedAttrString($idx); } else { // string $val = $this->_parseAttrString($idx); } $offset = $idx; return $val; } /** * Parse plain 'attributeValue' string. */ private function _parseAttrString(int &$offset) : string { $idx = $offset; $val = ''; $wsidx = null; while ($idx < $this->_len) { $c = $this->_dn[$idx]; // pair (escape sequence) if ($c === '\\') { ++$idx; $val .= $this->_parsePairAfterSlash($idx); $wsidx = null; continue; } if ($c === '"') { throw new UnexpectedValueException('Unexpected quotation.'); } if (\mb_strpos(self::SPECIAL_CHARS, $c, 0, '8bit') !== \false) { break; } // keep track of the first consecutive whitespace if ($c === ' ') { if ($wsidx === null) { $wsidx = $idx; } } else { $wsidx = null; } // stringchar $val .= $c; ++$idx; } // if there was non-escaped whitespace in the end of the value if ($wsidx !== null) { $val = \mb_substr($val, 0, -($idx - $wsidx), '8bit'); } $offset = $idx; return $val; } /** * Parse quoted 'attributeValue' string. * * @param int $offset Offset to starting quote */ private function _parseQuotedAttrString(int &$offset) : string { $idx = $offset + 1; $val = ''; while ($idx < $this->_len) { $c = $this->_dn[$idx]; if ($c === '\\') { // pair ++$idx; $val .= $this->_parsePairAfterSlash($idx); continue; } if ($c === '"') { ++$idx; break; } $val .= $c; ++$idx; } $offset = $idx; return $val; } /** * Parse 'attributeValue' of binary type. */ private function _parseAttrHexValue(int &$offset) : string { $idx = $offset; $hexstr = $this->_regexMatch('/^(?:[0-9a-f]{2})+/i', $idx); if ($hexstr === null) { throw new UnexpectedValueException('Invalid hexstring.'); } $data = \hex2bin($hexstr); $offset = $idx; return $data; } /** * Parse 'pair' after leading slash. */ private function _parsePairAfterSlash(int &$offset) : string { $idx = $offset; if ($idx >= $this->_len) { throw new UnexpectedValueException('Unexpected end of escape sequence.'); } $c = $this->_dn[$idx++]; // special | \ | " | SPACE if (\mb_strpos(self::SPECIAL_CHARS . '\\" ', $c, 0, '8bit') !== \false) { $val = $c; } else { // hexpair if ($idx >= $this->_len) { throw new UnexpectedValueException('Unexpected end of hexpair.'); } $val = @\hex2bin($c . $this->_dn[$idx++]); if ($val === \false) { throw new UnexpectedValueException('Invalid hexpair.'); } } $offset = $idx; return $val; } /** * Match DN to pattern and extract the last capture group. * * Updates offset to fully matched pattern. * * @return null|string Null if pattern doesn't match */ private function _regexMatch(string $pattern, int &$offset) : ?string { $idx = $offset; if (\preg_match($pattern, \mb_substr($this->_dn, $idx, null, '8bit'), $match) !== 1) { return null; } $idx += mb_strlen($match[0], '8bit'); $offset = $idx; return \end($match); } /** * Skip consecutive spaces. */ private function _skipWs(int &$offset) : void { $idx = $offset; while ($idx < $this->_len) { if ($this->_dn[$idx] !== ' ') { break; } ++$idx; } $offset = $idx; } }
Submit
FILE
FOLDER
INFO
Name
Size
Permission
Action
DNParser.php
10328 bytes
0644
N4ST4R_ID | Naxtarrr