DVDevClientPairing.parse constructor

DVDevClientPairing.parse(
  1. Uri link
)

Parses a pairing link, refusing anything it could not trust.

Implementation

factory DVDevClientPairing.parse(Uri link) {
  if (link.scheme != dvDevClientLinkScheme || link.host != 'pair') {
    throw const FormatException(
      'Not a Dartvel pairing link: $dvDevClientLinkScheme://pair?... '
      'expected.',
    );
  }
  final Map<String, String> q = link.queryParameters;

  final Uri? server = Uri.tryParse(q['server'] ?? '');
  if (server == null || server.scheme != 'https' || server.host.isEmpty) {
    // A dev server is reached over TLS pinned to the link's key. Over http
    // the token would cross the network in the clear, and anything that
    // saw it could read every page the server serves.
    throw const FormatException(
      'A pairing link names an https dev server. Scan the code a current '
      '`dartvel dev` prints.',
    );
  }
  final String branch = q['branch'] ?? '';
  if (branch.trim().isEmpty) {
    throw const FormatException('A pairing link names the branch it serves.');
  }

  final Uint8List key;
  try {
    key = dvWebPushBase64Decode(q['key'] ?? '');
  } on FormatException {
    throw const FormatException('The pairing key is not base64url.');
  }
  if (!_isP256Point(key)) {
    // Without a key there is nothing to verify a bundle against, and a
    // shell that loaded anyway would load from anybody.
    throw const FormatException(
      'A pairing link carries the server\'s P-256 public key.',
    );
  }

  final String token = q['token'] ?? '';
  Uint8List tokenBytes;
  try {
    tokenBytes = dvWebPushBase64Decode(token);
  } on FormatException {
    tokenBytes = Uint8List(0);
  }
  if (tokenBytes.length < 32) {
    throw const FormatException(
      'A pairing token is at least 32 random bytes; a shorter one can be '
      'guessed by anything on the network.',
    );
  }

  return DVDevClientPairing(
    server: server,
    branch: branch,
    publicKey: key,
    token: token,
  );
}