///|
async fn digest_reader(reader : &@io.Reader) -> String {
let hasher = @crypto.SHA256::new()
while reader.read_some(max_len=65536) is Some(chunk) {
hasher.update(chunk)
}
@crypto.bytes_to_hex_string(hasher.finalize())
}
///|
async fn digest_source(path : String) -> String {
if path == "-" {
digest_reader(@stdio.stdin)
} else {
let file = @fs.open(path, mode=ReadOnly)
defer file.close()
digest_reader(file)
}
}
///|
fn split_lines(text : String) -> Array[String] {
let lines : Array[String] = text.split("\n").map(v => v.to_owned()).collect()
if text.has_suffix("\n") {
ignore(lines.pop())
}
lines
}
///|
fn is_hex_digest(value : String) -> Bool {
if value.length() != 64 {
return false
}
for char in value {
if !(char is ('0'..='9' | 'a'..='f' | 'A'..='F')) {
return false
}
}
true
}
///|
fn encode_filename(name : String) -> (String, Bool) {
let output = StringBuilder()
let mut escaped = false
for char in name {
match char {
'\\' => {
output.write_string("\\\\")
escaped = true
}
'\n' => {
output.write_string("\\n")
escaped = true
}
_ => output.write_char(char)
}
}
(output.to_string(), escaped)
}
///|
fn decode_filename(value : String) -> String? {
let chars : Array[Char] = value.iter().collect()
let output = StringBuilder()
let mut index = 0
while index < chars.length() {
if chars[index] != '\\' {
output.write_char(chars[index])
index += 1
} else if index + 1 >= chars.length() {
return None
} else {
match chars[index + 1] {
'\\' => output.write_char('\\')
'n' => output.write_char('\n')
_ => return None
}
index += 2
}
}
Some(output.to_string())
}
///|
priv struct CheckSummary {
mut checked : Int
mut verified : Int
mut mismatched : Int
mut unreadable : Int
mut malformed : Int
}
///|
async fn check_manifest(
path : String,
quiet : Bool,
status_only : Bool,
ignore_missing : Bool,
warn_malformed : Bool,
) -> CheckSummary {
let text = if path == "-" {
@stdio.stdin.read_all().text()
} else {
@fs.read_file(path).text()
}
let summary : CheckSummary = {
checked: 0,
verified: 0,
mismatched: 0,
unreadable: 0,
malformed: 0,
}
for line_index, line in split_lines(text) {
if line == "" {
continue
}
let escaped = line.has_prefix("\\")
let record = if escaped { line[1:].to_owned() } else { line }
if record.length() < 67 ||
record[64] != ' ' ||
(record[65] != ' ' && record[65] != '*') ||
!is_hex_digest(record[0:64].to_owned()) {
if warn_malformed && !status_only {
@stdio.stderr.write(
"sha256sum: \{path}: \{line_index + 1}: improperly formatted SHA256 checksum line\n",
)
}
summary.malformed += 1
continue
}
let expected = record[0:64].to_owned().to_lower()
let encoded_name = record[66:].to_owned()
let name = if escaped {
match decode_filename(encoded_name) {
Some(value) => value
None => {
if warn_malformed && !status_only {
@stdio.stderr.write(
"sha256sum: \{path}: \{line_index + 1}: improperly formatted SHA256 checksum line\n",
)
}
summary.malformed += 1
continue
}
}
} else {
encoded_name
}
summary.checked += 1
if ignore_missing && name != "-" && !@fs.exists(name) {
continue
}
let actual = digest_source(name) catch {
err => {
@stdio.stderr.write("sha256sum: '\{name}': \{err}\n")
if !status_only {
@stdio.stdout.write("\{name}: FAILED open or read\n")
}
summary.unreadable += 1
continue
}
}
summary.verified += 1
if actual == expected {
if !status_only && !quiet {
@stdio.stdout.write("\{name}: OK\n")
}
} else {
if !status_only {
@stdio.stdout.write("\{name}: FAILED\n")
}
summary.mismatched += 1
}
}
summary
}
///|
async fn main {
let args = @env.args()[1:]
let parsed = @cli.parse(args, [
@cli.flag("check", short='c'),
@cli.flag("zero", short='z'),
@cli.flag("binary", short='b'),
@cli.flag("text", short='t'),
@cli.flag("quiet"),
@cli.flag("status"),
@cli.flag("strict"),
@cli.flag("warn", short='w'),
@cli.flag("ignore-missing"),
@cli.flag("help"),
]) catch {
@cli.CliError(option~, message~, ..) => {
@stdio.stderr.write("sha256sum: \{message}: '\{option}'\n")
@sys.exit(2)
return
}
}
if parsed.contains("help") {
@stdio.stdout.write(
"Usage: sha256sum [-c] [-z] [--quiet] [--status] [--strict] [--warn] [--ignore-missing] [FILE...]\n",
)
return
}
let files = parsed.operands
let check = parsed.contains("check")
let zero = parsed.contains("zero")
let quiet = parsed.contains("quiet")
let status_only = parsed.contains("status")
let strict = parsed.contains("strict")
let warn_malformed = parsed.contains("warn")
let ignore_missing = parsed.contains("ignore-missing")
let binary_output = parsed.last_occurrence(["binary", "text"]) ==
Some("binary")
let sources = if files.is_empty() { ["-"] } else { files }
let mut failed = false
if !check &&
(quiet || status_only || strict || warn_malformed || ignore_missing) {
@stdio.stderr.write("sha256sum: verification options require --check\n")
@sys.exit(2)
return
}
if check {
if zero {
@stdio.stderr.write("sha256sum: -z is not supported with -c\n")
@sys.exit(2)
return
}
for manifest in sources {
let summary = check_manifest(
manifest, quiet, status_only, ignore_missing, warn_malformed,
) catch {
err => {
@stdio.stderr.write("sha256sum: '\{manifest}': \{err}\n")
failed = true
continue
}
}
if !status_only && summary.malformed > 0 {
let noun = if summary.malformed == 1 { "line is" } else { "lines are" }
@stdio.stderr.write(
"sha256sum: WARNING: \{summary.malformed} \{noun} improperly formatted\n",
)
}
if !status_only && summary.mismatched > 0 {
let noun = if summary.mismatched == 1 {
"checksum did"
} else {
"checksums did"
}
@stdio.stderr.write(
"sha256sum: WARNING: \{summary.mismatched} computed \{noun} NOT match\n",
)
}
if !status_only && summary.unreadable > 0 {
let noun = if summary.unreadable == 1 {
"file could"
} else {
"files could"
}
@stdio.stderr.write(
"sha256sum: WARNING: \{summary.unreadable} listed \{noun} not be read\n",
)
}
if summary.checked == 0 {
if !status_only {
@stdio.stderr.write(
"sha256sum: \{manifest}: no properly formatted checksum lines found\n",
)
}
failed = true
}
if ignore_missing && summary.verified == 0 {
if !status_only {
@stdio.stderr.write("sha256sum: \{manifest}: no file was verified\n")
}
failed = true
}
if summary.mismatched > 0 ||
summary.unreadable > 0 ||
(strict && summary.malformed > 0) {
failed = true
}
}
} else {
let separator = if zero { "\u0000" } else { "\n" }
for source in sources {
let actual = digest_source(source) catch {
err => {
@stdio.stderr.write("sha256sum: '\{source}': \{err}\n")
failed = true
continue
}
}
if zero {
let marker = if binary_output { "*" } else { " " }
@stdio.stdout.write("\{actual} \{marker}\{source}\{separator}")
} else {
let (name, escaped) = encode_filename(source)
let prefix = if escaped { "\\" } else { "" }
let marker = if binary_output { "*" } else { " " }
@stdio.stdout.write("\{prefix}\{actual} \{marker}\{name}\{separator}")
}
}
}
if failed {
@sys.exit(1)
}
}