| 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 |