photoprism/internal/api/share_preview.go
Michael Mayer 91774c4068 Security: Create new files without execution permission #2809
Signed-off-by: Michael Mayer <michael@photoprism.app>
2022-10-31 15:01:48 +01:00

178 lines
4.4 KiB
Go

package api
import (
"fmt"
"image"
"image/color"
"net/http"
"os"
"path"
"time"
"github.com/disintegration/imaging"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/get"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/search"
"github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/clean"
"github.com/photoprism/photoprism/pkg/fs"
)
// SharePreview returns a link share preview image.
//
// GET /s/:token/:uid/preview
// TODO: Proof of concept, needs refactoring.
func SharePreview(router *gin.RouterGroup) {
router.GET("/:token/:shared/preview", func(c *gin.Context) {
conf := get.Config()
token := clean.Token(c.Param("token"))
shared := clean.Token(c.Param("shared"))
links := entity.FindLinks(token, shared)
if len(links) != 1 {
log.Warn("share: invalid token (preview)")
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return
}
thumbPath := path.Join(conf.ThumbCachePath(), "share")
if err := os.MkdirAll(thumbPath, fs.ModeDir); err != nil {
log.Error(err)
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return
}
previewFilename := fmt.Sprintf("%s/%s.jpg", thumbPath, shared)
yesterday := time.Now().Add(-24 * time.Hour)
if info, err := os.Stat(previewFilename); err != nil {
log.Debugf("share: creating new preview for %s", clean.Log(shared))
} else if info.ModTime().After(yesterday) {
log.Debugf("share: using cached preview for %s", clean.Log(shared))
c.File(previewFilename)
return
} else if err := os.Remove(previewFilename); err != nil {
log.Errorf("share: could not remove old preview of %s", clean.Log(shared))
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return
}
var f form.SearchPhotos
// Covers may only contain public content in shared albums.
f.Album = shared
f.Public = true
f.Private = false
f.Hidden = false
f.Archived = false
f.Review = false
f.Primary = true
// Get first 12 album entries.
f.Count = 12
f.Order = "relevance"
if err := f.ParseQueryString(); err != nil {
log.Errorf("preview: %s", err)
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return
}
p, count, err := search.Photos(f)
if err != nil {
log.Error(err)
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return
}
if count == 0 {
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return
} else if count < 12 {
f := p[0]
size, _ := thumb.Sizes[thumb.Fit720]
fileName := photoprism.FileName(f.FileRoot, f.FileName)
if !fs.FileExists(fileName) {
log.Errorf("share: file %s is missing (preview)", clean.Log(f.FileName))
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return
}
thumbnail, err := thumb.FromFile(fileName, f.FileHash, conf.ThumbCachePath(), size.Width, size.Height, f.FileOrientation, size.Options...)
if err != nil {
log.Error(err)
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return
}
c.File(thumbnail)
return
}
width := 908
height := 680
x := 0
y := 0
preview := imaging.New(width, height, color.NRGBA{255, 255, 255, 255})
size, _ := thumb.Sizes[thumb.Tile224]
for _, f := range p {
fileName := photoprism.FileName(f.FileRoot, f.FileName)
if !fs.FileExists(fileName) {
log.Errorf("share: file %s is missing (preview)", clean.Log(f.FileName))
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return
}
thumbnail, err := thumb.FromFile(fileName, f.FileHash, conf.ThumbCachePath(), size.Width, size.Height, f.FileOrientation, size.Options...)
if err != nil {
log.Error(err)
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return
}
src, err := imaging.Open(thumbnail)
if err != nil {
log.Error(err)
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return
}
preview = imaging.Paste(preview, src, image.Pt(x, y))
x += 228
if x > width {
x = 0
y += 228
}
}
// Save the resulting image as JPEG.
err = imaging.Save(preview, previewFilename)
if err != nil {
log.Error(err)
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return
}
c.File(previewFilename)
})
}