package runtime import ( "encoding/json" "errors" "strings" "unicode/utf8" ) func ScriptContentInsideStringLiteral[T any](v T, errs ...error) (string, error) { return scriptContent(v, true, errs...) } func ScriptContentOutsideStringLiteral[T any](v T, errs ...error) (string, error) { return scriptContent(v, false, errs...) } func scriptContent[T any](v T, insideStringLiteral bool, errs ...error) (string, error) { if errors.Join(errs...) != nil { return "", errors.Join(errs...) } if vs, ok := any(v).(string); ok && insideStringLiteral { return replace(vs, jsStrReplacementTable), nil } jd, err := json.Marshal(v) if err != nil { return "", err } if insideStringLiteral { return replace(string(jd), jsStrReplacementTable), nil } return string(jd), nil } // See https://cs.opensource.google/go/go/+/refs/tags/go1.23.6:src/html/template/js.go // replace replaces each rune r of s with replacementTable[r], provided that // r < len(replacementTable). If replacementTable[r] is the empty string then // no replacement is made. // It also replaces runes U+2028 and U+2029 with the raw strings `\u2028` and // `\u2029`. func replace(s string, replacementTable []string) string { var b strings.Builder r, w, written := rune(0), 0, 0 for i := 0; i < len(s); i += w { // See comment in htmlEscaper. r, w = utf8.DecodeRuneInString(s[i:]) var repl string switch { case int(r) < len(lowUnicodeReplacementTable): repl = lowUnicodeReplacementTable[r] case int(r) < len(replacementTable) && replacementTable[r] != "": repl = replacementTable[r] case r == '\u2028': repl = `\u2028` case r == '\u2029': repl = `\u2029` default: continue } if written == 0 { b.Grow(len(s)) } b.WriteString(s[written:i]) b.WriteString(repl) written = i + w } if written == 0 { return s } b.WriteString(s[written:]) return b.String() } var lowUnicodeReplacementTable = []string{ 0: `\u0000`, 1: `\u0001`, 2: `\u0002`, 3: `\u0003`, 4: `\u0004`, 5: `\u0005`, 6: `\u0006`, '\a': `\u0007`, '\b': `\u0008`, '\t': `\t`, '\n': `\n`, '\v': `\u000b`, // "\v" == "v" on IE 6. '\f': `\f`, '\r': `\r`, 0xe: `\u000e`, 0xf: `\u000f`, 0x10: `\u0010`, 0x11: `\u0011`, 0x12: `\u0012`, 0x13: `\u0013`, 0x14: `\u0014`, 0x15: `\u0015`, 0x16: `\u0016`, 0x17: `\u0017`, 0x18: `\u0018`, 0x19: `\u0019`, 0x1a: `\u001a`, 0x1b: `\u001b`, 0x1c: `\u001c`, 0x1d: `\u001d`, 0x1e: `\u001e`, 0x1f: `\u001f`, } var jsStrReplacementTable = []string{ 0: `\u0000`, '\t': `\t`, '\n': `\n`, '\v': `\u000b`, // "\v" == "v" on IE 6. '\f': `\f`, '\r': `\r`, // Encode HTML specials as hex so the output can be embedded // in HTML attributes without further encoding. '"': `\u0022`, '`': `\u0060`, '&': `\u0026`, '\'': `\u0027`, '+': `\u002b`, '/': `\/`, '<': `\u003c`, '>': `\u003e`, '\\': `\\`, }