Files
IKAR/lib/core/plugins/plugin_package.dart
T

89 lines
3.0 KiB
Dart

import 'dart:convert';
import 'dart:typed_data';
import 'package:archive/archive.dart';
import 'plugin_manifest.dart';
import 'plugin_models.dart';
import 'plugin_signing.dart';
const int _maxPackageBytes = 5 * 1024 * 1024;
const int _maxExtractedBytes = 10 * 1024 * 1024;
const int _maxFiles = 128;
const int _maxFileBytes = 2 * 1024 * 1024;
class PluginPackage {
const PluginPackage({required this.manifest, required this.files});
final PluginManifest manifest;
final Map<String, Uint8List> files;
static Future<PluginPackagePreview> preview(List<int> bytes) async {
final package = decode(bytes);
final verification = await PluginSigning.verify(
package.manifest,
package.files,
);
return PluginPackagePreview(
manifest: package.manifest,
bytes: bytes,
signatureStatus: verification.status,
signerFingerprint: verification.fingerprint,
);
}
static PluginPackage decode(List<int> bytes) {
if (bytes.isEmpty || bytes.length > _maxPackageBytes) {
throw const FormatException('Некорректный размер .kinet');
}
final archive = ZipDecoder().decodeBytes(bytes, verify: true);
if (archive.isEmpty || archive.length > _maxFiles) {
throw const FormatException('Некорректное количество файлов');
}
final files = <String, Uint8List>{};
var extractedSize = 0;
for (final entry in archive) {
if (entry.isSymbolicLink) {
throw const FormatException('Символические ссылки запрещены');
}
if (!entry.isFile) continue;
final name = _safePath(entry.name);
if (entry.size > _maxFileBytes) {
throw FormatException('Файл $name слишком большой');
}
final content = entry.content;
extractedSize += content.length;
if (extractedSize > _maxExtractedBytes) {
throw const FormatException('Распакованный плагин слишком большой');
}
if (files.containsKey(name)) {
throw FormatException('Файл $name объявлен дважды');
}
files[name] = content;
}
final manifestBytes = files['manifest.json'];
if (manifestBytes == null) {
throw const FormatException('manifest.json не найден');
}
final manifest = PluginManifest.decode(utf8.decode(manifestBytes));
if (!files.containsKey(manifest.main)) {
throw FormatException('${manifest.main} не найден');
}
for (final path in files.keys) {
if (path != 'manifest.json' && !path.endsWith('.js')) {
throw FormatException('Неподдерживаемый файл: $path');
}
}
return PluginPackage(manifest: manifest, files: Map.unmodifiable(files));
}
}
String _safePath(String raw) {
final path = raw.replaceAll('\\', '/');
if (path.startsWith('/') ||
path.split('/').any((part) => part.isEmpty || part == '..')) {
throw const FormatException('Опасный путь в архиве');
}
return path;
}