diff options
| -rwxr-xr-x | mail-overview/mail-notify.sh | 30 | ||||
| -rwxr-xr-x | mail-overview/test-mail-notify.sh | 33 |
2 files changed, 63 insertions, 0 deletions
diff --git a/mail-overview/mail-notify.sh b/mail-overview/mail-notify.sh index 1cadce4..ed0c3e3 100755 --- a/mail-overview/mail-notify.sh +++ b/mail-overview/mail-notify.sh @@ -80,6 +80,36 @@ parse_accounts() { return 0 } +# Renders notmuch search JSON into notification body text. +# $1 the JSON array from `notmuch search --format=json` +# $2 the true total for this batch, which may exceed the rows present +# +# dunst has body-markup in its capabilities, so a subject containing < or & +# would be parsed as markup and could vanish from the notification. Subjects +# are attacker-controlled text arriving from the internet, so the three XML +# characters are escaped here. This is the one place in this script where +# untrusted text reaches a renderer. +# +# Malformed JSON prints nothing and succeeds. A notification with no body is +# still worth sending: the summary already carries the account and the count. +build_body() { + local json="$1" total="$2" shown + + local body + body="$(printf '%s' "$json" | jq -r ' + .[] | ((.authors // "(unknown)") + " — " + (.subject // "(no subject)")) + | gsub("&"; "&") | gsub("<"; "<") | gsub(">"; ">") + ' 2>/dev/null)" || return 0 + [[ -n "$body" ]] || return 0 + + shown="$(printf '%s\n' "$body" | wc -l)" + printf '%s' "$body" + if [[ "$total" -gt "$shown" ]]; then + printf '\n+%d more' "$((total - shown))" + fi + printf '\n' +} + main() { echo "not implemented" } diff --git a/mail-overview/test-mail-notify.sh b/mail-overview/test-mail-notify.sh index 8be456c..fccf4b3 100755 --- a/mail-overview/test-mail-notify.sh +++ b/mail-overview/test-mail-notify.sh @@ -78,5 +78,38 @@ check "a non-account section is not an account" \ "" \ "$(parse_accounts <<<"$fixture" | awk -F'\t' '$1=="ui"{print $2}')" +# The shape notmuch search --format=json actually returns, trimmed to the two +# fields the body uses. +rows_json='[ + {"authors":"Alice Example","subject":"First subject"}, + {"authors":"Bob Example","subject":"Second subject"}, + {"authors":"Carol Example","subject":"Third subject"} +]' + +check "a body lists author and subject per row" \ + "Alice Example — First subject +Bob Example — Second subject +Carol Example — Third subject" \ + "$(build_body "$rows_json" 3)" + +check "a batch bigger than the rows shown is elided" \ + "Alice Example — First subject +Bob Example — Second subject +Carol Example — Third subject ++7 more" \ + "$(build_body "$rows_json" 10)" + +check "markup characters are escaped, not rendered" \ + "A & B — <script>" \ + "$(build_body '[{"authors":"A & B","subject":"<script>"}]' 1)" + +check "a missing subject says so rather than printing nothing" \ + "Alice Example — (no subject)" \ + "$(build_body '[{"authors":"Alice Example","subject":null}]' 1)" + +check "malformed json yields an empty body rather than an error" \ + "" \ + "$(build_body 'not json at all' 1 2>/dev/null)" + printf '\n%d passed, %d failed\n' "$pass" "$fail" [[ "$fail" -eq 0 ]] |
