| 1 | package calendar |
| 2 | |
| 3 | import ( |
| 4 | "errors" |
| 5 | "regexp" |
| 6 | ) |
| 7 | |
| 8 | // addressPattern matches an email address well enough to redact one. |
| 9 | // |
| 10 | // Deliberately loose. A redactor that misses is worse than one that |
| 11 | // over-matches, so this takes anything shaped like local@domain.tld |
| 12 | // rather than trying to implement RFC 5322 — the cost of a false |
| 13 | // positive here is a log line that says [address] where it could have |
| 14 | // said a hostname, and the cost of a false negative is a real person's |
| 15 | // address in platform-held storage we cannot go back and grep. |
| 16 | var addressPattern = regexp.MustCompile(`[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}`) |
| 17 | |
| 18 | // scrub removes email addresses from a string bound for the log. |
| 19 | // |
| 20 | // AGENTS.md §Logs: no title, place, note or address in any log line, |
| 21 | // error string or metric label. `carlos logs` is platform-held storage |
| 22 | // we cannot grep afterwards, so the rule has to be applied BEFORE the |
| 23 | // line is written, not apologised for later. |
| 24 | // |
| 25 | // This exists because the rule was being broken on every failed send |
| 26 | // and not by this app's own code: rastrillo's mailer formats the |
| 27 | // recipient into its error ("send to %s: ..."), and logging that error |
| 28 | // verbatim — which is the obvious, careful-looking thing to do — put |
| 29 | // the address straight into the log. An error from a dependency is |
| 30 | // still an error string we are writing. |
| 31 | func scrub(s string) string { |
| 32 | return addressPattern.ReplaceAllString(s, "[address]") |
| 33 | } |
| 34 | |
| 35 | // scrubErr is scrub for an error, returning a string rather than an |
| 36 | // error so a caller cannot accidentally log the original by wrapping |
| 37 | // the result. |
| 38 | func scrubErr(err error) string { |
| 39 | if err == nil { |
| 40 | return "" |
| 41 | } |
| 42 | return scrub(err.Error()) |
| 43 | } |
| 44 | |
| 45 | // mailCode reduces a sender's error to a word. The sender's TEXT is |
| 46 | // never logged: rastrillo's SMTP sender quotes the whole subject when |
| 47 | // it refuses a header, and a subject carries the title and the booker's |
| 48 | // name. Scrubbing addresses out of that is not enough. |
| 49 | func mailCode(err error) string { |
| 50 | switch { |
| 51 | case err == nil: |
| 52 | return "ok" |
| 53 | case errors.Is(err, ErrMailBusy): |
| 54 | return "busy" |
| 55 | case errors.Is(err, ErrMailTimeout): |
| 56 | return "timeout" |
| 57 | default: |
| 58 | return "refused" |
| 59 | } |
| 60 | } |
| 61 | |