| // Copyright (c) 2022, the Dart project authors. Please see the AUTHORS file | |
| // for details. All rights reserved. Use of this source code is governed by a | |
| // BSD-style license that can be found in the LICENSE file. | |
| @TestOn('vm') | |
| library; | |
| import 'dart:io'; | |
| import 'package:pub/src/exit_codes.dart' as exit_codes; | |
| import 'package:pub/src/path.dart'; | |
| import 'package:test/test.dart'; | |
| import 'package:yaml/yaml.dart'; | |
| import 'package:yaml_edit/yaml_edit.dart'; | |
| import 'descriptor.dart'; | |
| import 'test_pub.dart'; | |
| Future<void> main() async { | |
| test( | |
| 'archive_sha256 is stored in lockfile and cache upon download', | |
| () async { | |
| final server = await servePackages(); | |
| server.serve('foo', '1.0.0'); | |
| server.serveContentHashes = true; | |
| await appDir(dependencies: {'foo': 'any'}).create(); | |
| await pubGet(); | |
| final lockfile = loadYaml( | |
| File(p.join(sandbox, appPath, 'pubspec.lock')).readAsStringSync(), | |
| ); | |
| final sha256 = dig<String>(lockfile, [ | |
| 'packages', | |
| 'foo', | |
| 'description', | |
| 'sha256', | |
| ]); | |
| expect(sha256, hasLength(64)); | |
| await hostedHashesCache([file('foo-1.0.0.sha256', sha256)]).validate(); | |
| }, | |
| ); | |
| test('archive_sha256 is stored in lockfile upon download on legacy server ' | |
| 'without content hashes', () async { | |
| final server = await servePackages(); | |
| server.serveContentHashes = false; | |
| server.serve('foo', '1.0.0'); | |
| await appDir(dependencies: {'foo': 'any'}).create(); | |
| await pubGet(); | |
| final lockfile = loadYaml( | |
| File(p.join(sandbox, appPath, 'pubspec.lock')).readAsStringSync(), | |
| ); | |
| final sha256 = dig<String>(lockfile, [ | |
| 'packages', | |
| 'foo', | |
| 'description', | |
| 'sha256', | |
| ]); | |
| expect(sha256, hasLength(64)); | |
| await hostedHashesCache([file('foo-1.0.0.sha256', sha256)]).validate(); | |
| }); | |
| test('archive_sha256 is checked on download', () async { | |
| final server = await servePackages(); | |
| server.serve('foo', '1.0.0'); | |
| server.overrideArchiveSha256( | |
| 'foo', | |
| '1.0.0', | |
| 'e7a7a0f6d9873e4c40cf68cc3cc9ca5b6c8cef6a2220241bdada4b9cb0083279', | |
| ); | |
| await appDir(dependencies: {'foo': 'any'}).create(); | |
| await pubGet( | |
| exitCode: exit_codes.TEMP_FAIL, | |
| silent: contains('Attempt #2'), | |
| error: contains( | |
| 'Downloaded archive for foo-1.0.0 had wrong content-hash.', | |
| ), | |
| environment: {'PUB_MAX_HTTP_RETRIES': '2'}, | |
| ); | |
| }); | |
| test( | |
| 'If content is updated on server we warn and update the lockfile', | |
| () async { | |
| final server = await servePackages(); | |
| server.serveContentHashes = true; | |
| server.serve('foo', '1.0.0'); | |
| await appDir(dependencies: {'foo': 'any'}).create(); | |
| await pubGet(); | |
| server.serve( | |
| 'foo', | |
| '1.0.0', | |
| contents: [file('new_file.txt', 'This file could be malicious.')], | |
| ); | |
| // Pub get will not revisit the file-listing if everything resolves, and | |
| // only compare with a cached value. | |
| await pubGet(); | |
| // Deleting the version-listing cache will cause it to be refetched, and | |
| // the warning will happen. | |
| File( | |
| p.join(globalServer.cachingPath, '.cache', 'foo-versions.json'), | |
| ).deleteSync(); | |
| await pubGet( | |
| warning: allOf( | |
| contains( | |
| 'Cached version of foo-1.0.0 has wrong hash - redownloading.', | |
| ), | |
| contains( | |
| 'The existing content-hash ' | |
| 'from pubspec.lock doesn\'t match contents for:', | |
| ), | |
| contains('* foo-1.0.0 from "${server.url}"\n'), | |
| ), | |
| exitCode: exit_codes.SUCCESS, | |
| ); | |
| final lockfile = loadYaml( | |
| File(p.join(sandbox, appPath, 'pubspec.lock')).readAsStringSync(), | |
| ); | |
| final newHash = dig<String>(lockfile, [ | |
| 'packages', | |
| 'foo', | |
| 'description', | |
| 'sha256', | |
| ]); | |
| expect(newHash, await server.peekArchiveSha256('foo', '1.0.0')); | |
| }, | |
| ); | |
| test( | |
| 'If content is updated on legacy server, ' | |
| 'and the download needs refreshing we warn and update the lockfile', | |
| () async { | |
| final server = await servePackages(); | |
| server.serveContentHashes = false; | |
| server.serve('foo', '1.0.0'); | |
| await appDir(dependencies: {'foo': 'any'}).create(); | |
| await pubGet(); | |
| server.serve( | |
| 'foo', | |
| '1.0.0', | |
| contents: [file('new_file.txt', 'This file could be malicious.')], | |
| ); | |
| // Deleting the hash-file cache will cause it to be refetched, and the | |
| // warning will happen. | |
| File( | |
| p.join(globalServer.hashesCachingPath, 'foo-1.0.0.sha256'), | |
| ).deleteSync(); | |
| await pubGet( | |
| warning: allOf([ | |
| contains( | |
| 'The existing content-hash from pubspec.lock ' | |
| 'doesn\'t match contents for:', | |
| ), | |
| contains('* foo-1.0.0 from "${globalServer.url}"'), | |
| ]), | |
| exitCode: exit_codes.SUCCESS, | |
| ); | |
| final lockfile = loadYaml( | |
| File(p.join(sandbox, appPath, 'pubspec.lock')).readAsStringSync(), | |
| ); | |
| final newHash = dig<String>(lockfile, [ | |
| 'packages', | |
| 'foo', | |
| 'description', | |
| 'sha256', | |
| ]); | |
| expect(newHash, await server.peekArchiveSha256('foo', '1.0.0')); | |
| }, | |
| ); | |
| test( | |
| 'sha256 in cache is checked on pub get ' | |
| '- warning and redownload on legacy server without content-hashes', | |
| () async { | |
| final server = await servePackages(); | |
| server.serveContentHashes = false; | |
| server.serve('foo', '1.0.0'); | |
| await appDir(dependencies: {'foo': 'any'}).create(); | |
| await pubGet(); | |
| final lockfile = loadYaml( | |
| File(p.join(sandbox, appPath, 'pubspec.lock')).readAsStringSync(), | |
| ); | |
| final originalHash = dig<String>(lockfile, [ | |
| 'packages', | |
| 'foo', | |
| 'description', | |
| 'sha256', | |
| ]); | |
| // Create wrong hash on disk. | |
| await hostedHashesCache([ | |
| file( | |
| 'foo-1.0.0.sha256', | |
| 'e7a7a0f6d9873e4c40cf68cc3cc9ca5b6c8cef6a2220241bdada4b9cb0083279', | |
| ), | |
| ]).create(); | |
| await pubGet( | |
| warning: 'Cached version of foo-1.0.0 has wrong hash - redownloading.', | |
| ); | |
| await hostedHashesCache([ | |
| file('foo-1.0.0.sha256', originalHash), | |
| ]).validate(); | |
| }, | |
| ); | |
| test( | |
| 'sha256 in cache is checked on pub get - warning and redownload', | |
| () async { | |
| final server = await servePackages(); | |
| server.serveContentHashes = true; | |
| server.serve('foo', '1.0.0'); | |
| await appDir(dependencies: {'foo': 'any'}).create(); | |
| await pubGet(); | |
| final lockfile = loadYaml( | |
| File(p.join(sandbox, appPath, 'pubspec.lock')).readAsStringSync(), | |
| ); | |
| final originalHash = dig<String>(lockfile, [ | |
| 'packages', | |
| 'foo', | |
| 'description', | |
| 'sha256', | |
| ]); | |
| await hostedHashesCache([ | |
| file( | |
| 'foo-1.0.0.sha256', | |
| 'e7a7a0f6d9873e4c40cf68cc3cc9ca5b6c8cef6a2220241bdada4b9cb0083279', | |
| ), | |
| ]).create(); | |
| await pubGet( | |
| warning: 'Cached version of foo-1.0.0 has wrong hash - redownloading.', | |
| ); | |
| await hostedHashesCache([ | |
| file('foo-1.0.0.sha256', originalHash), | |
| ]).validate(); | |
| }, | |
| ); | |
| test( | |
| 'Legacy lockfile without content-hashes is updated ' | |
| 'with the hash on pub get on legacy server without content-hashes', | |
| () async { | |
| final server = await servePackages(); | |
| server.serve('foo', '1.0.0'); | |
| server.serveContentHashes = false; | |
| await appDir(dependencies: {'foo': 'any'}).create(); | |
| await pubGet(); | |
| // Pretend we had no hash in the lockfile. | |
| final lockfile = YamlEditor( | |
| File(p.join(sandbox, appPath, 'pubspec.lock')).readAsStringSync(), | |
| ); | |
| final originalContentHash = | |
| lockfile.remove(['packages', 'foo', 'description', 'sha256']).value | |
| as String; | |
| File( | |
| p.join(sandbox, appPath, 'pubspec.lock'), | |
| ).writeAsStringSync(lockfile.toString()); | |
| await pubGet(); | |
| final lockfile2 = YamlEditor( | |
| File(p.join(sandbox, appPath, 'pubspec.lock')).readAsStringSync(), | |
| ); | |
| expect( | |
| lockfile2.parseAt(['packages', 'foo', 'description', 'sha256']).value, | |
| originalContentHash, | |
| ); | |
| }, | |
| ); | |
| test('Legacy lockfile without content-hashes ' | |
| 'is updated with the hash on pub get', () async { | |
| final server = await servePackages(); | |
| server.serve('foo', '1.0.0'); | |
| server.serveContentHashes = true; | |
| await appDir(dependencies: {'foo': 'any'}).create(); | |
| await pubGet(); | |
| // Pretend we had no hash in the lockfile. | |
| final lockfile = YamlEditor( | |
| File(p.join(sandbox, appPath, 'pubspec.lock')).readAsStringSync(), | |
| ); | |
| final originalContentHash = | |
| lockfile.remove(['packages', 'foo', 'description', 'sha256']).value | |
| as String; | |
| File( | |
| p.join(sandbox, appPath, 'pubspec.lock'), | |
| ).writeAsStringSync(lockfile.toString()); | |
| await pubGet(); | |
| final lockfile2 = YamlEditor( | |
| File(p.join(sandbox, appPath, 'pubspec.lock')).readAsStringSync(), | |
| ); | |
| expect( | |
| lockfile2.parseAt(['packages', 'foo', 'description', 'sha256']).value, | |
| originalContentHash, | |
| ); | |
| }); | |
| test('Badly formatted hash - warning and redownload', () async { | |
| final server = await servePackages(); | |
| server.serveContentHashes = true; | |
| server.serve('foo', '1.0.0'); | |
| await appDir(dependencies: {'foo': 'any'}).create(); | |
| await pubGet(); | |
| final lockfile = loadYaml( | |
| File(p.join(sandbox, appPath, 'pubspec.lock')).readAsStringSync(), | |
| ); | |
| final originalHash = dig<String>(lockfile, [ | |
| 'packages', | |
| 'foo', | |
| 'description', | |
| 'sha256', | |
| ]); | |
| await hostedHashesCache([file('foo-1.0.0.sha256', 'e')]).create(); | |
| await pubGet( | |
| warning: 'Cached version of foo-1.0.0 has wrong hash - redownloading.', | |
| ); | |
| await hostedHashesCache([ | |
| file('foo-1.0.0.sha256', originalHash), | |
| ]).validate(); | |
| }); | |
| } |