summaryrefslogtreecommitdiff
path: root/test/cli.t
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-19 11:35:21 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-19 11:35:21 +0200
commitb084bba86fe9d394d6a5ea287161864e7695a609 (patch)
tree3cbb56ffec7f5ba5947aa7fd7128372662b0ef97 /test/cli.t
parenta9464100bb02d74c90d585f8334f449b1fefa9d7 (diff)
downloadcolitur-b084bba86fe9d394d6a5ea287161864e7695a609.tar.gz
colitur-b084bba86fe9d394d6a5ea287161864e7695a609.zip
fix(cli): guard publish's IO, validate --dtstamp, and list all commands
publish's own mkdir_p/write_file (unlike every other IO path on this branch) were unguarded: an unwritable --out parent raised a bare Unix.Unix_error(EACCES,...) and an --out naming an existing file raised ENOTDIR, both as uncaught exceptions with a stack trace rather than the project's one-line "colitur: ..." form. The same defect class commit 6bd741b already fixed once for template reads -- --out is user input too. Fixed by wrapping the whole publish_report call (not each write_file site) in one handler for Unix.Unix_error and Sys_error, mirroring why that earlier fix guarded the whole read and not only the open. Added a cram case using a read-only directory inside the test's own cram sandbox, not /tmp, so a failed cleanup cannot leave an unwritable directory behind in a shared location. --dtstamp was the only user string reaching output unescaped and unvalidated: "--dtstamp hello" silently emitted an invalid "DTSTAMP:hello", and a value carrying its own CRLF injected extra lines into every VEVENT. Fixed by rejecting anything not matching RFC 5545's UTC form (8 digits, 'T', 6 digits, 'Z') before either emit or publish does anything else, one line to stderr, exit 2. usage() was byte-unchanged from before the branch and listed only the six pre-existing commands, omitting all four commands this branch added (emit, table, render, publish). Added them; the three cram pins of the exact usage string are updated to match.
Diffstat (limited to 'test/cli.t')
-rw-r--r--test/cli.t43
1 files changed, 40 insertions, 3 deletions
diff --git a/test/cli.t b/test/cli.t
index a4fa696..80c8078 100644
--- a/test/cli.t
+++ b/test/cli.t
@@ -17,7 +17,7 @@ A year outside the supported domain is rejected (exit 2):
No/garbage arguments give a usage error (exit 2):
$ colitur
- colitur: usage: colitur easter <year> | colitur temporal <year> | colitur day <year> | colitur readings <year> | colitur check FILE | colitur new-overlay (try: colitur --help)
+ colitur: usage: colitur easter <year> | colitur temporal <year> | colitur day <year> | colitur readings <year> | colitur emit --format FMT --from Y --to Y | colitur table --year Y --template FILE | colitur render --template FILE --year Y | colitur publish --from Y --to Y --out DIR | colitur check FILE | colitur new-overlay (try: colitur --help)
[2]
The EF temporal cycle for a year, one line per day:
@@ -327,14 +327,14 @@ A flag needing a value, given none:
$ colitur day 2026 --overlay
colitur: --overlay needs a file path
- colitur: usage: colitur easter <year> | colitur temporal <year> | colitur day <year> | colitur readings <year> | colitur check FILE | colitur new-overlay (try: colitur --help)
+ colitur: usage: colitur easter <year> | colitur temporal <year> | colitur day <year> | colitur readings <year> | colitur emit --format FMT --from Y --to Y | colitur table --year Y --template FILE | colitur render --template FILE --year Y | colitur publish --from Y --to Y --out DIR | colitur check FILE | colitur new-overlay (try: colitur --help)
[2]
An unknown option is rejected rather than treated as a positional word:
$ colitur day 2026 --diocese
colitur: unknown option --diocese
- colitur: usage: colitur easter <year> | colitur temporal <year> | colitur day <year> | colitur readings <year> | colitur check FILE | colitur new-overlay (try: colitur --help)
+ colitur: usage: colitur easter <year> | colitur temporal <year> | colitur day <year> | colitur readings <year> | colitur emit --format FMT --from Y --to Y | colitur table --year Y --template FILE | colitur render --template FILE --year Y | colitur publish --from Y --to Y --out DIR | colitur check FILE | colitur new-overlay (try: colitur --help)
[2]
The shipped example overlay is runnable documentation, and it must actually
@@ -464,6 +464,28 @@ An unknown format is a usage error on stderr, exit 2:
colitur: unknown format "yaml" (want csv, json, sexp, xml or ics)
[2]
+--dtstamp is the only user string that reaches ICS output unescaped and
+unvalidated -- it must be exactly RFC 5545's UTC DATE-TIME form (8 digits,
+"T", 6 digits, "Z") or refused outright, rather than either silently
+emitting a malformed DTSTAMP or, worse, letting an embedded CRLF inject
+extra lines into every VEVENT:
+
+ $ colitur emit --format ics --from 2027 --to 2027 --dtstamp hello
+ colitur: --dtstamp "hello" is not RFC 5545 UTC form (want 8 digits, 'T', 6 digits, 'Z', e.g. 20270101T000000Z)
+ [2]
+
+ $ colitur emit --format ics --from 2027 --to 2027 --dtstamp "$(printf 'X\r\nBEGIN:VEVENT\r\nUID:evil')"
+ colitur: --dtstamp "X\r\nBEGIN:VEVENT\r\nUID:evil" is not RFC 5545 UTC form (want 8 digits, 'T', 6 digits, 'Z', e.g. 20270101T000000Z)
+ [2]
+
+A well-formed value is threaded through unchanged. (The events themselves
+end in CRLF per RFC 5545 -- match the substring, not a `$`-anchored full
+line, or a shell that does not mangle the trailing "\r" is doing the
+grep-anchor's job for it by accident.)
+
+ $ colitur emit --format ics --from 2027 --to 2027 --dtstamp 20270101T000000Z | grep -c 'DTSTAMP:20270101T000000Z'
+ 365
+
emit refuses a reversed range rather than emitting nothing:
$ colitur emit --format csv --from 2028 --to 2027
@@ -993,6 +1015,21 @@ publish never deletes a file it does not own:
colitur: publish requires --out DIR
[2]
+publish writes many files across a whole year range (mkdir_p/write_file),
+same as the template read guarded in commit 6bd741b -- --out is user input
+too, and an unwritable parent used to surface as an uncaught
+Unix.Unix_error instead of the project's one-line form. The read-only
+directory below lives in this test's own cram sandbox, not /tmp: a failed
+`rm -rf` of an unwritable directory would otherwise leave it behind in a
+shared location, so it is restored to writable before the test ends either
+way:
+
+ $ mkdir ro-parent && chmod 555 ro-parent
+ $ colitur publish --from 2027 --to 2027 --out ro-parent/sub
+ colitur: mkdir: ro-parent/sub: Permission denied
+ [2]
+ $ chmod 755 ro-parent
+
publish's own flags have no effect on the other commands, and other
commands' flags have no effect on publish -- refused rather than silently
ignored, the same discipline as everywhere else: