{"record":{"id":"629eac066ab0f80a","repo":"ellite/Wallos","slug":"invalid-uri","errorCode":null,"errorMessage":"Invalid URI.","messagePattern":"Invalid URI\\.","errorType":"validation","errorClass":"InvalidArgumentException","httpStatus":null,"severity":"error","filePath":"libs/OTPHP/Url.php","lineNumber":90,"sourceCode":"    /**\n     * @param non-empty-string $uri\n     */\n    public static function fromString(string $uri): self\n    {\n        $parsed_url = parse_url($uri);\n        $parsed_url !== false || throw new InvalidArgumentException('Invalid URI.');\n        foreach (['scheme', 'host', 'path', 'query'] as $key) {\n            array_key_exists($key, $parsed_url) || throw new InvalidArgumentException(\n                'Not a valid OTP provisioning URI'\n            );\n        }\n        $scheme = $parsed_url['scheme'] ?? null;\n        $host = $parsed_url['host'] ?? null;\n        $path = $parsed_url['path'] ?? null;\n        $query = $parsed_url['query'] ?? null;\n        $scheme === 'otpauth' || throw new InvalidArgumentException('Not a valid OTP provisioning URI');\n        is_string($host) || throw new InvalidArgumentException('Invalid URI.');\n        is_string($path) || throw new InvalidArgumentException('Invalid URI.');\n        is_string($query) || throw new InvalidArgumentException('Invalid URI.');\n        $parsedQuery = [];\n        parse_str($query, $parsedQuery);\n        array_key_exists('secret', $parsedQuery) || throw new InvalidArgumentException(\n            'Not a valid OTP provisioning URI'\n        );\n        $secret = $parsedQuery['secret'];\n        unset($parsedQuery['secret']);\n\n        return new self($scheme, $host, $path, $secret, $parsedQuery);\n    }\n}\n","sourceCodeStart":72,"sourceCodeEnd":103,"githubUrl":"https://github.com/ellite/Wallos/blob/52820e87ca5a6e105fdbb7f1c0c681bc0cfee2fd/libs/OTPHP/Url.php#L72-L103","documentation":"Url::fromString() parses an otpauth:// provisioning URI with parse_url() and validates each component's type. Line 89 throws 'Invalid URI.' when the parsed array has no usable string 'host' — i.e. the URI's issuer/type part after otpauth:// is missing or not parseable as a host.","triggerScenarios":"Passing a URI like 'otpauth://?secret=...' or 'otpauth:///path?secret=...' to $otp->loadProvisioningUri() where parse_url yields no string host component.","commonSituations":"Hand-built or truncated otpauth URIs pasted from emails/logs; URIs missing the totp/hotp type segment (otpauth://totp/...); template placeholders left unfilled (otpauth://%type%/%label%?...).","solutions":["Ensure the URI has a type segment: otpauth://totp/LABEL?secret=...","Validate the URI shape with a regex or parse_url in your code before calling loadProvisioningUri","Regenerate the URI with $totp->getProvisioningUri() instead of hand-assembling it"],"exampleFix":"// before\n$otp->loadProvisioningUri('otpauth://?secret=JBSWY3DPEHPK3PXP');\n// after\n$otp->loadProvisioningUri('otpauth://totp/alice?secret=JBSWY3DPEHPK3PXP');","handlingStrategy":"validation","validationCode":"$parts = parse_url($uri);\nif (($parts['scheme'] ?? null) !== 'otpauth'\n    || !is_string($parts['host'] ?? null)\n    || !in_array($parts['host'], ['totp', 'hotp'], true)) {\n    throw new \\LogicException('URI must be otpauth://totp/... or otpauth://hotp/...');\n}","typeGuard":null,"tryCatchPattern":"try {\n    $otp->loadProvisioningUri($uri);\n} catch (\\InvalidArgumentException $e) {\n    // fall back to manual construction from extracted secret\n    $otp = TOTP::createFromSecret($secret);\n}","preventionTips":["Only accept otpauth URIs that include the type segment (totp/hotp)","Regenerate URIs via getProvisioningUri() instead of hand-building them","Check for unfilled template placeholders before loading"],"tags":["php","otp","uri-parsing"],"backgroundTag":"invalid-url-format","analyzedSha":"52820e87ca5a6e105fdbb7f1c0c681bc0cfee2fd","analyzedAt":"2026-09-13T14:09:30.873Z","contentChangedAt":"2026-09-13T14:09:30.873Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}