fix: harden workspace version reader inputs
This commit is contained in:
parent
050fac23cd
commit
d41da4fc64
136
scripts/workspace-version.sh
Executable file
136
scripts/workspace-version.sh
Executable file
|
|
@ -0,0 +1,136 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
if [ "$#" -gt 1 ]; then
|
||||
printf 'workspace-version: expected zero or one manifest path argument\n' >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
script_dir=$(CDPATH= cd "$(dirname "$0")" && pwd)
|
||||
manifest=${1-"$script_dir/../Cargo.toml"}
|
||||
|
||||
if [ ! -f "$manifest" ] || [ ! -r "$manifest" ]; then
|
||||
printf 'workspace-version: cannot read manifest: %s\n' "$manifest" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if version=$(awk '
|
||||
function valid_core_number(value) {
|
||||
return value == "0" || value ~ /^[1-9][0-9]*$/
|
||||
}
|
||||
|
||||
function valid_identifiers(value, reject_leading_zero, count, i, identifiers) {
|
||||
if (value == "") {
|
||||
return 0
|
||||
}
|
||||
|
||||
count = split(value, identifiers, "[.]")
|
||||
for (i = 1; i <= count; i++) {
|
||||
if (identifiers[i] !~ /^[0-9A-Za-z-]+$/) {
|
||||
return 0
|
||||
}
|
||||
if (reject_leading_zero && identifiers[i] ~ /^[0-9]+$/ &&
|
||||
length(identifiers[i]) > 1 && substr(identifiers[i], 1, 1) == "0") {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
function valid_semver(value, base, core, prerelease, build, plus, dash, count, parts) {
|
||||
base = value
|
||||
plus = index(base, "+")
|
||||
if (plus > 0) {
|
||||
build = substr(base, plus + 1)
|
||||
if (!valid_identifiers(build, 0)) {
|
||||
return 0
|
||||
}
|
||||
base = substr(base, 1, plus - 1)
|
||||
}
|
||||
|
||||
dash = index(base, "-")
|
||||
if (dash > 0) {
|
||||
prerelease = substr(base, dash + 1)
|
||||
if (!valid_identifiers(prerelease, 1)) {
|
||||
return 0
|
||||
}
|
||||
core = substr(base, 1, dash - 1)
|
||||
} else {
|
||||
core = base
|
||||
}
|
||||
|
||||
count = split(core, parts, "[.]")
|
||||
return count == 3 && valid_core_number(parts[1]) &&
|
||||
valid_core_number(parts[2]) && valid_core_number(parts[3])
|
||||
}
|
||||
|
||||
/^[[:space:]]*\[[[:space:]]*workspace[[:space:]]*[.][[:space:]]*package[[:space:]]*\][[:space:]]*(#.*)?$/ {
|
||||
section_count++
|
||||
in_workspace_package = 1
|
||||
next
|
||||
}
|
||||
|
||||
/^[[:space:]]*\[/ {
|
||||
in_workspace_package = 0
|
||||
next
|
||||
}
|
||||
|
||||
in_workspace_package && /^[[:space:]]*version[[:space:]]*=/ {
|
||||
version_count++
|
||||
if ($0 !~ /^[[:space:]]*version[[:space:]]*=[[:space:]]*"[^"]*"[[:space:]]*(#.*)?$/) {
|
||||
invalid_assignment = 1
|
||||
next
|
||||
}
|
||||
|
||||
value = $0
|
||||
sub(/^[^"]*"/, "", value)
|
||||
sub(/".*$/, "", value)
|
||||
workspace_version = value
|
||||
}
|
||||
|
||||
END {
|
||||
if (section_count == 0) {
|
||||
exit 10
|
||||
}
|
||||
if (section_count > 1) {
|
||||
exit 11
|
||||
}
|
||||
if (version_count == 0) {
|
||||
exit 12
|
||||
}
|
||||
if (version_count > 1) {
|
||||
exit 13
|
||||
}
|
||||
if (invalid_assignment || !valid_semver(workspace_version)) {
|
||||
exit 14
|
||||
}
|
||||
|
||||
print workspace_version
|
||||
}
|
||||
' < "$manifest"); then
|
||||
printf '%s\n' "$version"
|
||||
else
|
||||
status=$?
|
||||
case "$status" in
|
||||
10)
|
||||
printf 'workspace-version: missing [workspace.package] section in %s\n' "$manifest" >&2
|
||||
;;
|
||||
11)
|
||||
printf 'workspace-version: expected exactly one [workspace.package] section in %s\n' "$manifest" >&2
|
||||
;;
|
||||
12)
|
||||
printf 'workspace-version: missing version in [workspace.package] in %s\n' "$manifest" >&2
|
||||
;;
|
||||
13)
|
||||
printf 'workspace-version: expected exactly one version in [workspace.package] in %s\n' "$manifest" >&2
|
||||
;;
|
||||
14)
|
||||
printf 'workspace-version: invalid version in [workspace.package] in %s\n' "$manifest" >&2
|
||||
;;
|
||||
*)
|
||||
printf 'workspace-version: failed to parse manifest: %s\n' "$manifest" >&2
|
||||
;;
|
||||
esac
|
||||
exit 1
|
||||
fi
|
||||
202
scripts/workspace-version.test.sh
Executable file
202
scripts/workspace-version.test.sh
Executable file
|
|
@ -0,0 +1,202 @@
|
|||
#!/bin/sh
|
||||
|
||||
set -eu
|
||||
|
||||
script_dir=$(CDPATH= cd "$(dirname "$0")" && pwd)
|
||||
reader="$script_dir/workspace-version.sh"
|
||||
tmp_dir=$(mktemp -d "${TMPDIR:-/tmp}/workspace-version.XXXXXX")
|
||||
trap 'rm -rf "$tmp_dir"' 0 HUP INT TERM
|
||||
|
||||
test_count=0
|
||||
|
||||
fail() {
|
||||
printf 'not ok %s - %s\n' "$test_count" "$1" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
write_manifest() {
|
||||
name=$1
|
||||
content=$2
|
||||
printf '%s\n' "$content" > "$tmp_dir/$name"
|
||||
}
|
||||
|
||||
assert_success() {
|
||||
name=$1
|
||||
label=$2
|
||||
expected=$3
|
||||
test_count=$((test_count + 1))
|
||||
|
||||
if ! "$reader" "$tmp_dir/$name" > "$tmp_dir/stdout" 2> "$tmp_dir/stderr"; then
|
||||
sed 's/^/ /' "$tmp_dir/stderr" >&2
|
||||
fail "$label unexpectedly failed"
|
||||
fi
|
||||
|
||||
printf '%s\n' "$expected" > "$tmp_dir/expected"
|
||||
if ! cmp -s "$tmp_dir/expected" "$tmp_dir/stdout"; then
|
||||
fail "$label did not print exactly '$expected'"
|
||||
fi
|
||||
if [ -s "$tmp_dir/stderr" ]; then
|
||||
fail "$label wrote unexpected stderr"
|
||||
fi
|
||||
|
||||
printf 'ok %s - %s\n' "$test_count" "$label"
|
||||
}
|
||||
|
||||
assert_relative_success() {
|
||||
name=$1
|
||||
label=$2
|
||||
expected=$3
|
||||
test_count=$((test_count + 1))
|
||||
|
||||
if ! (cd "$tmp_dir" && "$reader" "$name" </dev/null) > "$tmp_dir/stdout" 2> "$tmp_dir/stderr"; then
|
||||
sed 's/^/ /' "$tmp_dir/stderr" >&2
|
||||
fail "$label unexpectedly failed"
|
||||
fi
|
||||
|
||||
printf '%s\n' "$expected" > "$tmp_dir/expected"
|
||||
if ! cmp -s "$tmp_dir/expected" "$tmp_dir/stdout"; then
|
||||
fail "$label did not print exactly '$expected'"
|
||||
fi
|
||||
if [ -s "$tmp_dir/stderr" ]; then
|
||||
fail "$label wrote unexpected stderr"
|
||||
fi
|
||||
|
||||
printf 'ok %s - %s\n' "$test_count" "$label"
|
||||
}
|
||||
|
||||
assert_failure() {
|
||||
name=$1
|
||||
label=$2
|
||||
expected_error=$3
|
||||
test_count=$((test_count + 1))
|
||||
|
||||
if "$reader" "$tmp_dir/$name" > "$tmp_dir/stdout" 2> "$tmp_dir/stderr"; then
|
||||
fail "$label unexpectedly succeeded"
|
||||
fi
|
||||
if [ -s "$tmp_dir/stdout" ]; then
|
||||
fail "$label wrote unexpected stdout"
|
||||
fi
|
||||
printf 'workspace-version: %s in %s\n' "$expected_error" "$tmp_dir/$name" > "$tmp_dir/expected"
|
||||
if ! cmp -s "$tmp_dir/expected" "$tmp_dir/stderr"; then
|
||||
sed 's/^/ /' "$tmp_dir/stderr" >&2
|
||||
fail "$label stderr did not match the expected diagnostic"
|
||||
fi
|
||||
|
||||
printf 'ok %s - %s\n' "$test_count" "$label"
|
||||
}
|
||||
|
||||
write_manifest case01.toml '[workspace]
|
||||
members = []
|
||||
|
||||
[workspace.package]
|
||||
version = "0.8.1"'
|
||||
|
||||
write_manifest case02.toml '[workspace.package]
|
||||
version = "1.2.3-rc.1+build.4"'
|
||||
|
||||
write_manifest case03.toml '[workspace.package]
|
||||
version = "1.2.3+build.004"'
|
||||
|
||||
write_manifest 'version=fixture' '[workspace.package]
|
||||
version = "0.8.1"'
|
||||
|
||||
write_manifest case04.toml '[workspace]
|
||||
members = []
|
||||
|
||||
[package]
|
||||
name = "decoy"
|
||||
version = "9.9.9"'
|
||||
|
||||
write_manifest case05.toml '[workspace.package]
|
||||
edition = "2021"
|
||||
|
||||
[workspace.dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }'
|
||||
|
||||
write_manifest case06.toml '[workspace.package]
|
||||
version = "0.8.1"
|
||||
|
||||
[workspace.dependencies]
|
||||
serde = "1"
|
||||
|
||||
[workspace.package]
|
||||
version = "0.8.2"'
|
||||
|
||||
write_manifest case07.toml '[workspace.package]
|
||||
version = "0.8.1"
|
||||
version = "0.8.2"'
|
||||
|
||||
write_manifest case08.toml '[workspace.package]
|
||||
version = "v0.8.1"'
|
||||
|
||||
write_manifest case09.toml '[workspace.package]
|
||||
version = "0.8"'
|
||||
|
||||
write_manifest case10.toml '[workspace.package]
|
||||
version = "latest"'
|
||||
|
||||
write_manifest case11.toml '[workspace.package]
|
||||
version = "01.2.3"'
|
||||
|
||||
write_manifest case12.toml '[workspace.package]
|
||||
version = "1.02.3"'
|
||||
|
||||
write_manifest case13.toml '[workspace.package]
|
||||
version = "1.2.03"'
|
||||
|
||||
write_manifest case14.toml '[workspace.package]
|
||||
version = "1.2.3-01"'
|
||||
|
||||
write_manifest case15.toml '[workspace.package]
|
||||
version = "1.2.3-"'
|
||||
|
||||
write_manifest case16.toml '[workspace.package]
|
||||
version = "1.2.3+"'
|
||||
|
||||
write_manifest case17.toml '[workspace.package]
|
||||
version = "1.2.3-rc_1"'
|
||||
|
||||
write_manifest case18.toml '[workspace.package]
|
||||
version = "1.2.3-rc..1"'
|
||||
|
||||
write_manifest case19.toml '[workspace.package]
|
||||
version = "1.2.3+build..4"'
|
||||
|
||||
write_manifest case20.toml '[workspace.package]
|
||||
version = 0.8.1'
|
||||
|
||||
write_manifest case21.toml "[workspace.package]
|
||||
version = '0.8.1'"
|
||||
|
||||
write_manifest case22.toml '[workspace.package]
|
||||
version = "0.8.1'
|
||||
|
||||
write_manifest case23.toml '[workspace.package]
|
||||
version = "0.8.1" trailing'
|
||||
|
||||
assert_success case01.toml stable '0.8.1'
|
||||
assert_success case02.toml prerelease_build '1.2.3-rc.1+build.4'
|
||||
assert_success case03.toml build_with_leading_zero '1.2.3+build.004'
|
||||
assert_relative_success 'version=fixture' assignment_shaped_relative_path '0.8.1'
|
||||
assert_failure case04.toml missing_workspace_package 'missing [workspace.package] section'
|
||||
assert_failure case05.toml missing_version 'missing version in [workspace.package]'
|
||||
assert_failure case06.toml duplicate_sections 'expected exactly one [workspace.package] section'
|
||||
assert_failure case07.toml duplicate_versions 'expected exactly one version in [workspace.package]'
|
||||
assert_failure case08.toml malformed_v_prefix 'invalid version in [workspace.package]'
|
||||
assert_failure case09.toml malformed_short 'invalid version in [workspace.package]'
|
||||
assert_failure case10.toml malformed_latest 'invalid version in [workspace.package]'
|
||||
assert_failure case11.toml leading_zero_major 'invalid version in [workspace.package]'
|
||||
assert_failure case12.toml leading_zero_minor 'invalid version in [workspace.package]'
|
||||
assert_failure case13.toml leading_zero_patch 'invalid version in [workspace.package]'
|
||||
assert_failure case14.toml leading_zero_numeric_prerelease 'invalid version in [workspace.package]'
|
||||
assert_failure case15.toml empty_prerelease 'invalid version in [workspace.package]'
|
||||
assert_failure case16.toml empty_build 'invalid version in [workspace.package]'
|
||||
assert_failure case17.toml illegal_identifier_character 'invalid version in [workspace.package]'
|
||||
assert_failure case18.toml repeated_prerelease_separator 'invalid version in [workspace.package]'
|
||||
assert_failure case19.toml repeated_build_separator 'invalid version in [workspace.package]'
|
||||
assert_failure case20.toml unquoted_assignment 'invalid version in [workspace.package]'
|
||||
assert_failure case21.toml single_quoted_assignment 'invalid version in [workspace.package]'
|
||||
assert_failure case22.toml unterminated_quoted_assignment 'invalid version in [workspace.package]'
|
||||
assert_failure case23.toml trailing_assignment_content 'invalid version in [workspace.package]'
|
||||
|
||||
printf '1..%s\n' "$test_count"
|
||||
Loading…
Reference in a new issue