pack loads local product packs (format 1): t5de-product.json and the product's files, as product.chkn or files/, with index.xml's rating, type, gender and parent product. cfl writes and reads IMVU's product container, uncompressed. safehtml cleans product descriptions by an allowlist (bluemonday). Co-Authored-By: Claude Opus 5.5 <[email protected]>
154 lines
4.0 KiB
Go
154 lines
4.0 KiB
Go
package cfl
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"errors"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func write(t testing.TB, entries []Entry) []byte {
|
|
t.Helper()
|
|
var b bytes.Buffer
|
|
if err := Write(&b, entries); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return b.Bytes()
|
|
}
|
|
|
|
func TestWriteLayout(t *testing.T) {
|
|
got := write(t, []Entry{{Name: "a", Data: []byte("xy")}, {Name: "é", Data: nil}})
|
|
want := "" +
|
|
"43464c33" + "16000000" + "1e000000" + // CFL3, directory at 22, 30 bytes
|
|
"02000000" + "7879" + // "a"
|
|
"00000000" + // "é"
|
|
"00000000" + "1e000000" + // directory: uncompressed, 30 bytes
|
|
"02000000" + "0c000000" + "00000000" + "0100" + "61" +
|
|
"00000000" + "12000000" + "00000000" + "0100" + "e9"
|
|
if hex.EncodeToString(got) != want {
|
|
t.Errorf("got %x\nwant %s", got, want)
|
|
}
|
|
}
|
|
|
|
func TestRoundTrip(t *testing.T) {
|
|
entries := []Entry{
|
|
{Name: "index.xml", Data: []byte("<Template/>")},
|
|
{Name: "café.png", Data: []byte{0x89, 'P', 'N', 'G'}},
|
|
{Name: "empty", Data: []byte{}},
|
|
}
|
|
got, err := Read(write(t, entries))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !reflect.DeepEqual(got, entries) {
|
|
t.Errorf("got %q, want %q", got, entries)
|
|
}
|
|
|
|
if got, err := Read(write(t, nil)); err != nil || len(got) != 0 {
|
|
t.Errorf("empty CFL: %v, %v", got, err)
|
|
}
|
|
}
|
|
|
|
func TestWriteErrors(t *testing.T) {
|
|
for _, name := range []string{"", "snow☃.png", "\xff"} {
|
|
var b bytes.Buffer
|
|
if err := Write(&b, []Entry{{Name: name}}); err == nil {
|
|
t.Errorf("%q: no error", name)
|
|
}
|
|
}
|
|
}
|
|
|
|
// TestDFL builds a DFL3 by hand: one entry with a content hash after its directory entry.
|
|
func TestDFL(t *testing.T) {
|
|
var b []byte
|
|
le := binary.LittleEndian
|
|
b = append(b, "DFL3"...)
|
|
b = le.AppendUint32(b, 18)
|
|
dirSizeAt := len(b)
|
|
b = le.AppendUint32(b, 0)
|
|
b = le.AppendUint32(b, 2)
|
|
b = append(b, "hi"...)
|
|
var dir []byte
|
|
dir = le.AppendUint32(dir, 2)
|
|
dir = le.AppendUint32(dir, 12)
|
|
dir = le.AppendUint32(dir, 0)
|
|
dir = le.AppendUint16(dir, 1)
|
|
dir = append(dir, 'x')
|
|
dir = le.AppendUint32(dir, 3)
|
|
dir = append(dir, "abc"...)
|
|
le.PutUint32(b[dirSizeAt:], uint32(len(dir)))
|
|
b = le.AppendUint32(b, 0)
|
|
b = le.AppendUint32(b, uint32(len(dir)))
|
|
b = append(b, dir...)
|
|
|
|
got, err := Read(b)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if want := []Entry{{Name: "x", Data: []byte("hi")}}; !reflect.DeepEqual(got, want) {
|
|
t.Errorf("got %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestReadLZMA(t *testing.T) {
|
|
b := write(t, []Entry{{Name: "a", Data: []byte("xy")}})
|
|
dir := binary.LittleEndian.Uint32(b[4:])
|
|
|
|
c := bytes.Clone(b)
|
|
binary.LittleEndian.PutUint32(c[dir:], LZMA)
|
|
if _, err := Read(c); !errors.Is(err, ErrLZMA) {
|
|
t.Errorf("compressed directory: %v", err)
|
|
}
|
|
|
|
c = bytes.Clone(b)
|
|
binary.LittleEndian.PutUint32(c[dir+8+8:], LZMA) // the entry's flag
|
|
if _, err := Read(c); !errors.Is(err, ErrLZMA) {
|
|
t.Errorf("compressed entry: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestReadMalformed(t *testing.T) {
|
|
good := write(t, []Entry{{Name: "a", Data: []byte("xy")}})
|
|
for i := range len(good) {
|
|
if _, err := Read(good[:i]); err == nil {
|
|
t.Errorf("cut at %d: no error", i)
|
|
}
|
|
}
|
|
bad := bytes.Clone(good)
|
|
bad[0] = 'X'
|
|
if _, err := Read(bad); err == nil {
|
|
t.Error("bad header: no error")
|
|
}
|
|
bad = bytes.Clone(good)
|
|
binary.LittleEndian.PutUint32(bad[12:], 1000) // the entry's stored length
|
|
if _, err := Read(bad); err == nil {
|
|
t.Error("bad entry length: no error")
|
|
}
|
|
}
|
|
|
|
func FuzzRead(f *testing.F) {
|
|
f.Add(write(f, []Entry{{Name: "a", Data: []byte("xy")}, {Name: "b.png", Data: []byte{1, 2, 3}}}))
|
|
f.Add(write(f, nil))
|
|
f.Add([]byte("DFL3\x0c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"))
|
|
f.Fuzz(func(t *testing.T, data []byte) {
|
|
entries, err := Read(data)
|
|
if err != nil {
|
|
return
|
|
}
|
|
again, err := Read(write(t, entries))
|
|
if err != nil {
|
|
t.Fatalf("rewritten CFL: %v", err)
|
|
}
|
|
if len(again) != len(entries) {
|
|
t.Fatalf("rewritten CFL has %d entries, want %d", len(again), len(entries))
|
|
}
|
|
for i := range entries {
|
|
if again[i].Name != entries[i].Name || !bytes.Equal(again[i].Data, entries[i].Data) {
|
|
t.Fatalf("entry %d changed", i)
|
|
}
|
|
}
|
|
})
|
|
}
|