# psl(1) — libpsl 0.21.5, the Public Suffix List library. # # The first block is UPSTREAM'S OWN CONFORMANCE CORPUS, run on target rather # than re-invented. libpsl ships list/tests/tests.txt — publicsuffix.org's # checkPublicSuffix vectors, the same file upstream's # tests/test-registrable-domain.c reads — and this image has no compiler to # build that C driver with. The vectors ride in as data/libpsl-tests.txt and are # driven through psl(1) instead: 94 domain/expected pairs covering plain rules, # wildcards (*.mm, *.ck), exceptions (!www.ck), the two-level and multi-level # TLDs, the US K12 tree, leading dots, mixed case, and the same nine names twice # — once in UTF-8 and once punycoded. # # THEY ARE SPLIT INTO THREE CLASSES so a red is attributable to a mechanism # rather than to "the vector file". `ascii` and `puny` go through psl(1)'s STDIN # path, which normalises with psl_str_to_utf8lower() first — the path a batch # consumer uses. `idn` cannot: with no runtime IDNA library linked (this image's # libpsl is PACKAGECONFIG = "", see libpsl_%.bbappend) that function refuses # non-ASCII input, and psl(1) skips such a line with a diagnostic. The argv path # does no normalisation and hands the name straight to psl_registrable_domain(), # which for the BUILT-IN data needs none: is_public_suffix() short-circuits its # toASCII conversion for the builtin context because the compiled DAFSA already # carries every rule in both UTF-8 and punycode. So the idn class runs through # argv, and the stdin refusal is asserted as its own case rather than left to be # discovered — that asymmetry IS the cost of the dependency-free build, and it # is the only cost. # # The second block loads a list AT RUNTIME (psl_load_file, data/libpsl-mini.psl) # — a different code path from the compiled-in DAFSA, and the one that does # exercise libpsl's bundled punycode encoder: psl_load_fp() punycodes every # UTF-8 rule it reads as it inserts it. See that file's header. # # Expected values are upstream's, or byte-identical to a native x86_64 build of # the identical 0.21.5 source. Non-ASCII names are written with octal escapes so # this file stays 7-bit; the data files carry the real bytes. . "$BST_LIB" require psl psl/present tmp=${TMPDIR:-/tmp}/bst-libpsl.$$ vec=$BST_DATA/libpsl-tests.txt mini=$BST_DATA/libpsl-mini.psl # --- upstream's conformance vectors, by class -------------------------------- # ok iff every vector of the class answers exactly as upstream's file says; the # detail names up to three wrong answers with what was wanted. report_class() { _c=$1 _n=$(wc -l < "$tmp.d.$_c" 2>/dev/null || echo 0) if [ "$_n" -eq 0 ]; then bst_notok "libpsl/vec-$_c" "no vectors of this class parsed out of $vec" return fi if cmp -s "$tmp.o.$_c" "$tmp.e.$_c"; then bst_ok "libpsl/vec-$_c"; return; fi _bad=''; _nbad=0 exec 3< "$tmp.l.$_c" 4< "$tmp.e.$_c" 5< "$tmp.o.$_c" while read -r _d <&3; do read -r _e <&4 || _e='' read -r _a <&5 || _a='' [ "$_e" = "$_a" ] && continue _nbad=$((_nbad + 1)) [ "$_nbad" -le 3 ] && _bad="$_bad $_d=>'$_a'(want '$_e')" done exec 3<&- 4<&- 5<&- bst_notok "libpsl/vec-$_c" "$_nbad of $_n wrong:$(_bst_1line "$_bad")" } if [ ! -f "$vec" ]; then bst_notok libpsl/vectors-present "vector payload missing: $vec" else for c in ascii puny idn; do : > "$tmp.d.$c"; : > "$tmp.e.$c"; done # `//` comment lines and blanks are skipped, as upstream's own reader does. # The one `null null` vector is dropped: a NULL domain is an API case the # CLI has no spelling for, not a name psl(1) can be handed. while read -r d e rest; do case "$d" in ''|//*) continue ;; esac [ -n "$e" ] || continue [ "$d" = null ] && continue case "$d" in *[!!-~]*) c=idn ;; # any byte outside printable ASCII *xn--*) c=puny ;; *) c=ascii ;; esac printf '%s\n' "$d" >> "$tmp.d.$c" if [ "$e" = null ]; then printf '(null)\n' >> "$tmp.e.$c" else printf '%s\n' "$e" >> "$tmp.e.$c"; fi done < "$vec" # ASCII-lowercase every domain, which is exactly what psl_str_to_utf8lower() # does to an ASCII string and what upstream's driver does before each lookup; # `tr` is bytewise, so UTF-8 sequences pass through untouched. for c in ascii puny idn; do tr 'A-Z' 'a-z' < "$tmp.d.$c" > "$tmp.l.$c"; done for c in ascii puny; do psl --use-builtin-data --print-reg-domain -b < "$tmp.l.$c" > "$tmp.o.$c" 2>/dev/null report_class "$c" done set -- $(cat "$tmp.l.idn") psl --use-builtin-data --print-reg-domain -b "$@" > "$tmp.o.idn" 2>/dev/null report_class idn fi # The two paths, on the same name, stated explicitly. `shi shi.com.cn` is one of # upstream's own IDN vectors. idn=$(printf '\351\243\237\347\213\256.com.cn') check_eq libpsl/argv-utf8-resolves "$idn" \ "$(psl --use-builtin-data --print-reg-domain -b "$idn")" check_out libpsl/stdin-utf8-refused 'Failed to convert to lowercase UTF-8' \ sh -c "printf '%s\n' '$idn' | psl --use-builtin-data --print-reg-domain -b 2>&1" # --- the runtime-list path, and the bundled punycode encoder ------------------ if [ ! -f "$mini" ]; then bst_notok libpsl/minilist-present "list payload missing: $mini" else check_eq libpsl/file-plain 'a.mini.test' \ "$(psl --load-psl-file "$mini" --print-reg-domain -b a.mini.test)" check_eq libpsl/file-suffix '(null)' \ "$(psl --load-psl-file "$mini" --print-reg-domain -b mini.test)" # *.wild.test: x.wild.test is itself a suffix, so nothing at that depth is # registrable and the registrable name is one label further left. check_eq libpsl/file-wildcard '(null)' \ "$(psl --load-psl-file "$mini" --print-reg-domain -b x.wild.test)" check_eq libpsl/file-wildcard-reg 'a.x.wild.test' \ "$(psl --load-psl-file "$mini" --print-reg-domain -b a.x.wild.test)" # !keep.wild.test carves that one name back out of the wildcard, so it is # registrable although its siblings are not. check_eq libpsl/file-exception 'keep.wild.test' \ "$(psl --load-psl-file "$mini" --print-reg-domain -b a.keep.wild.test)" check_eq libpsl/file-private 'a.priv.mini.test' \ "$(psl --load-psl-file "$mini" --print-reg-domain -b a.priv.mini.test)" # The list carries the rule as UTF-8 only. Both answers below therefore # depend on psl_load_fp() having punycoded it on the way in, with libpsl's # own in-tree encoder — nothing else in this build can produce xn--fiqs8s. zh=$(printf '\344\270\255\345\233\275.test') check_eq libpsl/file-idn-utf8 '(null)' \ "$(psl --load-psl-file "$mini" --print-reg-domain -b "$zh")" check_eq libpsl/file-idn-punycoded '(null)' \ "$(psl --load-psl-file "$mini" --print-reg-domain -b xn--fiqs8s.test)" check_eq libpsl/file-idn-punycoded-reg 'a.xn--fiqs8s.test' \ "$(psl --load-psl-file "$mini" --print-reg-domain -b a.xn--fiqs8s.test)" fi # --- the built-in data's identity -------------------------------------------- # The sha1 is of list/public_suffix_list.dat as it shipped in the 0.21.5 # tarball, recorded by psl-make-dafsa when it generated the DAFSA during THIS # build. It is the evidence that the compiled-in list is the one upstream # released and that the generator actually ran; a build that lost the list would # still link, and would answer "not a public suffix" to everything. check_out libpsl/info-sha1 \ 'builtin SHA1 file hash: d60ac92ab2057010278034ae502fe2b3d50f8068' psl --print-info check_out libpsl/info-suffixes 'builtin suffixes: 9556' psl --print-info check_out libpsl/info-exceptions 'builtin exceptions: 8' psl --print-info check_out libpsl/info-wildcards 'builtin wildcards: 126' psl --print-info check_out libpsl/info-listfile 'list/public_suffix_list.dat' psl --print-info # The list's own mtime, as it shipped in the tarball: 2024-01-13. Pinning it # means a recipe that ever points --with-psl-file at a fetched list is a red. check_out libpsl/info-filetime 'builtin file time: 1705166163' psl --print-info # No PSL file exists at the generator's path on target, so the builtin can never # be reported stale here — which is also why psl_latest() falls back to it. check_out libpsl/info-outdated 'builtin outdated: 0' psl --print-info # psl_get_version() appends the runtime IDNA library it was configured with, so # this one line is the whole evidence that PACKAGECONFIG = "" reached configure: # an accidental icu or idn2 build says "libpsl 0.21.5" with no parenthesis. check_out libpsl/version 'libpsl 0.21.5 (no IDNA support)' psl --version # --- the rest of the CLI surface, on the built-in data ------------------------ check_eq libpsl/is-public-tld '1' "$(psl --is-public-suffix -b com)" check_eq libpsl/is-public-2level '1' "$(psl --is-public-suffix -b co.uk)" check_eq libpsl/is-public-name '0' "$(psl --is-public-suffix -b example.co.uk)" # The prevailing star rule makes every unlisted TLD a public suffix; --no-star-rule # turns it off, and is the only flag that changes an answer rather than a format. check_eq libpsl/star-rule '1' "$(psl --is-public-suffix -b nosuchtld)" check_eq libpsl/no-star-rule '0' "$(psl --is-public-suffix --no-star-rule -b nosuchtld)" check_eq libpsl/unreg-domain 'co.uk' "$(psl --print-unreg-domain -b www.example.co.uk)" check_eq libpsl/reg-domain 'example.co.uk' "$(psl --print-reg-domain -b www.example.co.uk)" # The default context (psl_latest) resolves to the builtin here, because the # generator's list path does not exist on this image; assert that rather than # assume it. check_eq libpsl/default-is-builtin 'example.co.uk' "$(psl --print-reg-domain -b www.example.co.uk)" # What a cookie jar actually asks: may this host set a cookie for this domain? check_eq libpsl/cookie-ok '1' "$(psl --is-cookie-domain-acceptable example.com -b www.example.com)" check_eq libpsl/cookie-psl '0' "$(psl --is-cookie-domain-acceptable co.uk -b www.example.co.uk)" check_eq libpsl/cookie-tld '0' "$(psl --is-cookie-domain-acceptable com -b www.example.com)" check_out libpsl/help "\`psl' explores the Public Suffix List" psl --help # --- exit-status conventions -------------------------------------------------- check_rc libpsl/rc-ok 0 sh -c "psl --print-reg-domain -b example.com >/dev/null" check_rc libpsl/rc-bad-option 1 sh -c "psl --no-such-option /dev/null" # No usable PSL data is exit 2 and is distinct from a usage error — the one # status a consumer has to tell apart, since it means "I answered nothing". check_rc libpsl/rc-no-data 2 sh -c "psl --load-psl-file /no/such/list --print-reg-domain -b example.com >/dev/null" # --- the -dev / -staticdev halves, and what the port deliberately leaves out -- # There is no C compiler on this image, so the library cannot be linked against # here; what CAN be asserted is that the static-only build produced the archive # and no shared object, and that a consumer finds the header and .pc where it # looks. psl-make-dafsa is the build-host python generator upstream installs # into ${bindir}; the bbappend removes it, because an image with no python has # no use for it — assert the removal so a recipe change that restores it is a # red rather than a surprise on the next image. check libpsl/hdr-installed test -f /usr/include/libpsl.h check libpsl/staticlib test -f /usr/lib/libpsl.a check_rc libpsl/no-shared-object 1 sh -c "ls /usr/lib/libpsl.so* 2>/dev/null | grep -q ." check_rc libpsl/no-dafsa-script 1 test -e /usr/bin/psl-make-dafsa check_out libpsl/pkgconfig-version 'Version: 0.21.5' cat /usr/lib/pkgconfig/libpsl.pc check_out libpsl/pkgconfig-libs '-lpsl' cat /usr/lib/pkgconfig/libpsl.pc rm -f "$tmp".*