Skip to content
quarry Artifact [6008a3e8c5]
Login

Artifact [6008a3e8c5]

Artifact 6008a3e8c517e26f07e6c8ab6f8d0baf7eef85d5caf681940e3545c3fd53bae2:


package api

import (
	"encoding/base64"
	"errors"
	"strings"
	"testing"

	"craftdesign.group/quarry/internal/attach"
	"craftdesign.group/quarry/internal/ops"
)

// A stub must never be answered as the file it stands for.
//
// This is the bug as it was measured: attachment.list gave src
// ee99187e, GET /attachment/ee99187e answered 200 with the attachment's
// REAL name, the stub's 143-byte size and the stub's bytes as content,
// and nothing in the answer distinguished it from the file. Somebody
// downloading a video got a few lines of text and no way to tell.
func TestAttachmentGetDoesNotServeTheStubAsTheFile(t *testing.T) {
	stub := attach.Stub{
		SHA256: "4f9a000000000000000000000000000000000000000000000000000000000e01c"[:64],
		Size:   18349022,
		Type:   "video/mp4",
		Name:   "screen-recording.mp4",
	}
	out, err := attachmentGetAnswer("ee99187e", "screen-recording.mp4", stub.Format())
	if err == nil {
		t.Fatalf("FAIL: an external attachment was ANSWERED (size %d, %d bytes of content): "+
			"a caller that writes what it is given writes a file that is not the file",
			out.Size, len(out.Content))
	}
	if !errors.Is(err, ops.ErrInvalid) {
		t.Errorf("FAIL: refused with %v, want an invalid-request so no client reads it as bytes", err)
	}
	// The refusal has to carry the three things a caller needs to
	// recover: that it is external, how big the real file is, and where
	// the bytes actually are.
	for _, want := range []string{"external", "18349022", "/link", stub.SHA256} {
		if !strings.Contains(err.Error(), want) {
			t.Errorf("FAIL: the refusal does not mention %q: %v", want, err)
		}
	}
	t.Logf("ok: refused, and said where to go (%v)", err)
}

// A real attachment is untouched: this fix must not cost the ordinary
// case, which is every attachment that arrived by import or sync.
func TestAttachmentGetStillServesARealFile(t *testing.T) {
	raw := []byte("\x89PNG\r\n\x1a\nIHDR and then some bytes")
	out, err := attachmentGetAnswer("abc123", "shot.png", raw)
	if err != nil {
		t.Fatalf("FAIL: a real file errored: %v", err)
	}
	if out.Size != len(raw) {
		t.Errorf("FAIL: size %d, want %d", out.Size, len(raw))
	}
	got, derr := base64.StdEncoding.DecodeString(out.Content)
	if derr != nil || string(got) != string(raw) {
		t.Errorf("FAIL: content did not round-trip (%v)", derr)
	}
	t.Logf("ok: a real file still serves its own %d bytes", len(raw))
}

// A blob that OPENS like a stub and does not parse is not a file
// either. Falling back to serving it would put the bug back for exactly
// the bytes least likely to be what they claim.
func TestAttachmentGetRefusesAnUnreadableDeclaration(t *testing.T) {
	broken := []byte(attach.Header + "\nsha256 not-a-hash\n")
	_, err := attachmentGetAnswer("bad", "thing.bin", broken)
	if err == nil {
		t.Fatal("FAIL: an unparseable declaration was answered rather than refused")
	}
	if !errors.Is(err, ops.ErrNotFound) {
		t.Errorf("FAIL: refused with %v, want a not-found", err)
	}
	t.Logf("ok: refused (%v)", err)
}