#!/usr/bin/env bash # Printing is driverless only, and the page must not be able to become # otherwise by accident. # # The reason is specific. Choosing a PPD or fetching a vendor driver is most of # what the panel this replaces does, and a wrong choice produces a printer that # accepts jobs, reports success, and prints nothing -- the worst failure this # page could ship, because it looks like it worked. So the helper adds printers # that describe their own capabilities over IPP and has no branch that selects # anything else. # # The second rule is the device URI. It is handed to a CUPS backend that runs as # root, so it is validated here rather than trusted from a settings page. # # Read-only: this reads printer state and exercises refusals. It never adds or # removes a real printer. set -uo pipefail repo_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" helper="$repo_dir/config/dot/quickshell/scripts/panama-printers" service="$repo_dir/config/dot/quickshell/services/Printers.qml" page="$repo_dir/config/dot/quickshell/modules/settings/PrintersPage.qml" fail() { printf 'printers contract: %s\n' "$1" >&2 exit 1 } for path in "$helper" "$service" "$page"; do [[ -r "$path" ]] || fail "missing $path" done [[ -x "$helper" ]] || fail 'panama-printers is not executable' # ── Driverless only ───────────────────────────────────────────────────────── grep -q 'DRIVERLESS_MODEL = "everywhere"' "$helper" \ || fail 'the driverless model is not named in one place, so the promise cannot be checked' # Exactly one place may set a model, and it must be that constant. model_uses="$(grep -c 'ppdname=' "$helper")" [[ "$model_uses" == "1" ]] \ || fail "ppdname is set in $model_uses places; driverless printing must have exactly one" grep -q 'ppdname=DRIVERLESS_MODEL' "$helper" \ || fail 'the printer is added with something other than the driverless model' # No PPD file handling at all. grep -qE '\.ppd|ppd-name|getPPDs|ppdFile|installDriver|foomatic' "$helper" \ && fail 'the helper reaches for PPDs or drivers, which this page deliberately does not do' # And the page must not offer a driver choice. Comments are stripped first: # the page explains the no-driver policy in prose, and an earlier version of # this check failed on the explanation rather than on any behaviour. page_code="$(grep -vE '^\s*//' "$page")" grep -qiE 'select.*driver|choose.*driver|ppdName|driverList' <<<"$page_code" \ && fail 'the page offers driver selection' # It must say so, rather than leaving someone guessing why their printer is absent. grep -qi 'driverless' "$page" \ || fail 'the page never explains that only driverless printers are supported' # ── Device URIs are validated, not trusted ────────────────────────────────── grep -q 'SAFE_SCHEMES' "$helper" || fail 'device URIs are not restricted by scheme' for scheme in file pipe; do grep -qE "\"$scheme\"" <<<"$(sed -n '/^SAFE_SCHEMES/,/)/p' "$helper")" \ && fail "the $scheme scheme is allowed, and it does not lead to a printer" done command -v jq >/dev/null 2>&1 || { printf 'printers contract: SKIP (no jq)\n'; exit 0; } refusal() { "$helper" "$@" 2>/dev/null | jq -r '.error // ""'; } # The REASON matters, not merely that something failed. Without validation these # reach CUPS, which refuses them too -- so a test that only checks for "an error" # passes with the validation deleted, and proves nothing about this helper. for bad in "file:///etc/passwd" "pipe:/bin/sh" "ipp://host; rm -rf /" "/etc/passwd" ""; do answer="$(refusal add "$bad" probe)" [[ -n "$answer" ]] || fail "the helper accepted \"$bad\" as a printer address" [[ "$answer" == "That address cannot be used to reach a printer." ]] \ || fail "\"$bad\" was rejected by the printing service rather than by this helper: $answer" done for bad in "../escape" "has space" "a#b" ""; do [[ -n "$(refusal remove "$bad")" ]] \ || fail "the helper accepted \"$bad\" as a printer name" done [[ -n "$(refusal cancel notanumber)" ]] || fail 'the helper accepted a job id that is not a number' [[ -n "$(refusal bogus-command)" ]] || fail 'an unknown command was accepted' # ── The snapshot describes the machine ────────────────────────────────────── snapshot="$("$helper" snapshot 2>/dev/null)" || fail 'snapshot failed' jq -e '(.printers | type == "array") and (.jobs | type == "array") and (.service | type == "object")' \ <<<"$snapshot" >/dev/null || fail 'the snapshot is missing printers, jobs, or service state' jq -e '.service | has("running") and has("startsAtBoot") and has("discoveryAvailable")' \ <<<"$snapshot" >/dev/null || fail 'the service state is incomplete' jq -e '[.printers[] | has("name") and has("state") and has("isDefault")] | all' \ <<<"$snapshot" >/dev/null || fail 'a printer is missing its name, state, or default flag' # At most one default, or the page would show two. [[ "$(jq '[.printers[] | select(.isDefault)] | length' <<<"$snapshot")" -le 1 ]] \ || fail 'more than one printer is reported as the default' # ── Removal is confirmed ──────────────────────────────────────────────────── grep -q 'confirmingRemoval' "$page" \ || fail 'the page removes a printer without a confirmation step' grep -q 'still queued for it is cancelled' "$page" \ || fail 'the page does not say that removing a printer cancels its queued jobs' # ── "On demand" is reported as normal, not as a fault ─────────────────────── # CUPS is socket-activated on this distribution; calling that a problem would # send people to fix something that is not broken. grep -q 'This is a normal configuration' "$page" \ || fail 'a socket-activated printing service is presented as a problem' printf 'printers contract: PASS (%d printer(s), %d job(s), driverless only)\n' \ "$(jq '.printers | length' <<<"$snapshot")" \ "$(jq '.jobs | length' <<<"$snapshot")"