• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2015 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'dart:async';
6
7import '../base/context.dart';
8import '../globals.dart';
9import 'common.dart';
10import 'io.dart';
11import 'platform.dart';
12
13const int kNetworkProblemExitCode = 50;
14
15typedef HttpClientFactory = HttpClient Function();
16
17/// Download a file from the given URL and return the bytes.
18Future<List<int>> fetchUrl(Uri url, {int maxAttempts}) async {
19  int attempts = 0;
20  int durationSeconds = 1;
21  while (true) {
22    attempts += 1;
23    final List<int> result = await _attempt(url);
24    if (result != null)
25      return result;
26    if (maxAttempts != null && attempts >= maxAttempts) {
27      printStatus('Download failed -- retry $attempts');
28      return null;
29    }
30    printStatus('Download failed -- attempting retry $attempts in '
31        '$durationSeconds second${ durationSeconds == 1 ? "" : "s"}...');
32    await Future<void>.delayed(Duration(seconds: durationSeconds));
33    if (durationSeconds < 64)
34      durationSeconds *= 2;
35  }
36}
37
38/// Check if the given URL points to a valid endpoint.
39Future<bool> doesRemoteFileExist(Uri url) async =>
40  (await _attempt(url, onlyHeaders: true)) != null;
41
42Future<List<int>> _attempt(Uri url, { bool onlyHeaders = false }) async {
43  printTrace('Downloading: $url');
44  HttpClient httpClient;
45  if (context.get<HttpClientFactory>() != null) {
46    httpClient = context.get<HttpClientFactory>()();
47  } else {
48    httpClient = HttpClient();
49  }
50  HttpClientRequest request;
51  try {
52    if (onlyHeaders) {
53      request = await httpClient.headUrl(url);
54    } else {
55      request = await httpClient.getUrl(url);
56    }
57  } on ArgumentError catch (error) {
58    final String overrideUrl = platform.environment['FLUTTER_STORAGE_BASE_URL'];
59    if (overrideUrl != null && url.toString().contains(overrideUrl)) {
60      printError(error.toString());
61      throwToolExit(
62        'The value of FLUTTER_STORAGE_BASE_URL ($overrideUrl) could not be '
63        'parsed as a valid url. Please see https://flutter.dev/community/china '
64        'for an example of how to use it.\n'
65        'Full URL: $url',
66        exitCode: kNetworkProblemExitCode,);
67    }
68    printError(error.toString());
69    rethrow;
70  } on HandshakeException catch (error) {
71    printTrace(error.toString());
72    throwToolExit(
73      'Could not authenticate download server. You may be experiencing a man-in-the-middle attack,\n'
74      'your network may be compromised, or you may have malware installed on your computer.\n'
75      'URL: $url',
76      exitCode: kNetworkProblemExitCode,
77    );
78  } on SocketException catch (error) {
79    printTrace('Download error: $error');
80    return null;
81  } on HttpException catch (error) {
82    printTrace('Download error: $error');
83    return null;
84  }
85  final HttpClientResponse response = await request.close();
86  // If we're making a HEAD request, we're only checking to see if the URL is
87  // valid.
88  if (onlyHeaders) {
89    return (response.statusCode == 200) ? <int>[] : null;
90  }
91  if (response.statusCode != 200) {
92    if (response.statusCode > 0 && response.statusCode < 500) {
93      throwToolExit(
94        'Download failed.\n'
95        'URL: $url\n'
96        'Error: ${response.statusCode} ${response.reasonPhrase}',
97        exitCode: kNetworkProblemExitCode,
98      );
99    }
100    // 5xx errors are server errors and we can try again
101    printTrace('Download error: ${response.statusCode} ${response.reasonPhrase}');
102    return null;
103  }
104  printTrace('Received response from server, collecting bytes...');
105  try {
106    final BytesBuilder responseBody = BytesBuilder(copy: false);
107    await response.forEach(responseBody.add);
108    return responseBody.takeBytes();
109  } on IOException catch (error) {
110    printTrace('Download error: $error');
111    return null;
112  }
113}
114