Add share link UI after upload

This commit is contained in:
2026-09-13 00:17:37 +02:00
parent da36645aaf
commit 505e4f472f
8 changed files with 211 additions and 47 deletions
+1
View File
@@ -122,6 +122,7 @@ type objectPage struct {
Size string
Expires string
URL string
InfoURL string
DeleteToken string
}
+66
View File
@@ -957,3 +957,69 @@ func TestDownloadCSPStaysInert(t *testing.T) {
}
}
}
// The result page is the only place a link to the info page is ever offered,
// so losing it strands that page with no way to discover it.
func TestResultPageOffersBothLinksAndAWayBack(t *testing.T) {
h := newHarness(t, nil)
var body bytes.Buffer
mw := multipart.NewWriter(&body)
fw, _ := mw.CreateFormFile("file", "thing.bin")
fw.Write([]byte("data"))
mw.Close()
req, _ := http.NewRequest("POST", h.ts.URL+"/api/upload", &body)
req.Header.Set("Content-Type", mw.FormDataContentType())
req.Header.Set("Accept", "text/html")
resp, err := h.ts.Client().Do(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
raw, _ := io.ReadAll(resp.Body)
page := string(raw)
id := h.store.List()[0].ID
for _, want := range []struct{ what, fragment string }{
{"the info page link", "/i/" + id},
{"the direct download link", "/d/" + id},
{"a way to upload another file", `href="/">Upload another file`},
{"the script that enables the copy buttons", "static/app.js"},
} {
if !strings.Contains(page, want.fragment) {
t.Errorf("the result page is missing %s (%q)", want.what, want.fragment)
}
}
// Copy buttons ship hidden, so a reader without JavaScript never sees a
// button that does nothing.
if strings.Count(page, `class="copy"`) != strings.Count(page, `hidden>Copy<`) {
t.Error("a copy button is not hidden by default")
}
}
// The JSON reply has to carry the same two links, since the script builds the
// result card from it alone.
func TestUploadJSONCarriesBothLinks(t *testing.T) {
h := newHarness(t, nil)
res := decode[uploadResult](t, h.upload(t, []byte("x"), nil))
if res.URL == "" || !strings.Contains(res.URL, "/d/"+res.ID) {
t.Errorf("url = %q, want the direct download", res.URL)
}
if res.InfoURL == "" || !strings.Contains(res.InfoURL, "/i/"+res.ID) {
t.Errorf("info_url = %q, want the info page", res.InfoURL)
}
// Both must actually resolve.
for _, u := range []string{res.URL, res.InfoURL} {
get, err := h.ts.Client().Get(u)
if err != nil {
t.Fatal(err)
}
get.Body.Close()
if get.StatusCode != http.StatusOK {
t.Errorf("GET %s => %s", u, get.Status)
}
}
}
+4 -1
View File
@@ -426,11 +426,14 @@ func (s *Server) respondUploaded(w http.ResponseWriter, r *http.Request, m *stor
// Rendered directly rather than redirected: a 303 would have to carry the
// delete token in the URL, where it would end up in logs and history.
s.render(w, http.StatusOK, "result.html", objectPage{
page: s.page("Uploaded", false),
// The script is loaded here only to enable the copy buttons, which stay
// hidden without it rather than sitting there dead.
page: s.page("Uploaded", true),
Meta: m,
Size: config.FormatSize(m.Size),
Expires: describeExpiry(m.Expires, s.now()),
URL: url,
InfoURL: s.absBase(r) + "i/" + m.ID,
DeleteToken: secret,
})
}