secret = \rtrim($secret, "="); } public static function fromString(string $uri): self { // This is definitely from writing Scheme. $runCheck = function(array $checkArray, $target) { foreach($checkArray as $c) { ($c[0]($target))? :throw new InvalidArgumentException($c[1]); } }; $uriChecks = [ [ function($arr) { return $arr !== false; }, $uri . " is not a valid URI."], [ function($arr) { return \array_key_exists('scheme', $arr);}, $uri . " has no scheme."], [ function($arr) { return $arr['scheme'] === 'otpauth';}, $uri . " is not an otpauth:// uri."], [ function($arr) { return \array_key_exists('path', $arr);}, $uri . " isn't tagged for a user or issuer."], [ function($arr) { return \array_key_exists('query', $arr);}, $uri . " lacks query information."] ]; $parsedUri = \parse_url($uri); $runCheck($uriChecks, $parsedUri); $queryChecks = [ [ function($que) { return \array_key_exists('issuer', $que);}, $parsedUri['query'] . " has no issuer information."], [ function($que) { return \array_key_exists('secret', $que);}, $parsedUri['query'] . "has no secret key."], ]; $labelChecks = [ [ function($lab) { return \count($lab) === 2;}, $parsedUri['path'] . " doesn't have the correct number of components."] ]; $label = \explode(":", $parsedUri['path']); $runCheck($labelChecks, $label); $parsedQuery = []; \parse_str($parsedUri['query'], $parsedQuery); $queryDefaults = [ "algorithm" => "SHA1", "period" => "30", "digits" => "6", "issuer" => \rawurldecode(\ltrim($label[0], "/")), ]; $parsedQuery = \array_merge($queryDefaults, $parsedQuery); $runCheck($queryChecks, $parsedQuery); $convertFields = function(array $conversion, array &$target) { foreach ($conversion as $k => $v) { $target[$k] = $v($target[$k]); } }; $queryConversions = [ "period" => '\intval', "digits" => '\intval', "secret" => '\rawurldecode' ]; $labelConversions = [ 0 => '\rawurldecode', ]; $convertFields($queryConversions, $parsedQuery); $convertFields($labelConversions, $label); // END SCHEMEING \ltrim($label[0], "/") === $parsedQuery['issuer'] || throw new InvalidArgumentException($uri . " has mismatching issuer information.\n" . \ltrim($label[0], "/") . "\n" . $parsedQuery['issuer']); return new self( $label[1], $parsedQuery['issuer'], $parsedQuery['secret'], $parsedQuery['algorithm'], $parsedQuery['digits'], $parsedQuery['period'] ); } public function emitStr(): string { $encodeIssuer = \rawurlencode($this->issuer); $label = $encodeIssuer . ":" . $this->userid; $issuer = "issuer=" . $encodeIssuer; $algo = "algorithm=" . $this->algorithm; $digits = "digits=" . $this->digits; $period = "period=" . $this->period; $secret = "secret=" . \rawurlencode($this->secret); $query = \implode("&", [$secret, $issuer, $algo, $period, $digits]); return "otpauth://totp/" . $label . "?" . $query; } } ?>