parseDeepLink function
Parse a neomage-cli:// URI into a structured action. Throws FormatException if the URI is malformed or contains dangerous characters.
Implementation
DeepLinkAction parseDeepLink(String uri) {
// Normalize: accept with or without trailing colon in protocol
String? normalized;
if (uri.startsWith('$deepLinkProtocol://')) {
normalized = uri;
} else if (uri.startsWith('$deepLinkProtocol:')) {
normalized = uri.replaceFirst('$deepLinkProtocol:', '$deepLinkProtocol://');
}
if (normalized == null) {
throw FormatException(
'Invalid deep link: expected $deepLinkProtocol:// scheme, got "$uri"',
);
}
final url = Uri.tryParse(normalized);
if (url == null) {
throw FormatException('Invalid deep link URL: "$uri"');
}
if (url.host != 'open') {
throw FormatException('Unknown deep link action: "${url.host}"');
}
final cwd = url.queryParameters['cwd'];
final repo = url.queryParameters['repo'];
final rawQuery = url.queryParameters['q'];
// Validate cwd if present — must be an absolute path
if (cwd != null &&
!cwd.startsWith('/') &&
!RegExp(r'^[a-zA-Z]:[/\\]').hasMatch(cwd)) {
throw FormatException(
'Invalid cwd in deep link: must be an absolute path, got "$cwd"',
);
}
// Reject control characters in cwd
if (cwd != null && _containsControlChars(cwd)) {
throw FormatException(
'Deep link cwd contains disallowed control characters',
);
}
if (cwd != null && cwd.length > maxCwdLength) {
throw FormatException(
'Deep link cwd exceeds $maxCwdLength characters (got ${cwd.length})',
);
}
// Validate repo slug format
if (repo != null && !_repoSlugPattern.hasMatch(repo)) {
throw FormatException(
'Invalid repo in deep link: expected "owner/repo", got "$repo"',
);
}
String? query;
if (rawQuery != null && rawQuery.trim().isNotEmpty) {
// Strip hidden Unicode characters (ASCII smuggling / hidden prompt injection)
query = _partiallySanitizeUnicode(rawQuery.trim());
if (_containsControlChars(query)) {
throw FormatException(
'Deep link query contains disallowed control characters',
);
}
if (query.length > maxQueryLength) {
throw FormatException(
'Deep link query exceeds $maxQueryLength characters (got ${query.length})',
);
}
}
return DeepLinkAction(query: query, cwd: cwd, repo: repo);
}