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