Prepare Sandwich Hime v1 release candidate source

This commit is contained in:
2026-08-23 16:25:02 -04:00
parent c11552b87a
commit efd5b997ed
64 changed files with 5276 additions and 156 deletions
+3
View File
@@ -16,4 +16,7 @@ type ComponentFunc func(context.Context, io.Writer) error
`Render` invokes components; generated code uses the context-specific writer helpers and checks `ABI`. Opaque `TrustedHTML`, `TrustedURL`, `TrustedJS`, and `TrustedCSS` values can be constructed only by conspicuous `Trust*` calls in trusted application code.
The exact exported v1 symbol, value, method, field, and signature inventory is
machine-checked in [`testdata/public-api-v1.txt`](testdata/public-api-v1.txt).
The runtime has its own `go.mod`, Apache-2.0 license, `COPYRIGHT` record, semantic version, and `sando/vX.Y.Z` tags. It never imports the AGPL compiler. Importing the runtime does not make an application AGPL under the project's terms. Applications may license their own code and generated files under terms they choose to the extent they hold the necessary rights, while redistribution of the runtime remains subject to Apache-2.0.
+106
View File
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: Apache-2.0
package sando
import (
"fmt"
"go/ast"
"go/importer"
"go/parser"
"go/token"
"go/types"
"os"
"sort"
"strings"
"testing"
)
func TestV1PublicAPIContract(t *testing.T) {
t.Parallel()
entries, err := os.ReadDir(".")
if err != nil {
t.Fatal(err)
}
fileSet := token.NewFileSet()
var files []*ast.File
for _, entry := range entries {
if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".go") || strings.HasSuffix(entry.Name(), "_test.go") {
continue
}
file, err := parser.ParseFile(fileSet, entry.Name(), nil, 0)
if err != nil {
t.Fatalf("parse %s: %v", entry.Name(), err)
}
files = append(files, file)
}
configuration := types.Config{Importer: importer.Default()}
checked, err := configuration.Check("gamertan.com/sandwich-hime/sando", fileSet, files, nil)
if err != nil {
t.Fatal(err)
}
qualifier := func(pkg *types.Package) string {
if pkg == nil || pkg.Path() == checked.Path() {
return ""
}
return pkg.Name()
}
var actual []string
for _, name := range checked.Scope().Names() {
if !token.IsExported(name) {
continue
}
object := checked.Scope().Lookup(name)
switch object := object.(type) {
case *types.Const:
actual = append(actual, fmt.Sprintf("const %s = %s", object.Name(), object.Val().ExactString()))
case *types.TypeName:
named, ok := object.Type().(*types.Named)
if !ok {
actual = append(actual, types.ObjectString(object, qualifier))
break
}
if structure, ok := named.Underlying().(*types.Struct); ok {
var fields []string
for index := 0; index < structure.NumFields(); index++ {
field := structure.Field(index)
if field.Exported() {
fields = append(fields, field.Name()+" "+types.TypeString(field.Type(), qualifier))
}
}
if len(fields) == 0 {
actual = append(actual, "type "+object.Name()+" struct{ /* opaque */ }")
} else {
actual = append(actual, "type "+object.Name()+" struct{"+strings.Join(fields, "; ")+"}")
}
} else {
actual = append(actual, types.ObjectString(object, qualifier))
}
default:
actual = append(actual, types.ObjectString(object, qualifier))
}
typeName, ok := object.(*types.TypeName)
if !ok {
continue
}
named, ok := typeName.Type().(*types.Named)
if !ok {
continue
}
for index := 0; index < named.NumMethods(); index++ {
method := named.Method(index)
if method.Exported() {
actual = append(actual, types.ObjectString(method, qualifier))
}
}
}
sort.Strings(actual)
got := strings.Join(actual, "\n") + "\n"
want, err := os.ReadFile("testdata/public-api-v1.txt")
if err != nil {
t.Fatal(err)
}
want = []byte(strings.TrimPrefix(string(want), "# SPDX-License-Identifier: Apache-2.0\n\n"))
if string(want) != got {
t.Fatalf("v1 public API drifted\n--- committed contract ---\n%s--- observed API ---\n%s", want, got)
}
}
+119
View File
@@ -0,0 +1,119 @@
// SPDX-License-Identifier: Apache-2.0
package sando
import (
"bytes"
"context"
"html/template"
"io"
"testing"
)
type benchmarkView struct {
Title string
URL string
Admin bool
Items []string
}
var (
benchmarkContext = context.Background()
benchmarkData = benchmarkView{
Title: `A typed <view> & its "output"`,
URL: "/projects/sandwich-hime/?from=benchmark&mode=equivalent",
Admin: true,
Items: []string{"compiler", "runtime", "language server", "editor tooling"},
}
benchmarkHTMLTemplate = template.Must(template.New("v1-corpus").Parse(`<article data-title="{{.Title}}"><h1>{{.Title}}</h1>{{if .Admin}}<strong>Admin</strong>{{end}}<ul>{{range .Items}}<li>{{.}}</li>{{end}}</ul><a href="{{.URL}}">Open</a></article>`))
)
func benchmarkSandoComponent(view benchmarkView) Component {
return ComponentFunc(func(_ context.Context, writer io.Writer) error {
if err := WriteString(writer, `<article data-title="`); err != nil {
return err
}
if err := WriteAttr(writer, view.Title); err != nil {
return err
}
if err := WriteString(writer, `"><h1>`); err != nil {
return err
}
if err := WriteText(writer, view.Title); err != nil {
return err
}
if err := WriteString(writer, `</h1>`); err != nil {
return err
}
if view.Admin {
if err := WriteString(writer, `<strong>Admin</strong>`); err != nil {
return err
}
}
if err := WriteString(writer, `<ul>`); err != nil {
return err
}
for _, item := range view.Items {
if err := WriteString(writer, `<li>`); err != nil {
return err
}
if err := WriteText(writer, item); err != nil {
return err
}
if err := WriteString(writer, `</li>`); err != nil {
return err
}
}
if err := WriteString(writer, `</ul><a href="`); err != nil {
return err
}
if err := WriteURL(writer, view.URL); err != nil {
return err
}
return WriteString(writer, `">Open</a></article>`)
})
}
func TestBenchmarkCorpusEquivalent(t *testing.T) {
t.Parallel()
var himeOutput bytes.Buffer
if err := Render(benchmarkContext, &himeOutput, benchmarkSandoComponent(benchmarkData)); err != nil {
t.Fatal(err)
}
var standardOutput bytes.Buffer
if err := benchmarkHTMLTemplate.Execute(&standardOutput, benchmarkData); err != nil {
t.Fatal(err)
}
if himeOutput.String() != standardOutput.String() {
t.Fatalf("benchmark corpus is not output-equivalent\nhtml/template: %q\nSandwich Hime: %q", standardOutput.String(), himeOutput.String())
}
}
func BenchmarkV1CorpusSandwichHime(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(renderedBenchmarkSize(b)))
for b.Loop() {
if err := Render(benchmarkContext, io.Discard, benchmarkSandoComponent(benchmarkData)); err != nil {
b.Fatal(err)
}
}
}
func BenchmarkV1CorpusHTMLTemplate(b *testing.B) {
b.ReportAllocs()
b.SetBytes(int64(renderedBenchmarkSize(b)))
for b.Loop() {
if err := benchmarkHTMLTemplate.Execute(io.Discard, benchmarkData); err != nil {
b.Fatal(err)
}
}
}
func renderedBenchmarkSize(tb testing.TB) int {
tb.Helper()
var output bytes.Buffer
if err := Render(benchmarkContext, &output, benchmarkSandoComponent(benchmarkData)); err != nil {
tb.Fatal(err)
}
return output.Len()
}
+225
View File
@@ -0,0 +1,225 @@
// SPDX-License-Identifier: Apache-2.0
package sando
import (
"bytes"
"errors"
"fmt"
"html/template"
"strings"
"testing"
"unicode/utf8"
)
func TestHTMLTemplateDifferentialCorpus(t *testing.T) {
t.Parallel()
tests := []struct {
name string
source string
prefix string
suffix string
write func(*bytes.Buffer, any) error
values []string
}{
{
name: "HTML text", source: `<p>{{.}}</p>`, prefix: `<p>`, suffix: `</p>`,
write: func(output *bytes.Buffer, value any) error { return WriteText(output, value) },
values: differentialTextValues(),
},
{
name: "quoted attribute", source: `<p title="{{.}}">x</p>`, prefix: `<p title="`, suffix: `">x</p>`,
write: func(output *bytes.Buffer, value any) error { return WriteAttr(output, value) },
values: differentialTextValues(),
},
{
name: "RCDATA", source: `<textarea>{{.}}</textarea>`, prefix: `<textarea>`, suffix: `</textarea>`,
write: func(output *bytes.Buffer, value any) error { return WriteRCDATA(output, value) },
values: differentialTextValues(),
},
{
name: "safe URL", source: `<a href="{{.}}">x</a>`, prefix: `<a href="`, suffix: `">x</a>`,
write: func(output *bytes.Buffer, value any) error { return WriteURL(output, value) },
values: []string{"", "/", "./relative", "?q=a&next=b", "#section", "https://example.test/a?x=1&y=2", "HTTP://example.test/", "mailto:reader@example.test"},
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
parsed := template.Must(template.New(test.name).Parse(test.source))
for _, value := range test.values {
var baseline bytes.Buffer
if err := parsed.Execute(&baseline, value); err != nil {
t.Fatalf("html/template value %q: %v", value, err)
}
var output bytes.Buffer
output.WriteString(test.prefix)
if err := test.write(&output, value); err != nil {
t.Fatalf("Sandwich Hime value %q: %v", value, err)
}
output.WriteString(test.suffix)
if output.String() != baseline.String() {
t.Fatalf("differential mismatch for %q\nhtml/template: %q\nSandwich Hime: %q", value, baseline.String(), output.String())
}
}
})
}
}
func TestHTMLTemplateDifferentialUnsafeURLPolicy(t *testing.T) {
t.Parallel()
parsed := template.Must(template.New("url").Parse(`<a href="{{.}}">x</a>`))
values := []string{
"javascript:alert(1)",
" JAVASCRIPT:alert(1) ",
"data:text/html,<script>alert(1)</script>",
"vbscript:msgbox(1)",
"unknown:opaque",
"java%73cript:alert(1)",
}
for _, value := range values {
var baseline bytes.Buffer
if err := parsed.Execute(&baseline, value); err != nil {
t.Fatalf("html/template value %q: %v", value, err)
}
if !strings.Contains(baseline.String(), "#ZgotmplZ") {
t.Fatalf("html/template did not block corpus URL %q: %q", value, baseline.String())
}
var output bytes.Buffer
err := WriteURL(&output, value)
if !errors.Is(err, ErrUnsafeURL) {
t.Fatalf("Sandwich Hime accepted corpus URL %q: output=%q err=%v", value, output.String(), err)
}
if output.Len() != 0 {
t.Fatalf("Sandwich Hime wrote bytes before rejecting %q: %q", value, output.String())
}
}
}
func TestHTMLTemplateDifferentialDocumentedStrictness(t *testing.T) {
t.Parallel()
t.Run("invalid UTF-8", func(t *testing.T) {
value := "invalid UTF-8: \xff:end"
parsed := template.Must(template.New("text").Parse(`<p>{{.}}</p>`))
var baseline bytes.Buffer
if err := parsed.Execute(&baseline, value); err != nil {
t.Fatal(err)
}
if utf8.Valid(baseline.Bytes()) {
t.Fatalf("baseline unexpectedly normalized invalid UTF-8: %q", baseline.Bytes())
}
var output bytes.Buffer
if err := WriteText(&output, value); err != nil {
t.Fatal(err)
}
if !utf8.Valid(output.Bytes()) || !strings.Contains(output.String(), "\uFFFD") {
t.Fatalf("Sandwich Hime did not normalize invalid UTF-8: %q", output.Bytes())
}
})
t.Run("control in otherwise allowed URL", func(t *testing.T) {
value := "https:\n//example.test/"
parsed := template.Must(template.New("url").Parse(`<a href="{{.}}">x</a>`))
var baseline bytes.Buffer
if err := parsed.Execute(&baseline, value); err != nil {
t.Fatal(err)
}
if !strings.Contains(strings.ToLower(baseline.String()), "%0a") {
t.Fatalf("baseline did not visibly encode the control: %q", baseline.String())
}
var output bytes.Buffer
if err := WriteURL(&output, value); !errors.Is(err, ErrUnsafeURL) {
t.Fatalf("Sandwich Hime did not fail closed: output=%q err=%v", output.String(), err)
}
if output.Len() != 0 {
t.Fatalf("Sandwich Hime wrote before rejecting the control: %q", output.String())
}
})
t.Run("explicit tel allowlist", func(t *testing.T) {
value := "tel:+15555550100"
parsed := template.Must(template.New("url").Parse(`<a href="{{.}}">x</a>`))
var baseline bytes.Buffer
if err := parsed.Execute(&baseline, value); err != nil {
t.Fatal(err)
}
if !strings.Contains(baseline.String(), "#ZgotmplZ") {
t.Fatalf("baseline URL policy changed: %q", baseline.String())
}
var output bytes.Buffer
if err := WriteURL(&output, value); err != nil {
t.Fatalf("Sandwich Hime rejected its documented tel scheme: %v", err)
}
if output.String() != value {
t.Fatalf("Sandwich Hime tel output = %q", output.String())
}
})
}
func TestHTMLTemplateDifferentialExplicitTrustedHTML(t *testing.T) {
t.Parallel()
value := `<strong data-note="reviewed & trusted">ok</strong>`
parsed := template.Must(template.New("trusted HTML").Parse(`<div>{{.}}</div>`))
var baseline bytes.Buffer
if err := parsed.Execute(&baseline, template.HTML(value)); err != nil { // #nosec G203 -- the test is the explicit trust-boundary comparison.
t.Fatal(err)
}
var output bytes.Buffer
output.WriteString("<div>")
if err := WriteText(&output, TrustHTML(value)); err != nil {
t.Fatal(err)
}
output.WriteString("</div>")
if output.String() != baseline.String() {
t.Fatalf("trusted HTML mismatch\nhtml/template: %q\nSandwich Hime: %q", baseline.String(), output.String())
}
}
func differentialTextValues() []string {
return []string{
"",
"ordinary text",
`<script>alert("x")</script>`,
`quotes: "double" and 'single' & ampersand`,
"Unicode: 雪 🥪 e\u0301",
"NUL:\x00:end",
"line separators: \u2028\u2029",
}
}
func FuzzWriteURLPolicy(f *testing.F) {
for _, seed := range []string{
"",
"/relative?one=1&two=2",
"https://example.test/path",
" JAVASCRIPT:alert(1) ",
"https:\n//example.test/",
"tel:+15555550100",
"invalid:\xff",
} {
f.Add(seed)
}
f.Fuzz(func(t *testing.T, value string) {
if len(value) > 64<<10 {
t.Skip()
}
var first, second bytes.Buffer
firstErr := WriteURL(&first, value)
secondErr := WriteURL(&second, value)
if first.String() != second.String() || fmt.Sprint(firstErr) != fmt.Sprint(secondErr) {
t.Fatal("URL policy was not deterministic")
}
if firstErr != nil {
if !errors.Is(firstErr, ErrUnsafeURL) || first.Len() != 0 {
t.Fatalf("URL rejection was not fail-closed: output=%q err=%v", first.String(), firstErr)
}
return
}
if !utf8.ValidString(first.String()) || strings.ContainsAny(first.String(), "\x00\r\n") {
t.Fatalf("accepted URL output is not valid single-line UTF-8: %q", first.String())
}
})
}
+32
View File
@@ -0,0 +1,32 @@
# SPDX-License-Identifier: Apache-2.0
const ABI = "sando.v1"
const ABISandoV1 = "sando.v1"
const RuntimeABI = "sando.v1"
func (*URLSafetyError).Error() string
func (*URLSafetyError).Unwrap() error
func (ComponentFunc).Render(ctx context.Context, w io.Writer) error
func Render(ctx context.Context, w io.Writer, component Component) error
func TrustCSS(value string) TrustedCSS
func TrustHTML(value string) TrustedHTML
func TrustJS(value string) TrustedJS
func TrustURL(value string) TrustedURL
func WriteAttr(w io.Writer, value any) error
func WriteCSS(w io.Writer, value TrustedCSS) error
func WriteHTML(w io.Writer, value TrustedHTML) error
func WriteJS(w io.Writer, value TrustedJS) error
func WriteRCDATA(w io.Writer, value any) error
func WriteString(w io.Writer, value string) error
func WriteText(w io.Writer, value any) error
func WriteURL(w io.Writer, value any) error
type Component interface{Render(context.Context, io.Writer) error}
type ComponentFunc func(context.Context, io.Writer) error
type TrustedCSS struct{ /* opaque */ }
type TrustedHTML struct{ /* opaque */ }
type TrustedJS struct{ /* opaque */ }
type TrustedURL struct{ /* opaque */ }
type URLSafetyError struct{Scheme string; Reason string}
var ErrNilComponent error
var ErrNilContext error
var ErrNilWriter error
var ErrUnsafeURL error