paivana

HTTP paywall reverse proxy
Log | Files | Refs | Submodules | README | LICENSE

test_base64url.sh (3792B)


      1 #!/bin/sh
      2 # Check that the paywall's base64url encoder agrees with the daemon's.
      3 #
      4 # The paivana ID is "<expiration>-<base64url(sha256(...))>".  The daemon
      5 # builds it with GNUNET_STRINGS_base64url_encode(); the browser rebuilds
      6 # it in paywall.js to recognise the payment it just made.  If the two
      7 # spell base64url differently the ID never matches, the payment appears
      8 # not to go through, and neither side reports anything wrong -- so this
      9 # compares them directly, on vectors the daemon itself generates.
     10 #
     11 # It is worth having because the two have already diverged twice: once
     12 # on the decode side (the daemon emits the RFC 4648 section 5 alphabet,
     13 # atob() only understands section 4) and once on the encode side (the
     14 # browser used Uint8Array.prototype.toBase64, which older engines do not
     15 # have).  Both broke the payment path.
     16 #
     17 # Exits 77 ("skipped") when there is no node to run the JavaScript in.
     18 #
     19 # Environment:
     20 #   SRCDIR    directory holding paywall.js (default: this script's ../frontend)
     21 #   BUILDDIR  directory holding base64url_vectors (default: $PWD)
     22 
     23 set -eu
     24 
     25 srcdir=${SRCDIR:-$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)}
     26 builddir=${BUILDDIR:-$PWD}
     27 frontend="$srcdir/../frontend"
     28 
     29 if ! command -v node >/dev/null 2>&1; then
     30   echo "no node found, skipping the base64url cross-check" >&2
     31   exit 77
     32 fi
     33 if [ ! -x "$builddir/base64url_vectors" ]; then
     34   echo "base64url_vectors not built at $builddir" >&2
     35   exit 1
     36 fi
     37 if [ ! -f "$frontend/paywall.js" ]; then
     38   echo "paywall.js not found at $frontend" >&2
     39   exit 1
     40 fi
     41 
     42 "$builddir/base64url_vectors" > "$builddir/base64url_vectors.txt"
     43 
     44 PAYWALL_JS="$frontend/paywall.js" \
     45 VECTORS="$builddir/base64url_vectors.txt" \
     46 node -e '
     47 const fs = require("fs");
     48 
     49 // Lift base64url() out of paywall.js rather than reimplementing it:
     50 // a copy here would be a second implementation to keep in step, and
     51 // this test exists precisely because keeping implementations in step
     52 // by hand is what failed.  The module cannot simply be imported --
     53 // it reads window.location at load -- so the one function is cut out
     54 // by matching braces from its declaration.
     55 const src = fs.readFileSync(process.env.PAYWALL_JS, "utf8");
     56 const start = src.indexOf("function base64url(");
     57 if (start < 0) {
     58   console.error("could not find base64url() in paywall.js;" +
     59                 " if it was renamed, update this test rather than delete it");
     60   process.exit(1);
     61 }
     62 let depth = 0, end = -1, seen = false;
     63 for (let i = src.indexOf("{", start); i < src.length; i++) {
     64   if (src[i] === "{") { depth++; seen = true; }
     65   else if (src[i] === "}") { depth--; }
     66   if (seen && depth === 0) { end = i + 1; break; }
     67 }
     68 if (end < 0) {
     69   console.error("could not delimit base64url() in paywall.js");
     70   process.exit(1);
     71 }
     72 const base64url = new Function(
     73   `${src.slice(start, end)}; return base64url;`)();
     74 
     75 const lines = fs.readFileSync(process.env.VECTORS, "utf8")
     76       .split("\n").filter((l) => l.length > 0);
     77 if (lines.length < 300) {
     78   console.error(`only ${lines.length} vectors; expected the generator` +
     79                 " to produce hundreds -- refusing to pass vacuously");
     80   process.exit(1);
     81 }
     82 let bad = 0;
     83 for (const line of lines) {
     84   const sp = line.indexOf(" ");
     85   const hex = line.slice(0, sp);
     86   const want = line.slice(sp + 1);
     87   const bytes = hex === "-"
     88         ? new Uint8Array(0)
     89         : Uint8Array.from(hex.match(/../g).map((h) => parseInt(h, 16)));
     90   const got = base64url(bytes);
     91   if (got !== want) {
     92     if (bad < 5) {
     93       console.error(`MISMATCH for ${hex}: paywall.js gave "${got}",` +
     94                     ` the daemon gives "${want}"`);
     95     }
     96     bad++;
     97   }
     98 }
     99 if (bad > 0) {
    100   console.error(`${bad} of ${lines.length} vectors disagree`);
    101   process.exit(1);
    102 }
    103 console.log(`base64url agrees with the daemon on ${lines.length} vectors`);
    104 '