fix/feat: validation improvements and EXIT trap for all bad-signature scripts
fix: treat qpdf exit code 3 (warnings only) as VALID in PDF check
- exit 0 and exit 3 both map to VALID; only exit 2 is a structural error
- error detail now includes first matching error line from qpdf output
fix: add EXIT trap so encryption flag and temp files are always cleaned up
- changed signal trap from to
- EXIT fires for any bash exit including syntax errors and unexpected crashes
- clean_up() now runs first to prevent re-entry / infinite loop
- applies to recover_bad_signature.sh (where the flag matters most),
restore_bad_signature.sh and recreate_bad_signature.sh
feat: check optional validation tools at startup and offer apt install
- new check_optional_validation_tools() prompts [j/N] in interactive mode
- covers: imagemagick (identify), ffmpeg (ffprobe), mp3val, flac, vorbis-tools (ogginfo)
feat: use optional tools for deeper file validation when available
- PNG / GIF / BMP / TIFF: identify -regard-warnings (full decode) with magic-byte fallback
- MP4 / MOV / M4V: ffprobe -show_streams (container parse) with ftyp-box fallback
- MP3: new dedicated case — mp3val frame check with ID3/sync-word fallback
- FLAC: new dedicated case — flac --silent --test with fLaC-signature fallback
- OGG / OGA / OGV / OPUS: new dedicated case — ogginfo with OggS-signature fallback
Affects: recover_bad_signature.sh, restore_bad_signature.sh, recreate_bad_signature.sh
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GfXh5sRbEaXiEX6KjAPivc
This commit is contained in:
+229
-23
@@ -192,6 +192,77 @@ clean_up() {
|
||||
|
||||
}
|
||||
|
||||
|
||||
check_optional_validation_tools() {
|
||||
|
||||
local _missing_tools=()
|
||||
local _missing_pkgs=()
|
||||
local _desc=()
|
||||
|
||||
command -v identify > /dev/null 2>&1 || {
|
||||
_missing_tools+=("identify")
|
||||
_missing_pkgs+=("imagemagick")
|
||||
_desc+=("identify (imagemagick) — PNG / TIFF / BMP / GIF: vollst. Dekodierung statt nur Magic-Bytes")
|
||||
}
|
||||
command -v ffprobe > /dev/null 2>&1 || {
|
||||
_missing_tools+=("ffprobe")
|
||||
_missing_pkgs+=("ffmpeg")
|
||||
_desc+=("ffprobe (ffmpeg) — MP4 / MOV / M4V: Container-Parsing statt nur ftyp-Box-Suche")
|
||||
}
|
||||
command -v mp3val > /dev/null 2>&1 || {
|
||||
_missing_tools+=("mp3val")
|
||||
_missing_pkgs+=("mp3val")
|
||||
_desc+=("mp3val (mp3val) — MP3: Frame-Struktur-Check statt nur ID3-Signatur")
|
||||
}
|
||||
command -v flac > /dev/null 2>&1 || {
|
||||
_missing_tools+=("flac")
|
||||
_missing_pkgs+=("flac")
|
||||
_desc+=("flac (flac) — FLAC: Decode-Test statt nur fLaC-Signatur")
|
||||
}
|
||||
command -v ogginfo > /dev/null 2>&1 || {
|
||||
_missing_tools+=("ogginfo")
|
||||
_missing_pkgs+=("vorbis-tools")
|
||||
_desc+=("ogginfo (vorbis-tools) — OGG / OGA / OPUS: Container-Check statt nur OggS-Signatur")
|
||||
}
|
||||
|
||||
[[ ${#_missing_tools[@]} -eq 0 ]] && return 0
|
||||
|
||||
if ! $terminal ; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local _pkg_list="${_missing_pkgs[*]}"
|
||||
|
||||
echo ""
|
||||
echo -e " \033[33mOptionale Validierungs-Tools fehlen – Checks laufen mit reduzierter Genauigkeit:\033[m"
|
||||
echo ""
|
||||
local _d
|
||||
for _d in "${_desc[@]}" ; do
|
||||
echo " ${_d}"
|
||||
done
|
||||
echo ""
|
||||
echo -n " Jetzt installieren? apt install ${_pkg_list} [j/N]: "
|
||||
read -r _yn
|
||||
echo ""
|
||||
if [[ "$_yn" =~ ^[jJyY]$ ]] ; then
|
||||
echononl " Installiere Pakete: ${_pkg_list}.."
|
||||
# shellcheck disable=SC2086
|
||||
if apt-get install -y ${_missing_pkgs[*]} > /dev/null 2>&1 ; then
|
||||
echo_ok
|
||||
else
|
||||
echo_failed
|
||||
echo ""
|
||||
echo " Bitte manuell installieren:"
|
||||
echo " apt install ${_pkg_list}"
|
||||
fi
|
||||
else
|
||||
echo " Übersprungen — Validierung läuft mit reduzierter Genauigkeit."
|
||||
fi
|
||||
echo ""
|
||||
|
||||
}
|
||||
|
||||
|
||||
is_number() {
|
||||
|
||||
return $(test ! -z "${1##*[!0-9]*}" > /dev/null 2>&1);
|
||||
@@ -340,37 +411,69 @@ validate_recovered_file() {
|
||||
fi
|
||||
;;
|
||||
png)
|
||||
local _head
|
||||
_head="$(head -c8 "$_f" 2> /dev/null | od -An -tx1 | tr -d ' \n')"
|
||||
if [[ "$_head" = "89504e470d0a1a0a" ]] ; then
|
||||
echo "VALID|PNG signature ok"
|
||||
if command -v identify > /dev/null 2>&1 ; then
|
||||
if identify -regard-warnings "$_f" > /dev/null 2>&1 ; then
|
||||
echo "VALID|ImageMagick 'identify' decoded PNG ok"
|
||||
else
|
||||
echo "INVALID|ImageMagick 'identify' failed to decode PNG"
|
||||
fi
|
||||
else
|
||||
echo "INVALID|PNG signature missing"
|
||||
local _head
|
||||
_head="$(head -c8 "$_f" 2> /dev/null | od -An -tx1 | tr -d ' \n')"
|
||||
if [[ "$_head" = "89504e470d0a1a0a" ]] ; then
|
||||
echo "VALID|PNG signature ok (identify not installed — no deep check)"
|
||||
else
|
||||
echo "INVALID|PNG signature missing"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
gif)
|
||||
local _head6
|
||||
_head6="$(head -c6 "$_f" 2> /dev/null)"
|
||||
if [[ "$_head6" = "GIF87a" || "$_head6" = "GIF89a" ]] ; then
|
||||
echo "VALID|GIF signature ok"
|
||||
if command -v identify > /dev/null 2>&1 ; then
|
||||
if identify -regard-warnings "$_f" > /dev/null 2>&1 ; then
|
||||
echo "VALID|ImageMagick 'identify' decoded GIF ok"
|
||||
else
|
||||
echo "INVALID|ImageMagick 'identify' failed to decode GIF"
|
||||
fi
|
||||
else
|
||||
echo "INVALID|GIF signature missing"
|
||||
local _head6
|
||||
_head6="$(head -c6 "$_f" 2> /dev/null)"
|
||||
if [[ "$_head6" = "GIF87a" || "$_head6" = "GIF89a" ]] ; then
|
||||
echo "VALID|GIF signature ok (identify not installed — no deep check)"
|
||||
else
|
||||
echo "INVALID|GIF signature missing"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
bmp)
|
||||
if [[ "$(head -c2 "$_f" 2> /dev/null)" = "BM" ]] ; then
|
||||
echo "VALID|BMP signature ok"
|
||||
if command -v identify > /dev/null 2>&1 ; then
|
||||
if identify -regard-warnings "$_f" > /dev/null 2>&1 ; then
|
||||
echo "VALID|ImageMagick 'identify' decoded BMP ok"
|
||||
else
|
||||
echo "INVALID|ImageMagick 'identify' failed to decode BMP"
|
||||
fi
|
||||
else
|
||||
echo "INVALID|BMP signature missing"
|
||||
if [[ "$(head -c2 "$_f" 2> /dev/null)" = "BM" ]] ; then
|
||||
echo "VALID|BMP signature ok (identify not installed — no deep check)"
|
||||
else
|
||||
echo "INVALID|BMP signature missing"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
tif|tiff)
|
||||
local _head4
|
||||
_head4="$(head -c4 "$_f" 2> /dev/null | od -An -tx1 | tr -d ' \n')"
|
||||
if [[ "$_head4" = "49492a00" || "$_head4" = "4d4d002a" ]] ; then
|
||||
echo "VALID|TIFF signature ok"
|
||||
if command -v identify > /dev/null 2>&1 ; then
|
||||
if identify -regard-warnings "$_f" > /dev/null 2>&1 ; then
|
||||
echo "VALID|ImageMagick 'identify' decoded TIFF ok"
|
||||
else
|
||||
echo "INVALID|ImageMagick 'identify' failed to decode TIFF"
|
||||
fi
|
||||
else
|
||||
echo "INVALID|TIFF signature missing"
|
||||
local _head4
|
||||
_head4="$(head -c4 "$_f" 2> /dev/null | od -An -tx1 | tr -d ' \n')"
|
||||
if [[ "$_head4" = "49492a00" || "$_head4" = "4d4d002a" ]] ; then
|
||||
echo "VALID|TIFF signature ok (identify not installed — no deep check)"
|
||||
else
|
||||
echo "INVALID|TIFF signature missing"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
pnm|pgm|ppm|pbm)
|
||||
@@ -420,10 +523,25 @@ validate_recovered_file() {
|
||||
fi
|
||||
;;
|
||||
mp4|mov|m4v)
|
||||
if head -c 64 "$_f" 2> /dev/null | grep -aq "ftyp" ; then
|
||||
echo "VALID|mp4 ftyp box found"
|
||||
if command -v ffprobe > /dev/null 2>&1 ; then
|
||||
local _ffprobe_out _ffprobe_exit
|
||||
_ffprobe_out="$(ffprobe -v error -show_streams "$_f" 2>&1)"
|
||||
_ffprobe_exit=$?
|
||||
if [[ $_ffprobe_exit -eq 0 ]] ; then
|
||||
local _streams
|
||||
_streams="$(echo "$_ffprobe_out" | grep -c '\[STREAM\]' || true)"
|
||||
echo "VALID|ffprobe parsed container ok (${_streams} stream(s) found)"
|
||||
else
|
||||
local _ffprobe_err
|
||||
_ffprobe_err="$(echo "$_ffprobe_out" | head -1)"
|
||||
echo "INVALID|ffprobe failed to parse container${_ffprobe_err:+ (${_ffprobe_err})}"
|
||||
fi
|
||||
else
|
||||
echo "INVALID|mp4 ftyp box not found"
|
||||
if head -c 64 "$_f" 2> /dev/null | grep -aq "ftyp" ; then
|
||||
echo "VALID|mp4 ftyp box found (ffprobe not installed — no deep check)"
|
||||
else
|
||||
echo "INVALID|mp4 ftyp box not found"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
doc|xls|ppt|ole|msi)
|
||||
@@ -515,6 +633,72 @@ validate_recovered_file() {
|
||||
echo "INVALID|GIMP (gimp xcf) signature missing"
|
||||
fi
|
||||
;;
|
||||
mp3)
|
||||
if command -v mp3val > /dev/null 2>&1 ; then
|
||||
local _mp3val_out
|
||||
_mp3val_out="$(mp3val "$_f" 2>&1)"
|
||||
if echo "$_mp3val_out" | grep -q 'No errors found' ; then
|
||||
echo "VALID|mp3val: no errors found"
|
||||
else
|
||||
local _mp3_err
|
||||
_mp3_err="$(echo "$_mp3val_out" | grep -iv '^mp3val\|^$' | head -2 | tr '\n' ' ' | sed 's/ $//')"
|
||||
echo "INVALID|mp3val reported errors${_mp3_err:+ (${_mp3_err})}"
|
||||
fi
|
||||
else
|
||||
local _head3 _head2hex
|
||||
_head3="$(head -c3 "$_f" 2>/dev/null)"
|
||||
_head2hex="$(head -c2 "$_f" 2>/dev/null | od -An -tx1 | tr -d ' \n')"
|
||||
if [[ "$_head3" = "ID3" ]] || echo "$_head2hex" | grep -qE '^fff[bef2]' ; then
|
||||
echo "VALID|MP3 ID3 tag / MPEG sync ok (mp3val not installed — no deep check)"
|
||||
else
|
||||
echo "INVALID|MP3: no ID3 header or MPEG frame sync found"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
flac)
|
||||
if command -v flac > /dev/null 2>&1 ; then
|
||||
local _flac_out _flac_exit
|
||||
_flac_out="$(flac --silent --test "$_f" 2>&1)"
|
||||
_flac_exit=$?
|
||||
if [[ $_flac_exit -eq 0 ]] ; then
|
||||
echo "VALID|flac --test: ok"
|
||||
else
|
||||
local _flac_err
|
||||
_flac_err="$(echo "$_flac_out" | grep -v '^$' | tail -1)"
|
||||
echo "INVALID|flac --test failed${_flac_err:+ (${_flac_err})}"
|
||||
fi
|
||||
else
|
||||
local _head4hex
|
||||
_head4hex="$(head -c4 "$_f" 2>/dev/null | od -An -tx1 | tr -d ' \n')"
|
||||
if [[ "$_head4hex" = "664c6143" ]] ; then
|
||||
echo "VALID|FLAC 'fLaC' signature ok (flac not installed — no deep check)"
|
||||
else
|
||||
echo "INVALID|FLAC 'fLaC' signature missing"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
ogg|oga|ogv|opus)
|
||||
if command -v ogginfo > /dev/null 2>&1 ; then
|
||||
local _ogg_out _ogg_exit
|
||||
_ogg_out="$(ogginfo "$_f" 2>&1)"
|
||||
_ogg_exit=$?
|
||||
if [[ $_ogg_exit -eq 0 ]] ; then
|
||||
echo "VALID|ogginfo: container parsed ok"
|
||||
else
|
||||
local _ogg_err
|
||||
_ogg_err="$(echo "$_ogg_out" | grep -iv '^Processing\|^$' | head -2 | tr '\n' ' ' | sed 's/ $//')"
|
||||
echo "INVALID|ogginfo failed${_ogg_err:+ (${_ogg_err})}"
|
||||
fi
|
||||
else
|
||||
local _head4ogg
|
||||
_head4ogg="$(head -c4 "$_f" 2>/dev/null)"
|
||||
if [[ "$_head4ogg" = "OggS" ]] ; then
|
||||
echo "VALID|OGG 'OggS' signature ok (ogginfo not installed — no deep check)"
|
||||
else
|
||||
echo "INVALID|OGG 'OggS' signature missing"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
wav)
|
||||
local _riff _wave
|
||||
_riff="$(head -c4 "$_f" 2> /dev/null)"
|
||||
@@ -586,6 +770,15 @@ validate_recovered_file() {
|
||||
esac
|
||||
}
|
||||
|
||||
## - qpdf exit codes:
|
||||
## - 0 no problems
|
||||
## - 2 structural errors → file is genuinely corrupt/unreadable
|
||||
## - 3 warnings only → minor spec non-conformances; every real
|
||||
## - viewer opens the file without issues
|
||||
## - Only exit code 2 is treated as INVALID here. Exit code 3 (warnings)
|
||||
## - is reported as VALID with a note, preventing false positives for
|
||||
## - real-world PDFs that have trivial non-conformances.
|
||||
## -
|
||||
_pdf_check() {
|
||||
local _f="$1"
|
||||
if [[ "$(head -c5 "$_f" 2> /dev/null)" != "%PDF-" ]] ; then
|
||||
@@ -593,11 +786,22 @@ _pdf_check() {
|
||||
return 1
|
||||
fi
|
||||
if command -v qpdf > /dev/null 2>&1 ; then
|
||||
if qpdf --check "$_f" > /dev/null 2>&1 ; then
|
||||
local _qpdf_out _qpdf_exit
|
||||
_qpdf_out="$(qpdf --check "$_f" 2>&1)"
|
||||
_qpdf_exit=$?
|
||||
if [[ $_qpdf_exit -eq 0 ]] ; then
|
||||
_pdf_check_detail="qpdf --check ok"
|
||||
return 0
|
||||
elif [[ $_qpdf_exit -eq 3 ]] ; then
|
||||
# Warnings only — no structural errors. File is readable by all
|
||||
# standard PDF viewers; non-conformances are minor/cosmetic.
|
||||
_pdf_check_detail="qpdf --check ok (warnings only — file is readable)"
|
||||
return 0
|
||||
else
|
||||
_pdf_check_detail="qpdf --check failed"
|
||||
# Exit code 2 (or unexpected): genuine structural errors.
|
||||
local _first_err
|
||||
_first_err="$(echo "$_qpdf_out" | grep -i 'error' | head -1 | sed 's/^[[:space:]]*//')"
|
||||
_pdf_check_detail="qpdf --check failed (exit ${_qpdf_exit}${_first_err:+: ${_first_err}})"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
@@ -1061,6 +1265,8 @@ done
|
||||
mkdir -p "$report_dir" 2> /dev/null
|
||||
recreate_report_file="${report_dir}/recreate_${WEBSITE}_${run_date}.tsv"
|
||||
|
||||
check_optional_validation_tools
|
||||
|
||||
if $terminal ; then
|
||||
echo ""
|
||||
if $DRY_RUN ; then
|
||||
|
||||
Reference in New Issue
Block a user