Files
clide/lib/builtin/deeplink/src/deep_link.dart
T
jpmschweitzerandClaude Opus 4.8 6d0ebab721 chore: adopt Dart 3.9 toolchain — honest floor + tall-style reformat (T-353)
Raise the declared minimums in pubspec.yaml to what our deps already
require: Flutter >=3.35.0 / Dart >=3.9.0 (was 3.19.0 / 3.5.0). alchemist
0.12 needs Flutter 3.32; Dart 3.9 first ships in Flutter 3.35, so 3.35 is
the binding floor. Pin the exact build toolchain in .fvmrc (Flutter
3.44.1).

Moving to the Dart 3.9 language level switches `dart format` to the new
"tall" style and enables two new lints. This commit is the resulting
mechanical churn, isolated from any behaviour change:
  - whole-tree `dart format` reformat (tall style)
  - `dart fix` for unnecessary_underscores + use_null_aware_elements

No runtime behaviour change; `make test` green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-11 12:11:53 +02:00

52 lines
2.0 KiB
Dart

/// Parsing + the paranoid allowlist for `clide://` deep links (T-56, D-90).
///
/// A clide:// URL is an UNTRUSTED external vector — any webpage can fire one at
/// the OS handler — so the surface is **default-deny**: only the actions in
/// [kDeepLinkSafeActions] are even parseable, and they are read-only /
/// navigation verbs. Execution is additionally gated by a user prompt (see the
/// deeplink extension). Pure (no Flutter): the allowlist + parsing are unit
/// tested in isolation.
library;
/// The ONLY actions a clide:// link may request. Default-deny: anything not in
/// this set is rejected outright. Keep it to side-effect-free navigation —
/// NEVER writes, process control, git, or arbitrary command passthrough.
const Set<String> kDeepLinkSafeActions = {'open'};
/// A validated, allowlisted deep-link action.
class DeepLinkAction {
const DeepLinkAction({required this.name, required this.path, this.line});
final String name;
final String path;
final int? line;
/// A human-readable description for the confirmation prompt.
String get describe => switch (name) {
'open' => 'Open $path${line != null ? ' (line $line)' : ''}',
_ => name,
};
}
/// Parse [url] into a [DeepLinkAction], or null when it is malformed, not a
/// `clide://` URL, not an allowlisted action, or missing required parameters.
/// Validation only — it never executes anything.
DeepLinkAction? parseDeepLink(String url) {
final uri = Uri.tryParse(url);
if (uri == null || uri.scheme != 'clide') return null;
if (!kDeepLinkSafeActions.contains(uri.host)) return null;
switch (uri.host) {
case 'open':
final path = uri.queryParameters['path'];
if (path == null || path.isEmpty) return null;
int? line;
final lineRaw = uri.queryParameters['line'];
if (lineRaw != null && lineRaw.isNotEmpty) {
line = int.tryParse(lineRaw);
if (line == null || line < 1) return null; // reject junk rather than guess
}
return DeepLinkAction(name: 'open', path: path, line: line);
}
return null;
}