| 1 | #!/usr/bin/env bash |
| 2 | # Fetch per-defect details from Coverity Scan for each CID in a table.json dump. |
| 3 | # |
| 4 | # Usage: |
| 5 | # fetch-details.sh <input-defects.json> <output-dir> |
| 6 | # |
| 7 | # Reads cookies/XSRF/UA from .env via _lib.sh. |
| 8 | # For each row, calls /sourcebrowser/defectdetails.json?defectInstanceId=<lastDefectInstanceId>. |
| 9 | # Skips rows whose output file already exists (idempotent; safe to re-run). |
| 10 | |
| 11 | set -euo pipefail |
| 12 | |
| 13 | # shellcheck source=./_lib.sh |
| 14 | # shellcheck disable=SC1091 |
| 15 | source "$(dirname "$0")/_lib.sh" |
| 16 | cov_load_env |
| 17 | |
| 18 | INPUT="${1:?usage: $0 <input-defects.json> <output-dir>}" |
| 19 | OUT_DIR="${2:?usage: $0 <input-defects.json> <output-dir>}" |
| 20 | |
| 21 | if [[ ! -f "${INPUT}" || ! -r "${INPUT}" ]]; then |
| 22 | echo -e "${COV_RED}[ERROR]${COV_NC} input file not a readable regular file: '${INPUT}'" >&2 |
| 23 | exit 1 |
| 24 | fi |
| 25 | |
| 26 | mkdir -p "${OUT_DIR}" |
| 27 | |
| 28 | TOTAL="$(jq 'length' "${INPUT}")" |
| 29 | echo -e "${COV_GRAY}Fetching details for ${TOTAL} defects into ${OUT_DIR}${COV_NC}" >&2 |
| 30 | |
| 31 | i=0 |
| 32 | fetched=0 |
| 33 | skipped=0 |
| 34 | failed=0 |
| 35 | |
| 36 | while IFS=$'\t' read -r cid defect_instance_id; do |
| 37 | i=$((i + 1)) |
| 38 | # cid comes from the input JSON (Coverity-supplied) and lands in the |
| 39 | # output filename. Validate numeric before path construction so a |
| 40 | # corrupted/manipulated INPUT cannot write outside OUT_DIR. |
| 41 | if [[ ! "${cid}" =~ ^[1-9][0-9]*$ ]]; then |
| 42 | failed=$((failed + 1)) |
| 43 | echo -e "${COV_RED}[${i}/${TOTAL}] non-numeric cid in input: '${cid}' -- skipping${COV_NC}" >&2 |
| 44 | continue |
| 45 | fi |
| 46 | if [[ ! "${defect_instance_id}" =~ ^[1-9][0-9]*$ ]]; then |
| 47 | failed=$((failed + 1)) |
| 48 | echo -e "${COV_RED}[${i}/${TOTAL}] cid=${cid} non-numeric defectInstanceId: '${defect_instance_id}' -- skipping${COV_NC}" >&2 |
| 49 | continue |
| 50 | fi |
| 51 | out_file="${OUT_DIR}/cid-${cid}.json" |
| 52 | if [[ -s "${out_file}" ]]; then |
| 53 | skipped=$((skipped + 1)) |
| 54 | continue |
| 55 | fi |
| 56 | sleep 0.3 |
| 57 | |
| 58 | http_code="$(curl -sS --max-time 30 \ |
| 59 | -w '%{http_code}' \ |
| 60 | -o "${out_file}" \ |
| 61 | -H "accept: application/json, text/plain, */*" \ |
| 62 | -H "referer: ${COVERITY_HOST}/" \ |
| 63 | -H "user-agent: ${COVERITY_USER_AGENT}" \ |
| 64 | -H "x-xsrf-token: ${COVERITY_XSRF}" \ |
| 65 | -b "${COVERITY_COOKIE}" \ |
| 66 | "${COVERITY_HOST}/sourcebrowser/defectdetails.json?projectId=${COVERITY_PROJECT_ID}&defectInstanceId=${defect_instance_id}" || echo "000")" |
| 67 | |
| 68 | if [[ "${http_code}" != "200" ]]; then |
| 69 | failed=$((failed + 1)) |
| 70 | echo -e "${COV_RED}[${i}/${TOTAL}] cid=${cid} diid=${defect_instance_id} HTTP=${http_code}${COV_NC}" >&2 |
| 71 | if [[ "${http_code}" == "401" || "${http_code}" == "403" ]]; then |
| 72 | echo -e "${COV_RED}Session likely expired. Recapture cookie from browser, update .env, retry.${COV_NC}" >&2 |
| 73 | rm -f "${out_file}" |
| 74 | exit 2 |
| 75 | fi |
| 76 | rm -f "${out_file}" |
| 77 | continue |
| 78 | fi |
| 79 | |
| 80 | fetched=$((fetched + 1)) |
| 81 | if (( i % 20 == 0 )); then |
| 82 | echo -e "${COV_GRAY}[${i}/${TOTAL}] fetched=${fetched} skipped=${skipped} failed=${failed}${COV_NC}" >&2 |
| 83 | fi |
| 84 | done < <(jq -r '.[] | "\(.cid)\t\(.lastDefectInstanceId)"' "${INPUT}") |
| 85 | |
| 86 | echo -e "${COV_GREEN}Done. fetched=${fetched} skipped=${skipped} failed=${failed} total=${TOTAL}${COV_NC}" >&2 |