#!/usr/bin/env bash # STEP047 - Porting Presenze 215.6 # # Audit mirato del flusso di stampa realmente utilizzato da Silvelox. # # Analizza esclusivamente: # - /libs/stimulsoft_legacy/reports/SimpleList.mrt realmente richiesto; # - /json_data/dataReportTable.json realmente caricato; # - viewer/app/handler legacy attivi; # - copie alternative dello stesso report; # - tabelle ax_file_report, ax_print_user, ax_report, ax_reports; # - codice che genera/seleziona report e JSON; # - richieste HTTP recenti del solo flusso attivo. # # Modalita': AUDIT ONLY - nessuna modifica. set -Eeuo pipefail umask 077 STEP_NAME="STEP047_2156_SILVELOX_ACTIVE_PRINT_TEMPLATE_DATA_AUDIT_ONLY_VERIFIED_20260731" EXPECTED_HOST="${EXPECTED_HOST:-ax-accessi}" EXPECTED_IP_FRAGMENT="${EXPECTED_IP_FRAGMENT:-192.168.215.6}" TARGET_ROOT="${TARGET_ROOT:-/var/www/html/presenze.silvelox}" BASE_ROOT="${BASE_ROOT:-/var/www/html/presenze.distribuite}" TARGET_DB="${TARGET_DB:-presenze_silvelox}" TARGET_HOST="${TARGET_HOST:-presenze.silvelox.axuno.it}" ALLOW_TEST="${ALLOW_TEST:-0}" SKIP_DB="${SKIP_DB:-0}" SKIP_HTTP="${SKIP_HTTP:-0}" SKIP_LOGS="${SKIP_LOGS:-0}" ACTIVE_REPORT="${ACTIVE_REPORT:-$TARGET_ROOT/libs/stimulsoft_legacy/reports/SimpleList.mrt}" ACTIVE_REPORT_NO="${ACTIVE_REPORT_NO:-$TARGET_ROOT/libs/stimulsoft_legacy/reports/SimpleList_no.mrt}" ACTIVE_JSON="${ACTIVE_JSON:-$TARGET_ROOT/json_data/dataReportTable.json}" ACTIVE_VIEWER="${ACTIVE_VIEWER:-$TARGET_ROOT/libs/stimulsoft_legacy/viewer.php}" ACTIVE_APP="${ACTIVE_APP:-$TARGET_ROOT/libs/stimulsoft_legacy/appCtrl.js}" ACTIVE_HANDLER="${ACTIVE_HANDLER:-$TARGET_ROOT/libs/stimulsoft_legacy/handler.php}" STAMP="$(date +%Y%m%d_%H%M%S)" OUTPUT_ZIP="$PWD/${STEP_NAME}_${STAMP}.zip" WORKDIR="$(mktemp -d "$PWD/.${STEP_NAME}.XXXXXX")" REPORT="$WORKDIR/${STEP_NAME}_REPORT.txt" CAPTURE="$WORKDIR/active_capture" SOURCE_CAPTURE="$WORKDIR/source_capture" INVENTORY="$WORKDIR/active_print_files_inventory.tsv" MRT_ANALYSIS="$WORKDIR/active_mrt_semantic_analysis.txt" DATA_ANALYSIS="$WORKDIR/active_data_json_analysis.txt" EXPECTED_KEYS="$WORKDIR/report_expected_keys.txt" DATA_KEYS="$WORKDIR/data_available_keys.txt" KEY_COMPARE="$WORKDIR/report_vs_data_keys.txt" COPY_COMPARE="$WORKDIR/report_copy_comparison.txt" DB_DUMP="$WORKDIR/print_database_tables.txt" CODE_MATCHES="$WORKDIR/print_generation_code_matches.txt" HTTP_CHECKS="$WORKDIR/active_print_http_checks.txt" LOG_CAPTURE="$WORKDIR/recent_active_print_requests_errors.txt" FINDINGS="$WORKDIR/automatic_findings.txt" cleanup() { rm -rf -- "$WORKDIR" } on_error() { local rc=$? local line="${1:-?}" printf 'ERRORE linea %s, codice %s\n' "$line" "$rc" >&2 exit "$rc" } trap cleanup EXIT INT TERM trap 'on_error "$LINENO"' ERR section() { printf '\n\n================================================================\n%s\n================================================================\n' "$1" >> "$REPORT" } progress() { printf '%s\n' "$1" printf '\n[%s] %s\n' "$(date '+%H:%M:%S')" "$1" >> "$REPORT" } limited() { local seconds="$1" shift if command -v timeout >/dev/null 2>&1; then timeout --signal=TERM --kill-after=5s "${seconds}s" "$@" else "$@" fi } HOST_NOW="$(hostname 2>/dev/null || true)" IPS_NOW="$(hostname -I 2>/dev/null || true)" if [[ "$ALLOW_TEST" != "1" ]]; then if [[ "$HOST_NOW" != "$EXPECTED_HOST" && "$IPS_NOW" != *"$EXPECTED_IP_FRAGMENT"* ]]; then echo "ERRORE: server errato; atteso 215.6 / ax-accessi." >&2 exit 10 fi fi [[ -d "$TARGET_ROOT" ]] || { echo "ERRORE: portale non trovato: $TARGET_ROOT" >&2 exit 11 } for required in "$ACTIVE_REPORT" "$ACTIVE_JSON" "$ACTIVE_VIEWER" "$ACTIVE_APP"; do [[ -f "$required" ]] || { echo "ERRORE: file attivo non trovato: $required" >&2 exit 12 } done PHP_BIN="$(command -v php5.6 || command -v php || true)" [[ -n "$PHP_BIN" ]] || { echo "ERRORE: PHP CLI non disponibile." >&2 exit 13 } mkdir -p "$CAPTURE" "$SOURCE_CAPTURE" for file in \ "$INVENTORY" "$MRT_ANALYSIS" "$DATA_ANALYSIS" \ "$EXPECTED_KEYS" "$DATA_KEYS" "$KEY_COMPARE" \ "$COPY_COMPARE" "$DB_DUMP" "$CODE_MATCHES" \ "$HTTP_CHECKS" "$LOG_CAPTURE" "$FINDINGS"; do : > "$file" done { echo "$STEP_NAME" echo "DATA: $(date --iso-8601=seconds 2>/dev/null || date)" echo "SERVER: $HOST_NOW" echo "IP: $IPS_NOW" echo "PORTALE: $TARGET_ROOT" echo "DATABASE: $TARGET_DB" echo "REPORT ATTIVO: $ACTIVE_REPORT" echo "JSON ATTIVO: $ACTIVE_JSON" echo "MODALITA': AUDIT ONLY" echo "NESSUNA MODIFICA A FILE, DATABASE O SERVIZI" } > "$REPORT" progress "[1/8] Inventario esatto dei file usati dalla stampa" declare -a CANDIDATES=( "$ACTIVE_REPORT" "$ACTIVE_REPORT_NO" "$ACTIVE_JSON" "$ACTIVE_VIEWER" "$ACTIVE_APP" "$ACTIVE_HANDLER" "$TARGET_ROOT/documents/reports/SimpleList.mrt" "$TARGET_ROOT/components/templates/custom_templates/ax_attendance/ax_print_manager/reports/simplelist.mrt" "$BASE_ROOT/libs/stimulsoft_legacy/reports/SimpleList.mrt" "$BASE_ROOT/libs/stimulsoft_legacy/reports/SimpleList_no.mrt" "$BASE_ROOT/documents/reports/SimpleList.mrt" "$BASE_ROOT/components/templates/custom_templates/ax_attendance/ax_print_manager/reports/simplelist.mrt" ) { printf 'exists\tsize\tmtime\tmode\towner\tsha256\tpath\n' for file in "${CANDIDATES[@]}"; do if [[ -f "$file" ]]; then size="$(stat -c '%s' "$file")" mtime="$(stat -c '%y' "$file")" mode="$(stat -c '%a' "$file")" owner="$(stat -c '%U:%G' "$file")" hash="$(sha256sum "$file" | awk '{print $1}')" printf 'YES\t%s\t%s\t%s\t%s\t%s\t%s\n' \ "$size" "$mtime" "$mode" "$owner" "$hash" "$file" else printf 'NO\t-\t-\t-\t-\t-\t%s\n' "$file" fi done } > "$INVENTORY" cat "$INVENTORY" >> "$REPORT" cp -a -- "$ACTIVE_REPORT" "$CAPTURE/SimpleList.active.mrt" cp -a -- "$ACTIVE_JSON" "$CAPTURE/dataReportTable.active.json" cp -a -- "$ACTIVE_VIEWER" "$CAPTURE/viewer.active.php" cp -a -- "$ACTIVE_APP" "$CAPTURE/appCtrl.active.js" [[ -f "$ACTIVE_HANDLER" ]] && cp -a -- "$ACTIVE_HANDLER" "$CAPTURE/handler.active.php" [[ -f "$ACTIVE_REPORT_NO" ]] && cp -a -- "$ACTIVE_REPORT_NO" "$CAPTURE/SimpleList_no.active.mrt" progress "[2/8] Analisi semantica del report MRT realmente caricato" ANALYZER="$WORKDIR/analyze_json.php" cat > "$ANALYZER" <<'PHP' 500) { $text = substr($text, 0, 500) . '...'; } return $text; } return gettype($value); } function isListArray($array) { if (!is_array($array)) { return false; } $expected = 0; foreach ($array as $key => $value) { if ((string) $key !== (string) $expected) { return false; } $expected++; } return true; } function addLine(&$lines, $line, $maxLines) { if (count($lines) < $maxLines) { $lines[] = $line; } } function walkMrt( $value, $path, &$lines, &$keys, $maxLines, $depth ) { if ($depth > 30) { return; } if (is_array($value)) { foreach ($value as $key => $child) { $childPath = $path === '' ? (string) $key : $path . '.' . $key; if ( in_array( (string) $key, array( 'ReportVersion', 'ReportName', 'ReportAlias', 'ReportUnit', 'PageWidth', 'PageHeight', 'Orientation', 'Margins', 'Columns', 'ColumnWidth', 'ColumnGaps', 'Font', 'FontName', 'Name', 'Alias', 'NameInSource', 'DataSourceName', 'Value', 'Expression', 'Text' ), true ) ) { addLine( $lines, 'MRT|' . $childPath . '|' . scalarText($child), $maxLines ); } if (is_string($child)) { if ( preg_match_all( '/\{([^{}]{1,200})\}/', $child, $matches ) ) { foreach ($matches[1] as $expression) { $expression = trim($expression); addLine( $lines, 'EXPRESSION|' . $expression, $maxLines ); $parts = preg_split('/[.]/', $expression); $last = end($parts); if ( preg_match( '/^[A-Za-z_][A-Za-z0-9_]*$/', $last ) ) { $keys[$last] = true; } } } } walkMrt( $child, $childPath, $lines, $keys, $maxLines, $depth + 1 ); } return; } } function collectRowKeys($rows, &$keys) { $count = 0; foreach ($rows as $row) { if (!is_array($row)) { continue; } foreach ($row as $key => $value) { $keys[(string) $key] = true; } $count++; if ($count >= 200) { break; } } } function walkData( $value, $path, &$lines, &$keys, $maxLines, $depth ) { if ($depth > 20) { return; } if (!is_array($value)) { return; } $count = count($value); $kind = isListArray($value) ? 'LIST' : 'OBJECT'; addLine( $lines, 'NODE|' . ($path === '' ? '' : $path) . '|' . $kind . '|count=' . $count, $maxLines ); if ($kind === 'LIST' && $count > 0) { $first = reset($value); if (is_array($first)) { collectRowKeys($value, $keys); addLine( $lines, 'TABLE|' . ($path === '' ? '' : $path) . '|rows=' . $count, $maxLines ); $sampleCount = 0; foreach ($value as $index => $row) { if (!is_array($row)) { continue; } $encoded = json_encode( $row, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES ); if (strlen($encoded) > 1500) { $encoded = substr($encoded, 0, 1500) . '...'; } addLine( $lines, 'SAMPLE|' . $path . '[' . $index . ']|' . $encoded, $maxLines ); $sampleCount++; if ($sampleCount >= 3) { break; } } } } $visited = 0; foreach ($value as $key => $child) { $childPath = $path === '' ? (string) $key : $path . '.' . $key; if (is_array($child)) { walkData( $child, $childPath, $lines, $keys, $maxLines, $depth + 1 ); } $visited++; if ($visited >= 5000) { break; } } } if ($mode === 'mrt') { addLine($lines, 'JSON_STATUS|OK', $maxLines); addLine($lines, 'FILE_SIZE|' . strlen($raw), $maxLines); walkMrt($data, '', $lines, $keys, $maxLines, 0); } elseif ($mode === 'data') { addLine($lines, 'JSON_STATUS|OK', $maxLines); addLine($lines, 'FILE_SIZE|' . strlen($raw), $maxLines); addLine($lines, 'TOP_TYPE|' . gettype($data), $maxLines); walkData($data, '', $lines, $keys, $maxLines, 0); } else { fwrite(STDERR, "Modalita' non valida\n"); exit(5); } ksort($keys, SORT_NATURAL | SORT_FLAG_CASE); file_put_contents($keyFile, implode("\n", array_keys($keys)) . "\n"); echo implode("\n", $lines) . "\n"; PHP "$PHP_BIN" "$ANALYZER" mrt "$ACTIVE_REPORT" "$EXPECTED_KEYS" \ > "$MRT_ANALYSIS" cat "$MRT_ANALYSIS" >> "$REPORT" progress "[3/8] Analisi del JSON dati realmente caricato" "$PHP_BIN" "$ANALYZER" data "$ACTIVE_JSON" "$DATA_KEYS" \ > "$DATA_ANALYSIS" cat "$DATA_ANALYSIS" >> "$REPORT" sort -fu "$EXPECTED_KEYS" -o "$EXPECTED_KEYS" sort -fu "$DATA_KEYS" -o "$DATA_KEYS" { echo "===== CHIAVI RICHIESTE DAL REPORT =====" cat "$EXPECTED_KEYS" echo echo "===== CHIAVI DISPONIBILI NEL JSON =====" cat "$DATA_KEYS" echo echo "===== RICHIESTE MA ASSENTI NEL JSON =====" comm -23 "$EXPECTED_KEYS" "$DATA_KEYS" || true echo echo "===== DISPONIBILI MA NON RICHIAMATE DAL REPORT =====" comm -13 "$EXPECTED_KEYS" "$DATA_KEYS" || true } > "$KEY_COMPARE" cat "$KEY_COMPARE" >> "$REPORT" progress "[4/8] Confronto delle copie SimpleList presenti" { for left in \ "$ACTIVE_REPORT" \ "$ACTIVE_REPORT_NO" \ "$TARGET_ROOT/documents/reports/SimpleList.mrt" \ "$TARGET_ROOT/components/templates/custom_templates/ax_attendance/ax_print_manager/reports/simplelist.mrt" \ "$BASE_ROOT/libs/stimulsoft_legacy/reports/SimpleList.mrt"; do [[ -f "$left" ]] || continue echo echo "===== $left =====" stat -c 'SIZE=%s MTIME=%y MODE=%a OWNER=%U:%G' "$left" sha256sum "$left" TMP_KEYS="$WORKDIR/keys_$(echo "$left" | sha256sum | awk '{print $1}').txt" TMP_ANALYSIS="$WORKDIR/analysis_$(echo "$left" | sha256sum | awk '{print $1}').txt" if "$PHP_BIN" "$ANALYZER" mrt "$left" "$TMP_KEYS" \ > "$TMP_ANALYSIS" 2>&1; then grep -E \ '^(JSON_STATUS|FILE_SIZE|MRT[|].*(ReportVersion|ReportName|ReportAlias|ReportUnit|PageWidth|PageHeight|Orientation|Margins)[|])' \ "$TMP_ANALYSIS" | head -n 500 echo "EXPECTED_KEY_COUNT=$(grep -c . "$TMP_KEYS" 2>/dev/null || true)" else echo "ANALISI_JSON=NON_COMPATIBILE" head -n 20 "$TMP_ANALYSIS" fi done } > "$COPY_COMPARE" cat "$COPY_COMPARE" >> "$REPORT" progress "[5/8] Tabelle database che selezionano report e stampa" if [[ "$SKIP_DB" == "1" ]]; then echo "Controllo DB saltato in ambiente simulato." > "$DB_DUMP" else MYSQL_BIN="$(command -v mariadb || command -v mysql || true)" [[ -n "$MYSQL_BIN" ]] || { echo "ERRORE: client MariaDB/MySQL non disponibile." >&2 exit 20 } { for table in ax_file_report ax_print_user ax_report ax_reports; do echo echo "===== SHOW CREATE TABLE $TARGET_DB.$table =====" "$MYSQL_BIN" --batch --raw -e \ "SHOW CREATE TABLE \`${TARGET_DB}\`.\`${table}\`;" \ 2>&1 || true echo echo "===== COUNT $TARGET_DB.$table =====" "$MYSQL_BIN" --batch --raw -e \ "SELECT COUNT(*) AS row_count FROM \`${TARGET_DB}\`.\`${table}\`;" \ 2>&1 || true echo echo "===== SAMPLE $TARGET_DB.$table =====" "$MYSQL_BIN" --batch --raw -e \ "SELECT * FROM \`${TARGET_DB}\`.\`${table}\` LIMIT 100;" \ 2>&1 || true done } > "$DB_DUMP" fi cat "$DB_DUMP" >> "$REPORT" progress "[6/8] Codice esatto che genera il JSON e seleziona il report" SEARCH_ROOTS=( "$TARGET_ROOT/ax_stampe.php" "$TARGET_ROOT/libs/stimulsoft_legacy" "$TARGET_ROOT/components/templates/custom_templates/ax_attendance/ax_print_manager" "$TARGET_ROOT/components/templates/custom_templates/ax_attendance/ax_schede_presenze_modA" "$TARGET_ROOT/components/a000_angular_app" ) MATCH_PATTERN='dataReportTable[.]json|SimpleList(_no)?[.]mrt|stimulsoft_report_key|ax_file_report|ax_print_user|ax_report|json_data|file_put_contents|json_encode' for root in "${SEARCH_ROOTS[@]}"; do [[ -e "$root" ]] || continue if [[ -f "$root" ]]; then limited 8 grep -nEi "$MATCH_PATTERN" "$root" 2>/dev/null \ | sed "s#^#$root:#" || true else limited 25 grep -RniIE \ --exclude-dir=node_modules \ --exclude-dir=bower_components \ --exclude-dir=vendor \ --exclude-dir=templates_c \ --exclude='*.min.js' \ --exclude='*.map' \ "$MATCH_PATTERN" "$root" 2>/dev/null \ | head -n 6000 || true fi done | sort -u > "$CODE_MATCHES" cat "$CODE_MATCHES" >> "$REPORT" cut -d: -f1 "$CODE_MATCHES" | sort -u | head -n 150 \ > "$WORKDIR/source_file_list.txt" while IFS= read -r file; do [[ -f "$file" ]] || continue [[ "$(stat -c '%s' "$file")" -le 2097152 ]] || continue rel="${file#"$TARGET_ROOT"/}" if [[ "$rel" == "$file" ]]; then rel="external/$(basename "$file")" fi mkdir -p "$SOURCE_CAPTURE/$(dirname "$rel")" cp -a -- "$file" "$SOURCE_CAPTURE/$rel" done < "$WORKDIR/source_file_list.txt" progress "[7/8] Ultime richieste reali e test HTTP dei file attivi" if [[ "$SKIP_LOGS" == "1" ]]; then echo "Log saltati in ambiente simulato." > "$LOG_CAPTURE" else for log in \ /var/log/apache2/presenze.silvelox.axuno.it_access.log \ /var/log/apache2/presenze.silvelox.axuno.it_error.log; do [[ -r "$log" ]] || continue { echo echo "===== $log =====" limited 10 tail -n 60000 "$log" 2>/dev/null \ | grep -Ei \ 'ax_stampe[.]php|libs/stimulsoft_legacy|dataReportTable[.]json|SimpleList(_no)?[.]mrt|stimulsoft_report_key|PHP (Fatal|Warning|Notice)|Uncaught|Exception| 4[0-9][0-9] | 5[0-9][0-9] ' \ | tail -n 5000 || true } >> "$LOG_CAPTURE" done fi if [[ "$SKIP_HTTP" == "1" || "$ALLOW_TEST" == "1" ]]; then echo "Test HTTP saltato in ambiente simulato." > "$HTTP_CHECKS" else URLS=( "/libs/stimulsoft_legacy/viewer.php?stimulsoft_report_key=SimpleList.mrt" "/libs/stimulsoft_legacy/reports/SimpleList.mrt" "/json_data/dataReportTable.json" "/libs/stimulsoft_legacy/scripts/stimulsoft.reports.js" "/libs/stimulsoft_legacy/scripts/stimulsoft.viewer.js" "/libs/stimulsoft_legacy/css/stimulsoft.viewer.office2013.whiteteal.css" ) for url in "${URLS[@]}"; do body="$WORKDIR/http_$(echo "$url" | tr '/?&=:' '_______').body" result="$( curl -sS --max-time 30 \ -H "Host: $TARGET_HOST" \ -o "$body" \ -w '%{http_code}|%{content_type}|%{size_download}|%{time_total}' \ "http://127.0.0.1$url" 2>&1 || true )" printf '%s\t%s\n' "$url" "$result" done > "$HTTP_CHECKS" fi cat "$LOG_CAPTURE" >> "$REPORT" cat "$HTTP_CHECKS" >> "$REPORT" progress "[8/8] Esito automatico e ZIP" MISSING_KEYS="$(comm -23 "$EXPECTED_KEYS" "$DATA_KEYS" | grep -c . || true)" EXPECTED_COUNT="$(grep -c . "$EXPECTED_KEYS" 2>/dev/null || true)" DATA_KEY_COUNT="$(grep -c . "$DATA_KEYS" 2>/dev/null || true)" DATA_SIZE="$(stat -c '%s' "$ACTIVE_JSON")" REPORT_SIZE="$(stat -c '%s' "$ACTIVE_REPORT")" HTTP_BAD="$( grep -Ec $'\t(4|5)[0-9][0-9][|]' "$HTTP_CHECKS" 2>/dev/null || true )" ACTIVE_HASH="$(sha256sum "$ACTIVE_REPORT" | awk '{print $1}')" BASE_ACTIVE="$BASE_ROOT/libs/stimulsoft_legacy/reports/SimpleList.mrt" if [[ -f "$BASE_ACTIVE" ]]; then BASE_HASH="$(sha256sum "$BASE_ACTIVE" | awk '{print $1}')" else BASE_HASH="ASSENTE" fi if [[ "$ACTIVE_HASH" == "$BASE_HASH" ]]; then BASE_RELATION="IDENTICO" else BASE_RELATION="DIVERSO_O_ASSENTE" fi { echo "Report attivo bytes: $REPORT_SIZE" echo "JSON dati bytes: $DATA_SIZE" echo "Chiavi richieste dal report: $EXPECTED_COUNT" echo "Chiavi disponibili nel JSON: $DATA_KEY_COUNT" echo "Chiavi richieste ma assenti: $MISSING_KEYS" echo "Report attivo rispetto alla base: $BASE_RELATION" echo "Test HTTP 4xx/5xx: $HTTP_BAD" echo echo "ESITO: AUDIT COMPLETATO" echo "NESSUNA MODIFICA ESEGUITA" } > "$FINDINGS" cat "$FINDINGS" >> "$REPORT" ( cd "$WORKDIR" zip -q -9 -r "$OUTPUT_ZIP" \ "$(basename "$REPORT")" \ "$(basename "$INVENTORY")" \ "$(basename "$MRT_ANALYSIS")" \ "$(basename "$DATA_ANALYSIS")" \ "$(basename "$EXPECTED_KEYS")" \ "$(basename "$DATA_KEYS")" \ "$(basename "$KEY_COMPARE")" \ "$(basename "$COPY_COMPARE")" \ "$(basename "$DB_DUMP")" \ "$(basename "$CODE_MATCHES")" \ "$(basename "$HTTP_CHECKS")" \ "$(basename "$LOG_CAPTURE")" \ "$(basename "$FINDINGS")" \ active_capture \ source_capture ) zip -T "$OUTPUT_ZIP" >/dev/null echo "STEP047 COMPLETATO" echo "ZIP: $OUTPUT_ZIP"