Skip to content
quarry Artifact [f96ee451d3]
Login

Artifact [f96ee451d3]

Artifact f96ee451d35c931e9bdc7d4d6c08d4c51e9dc1ed7139030f3fbab09694cfab2c:


package api

import (
	"encoding/base64"
	"errors"
	"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: a well-formed stub errored: %v", err)
	}
	if out.Content != "" {
		t.Error("FAIL: the answer carries content, so a caller downloads the declaration " +
			"believing it is the file")
	}
	if !out.External {
		t.Error("FAIL: external is not set, so nothing in the answer says the bytes are elsewhere")
	}
	if out.Size != int(stub.Size) {
		t.Errorf("FAIL: size is %d, want the FILE's %d: reporting the declaration's length "+
			"is what made the truncation invisible", out.Size, stub.Size)
	}
	if out.SHA256 != stub.SHA256 || out.Mimetype != stub.Type {
		t.Errorf("FAIL: sha256 %q type %q, want %q %q", out.SHA256, out.Mimetype, stub.SHA256, stub.Type)
	}
	if out.Reason == "" {
		t.Error("FAIL: no reason names the route that does have the bytes")
	}
	t.Logf("ok: external answered with the file's size and no content (%s)", out.Reason)
}

// 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.External {
		t.Fatal("FAIL: a real file was reported external")
	}
	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)
}