parseDeepLink function

DeepLinkAction parseDeepLink(
  1. String uri
)

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);
}