|
| 1 | +/** |
| 2 | + * Percent-encode every character outside printable ASCII so a tag value can be |
| 3 | + * safely serialized as part of the `x-next-cache-tags` HTTP header. |
| 4 | + * |
| 5 | + * Node's `validateHeaderValue` rejects any code unit outside `\t\x20-\x7e`, so |
| 6 | + * a matched route path or user-supplied tag containing a non-ASCII character |
| 7 | + * (Hebrew, Arabic, Chinese, emoji, …) would otherwise throw `ERR_INVALID_CHAR` |
| 8 | + * and crash ISR on every affected request. |
| 9 | + * |
| 10 | + * This is applied at the public boundaries — tag construction |
| 11 | + * (`getImplicitTags`, `validateTags`) and invalidation input (`revalidatePath`, |
| 12 | + * `revalidateTag`, `updateTag`) — so storage, comparison, and the wire all see |
| 13 | + * the same canonical ASCII-safe form. |
| 14 | + * |
| 15 | + * The character class `[\t\x20-\x7e]` mirrors Node's `validHdrChars` table — |
| 16 | + * `\t` plus printable ASCII through `~`. Anything outside that is rejected |
| 17 | + * by `validateHeaderValue`, so we encode runs of those characters and leave |
| 18 | + * everything else (`,`, `/`, `%`, `[`, `]`, `_`, `-`, `\t`, …) byte-for-byte |
| 19 | + * unchanged. This preserves the comma-separated header format and the |
| 20 | + * dynamic-segment markers in derived tags (`_N_T_/[slug]/page`). |
| 21 | + * |
| 22 | + * Properties: |
| 23 | + * - Fast-path: input that already fits the validation class is returned |
| 24 | + * unchanged. This makes the encoder idempotent on already-encoded `%xx` |
| 25 | + * sequences. |
| 26 | + * - Matches *runs* of out-of-class code units so surrogate pairs (e.g. an |
| 27 | + * emoji) are handed to `encodeURIComponent` as a complete code point — a |
| 28 | + * per-code-unit regex would split the pair and throw `URIError`. |
| 29 | + */ |
| 30 | +const OUT_OF_CLASS_CHAR = /[^\t\x20-\x7e]/ |
| 31 | +const OUT_OF_CLASS_RUN = /[^\t\x20-\x7e]+/g |
| 32 | + |
| 33 | +export function encodeCacheTag(tag: string): string { |
| 34 | + return OUT_OF_CLASS_CHAR.test(tag) |
| 35 | + ? tag.replace(OUT_OF_CLASS_RUN, (run) => encodeURIComponent(run)) |
| 36 | + : tag |
| 37 | +} |
0 commit comments