Skip to content
quarry Artifact [0a00dcf80d]
Login

Artifact [0a00dcf80d]

Artifact 0a00dcf80db5c27a713e24a72594e9c560bd893584161a6b740ef87161334207:


package markup

import (
	"strings"
	"testing"
)

// The crash: Outline lowercased the whole document into a second string
// and indexed it with offsets computed on the first. strings.ToLower
// does not preserve length — "Ⱥ" is two bytes and lowercases to three —
// so after one such character every offset addressed a different byte
// in each string, and the slice ran off the end. Every reader of that
// page got a 500. Found on pkgsrc's own documentation.
func TestADocumentThatChangesLengthWhenLoweredDoesNotPanic(t *testing.T) {
	for _, tc := range []struct{ name, char string }{
		{"a character that grows when lowered", "Ⱥ"},
		{"a character that shrinks when lowered", "İ"},
		{"the kelvin sign", "K"},
	} {
		// Enough of them to push a later heading well past the drift.
		doc := "<p>" + strings.Repeat(tc.char, 400) + "</p>\n<h2>After</h2>\n<p>body</p>"
		func() {
			defer func() {
				if p := recover(); p != nil {
					t.Errorf("FAIL: %s: %v", tc.name, p)
				}
			}()
			html, heads := Outline([]byte(doc))
			if len(heads) != 1 || heads[0].Text != "After" {
				t.Errorf("FAIL: %s: headings = %+v", tc.name, heads)
				return
			}
			if !strings.Contains(string(html), tc.char) {
				t.Errorf("FAIL: %s: the document's own characters did not survive", tc.name)
				return
			}
			t.Logf("ok: %s", tc.name)
		}()
	}
}

// The lowering used for searching must never change the length, which
// is the property the offsets depend on.
func TestAsciiLowerPreservesLength(t *testing.T) {
	for _, s := range []string{"", "abc", "ABC", "<H2>Ⱥ</H2>", "İstanbul", "K", "\xff\xfe"} {
		if got := asciiLower(s); len(got) != len(s) {
			t.Errorf("FAIL: asciiLower(%q) is %d bytes, want %d", s, len(got), len(s))
			continue
		}
		t.Logf("ok: %q keeps its length", s)
	}
	if asciiLower("<H2 ID=\"x\">") != "<h2 id=\"x\">" {
		t.Fatal("FAIL: ASCII was not lowered")
	}
	if asciiLower("Ⱥ") != "Ⱥ" {
		t.Fatal("FAIL: a non-ASCII rune was altered")
	}
}