in pkg/pub_package_reader/lib/pub_package_reader.dart [69:268]
Future<PackageSummary> summarizePackageArchive(
String archivePath, {
/// The maximum length of the extracted content text.
int maxContentLength = 128 * 1024,
/// The maximum file size of the archive (gzipped or compressed) and
/// the maximum total size of the files inside the archive.
int maxArchiveSize = 100 * 1024 * 1024,
/// The maximum number of files in the archive.
/// TODO: set this lower once we scan the existing archives
int maxFileCount = 64 * 1024,
}) async {
final issues = <ArchiveIssue>[];
// Run scans before tar parsing...
issues.addAll(
await scanArchiveSurface(archivePath, maxArchiveSize: maxArchiveSize)
.toList());
if (issues.isNotEmpty) {
return PackageSummary(issues: issues);
}
TarArchive tar;
try {
tar = await TarArchive.scan(
archivePath,
maxFileCount: maxFileCount,
maxTotalLengthBytes: maxArchiveSize,
);
} catch (e, st) {
_logger.info('Failed to scan tar archive.', e, st);
return PackageSummary.fail(
ArchiveIssue('Failed to scan tar archive. ($e)'));
}
// symlinks check
final brokenSymlinks = tar.brokenSymlinks();
if (brokenSymlinks.isNotEmpty) {
final from = brokenSymlinks.keys.first;
final to = brokenSymlinks[from];
return PackageSummary.fail(ArchiveIssue(
'Package archive contains a broken symlink: `$from` -> `$to`.'));
}
// processing pubspec.yaml
final pubspecPath = tar.firstFileNameOrNull(['pubspec.yaml']);
if (pubspecPath == null) {
issues.add(ArchiveIssue('pubspec.yaml is missing.'));
return PackageSummary(issues: issues);
}
final pubspecContent = await tar.readContentAsString(pubspecPath);
// Large pubspec content should be rejected, as either a storage limit will be
// limiting it, or it will slow down queries and processing for very little
// reason.
if (pubspecContent.length > 128 * 1024) {
issues.add(ArchiveIssue('pubspec.yaml is too large.'));
}
// Reject packages using aliases in `pubspec.yaml`, these are poorly supported
// when transcoding to json.
if (yamlContainsAliases(pubspecContent)) {
issues.add(ArchiveIssue(
'pubspec.yaml may not use references (alias/anchors), '
'only the subset of YAML that can be encoded as JSON is allowed.',
));
return PackageSummary(issues: issues);
}
Pubspec? pubspec;
try {
pubspec = Pubspec.parse(pubspecContent);
} on YamlException catch (e) {
issues.add(ArchiveIssue('Error parsing pubspec.yaml: $e'));
return PackageSummary(issues: issues);
} on Exception catch (e) {
issues.add(ArchiveIssue('Error parsing pubspec.yaml: $e'));
}
// Try again with lenient parsing.
try {
pubspec ??= Pubspec.parse(pubspecContent, lenient: true);
} on Exception catch (e) {
issues.add(ArchiveIssue('Error parsing pubspec.yaml: $e'));
return PackageSummary(issues: issues);
}
issues.addAll(checkValidJson(pubspecContent));
issues.addAll(checkAuthors(pubspecContent));
issues.addAll(checkPlatforms(pubspecContent));
issues.addAll(checkStrictVersions(pubspec));
issues.addAll(checkSdkVersionRange(pubspec));
// Check whether the files can be extracted on case-preserving file systems
// (e.g. on Windows). We can't allow two files with the same case-insensitive
// name.
final lowerCaseFiles = <String, List<String>>{};
for (final file in tar.fileNames) {
final lower = file.toLowerCase();
lowerCaseFiles.putIfAbsent(lower, () => <String>[]).add(file);
}
final fileNameCollisions =
lowerCaseFiles.values.firstWhereOrNull((l) => l.length > 1);
if (fileNameCollisions != null) {
issues.add(ArchiveIssue(
'Filename collision on case-preserving file systems: ${fileNameCollisions.join(' vs. ')}.'));
}
if (pubspec.name.trim().isEmpty) {
issues.add(ArchiveIssue('pubspec.yaml is missing `name`.'));
return PackageSummary(issues: issues);
}
if (pubspec.version == null) {
issues.add(ArchiveIssue('pubspec.yaml is missing `version`.'));
return PackageSummary(issues: issues);
}
String? readmePath = tar.firstFileNameOrNull(readmeFileNames);
String? changelogPath = tar.firstFileNameOrNull(changelogFileNames);
String? examplePath =
tar.firstFileNameOrNull(exampleFileCandidates(pubspec.name));
String? licensePath = tar.firstFileNameOrNull(licenseFileNames);
final contentBytes = await tar.scanAndReadFiles(
[readmePath, changelogPath, examplePath, licensePath]
.whereType<String>()
.toList(),
maxLength: maxContentLength,
);
String? tryParseContentBytes(String? contentPath) {
if (contentPath == null) return null;
final bytes = contentBytes[contentPath];
if (bytes == null) return null;
if (bytes.length > maxContentLength) {
issues.add(ArchiveIssue(
'`$contentPath` exceeds the maximum content length ($maxContentLength bytes).'));
}
String content = utf8.decode(bytes, allowMalformed: true);
if (content.length > maxContentLength) {
content = content.substring(0, maxContentLength) + '[...]\n\n';
}
return content;
}
final readmeContent = tryParseContentBytes(readmePath);
if (readmeContent == null) {
readmePath = null;
}
final changelogContent = tryParseContentBytes(changelogPath);
if (changelogContent == null) {
changelogPath = null;
}
final exampleContent = tryParseContentBytes(examplePath);
if (exampleContent == null) {
examplePath = null;
}
final licenseContent = tryParseContentBytes(licensePath);
if (licenseContent == null) {
licensePath = null;
}
final libraries = tar.fileNames
.where((file) => file.startsWith('lib/'))
.where((file) => !file.startsWith('lib/src'))
.where((file) => file.endsWith('.dart'))
.map((file) => file.substring('lib/'.length))
.toList();
issues.addAll(validatePackageName(pubspec.name));
issues.addAll(validatePackageVersion(pubspec.version));
issues.addAll(validatePublishTo(pubspec.publishTo));
issues.addAll(validateZalgo('description', pubspec.description));
issues.addAll(syntaxCheckUrl(pubspec.homepage, 'homepage'));
issues.addAll(syntaxCheckUrl(pubspec.repository?.toString(), 'repository'));
issues.addAll(syntaxCheckUrl(pubspec.documentation, 'documentation'));
issues
.addAll(syntaxCheckUrl(pubspec.issueTracker?.toString(), 'issueTracker'));
issues.addAll(validateDependencies(pubspec));
issues.addAll(forbidGitDependencies(pubspec));
// TODO: re-enable or remove after version pinning gets resolved
// https://github.com/dart-lang/pub/issues/2557
// issues.addAll(forbidPreReleaseSdk(pubspec));
issues.addAll(requireIosFolderOrFlutter2_20(pubspec, tar.fileNames));
issues.addAll(requireNonEmptyLicense(licensePath, licenseContent));
issues.addAll(checkScreenshots(pubspec, tar.fileNames));
return PackageSummary(
issues: issues,
pubspecContent: pubspecContent,
readmePath: readmePath,
readmeContent: readmeContent,
changelogPath: changelogPath,
changelogContent: changelogContent,
examplePath: examplePath,
exampleContent: exampleContent,
licensePath: licensePath,
licenseContent: licenseContent,
libraries: libraries,
);
}