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:
2026-09-16 19:06:03 +02:00
co-authored by Claude Sonnet 4.6
parent 8234c2d9f3
commit dfacd0ce83
3 changed files with 697 additions and 69 deletions
+229 -23
View File
@@ -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