OLD | NEW |
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
4 | 4 |
5 import 'dart:async'; | 5 import 'dart:async'; |
6 import 'dart:io' as io; | 6 import 'dart:io' as io; |
7 import "dart:convert"; | 7 import "dart:convert"; |
8 | 8 |
9 import 'package:http/http.dart' as http; | 9 import 'package:http/http.dart' as http; |
10 import 'package:path/path.dart' as path; | 10 import 'package:path/path.dart' as p; |
11 import 'package:pub_semver/pub_semver.dart'; | 11 import 'package:pub_semver/pub_semver.dart'; |
12 | 12 |
13 import '../exceptions.dart'; | 13 import '../exceptions.dart'; |
14 import '../http.dart'; | 14 import '../http.dart'; |
15 import '../io.dart'; | 15 import '../io.dart'; |
16 import '../log.dart' as log; | 16 import '../log.dart' as log; |
17 import '../package.dart'; | 17 import '../package.dart'; |
18 import '../pubspec.dart'; | 18 import '../pubspec.dart'; |
19 import '../utils.dart'; | 19 import '../utils.dart'; |
20 import 'cached.dart'; | 20 import 'cached.dart'; |
(...skipping 86 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
107 | 107 |
108 return new Pubspec.fromMap( | 108 return new Pubspec.fromMap( |
109 version['pubspec'], systemCache.sources, | 109 version['pubspec'], systemCache.sources, |
110 expectedName: id.name, location: url); | 110 expectedName: id.name, location: url); |
111 } | 111 } |
112 | 112 |
113 /// Downloads the package identified by [id] to the system cache. | 113 /// Downloads the package identified by [id] to the system cache. |
114 Future<Package> downloadToSystemCache(PackageId id) async { | 114 Future<Package> downloadToSystemCache(PackageId id) async { |
115 if (!isInSystemCache(id)) { | 115 if (!isInSystemCache(id)) { |
116 var packageDir = getDirectory(id); | 116 var packageDir = getDirectory(id); |
117 ensureDir(path.dirname(packageDir)); | 117 ensureDir(p.dirname(packageDir)); |
118 var parsed = _parseDescription(id.description); | 118 var parsed = _parseDescription(id.description); |
119 await _download(parsed.last, parsed.first, id.version, packageDir); | 119 await _download(parsed.last, parsed.first, id.version, packageDir); |
120 } | 120 } |
121 | 121 |
122 return new Package.load(id.name, getDirectory(id), systemCache.sources); | 122 return new Package.load(id.name, getDirectory(id), systemCache.sources); |
123 } | 123 } |
124 | 124 |
125 /// The system cache directory for the hosted source contains subdirectories | 125 /// The system cache directory for the hosted source contains subdirectories |
126 /// for each separate repository URL that's used on the system. | 126 /// for each separate repository URL that's used on the system. |
127 /// | 127 /// |
128 /// Each of these subdirectories then contains a subdirectory for each | 128 /// Each of these subdirectories then contains a subdirectory for each |
129 /// package downloaded from that site. | 129 /// package downloaded from that site. |
130 String getDirectory(PackageId id) { | 130 String getDirectory(PackageId id) { |
131 var parsed = _parseDescription(id.description); | 131 var parsed = _parseDescription(id.description); |
132 var dir = _urlToDirectory(parsed.last); | 132 var dir = _urlToDirectory(parsed.last); |
133 return path.join(systemCacheRoot, dir, "${parsed.first}-${id.version}"); | 133 return p.join(systemCacheRoot, dir, "${parsed.first}-${id.version}"); |
134 } | 134 } |
135 | 135 |
136 String packageName(description) => _parseDescription(description).first; | 136 String packageName(description) => _parseDescription(description).first; |
137 | 137 |
138 bool descriptionsEqual(description1, description2) => | 138 bool descriptionsEqual(description1, description2) => |
139 _parseDescription(description1) == _parseDescription(description2); | 139 _parseDescription(description1) == _parseDescription(description2); |
140 | 140 |
141 /// Ensures that [description] is a valid hosted package description. | 141 /// Ensures that [description] is a valid hosted package description. |
142 /// | 142 /// |
143 /// There are two valid formats. A plain string refers to a package with the | 143 /// There are two valid formats. A plain string refers to a package with the |
(...skipping 11 matching lines...) Expand all Loading... |
155 | 155 |
156 /// Re-downloads all packages that have been previously downloaded into the | 156 /// Re-downloads all packages that have been previously downloaded into the |
157 /// system cache from any server. | 157 /// system cache from any server. |
158 Future<Pair<List<PackageId>, List<PackageId>>> repairCachedPackages() async { | 158 Future<Pair<List<PackageId>, List<PackageId>>> repairCachedPackages() async { |
159 if (!dirExists(systemCacheRoot)) return new Pair([], []); | 159 if (!dirExists(systemCacheRoot)) return new Pair([], []); |
160 | 160 |
161 var successes = []; | 161 var successes = []; |
162 var failures = []; | 162 var failures = []; |
163 | 163 |
164 for (var serverDir in listDir(systemCacheRoot)) { | 164 for (var serverDir in listDir(systemCacheRoot)) { |
165 var url = _directoryToUrl(path.basename(serverDir)); | 165 var url = _directoryToUrl(p.basename(serverDir)); |
166 var packages = _getCachedPackagesInDirectory(path.basename(serverDir)); | 166 var packages = _getCachedPackagesInDirectory(p.basename(serverDir)); |
167 packages.sort(Package.orderByNameAndVersion); | 167 packages.sort(Package.orderByNameAndVersion); |
168 | 168 |
169 for (var package in packages) { | 169 for (var package in packages) { |
170 var id = idFor(package.name, package.version, url: url); | 170 var id = idFor(package.name, package.version, url: url); |
171 | 171 |
172 try { | 172 try { |
173 await _download(url, package.name, package.version, package.dir); | 173 await _download(url, package.name, package.version, package.dir); |
174 successes.add(id); | 174 successes.add(id); |
175 } catch (error, stackTrace) { | 175 } catch (error, stackTrace) { |
176 failures.add(id); | 176 failures.add(id); |
(...skipping 13 matching lines...) Expand all Loading... |
190 | 190 |
191 /// Gets all of the packages that have been downloaded into the system cache | 191 /// Gets all of the packages that have been downloaded into the system cache |
192 /// from the default server. | 192 /// from the default server. |
193 List<Package> getCachedPackages() { | 193 List<Package> getCachedPackages() { |
194 return _getCachedPackagesInDirectory(_urlToDirectory(defaultUrl)); | 194 return _getCachedPackagesInDirectory(_urlToDirectory(defaultUrl)); |
195 } | 195 } |
196 | 196 |
197 /// Gets all of the packages that have been downloaded into the system cache | 197 /// Gets all of the packages that have been downloaded into the system cache |
198 /// into [dir]. | 198 /// into [dir]. |
199 List<Package> _getCachedPackagesInDirectory(String dir) { | 199 List<Package> _getCachedPackagesInDirectory(String dir) { |
200 var cacheDir = path.join(systemCacheRoot, dir); | 200 var cacheDir = p.join(systemCacheRoot, dir); |
201 if (!dirExists(cacheDir)) return []; | 201 if (!dirExists(cacheDir)) return []; |
202 | 202 |
203 return listDir(cacheDir) | 203 return listDir(cacheDir) |
204 .map((entry) => new Package.load(null, entry, systemCache.sources)) | 204 .map((entry) => new Package.load(null, entry, systemCache.sources)) |
205 .toList(); | 205 .toList(); |
206 } | 206 } |
207 | 207 |
208 /// Downloads package [package] at [version] from [server], and unpacks it | 208 /// Downloads package [package] at [version] from [server], and unpacks it |
209 /// into [destPath]. | 209 /// into [destPath]. |
210 Future _download(String server, String package, Version version, | 210 Future _download(String server, String package, Version version, |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
256 /// This uses the system cache to get the list of available packages and does | 256 /// This uses the system cache to get the list of available packages and does |
257 /// no network access. | 257 /// no network access. |
258 class OfflineHostedSource extends HostedSource { | 258 class OfflineHostedSource extends HostedSource { |
259 /// Gets the list of all versions of [ref] that are in the system cache. | 259 /// Gets the list of all versions of [ref] that are in the system cache. |
260 Future<List<PackageId>> doGetVersions(PackageRef ref) async { | 260 Future<List<PackageId>> doGetVersions(PackageRef ref) async { |
261 var parsed = _parseDescription(ref.description); | 261 var parsed = _parseDescription(ref.description); |
262 var server = parsed.last; | 262 var server = parsed.last; |
263 log.io("Finding versions of ${ref.name} in " | 263 log.io("Finding versions of ${ref.name} in " |
264 "$systemCacheRoot/${_urlToDirectory(server)}"); | 264 "$systemCacheRoot/${_urlToDirectory(server)}"); |
265 | 265 |
266 var dir = path.join(systemCacheRoot, _urlToDirectory(server)); | 266 var dir = p.join(systemCacheRoot, _urlToDirectory(server)); |
267 | 267 |
268 var versions; | 268 var versions; |
269 if (dirExists(dir)) { | 269 if (dirExists(dir)) { |
270 versions = await listDir(dir).map((entry) { | 270 versions = await listDir(dir).map((entry) { |
271 var components = path.basename(entry).split("-"); | 271 var components = p.basename(entry).split("-"); |
272 if (components.first != ref.name) return null; | 272 if (components.first != ref.name) return null; |
273 return HostedSource.idFor( | 273 return HostedSource.idFor( |
274 ref.name, new Version.parse(components.skip(1).join("-")), | 274 ref.name, new Version.parse(components.skip(1).join("-")), |
275 url: _serverFor(ref.description)); | 275 url: _serverFor(ref.description)); |
276 }).where((id) => id != null).toList(); | 276 }).where((id) => id != null).toList(); |
277 } else { | 277 } else { |
278 versions = []; | 278 versions = []; |
279 } | 279 } |
280 | 280 |
281 // If there are no versions in the cache, report a clearer error. | 281 // If there are no versions in the cache, report a clearer error. |
(...skipping 26 matching lines...) Expand all Loading... |
308 /// incorrectly: it uses the character's *decimal* ASCII value instead of hex. | 308 /// incorrectly: it uses the character's *decimal* ASCII value instead of hex. |
309 /// | 309 /// |
310 /// This could cause an ambiguity since some characters get encoded as three | 310 /// This could cause an ambiguity since some characters get encoded as three |
311 /// digits and others two. It's possible for one to be a prefix of the other. | 311 /// digits and others two. It's possible for one to be a prefix of the other. |
312 /// In practice, the set of characters that are encoded don't happen to have | 312 /// In practice, the set of characters that are encoded don't happen to have |
313 /// any collisions, so the encoding is reversible. | 313 /// any collisions, so the encoding is reversible. |
314 /// | 314 /// |
315 /// This behavior is a bug, but is being preserved for compatibility. | 315 /// This behavior is a bug, but is being preserved for compatibility. |
316 String _urlToDirectory(String url) { | 316 String _urlToDirectory(String url) { |
317 // Normalize all loopback URLs to "localhost". | 317 // Normalize all loopback URLs to "localhost". |
318 url = url.replaceAllMapped(new RegExp(r"^https?://(127\.0\.0\.1|\[::1\])?"), | 318 url = url.replaceAllMapped( |
319 (match) => match[1] == null ? '' : 'localhost'); | 319 new RegExp(r"^(https?://)(127\.0\.0\.1|\[::1\]|localhost)?"), |
| 320 (match) { |
| 321 // Don't include the scheme for HTTPS URLs. This makes the directory names |
| 322 // nice for the default and most recommended scheme. We also don't include |
| 323 // it for localhost URLs, since they're always known to be HTTP. |
| 324 var localhost = match[2] == null ? '' : 'localhost'; |
| 325 var scheme = match[1] == 'https://' || localhost.isNotEmpty ? '' : match[1]; |
| 326 return "$scheme$localhost"; |
| 327 }); |
320 return replace(url, new RegExp(r'[<>:"\\/|?*%]'), | 328 return replace(url, new RegExp(r'[<>:"\\/|?*%]'), |
321 (match) => '%${match[0].codeUnitAt(0)}'); | 329 (match) => '%${match[0].codeUnitAt(0)}'); |
322 } | 330 } |
323 | 331 |
324 /// Given a directory name in the system cache, returns the URL of the server | 332 /// Given a directory name in the system cache, returns the URL of the server |
325 /// whose packages it contains. | 333 /// whose packages it contains. |
326 /// | 334 /// |
327 /// See [_urlToDirectory] for details on the mapping. Note that because the | 335 /// See [_urlToDirectory] for details on the mapping. Note that because the |
328 /// directory name does not preserve the scheme, this has to guess at it. It | 336 /// directory name does not preserve the scheme, this has to guess at it. It |
329 /// chooses "http" for loopback URLs (mainly to support the pub tests) and | 337 /// chooses "http" for loopback URLs (mainly to support the pub tests) and |
330 /// "https" for all others. | 338 /// "https" for all others. |
331 String _directoryToUrl(String url) { | 339 String _directoryToUrl(String url) { |
332 // Decode the pseudo-URL-encoded characters. | 340 // Decode the pseudo-URL-encoded characters. |
333 var chars = '<>:"\\/|?*%'; | 341 var chars = '<>:"\\/|?*%'; |
334 for (var i = 0; i < chars.length; i++) { | 342 for (var i = 0; i < chars.length; i++) { |
335 var c = chars.substring(i, i + 1); | 343 var c = chars.substring(i, i + 1); |
336 url = url.replaceAll("%${c.codeUnitAt(0)}", c); | 344 url = url.replaceAll("%${c.codeUnitAt(0)}", c); |
337 } | 345 } |
338 | 346 |
339 // Figure out the scheme. | 347 // If the URL has an explicit scheme, use that. |
340 var scheme = "https"; | 348 if (url.contains("://")) return url; |
341 | 349 |
342 // See if it's a loopback IP address. | 350 // Otherwise, default to http for localhost and https for everything else. |
343 if (isLoopback(url.replaceAll(new RegExp(":.*"), ""))) scheme = "http"; | 351 var scheme = |
| 352 isLoopback(url.replaceAll(new RegExp(":.*"), "")) ? "http" : "https"; |
344 return "$scheme://$url"; | 353 return "$scheme://$url"; |
345 } | 354 } |
346 | 355 |
347 /// Parses [description] into its server and package name components, then | 356 /// Parses [description] into its server and package name components, then |
348 /// converts that to a Uri given [pattern]. | 357 /// converts that to a Uri given [pattern]. |
349 /// | 358 /// |
350 /// Ensures the package name is properly URL encoded. | 359 /// Ensures the package name is properly URL encoded. |
351 Uri _makeUrl(description, String pattern(String server, String package)) { | 360 Uri _makeUrl(description, String pattern(String server, String package)) { |
352 var parsed = _parseDescription(description); | 361 var parsed = _parseDescription(description); |
353 var server = parsed.last; | 362 var server = parsed.last; |
(...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
393 var name = description["name"]; | 402 var name = description["name"]; |
394 if (name is! String) { | 403 if (name is! String) { |
395 throw new FormatException("The 'name' key must have a string value."); | 404 throw new FormatException("The 'name' key must have a string value."); |
396 } | 405 } |
397 | 406 |
398 var url = description["url"]; | 407 var url = description["url"]; |
399 if (url == null) url = HostedSource.defaultUrl; | 408 if (url == null) url = HostedSource.defaultUrl; |
400 | 409 |
401 return new Pair<String, String>(name, url); | 410 return new Pair<String, String>(name, url); |
402 } | 411 } |
OLD | NEW |