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