halkyon/packages/halkyon_app/test/files/file_saver_test.dart

126 lines
4.5 KiB
Dart
Raw Normal View History

import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter_test/flutter_test.dart';
import 'package:halkyon_app/halkyon_app.dart';
import 'package:path/path.dart' as p;
void main() {
late Directory folder;
setUp(() async {
folder = await Directory.systemTemp.createTemp('file_saver_test');
});
tearDown(() => folder.delete(recursive: true));
test("a saved file is the own user's alone where the platform has modes, "
'set before anything is written', () async {
final path = p.join(folder.path, 'halkyon-recovery-codes.txt');
// One there before, readable by everyone.
await File(path).writeAsString('old');
if (!Platform.isWindows) {
await Process.run('chmod', ['644', path]);
}
await writePrivately(path, Uint8List.fromList([1, 2, 3]));
expect(await File(path).readAsBytes(), [1, 2, 3]);
if (Platform.isLinux || Platform.isMacOS) {
expect(File(path).statSync().mode & 0x1ff, 0x180, reason: '0600');
}
}, skip: Platform.isWindows ? 'no modes' : false);
final hasModes = Platform.isLinux || Platform.isMacOS;
test('a saved file replaces the one there without writing into it: '
'whoever held the old file open, or a link to it, never reads what is '
'saved (the reading of the fixes)', () async {
final path = p.join(folder.path, 'halkyon-recovery-codes.txt');
await File(path).writeAsString('old');
await Process.run('chmod', ['644', path]);
// Another name for the same file, as a descriptor opened earlier is.
final elsewhere = p.join(folder.path, 'held-open');
await Process.run('ln', [path, elsewhere]);
await writePrivately(path, Uint8List.fromList(utf8.encode('secret')));
expect(await File(path).readAsString(), 'secret');
expect(File(path).statSync().mode & 0x1ff, 0x180, reason: '0600');
expect(await File(elsewhere).readAsString(), 'old');
// Nothing left beside it.
expect(
folder.listSync().map((entry) => p.basename(entry.path)).toSet(),
{'halkyon-recovery-codes.txt', 'held-open'},
);
}, skip: hasModes ? false : 'no modes');
test('never follows a symbolic link where the file is to be saved: '
'refused, the file it points to untouched', () async {
final target = p.join(folder.path, 'someone-elses.txt');
await File(target).writeAsString('theirs');
final path = p.join(folder.path, 'halkyon-recovery-phrase.txt');
await Link(path).create(target);
await expectLater(
writePrivately(path, Uint8List.fromList(utf8.encode('secret'))),
throwsA(isA<FileSystemException>()),
);
expect(await File(target).readAsString(), 'theirs');
expect(FileSystemEntity.isLinkSync(path), isTrue);
expect(
folder.listSync().map((entry) => p.basename(entry.path)).toSet(),
{'someone-elses.txt', 'halkyon-recovery-phrase.txt'},
);
}, skip: hasModes ? false : 'no modes');
test(
"refuses a file another user owns, writing nothing: a root's file "
'stands in for one in a shared folder',
() async {
const rootsFile = '/etc/hostname';
final before = File(rootsFile).readAsStringSync();
await expectLater(
writePrivately(rootsFile, Uint8List.fromList([1])),
throwsA(
isA<FileSystemException>().having(
(error) => error.message,
'message',
"Another user's file",
),
),
);
expect(File(rootsFile).readAsStringSync(), before);
},
skip: !hasModes || !File('/etc/hostname').existsSync()
? 'no such file'
: false,
);
test('a new file is made 0600 from the start, with nothing left beside '
'it', () async {
final path = p.join(folder.path, 'new.txt');
await writePrivately(path, Uint8List.fromList([4, 5, 6]));
expect(await File(path).readAsBytes(), [4, 5, 6]);
expect(File(path).statSync().mode & 0x1ff, 0x180, reason: '0600');
expect(folder.listSync(), hasLength(1));
}, skip: hasModes ? false : 'no modes');
test("the system picker's copy in the temporary folder is removed, "
'others left', () async {
final copy = File(p.join(folder.path, 'halkyon-recovery-phrase.txt'));
final other = File(p.join(folder.path, 'something-else.txt'));
await copy.writeAsString('the phrase');
await other.writeAsString('other');
await forgetPickerCopy('halkyon-recovery-phrase.txt', temporary: folder);
await forgetPickerCopy('not-there.txt', temporary: folder);
expect(copy.existsSync(), isFalse);
expect(other.existsSync(), isTrue);
});
}