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>
52 lines
2.0 KiB
Dart
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;
|
|
}
|