blob: 71f1dec5c1e4d12cdeda2bee0f2cf648ffb81c30 [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'base/io.dart';
import 'base/net.dart';
import 'base/platform.dart';
import 'doctor_validator.dart';
import 'features.dart';
/// Common Flutter HTTP hosts.
const String kCloudHost = 'https://storage.googleapis.com/';
const String kCocoaPods = 'https://cocoapods.org/';
const String kGitHub = 'https://github.com/';
const String kMaven = 'https://maven.google.com/';
const String kPubDev = 'https://pub.dev/';
// Overridable environment variables.
const String kPubDevOverride = 'PUB_HOSTED_URL'; // https://dart.dev/tools/pub/environment-variables
// Validator that checks all provided hosts are reachable and responsive
class HttpHostValidator extends DoctorValidator {
HttpHostValidator({
required Platform platform,
required FeatureFlags featureFlags,
required HttpClient httpClient,
}) : _platform = platform,
_featureFlags = featureFlags,
_httpClient = httpClient,
super('Network resources');
final Platform _platform;
final FeatureFlags _featureFlags;
final HttpClient _httpClient;
final Set<Uri> _activeHosts = <Uri>{};
@override
String get slowWarning {
if (_activeHosts.isEmpty) {
return 'Network resources check is taking a long time...';
}
return 'Attempting to reach ${_activeHosts.map((Uri url) => url.host).join(", ")}...';
}
/// Make a head request to the HTTP host for checking availability
Future<String?> _checkHostAvailability(Uri host) async {
try {
assert(!_activeHosts.contains(host));
_activeHosts.add(host);
final HttpClientRequest req = await _httpClient.headUrl(host);
await req.close();
// HTTP host is available if no exception happened.
return null;
} on SocketException catch (error) {
return 'A network error occurred while checking "$host": ${error.message}';
} on HttpException catch (error) {
return 'An HTTP error occurred while checking "$host": ${error.message}';
} on HandshakeException catch (error) {
return 'A cryptographic error occurred while checking "$host": ${error.message}\n'
'You may be experiencing a man-in-the-middle attack, your network may be '
'compromised, or you may have malware installed on your computer.';
} on OSError catch (error) {
return 'An error occurred while checking "$host": ${error.message}';
} finally {
_activeHosts.remove(host);
}
}
static Uri? _parseUrl(String value) {
final Uri? url = Uri.tryParse(value);
if (url == null || !url.hasScheme || !url.hasAuthority || (!url.hasEmptyPath && !url.hasAbsolutePath) || url.hasFragment) {
return null;
}
return url;
}
@override
Future<ValidationResult> validate() async {
final List<String?> availabilityResults = <String?>[];
final List<Uri> requiredHosts = <Uri>[];
if (_platform.environment.containsKey(kPubDevOverride)) {
final Uri? url = _parseUrl(_platform.environment[kPubDevOverride]!);
if (url == null) {
availabilityResults.add(
'Environment variable $kPubDevOverride does not specify a valid URL: "${_platform.environment[kPubDevOverride]}"\n'
'Please see https://flutter.dev/community/china for an example of how to use it.'
);
} else {
requiredHosts.add(url);
}
} else {
requiredHosts.add(Uri.parse(kPubDev));
}
if (_platform.environment.containsKey(kFlutterStorageBaseUrl)) {
final Uri? url = _parseUrl(_platform.environment[kFlutterStorageBaseUrl]!);
if (url == null) {
availabilityResults.add(
'Environment variable $kFlutterStorageBaseUrl does not specify a valid URL: "${_platform.environment[kFlutterStorageBaseUrl]}"\n'
'Please see https://flutter.dev/community/china for an example of how to use it.'
);
} else {
requiredHosts.add(url);
}
} else {
requiredHosts.add(Uri.parse(kCloudHost));
if (_featureFlags.isAndroidEnabled) {
// if kFlutterStorageBaseUrl is set it is used instead of Maven
requiredHosts.add(Uri.parse(kMaven));
}
}
if (_featureFlags.isMacOSEnabled) {
requiredHosts.add(Uri.parse(kCocoaPods));
}
requiredHosts.add(Uri.parse(kGitHub));
// Check all the hosts simultaneously.
availabilityResults.addAll(await Future.wait<String?>(requiredHosts.map(_checkHostAvailability)));
int failures = 0;
int successes = 0;
final List<ValidationMessage> messages = <ValidationMessage>[];
for (final String? message in availabilityResults) {
if (message == null) {
successes += 1;
} else {
failures += 1;
messages.add(ValidationMessage.error(message));
}
}
if (failures == 0) {
assert(successes > 0);
assert(messages.isEmpty);
return const ValidationResult(
ValidationType.success,
<ValidationMessage>[ValidationMessage('All expected network resources are available.')],
);
}
assert(messages.isNotEmpty);
return ValidationResult(
successes == 0 ? ValidationType.notAvailable : ValidationType.partial,
messages,
);
}
}