inspection.nct
see ./index.nct
see ./source.nct
see ./storage.nct
use std/archive
use std/archive.{TarEntryKind, TarStream, TarStreamStep}
use std/compress.BlockingGzipReader
use std/io.BlockingReader
use std/set.Set
use std/string.String
use std/vec.Vec
const DECODE_BUFFER_BYTES: usize = 8192
noalloc func entry_limit_error(): error {
return error.new("archive-inspect.entry_limit", "archive entry count exceeds its configured limit")
}
noalloc func path_limit_error(): error {
return error.new("archive-inspect.path_limit", "archive path exceeds its configured limit")
}
noalloc func duplicate_path_error(): error {
return error.new("archive-inspect.duplicate_path", "archive contains a duplicate normalized path")
}
noalloc func unsupported_entry_error(): error {
return error.new("archive-inspect.unsupported_entry", "archive entry kind is not inspectable")
}
noalloc func missing_entry_error(): error {
return error.new("archive-inspect.invalid_progress", "archive published an entry event without metadata")
}
blocking func inspect<R>(source: R, limits: ArchiveLimits): ArchiveReport! where R impl BlockingReader {
let compressed = LimitedSource.compressed(move source, limits.input_bytes)
let gzip = BlockingGzipReader.with_capacity(move compressed, DECODE_BUFFER_BYTES)
var decoded = LimitedSource.decoded(move gzip, limits.decoded_bytes)
var tar = TarStream.with_capacity(DECODE_BUFFER_BYTES)
var output = source_buffer(DECODE_BUFFER_BYTES)
var paths: Set<String> = Set.empty()
var entries: Vec<InspectedEntry> = Vec.empty()
var declared_body_bytes: u64 = 0
loop {
let step = archive.next_blocking(&+tar, &+decoded, &+output)?
match step {
TarStreamStep.entry {
if entries.len() >= limits.entries { return entry_limit_error() }
let entry = tar.entry() otherwise { return missing_entry_error() }
let path = entry.path()
if path.len() > limits.path_bytes { return path_limit_error() }
let candidate = String.copy(path)
if paths.contains(&candidate) { return duplicate_path_error() }
if entry.size() > limits.decoded_bytes - declared_body_bytes {
return decoded_output_limit_error()
}
var directory = false
match entry.kind() {
TarEntryKind.file {}
TarEntryKind.directory {
if entry.size() != 0 { return unsupported_entry_error() }
directory = true
}
TarEntryKind.unsupported(_) { return unsupported_entry_error() }
}
declared_body_bytes += entry.size()
let stored_path = String.copy(path)
let _ = paths.insert(move candidate)
entries.push(
InspectedEntry {
path: move stored_path,
size: entry.size(),
directory: directory,
},
)
}
TarStreamStep.body(_) {}
TarStreamStep.finished { break }
}
}
return ArchiveReport { entries: move entries }
}
instance ArchiveReport {
noalloc method &self.entry_count(): usize { return self.entries.len() }
noalloc method &self.entry_path(index: usize): &str? from self {
let entry = self.entries.get(index)?
return &entry.path as &str
}
noalloc method &self.entry_size(index: usize): u64? {
let entry = self.entries.get(index)?
return entry.size
}
noalloc method &self.entry_is_directory(index: usize): bool? {
let entry = self.entries.get(index)?
return entry.directory
}
}