This commit is contained in:
@@ -43,6 +43,7 @@ type Content interface {
|
||||
RestoreRevision(p *post.Post, name string) *post.Post
|
||||
InvalidateCache()
|
||||
StoreUpload(originalName string, data []byte) (string, error)
|
||||
StoreAvatar(data []byte) (string, error)
|
||||
MediaPath(name string) (string, error)
|
||||
DeleteMedia(url string) bool
|
||||
ListMedia() []store.Media
|
||||
|
||||
@@ -19,6 +19,25 @@ func (a *Admin) registerMediaRoutes(mux *http.ServeMux) {
|
||||
func (a *Admin) handleMediaLibrary(w http.ResponseWriter, r *http.Request) {
|
||||
data := a.pageData(r)
|
||||
items := a.deps.Store.ListMedia()
|
||||
// A profile photo of any account is not library content. Avatars
|
||||
// stored since the avatar directory existed never reach this list
|
||||
// at all; the filter keeps the flat photos of installations that
|
||||
// predate it out of the tiles the same way.
|
||||
photos := make(map[string]bool)
|
||||
for _, user := range a.deps.Users.All() {
|
||||
if user.Photo != "" {
|
||||
photos[user.Photo] = true
|
||||
}
|
||||
}
|
||||
if len(photos) > 0 {
|
||||
filtered := items[:0]
|
||||
for _, item := range items {
|
||||
if !photos[item.URL] {
|
||||
filtered = append(filtered, item)
|
||||
}
|
||||
}
|
||||
items = filtered
|
||||
}
|
||||
data.MediaItems = mediaRows(items)
|
||||
data.MediaTotal = humanSize(totalSize(items))
|
||||
data.Crumbs = []Crumb{{Label: "Media", IsLast: true, UI: true}}
|
||||
|
||||
@@ -149,7 +149,7 @@ func (a *Admin) handleSettingsPhoto(w http.ResponseWriter, r *http.Request) {
|
||||
if !a.requireCSRF(w, r) {
|
||||
return
|
||||
}
|
||||
file, header, err := r.FormFile("photo")
|
||||
file, _, err := r.FormFile("photo")
|
||||
if err != nil {
|
||||
a.renderSettings(w, r, a.tr(r, "No file selected."), "", http.StatusUnprocessableEntity)
|
||||
return
|
||||
@@ -166,7 +166,10 @@ func (a *Admin) handleSettingsPhoto(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
username := a.currentUser(r)
|
||||
url, err := a.deps.Store.StoreUpload(header.Filename, raw)
|
||||
// The photo is account state, not library content: it is stored
|
||||
// under avatars/ inside the media directory, which the media
|
||||
// library never lists as a tile.
|
||||
url, err := a.deps.Store.StoreAvatar(raw)
|
||||
if err != nil {
|
||||
a.renderSettings(w, r, a.tr(r, "The photo could not be stored."), "", http.StatusInternalServerError)
|
||||
return
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
@@ -505,9 +506,18 @@ func TestPhotoUploadAndRemove(t *testing.T) {
|
||||
if !strings.Contains(rec.Body.String(), "Profile photo updated.") {
|
||||
t.Fatalf("body = %s", rec.Body.String())
|
||||
}
|
||||
if f.users.Find("admin").Photo == "" {
|
||||
photo := f.users.Find("admin").Photo
|
||||
if photo == "" {
|
||||
t.Fatal("photo not stored on the user")
|
||||
}
|
||||
// The photo is account state, not a library tile: it lives under
|
||||
// avatars/ and the media library does not list it.
|
||||
if !strings.HasPrefix(photo, "/media/avatars/") {
|
||||
t.Fatalf("photo url = %q, want the avatar namespace", photo)
|
||||
}
|
||||
if media := f.storeObj.ListMedia(); len(media) != 0 {
|
||||
t.Fatalf("the avatar leaked into the library: %v", media)
|
||||
}
|
||||
|
||||
rec = postForm(t, f, "/admin/settings/photo/remove", url.Values{"_csrf": {csrf}}, cookie)
|
||||
if !strings.Contains(rec.Body.String(), "Profile photo removed.") {
|
||||
@@ -516,6 +526,44 @@ func TestPhotoUploadAndRemove(t *testing.T) {
|
||||
if f.users.Find("admin").Photo != "" {
|
||||
t.Fatal("photo not cleared")
|
||||
}
|
||||
if _, err := f.storeObj.MediaPath(strings.TrimPrefix(photo, "/media/")); err == nil {
|
||||
t.Fatal("the avatar file survived the removal")
|
||||
}
|
||||
}
|
||||
|
||||
// Installations that predate the avatar directory kept their photos flat
|
||||
// in the media directory; those files still serve, but the library hides
|
||||
// every URL an account photo references, so the tiles stay content-only.
|
||||
func TestMediaLibraryHidesAccountPhotos(t *testing.T) {
|
||||
f := newFixture(t)
|
||||
cookie := login(t, f, "admin", "correct-horse-9")
|
||||
|
||||
webpData := append([]byte("RIFF"), 0, 0, 0, 0)
|
||||
webpData = append(webpData, []byte("WEBPVP8 ")...)
|
||||
legacy, err := f.storeObj.StoreUpload("legacy.png", webpData)
|
||||
if err != nil {
|
||||
t.Fatalf("StoreUpload: %v", err)
|
||||
}
|
||||
if _, err := f.users.UpdatePhoto("admin", legacy); err != nil {
|
||||
t.Fatalf("UpdatePhoto: %v", err)
|
||||
}
|
||||
plain, err := f.storeObj.StoreUpload("plain.png", webpData)
|
||||
if err != nil {
|
||||
t.Fatalf("StoreUpload: %v", err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "/admin/media", nil)
|
||||
req.AddCookie(cookie)
|
||||
body := f.do(t, req).Body.String()
|
||||
// The tile is identified by its data-name attribute: the signed-in
|
||||
// user's topbar avatar legitimately carries the photo URL too, so a
|
||||
// bare name search would match the chrome, not the library.
|
||||
if strings.Contains(body, `data-name="`+path.Base(legacy)+`"`) {
|
||||
t.Fatal("the account photo appears as a library tile")
|
||||
}
|
||||
if !strings.Contains(body, `data-name="`+path.Base(plain)+`"`) {
|
||||
t.Fatal("an ordinary media file is missing from the library")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWebhookTestDelivery(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user