Add link previews
This commit is contained in:
@@ -2346,3 +2346,130 @@ func TestFilesPageIsLaidOutForATable(t *testing.T) {
|
||||
t.Error("the listing still relies on a horizontal scroll container")
|
||||
}
|
||||
}
|
||||
|
||||
// --- link previews -------------------------------------------------------
|
||||
|
||||
// metaContent pulls a meta tag's content out of a rendered page, keyed by
|
||||
// either the property or the name attribute, so a test reads what an unfurler
|
||||
// would rather than matching a whole tag it does not care about.
|
||||
func metaContent(page, key string) string {
|
||||
for _, attr := range []string{"property", "name"} {
|
||||
marker := fmt.Sprintf("<meta %s=%q content=", attr, key)
|
||||
i := strings.Index(page, marker)
|
||||
if i < 0 {
|
||||
continue
|
||||
}
|
||||
rest := page[i+len(marker):]
|
||||
if !strings.HasPrefix(rest, `"`) {
|
||||
continue
|
||||
}
|
||||
if end := strings.Index(rest[1:], `"`); end >= 0 {
|
||||
return rest[1 : 1+end]
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// A /i/ link is the thing people paste at each other, so it has to unfurl into
|
||||
// something that says what is on the other end of it.
|
||||
func TestInfoPageUnfurls(t *testing.T) {
|
||||
h := newHarness(t, func(c *config.Config) { c.PublicURL = "https://drop.example" })
|
||||
res := decode[uploadResult](t, h.uploadReader(t,
|
||||
strings.NewReader(strings.Repeat("x", 4096)),
|
||||
map[string]string{
|
||||
"Content-Disposition": `attachment; filename="MyGame.zip"`,
|
||||
"Authorization": "Bearer " + h.token,
|
||||
}))
|
||||
|
||||
info := h.get(t, "/i/"+res.ID, "")
|
||||
raw, _ := io.ReadAll(info.Body)
|
||||
info.Body.Close()
|
||||
page := string(raw)
|
||||
|
||||
if got, want := metaContent(page, "og:title"), "MyGame.zip - Uncensored Send"; got != want {
|
||||
t.Errorf("og:title = %q, want %q", got, want)
|
||||
}
|
||||
if got, want := metaContent(page, "og:url"), "https://drop.example/i/"+res.ID; got != want {
|
||||
t.Errorf("og:url = %q, want %q", got, want)
|
||||
}
|
||||
if got, want := metaContent(page, "author"), "friend"; got != want {
|
||||
t.Errorf("author = %q, want %q", got, want)
|
||||
}
|
||||
if metaContent(page, "twitter:card") != "summary" {
|
||||
t.Error("no twitter:card, so clients holding to that vocabulary draw nothing")
|
||||
}
|
||||
|
||||
desc := metaContent(page, "og:description")
|
||||
for _, want := range []string{"4 KiB", "uploaded by friend", "expires 2026-09-15 10:00 UTC"} {
|
||||
if !strings.Contains(desc, want) {
|
||||
t.Errorf("og:description = %q, missing %q", desc, want)
|
||||
}
|
||||
}
|
||||
if !strings.Contains(desc, "Open the page") {
|
||||
t.Errorf("og:description = %q, says nothing about what to do with the link", desc)
|
||||
}
|
||||
// The deadline is cached by the chat platform and read back days later, so
|
||||
// it has to be an absolute time rather than a countdown that goes stale.
|
||||
if strings.Contains(desc, "in 3d") {
|
||||
t.Errorf("og:description = %q, carries a relative deadline", desc)
|
||||
}
|
||||
if metaContent(page, "description") != desc {
|
||||
t.Error("the plain description tag disagrees with the Open Graph one")
|
||||
}
|
||||
}
|
||||
|
||||
// An anonymous upload has no uploader to name, and the card must not claim one.
|
||||
func TestAnonymousUploadHasNoAuthorInItsCard(t *testing.T) {
|
||||
h := newHarness(t, nil)
|
||||
res := decode[uploadResult](t, h.upload(t, []byte("x"), nil))
|
||||
|
||||
info := h.get(t, "/i/"+res.ID, "")
|
||||
raw, _ := io.ReadAll(info.Body)
|
||||
info.Body.Close()
|
||||
page := string(raw)
|
||||
|
||||
if got := metaContent(page, "author"); got != "" {
|
||||
t.Errorf("author = %q, want none", got)
|
||||
}
|
||||
if desc := metaContent(page, "og:description"); strings.Contains(desc, "uploaded by") {
|
||||
t.Errorf("og:description = %q, names an uploader there is none of", desc)
|
||||
}
|
||||
if !strings.Contains(page, "anonymous") {
|
||||
t.Error("the page itself does not say the upload was anonymous")
|
||||
}
|
||||
}
|
||||
|
||||
// A filename is caller-supplied and ends up inside an attribute; the escaping
|
||||
// has to hold there as well as in the body.
|
||||
func TestPreviewEscapesTheFilename(t *testing.T) {
|
||||
h := newHarness(t, nil)
|
||||
res := decode[uploadResult](t, h.upload(t, []byte("x"), map[string]string{
|
||||
"Content-Disposition": `attachment; filename="\" onload=\"alert(1)"`}))
|
||||
|
||||
info := h.get(t, "/i/"+res.ID, "")
|
||||
raw, _ := io.ReadAll(info.Body)
|
||||
info.Body.Close()
|
||||
page := string(raw)
|
||||
|
||||
if strings.Contains(page, `onload="alert(1)"`) {
|
||||
t.Fatalf("a filename broke out of the meta attribute:\n%s", page)
|
||||
}
|
||||
if got, want := metaContent(page, "og:title"), res.Filename; !strings.Contains(got, "onload") && got != want {
|
||||
// The value is escaped, so this only checks the tag is there at all.
|
||||
t.Errorf("og:title = %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// Pages whose content depends on who is asking say nothing to an unfurler,
|
||||
// which presents no token and would otherwise be shown a stranger's view.
|
||||
func TestOnlyTheInfoPageCarriesACard(t *testing.T) {
|
||||
h := newHarness(t, nil)
|
||||
for _, path := range []string{"/", "/login", "/files"} {
|
||||
resp := h.get(t, path, "")
|
||||
raw, _ := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if got := metaContent(string(raw), "og:title"); got != "" {
|
||||
t.Errorf("%s carries og:title = %q", path, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user