110 lines
2.8 KiB
Go
110 lines
2.8 KiB
Go
package web
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"git.noroutes.net/NoRoutes/vibetunes/internal/database"
|
|
"git.noroutes.net/NoRoutes/vibetunes/internal/streaming"
|
|
)
|
|
|
|
// handleSharePage serves a public, unauthenticated page for a share
|
|
// token — the whole point of a share link is that the recipient doesn't
|
|
// need a VibeSonic account. Expired or unknown tokens 404 rather than
|
|
// leaking which is which.
|
|
func (h *Web) handleSharePage(w http.ResponseWriter, r *http.Request) {
|
|
token := r.PathValue("token")
|
|
share, err := database.GetShareByToken(h.DB, token)
|
|
if err != nil || share.IsExpired() {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
_ = database.RecordShareVisit(h.DB, share.ID)
|
|
|
|
songIDs, err := database.ShareSongIDs(h.DB, share.ID)
|
|
if err != nil {
|
|
http.Error(w, "internal error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
var rows []SongRow
|
|
for _, id := range songIDs {
|
|
song, serr := database.GetSong(h.DB, id)
|
|
if serr != nil {
|
|
continue
|
|
}
|
|
rows = append(rows, toSongRow(h.DB, song, albumOf(h.DB, song), "", false))
|
|
}
|
|
|
|
owner, _ := database.GetUserByID(h.DB, share.OwnerID)
|
|
ownerName := ""
|
|
if owner != nil {
|
|
ownerName = owner.Username
|
|
}
|
|
|
|
render(w, "share", PageData{
|
|
Title: "Shared music",
|
|
Data: struct {
|
|
Description string
|
|
OwnerName string
|
|
Songs []SongRow
|
|
Token string
|
|
}{share.Description, ownerName, rows, token},
|
|
})
|
|
}
|
|
|
|
// handleShareStream serves one song from a share, publicly and without
|
|
// a session — but only a song that is actually part of that share, and
|
|
// only through the same path-validated streaming.ServeFile used
|
|
// everywhere else, so a share link can never be used to fetch arbitrary
|
|
// filesystem paths.
|
|
func (h *Web) handleShareStream(w http.ResponseWriter, r *http.Request) {
|
|
token := r.PathValue("token")
|
|
songID := r.PathValue("songID")
|
|
|
|
share, err := database.GetShareByToken(h.DB, token)
|
|
if err != nil || share.IsExpired() {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
|
|
songIDs, err := database.ShareSongIDs(h.DB, share.ID)
|
|
if err != nil {
|
|
http.Error(w, "internal error", http.StatusInternalServerError)
|
|
return
|
|
}
|
|
allowed := false
|
|
for _, id := range songIDs {
|
|
if id == songID {
|
|
allowed = true
|
|
break
|
|
}
|
|
}
|
|
if !allowed {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
|
|
song, err := database.GetSong(h.DB, songID)
|
|
if err != nil {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
mf, err := database.GetMediaFile(h.DB, song.MediaFileID)
|
|
if err != nil {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
folder, err := database.GetMusicFolder(h.DB, mf.MusicFolderID)
|
|
if err != nil {
|
|
http.NotFound(w, r)
|
|
return
|
|
}
|
|
absPath, perr := streaming.ResolvePath(folder.Path, mf.RelativePath)
|
|
if perr != nil {
|
|
http.Error(w, "403 Forbidden", http.StatusForbidden)
|
|
return
|
|
}
|
|
if err := streaming.ServeFile(w, r, absPath, mf.RelativePath); err != nil {
|
|
http.Error(w, "could not read media file", http.StatusInternalServerError)
|
|
}
|
|
}
|