API: Improve request parameter sanitation #1814

This commit is contained in:
Michael Mayer
2021-12-14 18:34:52 +01:00
parent 9a8144c046
commit 4e94919030
34 changed files with 338 additions and 115 deletions

8
go.sum
View File

@@ -84,13 +84,10 @@ github.com/esimov/pigo v1.4.5 h1:ySG0QqMh02VNALvHnx04L1ScRu66N6XA5vLLga8GiLg=
github.com/esimov/pigo v1.4.5/go.mod h1:SGkOUpm4wlEmQQJKlaymAkThY8/8iP+XE0gFo7g8G6w= github.com/esimov/pigo v1.4.5/go.mod h1:SGkOUpm4wlEmQQJKlaymAkThY8/8iP+XE0gFo7g8G6w=
github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/gin-contrib/gzip v0.0.3 h1:etUaeesHhEORpZMp18zoOhepboiWnFtXrBZxszWUn4k=
github.com/gin-contrib/gzip v0.0.3/go.mod h1:YxxswVZIqOvcHEQpsSn+QF5guQtO1dCfy0shBPy4jFc=
github.com/gin-contrib/gzip v0.0.5 h1:mhnVU32YnnBh2LPH2iqRqsA/eR7SAqRaD388jL2s/j0= github.com/gin-contrib/gzip v0.0.5 h1:mhnVU32YnnBh2LPH2iqRqsA/eR7SAqRaD388jL2s/j0=
github.com/gin-contrib/gzip v0.0.5/go.mod h1:OPIK6HR0Um2vNmBUTlayD7qle4yVVRZT0PyhdUigrKk= github.com/gin-contrib/gzip v0.0.5/go.mod h1:OPIK6HR0Um2vNmBUTlayD7qle4yVVRZT0PyhdUigrKk=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M=
github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY= github.com/gin-gonic/gin v1.7.4/go.mod h1:jD2toBW3GZUr5UMcdrwQA10I7RuaFOl/SGeDjXkfUtY=
github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs= github.com/gin-gonic/gin v1.7.7 h1:3DoBmSbJbZAWqXJC3SLjAPfutPJJRN1U5pALB7EeTTs=
github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U=
@@ -114,7 +111,6 @@ github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho=
github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA=
github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A= github.com/go-playground/validator/v10 v10.9.0 h1:NgTtmN58D0m8+UuxtYmGztBJB7VnPgjj221I1QHci2A=
github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= github.com/go-playground/validator/v10 v10.9.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos=
@@ -164,8 +160,6 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gosimple/slug v1.11.2 h1:MxFR0TmQ/qz0KvIrBbf4phu+G0RBgpwxOn6jPKFKFOw=
github.com/gosimple/slug v1.11.2/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
github.com/gosimple/slug v1.12.0 h1:xzuhj7G7cGtd34NXnW/yF0l+AGNfWqwgh/IXgFy7dnc= github.com/gosimple/slug v1.12.0 h1:xzuhj7G7cGtd34NXnW/yF0l+AGNfWqwgh/IXgFy7dnc=
github.com/gosimple/slug v1.12.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ= github.com/gosimple/slug v1.12.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o= github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
@@ -419,8 +413,6 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 h1:TyHqChC80pFkXWraUUf6RuB5IqFdQieMLwwCJokV2pc=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211213223007-03aa0b5f6827 h1:A0Qkn7Z/n8zC1xd9LTw17AiKlBRK64tw3ejWQiEqca0= golang.org/x/sys v0.0.0-20211213223007-03aa0b5f6827 h1:A0Qkn7Z/n8zC1xd9LTw17AiKlBRK64tw3ejWQiEqca0=
golang.org/x/sys v0.0.0-20211213223007-03aa0b5f6827/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211213223007-03aa0b5f6827/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20191110171634-ad39bd3f0407/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=

View File

@@ -17,7 +17,9 @@ import (
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/workers" "github.com/photoprism/photoprism/internal/workers"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
) )
// Namespaces for caching and logs. // Namespaces for caching and logs.
@@ -47,7 +49,7 @@ func GetAccount(router *gin.RouterGroup) {
return return
} }
id := ParseUint(c.Param("id")) id := sanitize.IdUint(c.Param("id"))
if m, err := query.AccountByID(id); err == nil { if m, err := query.AccountByID(id); err == nil {
c.JSON(http.StatusOK, m) c.JSON(http.StatusOK, m)
@@ -80,7 +82,7 @@ func GetAccountFolders(router *gin.RouterGroup) {
} }
start := time.Now() start := time.Now()
id := ParseUint(c.Param("id")) id := sanitize.IdUint(c.Param("id"))
cache := service.FolderCache() cache := service.FolderCache()
cacheKey := fmt.Sprintf("%s:%d", accountFolder, id) cacheKey := fmt.Sprintf("%s:%d", accountFolder, id)
@@ -128,7 +130,7 @@ func ShareWithAccount(router *gin.RouterGroup) {
return return
} }
id := ParseUint(c.Param("id")) id := sanitize.IdUint(c.Param("id"))
m, err := query.AccountByID(id) m, err := query.AccountByID(id)
@@ -237,7 +239,7 @@ func UpdateAccount(router *gin.RouterGroup) {
return return
} }
id := ParseUint(c.Param("id")) id := sanitize.IdUint(c.Param("id"))
m, err := query.AccountByID(id) m, err := query.AccountByID(id)
@@ -306,7 +308,7 @@ func DeleteAccount(router *gin.RouterGroup) {
return return
} }
id := ParseUint(c.Param("id")) id := sanitize.IdUint(c.Param("id"))
m, err := query.AccountByID(id) m, err := query.AccountByID(id)

View File

@@ -8,6 +8,7 @@ import (
"time" "time"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
@@ -17,7 +18,9 @@ import (
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/search" "github.com/photoprism/photoprism/internal/search"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/txt" "github.com/photoprism/photoprism/pkg/txt"
) )
@@ -51,7 +54,7 @@ func GetAlbum(router *gin.RouterGroup) {
return return
} }
id := c.Param("uid") id := sanitize.IdString(c.Param("uid"))
a, err := query.AlbumByUID(id) a, err := query.AlbumByUID(id)
if err != nil { if err != nil {
@@ -114,7 +117,7 @@ func UpdateAlbum(router *gin.RouterGroup) {
return return
} }
uid := c.Param("uid") uid := sanitize.IdString(c.Param("uid"))
a, err := query.AlbumByUID(uid) a, err := query.AlbumByUID(uid)
if err != nil { if err != nil {
@@ -166,7 +169,7 @@ func DeleteAlbum(router *gin.RouterGroup) {
return return
} }
id := c.Param("uid") id := sanitize.IdString(c.Param("uid"))
a, err := query.AlbumByUID(id) a, err := query.AlbumByUID(id)
@@ -217,7 +220,7 @@ func LikeAlbum(router *gin.RouterGroup) {
return return
} }
id := c.Param("uid") id := sanitize.IdString(c.Param("uid"))
a, err := query.AlbumByUID(id) a, err := query.AlbumByUID(id)
if err != nil { if err != nil {
@@ -255,7 +258,7 @@ func DislikeAlbum(router *gin.RouterGroup) {
return return
} }
id := c.Param("uid") id := sanitize.IdString(c.Param("uid"))
a, err := query.AlbumByUID(id) a, err := query.AlbumByUID(id)
if err != nil { if err != nil {
@@ -290,7 +293,7 @@ func CloneAlbums(router *gin.RouterGroup) {
return return
} }
a, err := query.AlbumByUID(c.Param("uid")) a, err := query.AlbumByUID(sanitize.IdString(c.Param("uid")))
if err != nil { if err != nil {
Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound) Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound)
@@ -355,7 +358,7 @@ func AddPhotosToAlbum(router *gin.RouterGroup) {
return return
} }
uid := c.Param("uid") uid := sanitize.IdString(c.Param("uid"))
a, err := query.AlbumByUID(uid) a, err := query.AlbumByUID(uid)
if err != nil { if err != nil {
@@ -415,7 +418,7 @@ func RemovePhotosFromAlbum(router *gin.RouterGroup) {
return return
} }
a, err := query.AlbumByUID(c.Param("uid")) a, err := query.AlbumByUID(sanitize.IdString(c.Param("uid")))
if err != nil { if err != nil {
Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound) Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound)
@@ -453,7 +456,7 @@ func DownloadAlbum(router *gin.RouterGroup) {
} }
start := time.Now() start := time.Now()
a, err := query.AlbumByUID(c.Param("uid")) a, err := query.AlbumByUID(sanitize.IdString(c.Param("uid")))
if err != nil { if err != nil {
Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound) Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound)

View File

@@ -35,6 +35,7 @@ import (
"net/http" "net/http"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
@@ -64,7 +65,7 @@ func UpdateClientConfig() {
func Abort(c *gin.Context, code int, id i18n.Message, params ...interface{}) { func Abort(c *gin.Context, code int, id i18n.Message, params ...interface{}) {
resp := i18n.NewResponse(code, id, params...) resp := i18n.NewResponse(code, id, params...)
log.Debugf("api: abort %s with code %d (%s)", c.FullPath(), code, resp.String()) log.Debugf("api: abort %s with code %d (%s)", txt.LogParam(c.FullPath()), code, resp.String())
c.AbortWithStatusJSON(code, resp) c.AbortWithStatusJSON(code, resp)
} }
@@ -74,7 +75,7 @@ func Error(c *gin.Context, code int, err error, id i18n.Message, params ...inter
if err != nil { if err != nil {
resp.Details = err.Error() resp.Details = err.Error()
log.Errorf("api: error %s with code %d in %s (%s)", txt.LogParam(err.Error()), code, c.FullPath(), resp.String()) log.Errorf("api: error %s with code %d in %s (%s)", txt.LogParam(err.Error()), code, txt.LogParam(c.FullPath()), resp.String())
} }
c.AbortWithStatusJSON(code, resp) c.AbortWithStatusJSON(code, resp)

View File

@@ -3,17 +3,19 @@ package api
import ( import (
"net/http" "net/http"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/jinzhu/gorm" "github.com/jinzhu/gorm"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/txt"
) )
// BatchPhotosArchive moves multiple photos to the archive. // BatchPhotosArchive moves multiple photos to the archive.
@@ -40,7 +42,7 @@ func BatchPhotosArchive(router *gin.RouterGroup) {
return return
} }
log.Infof("photos: archiving %s", f.String()) log.Infof("photos: archiving %s", txt.LogParam(f.String()))
if service.Config().BackupYaml() { if service.Config().BackupYaml() {
photos, err := query.PhotoSelection(f) photos, err := query.PhotoSelection(f)
@@ -103,7 +105,7 @@ func BatchPhotosRestore(router *gin.RouterGroup) {
return return
} }
log.Infof("photos: restoring %s", f.String()) log.Infof("photos: restoring %s", txt.LogParam(f.String()))
if service.Config().BackupYaml() { if service.Config().BackupYaml() {
photos, err := query.PhotoSelection(f) photos, err := query.PhotoSelection(f)
@@ -165,7 +167,7 @@ func BatchPhotosApprove(router *gin.RouterGroup) {
return return
} }
log.Infof("photos: approving %s", f.String()) log.Infof("photos: approving %s", txt.LogParam(f.String()))
photos, err := query.PhotoSelection(f) photos, err := query.PhotoSelection(f)
@@ -217,7 +219,7 @@ func BatchAlbumsDelete(router *gin.RouterGroup) {
return return
} }
log.Infof("albums: deleting %s", f.String()) log.Infof("albums: deleting %s", txt.LogParam(f.String()))
entity.Db().Where("album_uid IN (?)", f.Albums).Delete(&entity.Album{}) entity.Db().Where("album_uid IN (?)", f.Albums).Delete(&entity.Album{})
entity.Db().Where("album_uid IN (?)", f.Albums).Delete(&entity.PhotoAlbum{}) entity.Db().Where("album_uid IN (?)", f.Albums).Delete(&entity.PhotoAlbum{})
@@ -254,7 +256,7 @@ func BatchPhotosPrivate(router *gin.RouterGroup) {
return return
} }
log.Infof("photos: updating private flag for %s", f.String()) log.Infof("photos: updating private flag for %s", txt.LogParam(f.String()))
if err := entity.Db().Model(entity.Photo{}).Where("photo_uid IN (?)", f.Photos).UpdateColumn("photo_private", if err := entity.Db().Model(entity.Photo{}).Where("photo_uid IN (?)", f.Photos).UpdateColumn("photo_private",
gorm.Expr("CASE WHEN photo_private > 0 THEN 0 ELSE 1 END")).Error; err != nil { gorm.Expr("CASE WHEN photo_private > 0 THEN 0 ELSE 1 END")).Error; err != nil {
@@ -307,7 +309,7 @@ func BatchLabelsDelete(router *gin.RouterGroup) {
return return
} }
log.Infof("labels: deleting %s", f.String()) log.Infof("labels: deleting %s", txt.LogParam(f.String()))
var labels entity.Labels var labels entity.Labels
@@ -359,7 +361,7 @@ func BatchPhotosDelete(router *gin.RouterGroup) {
return return
} }
log.Infof("photos: deleting %s", f.String()) log.Infof("photos: deleting %s", txt.LogParam(f.String()))
photos, err := query.PhotoSelection(f) photos, err := query.PhotoSelection(f)

View File

@@ -5,6 +5,8 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
@@ -37,13 +39,13 @@ func AlbumCover(router *gin.RouterGroup) {
start := time.Now() start := time.Now()
conf := service.Config() conf := service.Config()
thumbName := thumb.Name(c.Param("size")) thumbName := thumb.Name(sanitize.Token(c.Param("size")))
uid := c.Param("uid") uid := sanitize.IdString(c.Param("uid"))
size, ok := thumb.Sizes[thumbName] size, ok := thumb.Sizes[thumbName]
if !ok { if !ok {
log.Errorf("%s: invalid size %s", albumCover, thumbName) log.Errorf("%s: invalid size %s", albumCover, txt.LogParam(thumbName.String()))
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg) c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
return return
} }
@@ -84,7 +86,7 @@ func AlbumCover(router *gin.RouterGroup) {
fileName := photoprism.FileName(f.FileRoot, f.FileName) fileName := photoprism.FileName(f.FileRoot, f.FileName)
if !fs.FileExists(fileName) { if !fs.FileExists(fileName) {
log.Errorf("%s: found no original for %s", albumCover, fileName) log.Errorf("%s: found no original for %s", albumCover, txt.LogParam(fileName))
c.Data(http.StatusOK, "image/svg+xml", albumIconSvg) c.Data(http.StatusOK, "image/svg+xml", albumIconSvg)
// Set missing flag so that the file doesn't show up in search results anymore. // Set missing flag so that the file doesn't show up in search results anymore.
@@ -149,13 +151,13 @@ func LabelCover(router *gin.RouterGroup) {
start := time.Now() start := time.Now()
conf := service.Config() conf := service.Config()
thumbName := thumb.Name(c.Param("size")) thumbName := thumb.Name(sanitize.Token(c.Param("size")))
uid := c.Param("uid") uid := sanitize.IdString(c.Param("uid"))
size, ok := thumb.Sizes[thumbName] size, ok := thumb.Sizes[thumbName]
if !ok { if !ok {
log.Errorf("%s: invalid size %s", labelCover, thumbName) log.Errorf("%s: invalid size %s", labelCover, txt.LogParam(thumbName.String()))
c.Data(http.StatusOK, "image/svg+xml", labelIconSvg) c.Data(http.StatusOK, "image/svg+xml", labelIconSvg)
return return
} }

View File

@@ -3,16 +3,16 @@ package api
import ( import (
"net/http" "net/http"
"github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/txt"
"github.com/gin-gonic/gin" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/txt"
) )
// TODO: GET /api/v1/dl/file/:hash // TODO: GET /api/v1/dl/file/:hash
@@ -44,7 +44,7 @@ func GetDownload(router *gin.RouterGroup) {
return return
} }
fileHash := c.Param("hash") fileHash := sanitize.Token(c.Param("hash"))
f, err := query.FileByHash(fileHash) f, err := query.FileByHash(fileHash)

View File

@@ -3,6 +3,8 @@ package api
import ( import (
"net/http" "net/http"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
@@ -56,7 +58,7 @@ func UpdateFace(router *gin.RouterGroup) {
return return
} }
faceId := c.Param("id") faceId := sanitize.Token(c.Param("id"))
m := entity.FindFace(faceId) m := entity.FindFace(faceId)
if m == nil { if m == nil {

View File

@@ -3,6 +3,8 @@ package api
import ( import (
"net/http" "net/http"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
@@ -22,7 +24,7 @@ func GetFile(router *gin.RouterGroup) {
return return
} }
p, err := query.FileByHash(c.Param("hash")) p, err := query.FileByHash(sanitize.Token(c.Param("hash")))
if err != nil { if err != nil {
AbortEntityNotFound(c) AbortEntityNotFound(c)

View File

@@ -4,6 +4,8 @@ import (
"net/http" "net/http"
"path/filepath" "path/filepath"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
@@ -35,8 +37,8 @@ func DeleteFile(router *gin.RouterGroup) {
return return
} }
photoUID := c.Param("uid") photoUID := sanitize.IdString(c.Param("uid"))
fileUID := c.Param("file_uid") fileUID := sanitize.IdString(c.Param("file_uid"))
file, err := query.FileByUID(fileUID) file, err := query.FileByUID(fileUID)

View File

@@ -5,6 +5,8 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
@@ -36,7 +38,7 @@ func FolderCover(router *gin.RouterGroup) {
start := time.Now() start := time.Now()
conf := service.Config() conf := service.Config()
uid := c.Param("uid") uid := c.Param("uid")
thumbName := thumb.Name(c.Param("size")) thumbName := thumb.Name(sanitize.Token(c.Param("size")))
download := c.Query("download") != "" download := c.Query("download") != ""
size, ok := thumb.Sizes[thumbName] size, ok := thumb.Sizes[thumbName]

View File

@@ -23,7 +23,7 @@ func AddCoverCacheHeader(c *gin.Context) {
AddCacheHeader(c, CoverCacheTTL) AddCacheHeader(c, CoverCacheTTL)
} }
// AddCacheHeader adds thumbnail cache control headers to the response. // AddThumbCacheHeader adds thumbnail cache control headers to the response.
func AddThumbCacheHeader(c *gin.Context) { func AddThumbCacheHeader(c *gin.Context) {
c.Header("Cache-Control", fmt.Sprintf("private, max-age=%s, no-transform, immutable", ThumbCacheTTL.String())) c.Header("Cache-Control", fmt.Sprintf("private, max-age=%s, no-transform, immutable", ThumbCacheTTL.String()))
} }

View File

@@ -77,7 +77,7 @@ func StartImport(router *gin.RouterGroup) {
} }
if len(f.Albums) > 0 { if len(f.Albums) > 0 {
log.Debugf("import: adding files to album %s", strings.Join(f.Albums, " and ")) log.Debugf("import: adding files to album %s", txt.LogParam(strings.Join(f.Albums, " and ")))
opt.Albums = f.Albums opt.Albums = f.Albums
} }

View File

@@ -3,6 +3,8 @@ package api
import ( import (
"net/http" "net/http"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
@@ -33,7 +35,7 @@ func UpdateLabel(router *gin.RouterGroup) {
return return
} }
id := c.Param("uid") id := sanitize.IdString(c.Param("uid"))
m, err := query.LabelByUID(id) m, err := query.LabelByUID(id)
if err != nil { if err != nil {
@@ -67,7 +69,7 @@ func LikeLabel(router *gin.RouterGroup) {
return return
} }
id := c.Param("uid") id := sanitize.IdString(c.Param("uid"))
label, err := query.LabelByUID(id) label, err := query.LabelByUID(id)
if err != nil { if err != nil {
@@ -107,7 +109,7 @@ func DislikeLabel(router *gin.RouterGroup) {
return return
} }
id := c.Param("uid") id := sanitize.IdString(c.Param("uid"))
label, err := query.LabelByUID(id) label, err := query.LabelByUID(id)
if err != nil { if err != nil {

View File

@@ -5,12 +5,15 @@ import (
"strings" "strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/event"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
"github.com/photoprism/photoprism/internal/i18n" "github.com/photoprism/photoprism/internal/i18n"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/txt" "github.com/photoprism/photoprism/pkg/txt"
) )
@@ -30,7 +33,7 @@ func UpdateLink(c *gin.Context) {
return return
} }
link := entity.FindLink(c.Param("link")) link := entity.FindLink(sanitize.Token(c.Param("link")))
link.SetSlug(f.ShareSlug) link.SetSlug(f.ShareSlug)
link.MaxViews = f.MaxViews link.MaxViews = f.MaxViews
@@ -70,7 +73,7 @@ func DeleteLink(c *gin.Context) {
return return
} }
link := entity.FindLink(c.Param("link")) link := entity.FindLink(sanitize.Token(c.Param("link")))
if err := link.Delete(); err != nil { if err := link.Delete(); err != nil {
c.AbortWithStatusJSON(http.StatusConflict, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusConflict, gin.H{"error": txt.UcFirst(err.Error())})
@@ -102,7 +105,7 @@ func CreateLink(c *gin.Context) {
return return
} }
link := entity.NewLink(c.Param("uid"), f.CanComment, f.CanEdit) link := entity.NewLink(sanitize.IdString(c.Param("uid")), f.CanComment, f.CanEdit)
link.SetSlug(f.ShareSlug) link.SetSlug(f.ShareSlug)
link.MaxViews = f.MaxViews link.MaxViews = f.MaxViews
@@ -132,7 +135,7 @@ func CreateLink(c *gin.Context) {
// POST /api/v1/albums/:uid/links // POST /api/v1/albums/:uid/links
func CreateAlbumLink(router *gin.RouterGroup) { func CreateAlbumLink(router *gin.RouterGroup) {
router.POST("/albums/:uid/links", func(c *gin.Context) { router.POST("/albums/:uid/links", func(c *gin.Context) {
if _, err := query.AlbumByUID(c.Param("uid")); err != nil { if _, err := query.AlbumByUID(sanitize.IdString(c.Param("uid"))); err != nil {
Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound) Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound)
return return
} }
@@ -158,7 +161,7 @@ func DeleteAlbumLink(router *gin.RouterGroup) {
// GET /api/v1/albums/:uid/links // GET /api/v1/albums/:uid/links
func GetAlbumLinks(router *gin.RouterGroup) { func GetAlbumLinks(router *gin.RouterGroup) {
router.GET("/albums/:uid/links", func(c *gin.Context) { router.GET("/albums/:uid/links", func(c *gin.Context) {
m, err := query.AlbumByUID(c.Param("uid")) m, err := query.AlbumByUID(sanitize.IdString(c.Param("uid")))
if err != nil { if err != nil {
Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound) Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound)
@@ -172,7 +175,7 @@ func GetAlbumLinks(router *gin.RouterGroup) {
// POST /api/v1/photos/:uid/links // POST /api/v1/photos/:uid/links
func CreatePhotoLink(router *gin.RouterGroup) { func CreatePhotoLink(router *gin.RouterGroup) {
router.POST("/photos/:uid/links", func(c *gin.Context) { router.POST("/photos/:uid/links", func(c *gin.Context) {
if _, err := query.PhotoByUID(c.Param("uid")); err != nil { if _, err := query.PhotoByUID(sanitize.IdString(c.Param("uid"))); err != nil {
AbortEntityNotFound(c) AbortEntityNotFound(c)
return return
} }
@@ -198,7 +201,7 @@ func DeletePhotoLink(router *gin.RouterGroup) {
// GET /api/v1/photos/:uid/links // GET /api/v1/photos/:uid/links
func GetPhotoLinks(router *gin.RouterGroup) { func GetPhotoLinks(router *gin.RouterGroup) {
router.GET("/photos/:uid/links", func(c *gin.Context) { router.GET("/photos/:uid/links", func(c *gin.Context) {
m, err := query.PhotoByUID(c.Param("uid")) m, err := query.PhotoByUID(sanitize.IdString(c.Param("uid")))
if err != nil { if err != nil {
Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound) Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound)
@@ -212,7 +215,7 @@ func GetPhotoLinks(router *gin.RouterGroup) {
// POST /api/v1/labels/:uid/links // POST /api/v1/labels/:uid/links
func CreateLabelLink(router *gin.RouterGroup) { func CreateLabelLink(router *gin.RouterGroup) {
router.POST("/labels/:uid/links", func(c *gin.Context) { router.POST("/labels/:uid/links", func(c *gin.Context) {
if _, err := query.LabelByUID(c.Param("uid")); err != nil { if _, err := query.LabelByUID(sanitize.IdString(c.Param("uid"))); err != nil {
Abort(c, http.StatusNotFound, i18n.ErrLabelNotFound) Abort(c, http.StatusNotFound, i18n.ErrLabelNotFound)
return return
} }
@@ -238,7 +241,7 @@ func DeleteLabelLink(router *gin.RouterGroup) {
// GET /api/v1/labels/:uid/links // GET /api/v1/labels/:uid/links
func GetLabelLinks(router *gin.RouterGroup) { func GetLabelLinks(router *gin.RouterGroup) {
router.GET("/labels/:uid/links", func(c *gin.Context) { router.GET("/labels/:uid/links", func(c *gin.Context) {
m, err := query.LabelByUID(c.Param("uid")) m, err := query.LabelByUID(sanitize.IdString(c.Param("uid")))
if err != nil { if err != nil {
Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound) Abort(c, http.StatusNotFound, i18n.ErrAlbumNotFound)

View File

@@ -14,7 +14,9 @@ import (
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/txt" "github.com/photoprism/photoprism/pkg/txt"
) )
@@ -50,7 +52,7 @@ func GetPhoto(router *gin.RouterGroup) {
return return
} }
p, err := query.PhotoPreloadByUID(c.Param("uid")) p, err := query.PhotoPreloadByUID(sanitize.IdString(c.Param("uid")))
if err != nil { if err != nil {
AbortEntityNotFound(c) AbortEntityNotFound(c)
@@ -73,7 +75,7 @@ func UpdatePhoto(router *gin.RouterGroup) {
return return
} }
uid := c.Param("uid") uid := sanitize.IdString(c.Param("uid"))
m, err := query.PhotoByUID(uid) m, err := query.PhotoByUID(uid)
if err != nil { if err != nil {
@@ -135,7 +137,7 @@ func GetPhotoDownload(router *gin.RouterGroup) {
return return
} }
f, err := query.FileByPhotoUID(c.Param("uid")) f, err := query.FileByPhotoUID(sanitize.IdString(c.Param("uid")))
if err != nil { if err != nil {
c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg) c.Data(http.StatusNotFound, "image/svg+xml", photoIconSvg)
@@ -171,7 +173,7 @@ func GetPhotoYaml(router *gin.RouterGroup) {
return return
} }
p, err := query.PhotoPreloadByUID(c.Param("uid")) p, err := query.PhotoPreloadByUID(sanitize.IdString(c.Param("uid")))
if err != nil { if err != nil {
c.AbortWithStatus(http.StatusNotFound) c.AbortWithStatus(http.StatusNotFound)
@@ -186,7 +188,7 @@ func GetPhotoYaml(router *gin.RouterGroup) {
} }
if c.Query("download") != "" { if c.Query("download") != "" {
AddDownloadHeader(c, c.Param("uid")+fs.YamlExt) AddDownloadHeader(c, sanitize.IdString(c.Param("uid"))+fs.YamlExt)
} }
c.Data(http.StatusOK, "text/x-yaml; charset=utf-8", data) c.Data(http.StatusOK, "text/x-yaml; charset=utf-8", data)
@@ -206,7 +208,7 @@ func ApprovePhoto(router *gin.RouterGroup) {
return return
} }
id := c.Param("uid") id := sanitize.IdString(c.Param("uid"))
m, err := query.PhotoByUID(id) m, err := query.PhotoByUID(id)
if err != nil { if err != nil {
@@ -241,7 +243,7 @@ func LikePhoto(router *gin.RouterGroup) {
return return
} }
id := c.Param("uid") id := sanitize.IdString(c.Param("uid"))
m, err := query.PhotoByUID(id) m, err := query.PhotoByUID(id)
if err != nil { if err != nil {
@@ -276,7 +278,7 @@ func DislikePhoto(router *gin.RouterGroup) {
return return
} }
id := c.Param("uid") id := sanitize.IdString(c.Param("uid"))
m, err := query.PhotoByUID(id) m, err := query.PhotoByUID(id)
if err != nil { if err != nil {
@@ -312,8 +314,8 @@ func PhotoPrimary(router *gin.RouterGroup) {
return return
} }
uid := c.Param("uid") uid := sanitize.IdString(c.Param("uid"))
fileUID := c.Param("file_uid") fileUID := sanitize.IdString(c.Param("file_uid"))
err := query.SetPhotoPrimary(uid, fileUID) err := query.SetPhotoPrimary(uid, fileUID)
if err != nil { if err != nil {

View File

@@ -4,6 +4,8 @@ import (
"net/http" "net/http"
"strconv" "strconv"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/classify" "github.com/photoprism/photoprism/internal/classify"
@@ -27,7 +29,7 @@ func AddPhotoLabel(router *gin.RouterGroup) {
return return
} }
m, err := query.PhotoByUID(c.Param("uid")) m, err := query.PhotoByUID(sanitize.IdString(c.Param("uid")))
if err != nil { if err != nil {
AbortEntityNotFound(c) AbortEntityNotFound(c)
@@ -68,7 +70,7 @@ func AddPhotoLabel(router *gin.RouterGroup) {
} }
} }
p, err := query.PhotoPreloadByUID(c.Param("uid")) p, err := query.PhotoPreloadByUID(sanitize.IdString(c.Param("uid")))
if err != nil { if err != nil {
AbortEntityNotFound(c) AbortEntityNotFound(c)
@@ -102,14 +104,14 @@ func RemovePhotoLabel(router *gin.RouterGroup) {
return return
} }
m, err := query.PhotoByUID(c.Param("uid")) m, err := query.PhotoByUID(sanitize.IdString(c.Param("uid")))
if err != nil { if err != nil {
AbortEntityNotFound(c) AbortEntityNotFound(c)
return return
} }
labelId, err := strconv.Atoi(c.Param("id")) labelId, err := strconv.Atoi(sanitize.Token(c.Param("id")))
if err != nil { if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": txt.UcFirst(err.Error())})
@@ -130,7 +132,7 @@ func RemovePhotoLabel(router *gin.RouterGroup) {
logError("label", entity.Db().Save(&label).Error) logError("label", entity.Db().Save(&label).Error)
} }
p, err := query.PhotoPreloadByUID(c.Param("uid")) p, err := query.PhotoPreloadByUID(sanitize.IdString(c.Param("uid")))
if err != nil { if err != nil {
AbortEntityNotFound(c) AbortEntityNotFound(c)
@@ -144,7 +146,7 @@ func RemovePhotoLabel(router *gin.RouterGroup) {
return return
} }
PublishPhotoEvent(EntityUpdated, c.Param("uid"), c) PublishPhotoEvent(EntityUpdated, sanitize.IdString(c.Param("uid")), c)
event.Success("label removed") event.Success("label removed")
@@ -168,14 +170,14 @@ func UpdatePhotoLabel(router *gin.RouterGroup) {
// TODO: Code clean-up, simplify // TODO: Code clean-up, simplify
m, err := query.PhotoByUID(c.Param("uid")) m, err := query.PhotoByUID(sanitize.IdString(c.Param("uid")))
if err != nil { if err != nil {
AbortEntityNotFound(c) AbortEntityNotFound(c)
return return
} }
labelId, err := strconv.Atoi(c.Param("id")) labelId, err := strconv.Atoi(sanitize.Token(c.Param("id")))
if err != nil { if err != nil {
c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": txt.UcFirst(err.Error())}) c.AbortWithStatusJSON(http.StatusNotFound, gin.H{"error": txt.UcFirst(err.Error())})
@@ -199,7 +201,7 @@ func UpdatePhotoLabel(router *gin.RouterGroup) {
return return
} }
p, err := query.PhotoPreloadByUID(c.Param("uid")) p, err := query.PhotoPreloadByUID(sanitize.IdString(c.Param("uid")))
if err != nil { if err != nil {
AbortEntityNotFound(c) AbortEntityNotFound(c)
@@ -211,7 +213,7 @@ func UpdatePhotoLabel(router *gin.RouterGroup) {
return return
} }
PublishPhotoEvent(EntityUpdated, c.Param("uid"), c) PublishPhotoEvent(EntityUpdated, sanitize.IdString(c.Param("uid")), c)
event.Success("label saved") event.Success("label saved")

View File

@@ -5,15 +5,16 @@ import (
"path/filepath" "path/filepath"
"time" "time"
"github.com/photoprism/photoprism/internal/crop"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/crop"
"github.com/photoprism/photoprism/internal/photoprism" "github.com/photoprism/photoprism/internal/photoprism"
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/internal/thumb" "github.com/photoprism/photoprism/internal/thumb"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/pkg/txt" "github.com/photoprism/photoprism/pkg/txt"
) )
@@ -37,16 +38,16 @@ func GetThumb(router *gin.RouterGroup) {
start := time.Now() start := time.Now()
conf := service.Config() conf := service.Config()
download := c.Query("download") != "" download := c.Query("download") != ""
fileHash, cropArea := crop.ParseThumb(c.Param("thumb")) fileHash, cropArea := crop.ParseThumb(sanitize.Token(c.Param("thumb")))
// Is cropped thumbnail? // Is cropped thumbnail?
if cropArea != "" { if cropArea != "" {
cropName := crop.Name(c.Param("size")) cropName := crop.Name(sanitize.Token(c.Param("size")))
cropSize, ok := crop.Sizes[cropName] cropSize, ok := crop.Sizes[cropName]
if !ok { if !ok {
log.Errorf("%s: invalid size %s", logPrefix, cropName) log.Errorf("%s: invalid size %s", logPrefix, txt.LogParam(string(cropName)))
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg) c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
return return
} }
@@ -74,12 +75,12 @@ func GetThumb(router *gin.RouterGroup) {
return return
} }
thumbName := thumb.Name(c.Param("size")) thumbName := thumb.Name(sanitize.Token(c.Param("size")))
size, ok := thumb.Sizes[thumbName] size, ok := thumb.Sizes[thumbName]
if !ok { if !ok {
log.Errorf("%s: invalid size %s", logPrefix, thumbName) log.Errorf("%s: invalid size %s", logPrefix, txt.LogParam(thumbName.String()))
c.Data(http.StatusOK, "image/svg+xml", photoIconSvg) c.Data(http.StatusOK, "image/svg+xml", photoIconSvg)
return return
} }

View File

@@ -5,6 +5,8 @@ import (
"net/http" "net/http"
"path/filepath" "path/filepath"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
@@ -31,7 +33,7 @@ func PhotoUnstack(router *gin.RouterGroup) {
} }
conf := service.Config() conf := service.Config()
fileUID := c.Param("file_uid") fileUID := sanitize.IdString(c.Param("file_uid"))
file, err := query.FileByUID(fileUID) file, err := query.FileByUID(fileUID)
if err != nil { if err != nil {

View File

@@ -3,6 +3,8 @@ package api
import ( import (
"net/http" "net/http"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/gin-gonic/gin/binding" "github.com/gin-gonic/gin/binding"
@@ -59,7 +61,7 @@ func SearchGeo(router *gin.RouterGroup) {
var resp []byte var resp []byte
// Render JSON response. // Render JSON response.
switch c.Param("format") { switch sanitize.Token(c.Param("format")) {
case "view": case "view":
conf := service.Config() conf := service.Config()
resp, err = photos.ViewerJSON(conf.ContentUri(), conf.ApiUri(), conf.PreviewToken(), conf.DownloadToken()) resp, err = photos.ViewerJSON(conf.ContentUri(), conf.ApiUri(), conf.PreviewToken(), conf.DownloadToken())

View File

@@ -3,6 +3,8 @@ package api
import ( import (
"net/http" "net/http"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
@@ -89,7 +91,7 @@ func CreateSession(router *gin.RouterGroup) {
// DELETE /api/v1/session/:id // DELETE /api/v1/session/:id
func DeleteSession(router *gin.RouterGroup) { func DeleteSession(router *gin.RouterGroup) {
router.DELETE("/session/:id", func(c *gin.Context) { router.DELETE("/session/:id", func(c *gin.Context) {
id := c.Param("id") id := sanitize.Token(c.Param("id"))
service.Session().Delete(id) service.Session().Delete(id)
@@ -126,7 +128,7 @@ func Auth(id string, resource acl.Resource, action acl.Action) session.Data {
// InvalidPreviewToken returns true if the token is invalid. // InvalidPreviewToken returns true if the token is invalid.
func InvalidPreviewToken(c *gin.Context) bool { func InvalidPreviewToken(c *gin.Context) bool {
token := c.Param("token") token := sanitize.Token(c.Param("token"))
if token == "" { if token == "" {
token = c.Query("t") token = c.Query("t")

View File

@@ -9,6 +9,8 @@ import (
"path" "path"
"time" "time"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/disintegration/imaging" "github.com/disintegration/imaging"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
@@ -29,8 +31,8 @@ func SharePreview(router *gin.RouterGroup) {
router.GET("/:token/:share/preview", func(c *gin.Context) { router.GET("/:token/:share/preview", func(c *gin.Context) {
conf := service.Config() conf := service.Config()
token := c.Param("token") token := sanitize.Token(c.Param("token"))
share := c.Param("share") share := sanitize.Token(c.Param("share"))
links := entity.FindLinks(token, share) links := entity.FindLinks(token, share)
if len(links) != 1 { if len(links) != 1 {
@@ -51,13 +53,13 @@ func SharePreview(router *gin.RouterGroup) {
yesterday := time.Now().Add(-24 * time.Hour) yesterday := time.Now().Add(-24 * time.Hour)
if info, err := os.Stat(previewFilename); err != nil { if info, err := os.Stat(previewFilename); err != nil {
log.Debugf("share: creating new preview for %s", share) log.Debugf("share: creating new preview for %s", txt.LogParam(share))
} else if info.ModTime().After(yesterday) { } else if info.ModTime().After(yesterday) {
log.Debugf("share: using cached preview for %s", share) log.Debugf("share: using cached preview for %s", txt.LogParam(share))
c.File(previewFilename) c.File(previewFilename)
return return
} else if err := os.Remove(previewFilename); err != nil { } else if err := os.Remove(previewFilename); err != nil {
log.Errorf("share: could not remove old preview of %s", share) log.Errorf("share: could not remove old preview of %s", txt.LogParam(share))
c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview()) c.Redirect(http.StatusTemporaryRedirect, conf.SitePreview())
return return
} }

View File

@@ -3,6 +3,8 @@ package api
import ( import (
"net/http" "net/http"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
@@ -26,7 +28,7 @@ func GetSubject(router *gin.RouterGroup) {
return return
} }
if subj := entity.FindSubject(c.Param("uid")); subj == nil { if subj := entity.FindSubject(sanitize.IdString(c.Param("uid"))); subj == nil {
Abort(c, http.StatusNotFound, i18n.ErrSubjectNotFound) Abort(c, http.StatusNotFound, i18n.ErrSubjectNotFound)
return return
} else { } else {
@@ -54,7 +56,7 @@ func UpdateSubject(router *gin.RouterGroup) {
return return
} }
uid := c.Param("uid") uid := sanitize.IdString(c.Param("uid"))
m := entity.FindSubject(uid) m := entity.FindSubject(uid)
if m == nil { if m == nil {
@@ -107,7 +109,7 @@ func LikeSubject(router *gin.RouterGroup) {
return return
} }
uid := c.Param("uid") uid := sanitize.IdString(c.Param("uid"))
subj := entity.FindSubject(uid) subj := entity.FindSubject(uid)
if subj == nil { if subj == nil {
@@ -141,7 +143,7 @@ func DislikeSubject(router *gin.RouterGroup) {
return return
} }
uid := c.Param("uid") uid := sanitize.IdString(c.Param("uid"))
subj := entity.FindSubject(uid) subj := entity.FindSubject(uid)
if subj == nil { if subj == nil {

View File

@@ -63,7 +63,7 @@ func Upload(router *gin.RouterGroup) {
log.Debugf("upload: saving file %s", txt.LogParam(file.Filename)) log.Debugf("upload: saving file %s", txt.LogParam(file.Filename))
if err := c.SaveUploadedFile(file, filename); err != nil { if err := c.SaveUploadedFile(file, filename); err != nil {
log.Errorf("upload: failed saving file %s", filepath.Base(file.Filename)) log.Errorf("upload: failed saving file %s", txt.LogParam(filepath.Base(file.Filename)))
AbortBadRequest(c) AbortBadRequest(c)
return return
} }

View File

@@ -3,6 +3,8 @@ package api
import ( import (
"net/http" "net/http"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/entity"
@@ -28,7 +30,7 @@ func ChangePassword(router *gin.RouterGroup) {
return return
} }
uid := c.Param("uid") uid := sanitize.IdString(c.Param("uid"))
m := entity.FindUserByUID(uid) m := entity.FindUserByUID(uid)
if s.User.UserUID != m.UserUID { if s.User.UserUID != m.UserUID {

View File

@@ -3,6 +3,8 @@ package api
import ( import (
"net/http" "net/http"
"github.com/photoprism/photoprism/pkg/sanitize"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@@ -24,8 +26,8 @@ func GetVideo(router *gin.RouterGroup) {
return return
} }
fileHash := c.Param("hash") fileHash := sanitize.Token(c.Param("hash"))
typeName := c.Param("type") typeName := sanitize.Token(c.Param("type"))
videoType, ok := video.Types[typeName] videoType, ok := video.Types[typeName]

View File

@@ -11,7 +11,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/photoprism/photoprism/pkg/rnd" "github.com/gin-gonic/gin"
"github.com/photoprism/photoprism/internal/acl" "github.com/photoprism/photoprism/internal/acl"
"github.com/photoprism/photoprism/internal/form" "github.com/photoprism/photoprism/internal/form"
@@ -20,9 +20,8 @@ import (
"github.com/photoprism/photoprism/internal/query" "github.com/photoprism/photoprism/internal/query"
"github.com/photoprism/photoprism/internal/service" "github.com/photoprism/photoprism/internal/service"
"github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/fs"
"github.com/photoprism/photoprism/pkg/rnd"
"github.com/photoprism/photoprism/pkg/txt" "github.com/photoprism/photoprism/pkg/txt"
"github.com/gin-gonic/gin"
) )
// POST /api/v1/zip // POST /api/v1/zip
@@ -146,7 +145,7 @@ func DownloadZip(router *gin.RouterGroup) {
zipFileName := path.Join(zipPath, zipBaseName) zipFileName := path.Join(zipPath, zipBaseName)
if !fs.FileExists(zipFileName) { if !fs.FileExists(zipFileName) {
log.Errorf("could not find zip file: %s", zipFileName) log.Errorf("could not find zip file: %s", txt.LogParam(zipFileName))
c.Data(404, "image/svg+xml", photoIconSvg) c.Data(404, "image/svg+xml", photoIconSvg)
return return
} }

25
pkg/sanitize/hex.go Normal file
View File

@@ -0,0 +1,25 @@
package sanitize
import (
"strings"
)
// Hex removes invalid character from a hex string and makes it lowercase.
func Hex(s string) string {
if s == "" {
return s
}
s = strings.ToLower(s)
// Remove all invalid characters.
s = strings.Map(func(r rune) rune {
if (r < '0' || r > '9') && (r < 'a' || r > 'f') {
return -1
}
return r
}, s)
return s
}

19
pkg/sanitize/hex_test.go Normal file
View File

@@ -0,0 +1,19 @@
package sanitize
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestHex(t *testing.T) {
t.Run("UUID", func(t *testing.T) {
assert.Equal(t, "123e4567e89b12d3a456426614174000", Hex("123e4567-e89b-12d3-A456-426614174000 "))
})
t.Run("ThumbSize", func(t *testing.T) {
assert.Equal(t, "ef224", Hex("left_224"))
})
t.Run("SHA1", func(t *testing.T) {
assert.Equal(t, "5c50ae14f339364eb8224f23c2d3abc7e79016f3eaded", Hex("5c50ae14f339364eb8224f23c2d3abc7e79016f3 README.md"))
})
}

41
pkg/sanitize/id.go Normal file
View File

@@ -0,0 +1,41 @@
package sanitize
import (
"strconv"
"strings"
)
// IdString removes invalid character from an id string.
func IdString(s string) string {
if s == "" || len(s) > 256 {
return ""
}
s = strings.ToLower(s)
// Remove all invalid characters.
s = strings.Map(func(r rune) rune {
if (r < '0' || r > '9') && (r < 'a' || r > 'z') && r != '-' && r != '_' && r != ':' {
return -1
}
return r
}, s)
return s
}
// IdUint converts the string converted to an unsigned integer and 0 if the string is invalid.
func IdUint(s string) uint {
if s == "" || len(s) > 64 {
return 0
}
result, err := strconv.ParseUint(s, 10, 32)
if err != nil {
return 0
}
return uint(result)
}

31
pkg/sanitize/id_test.go Normal file
View File

@@ -0,0 +1,31 @@
package sanitize
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestIdString(t *testing.T) {
t.Run("UUID", func(t *testing.T) {
assert.Equal(t, "123e4567-e89b-12d3-a456-426614174000", IdString("123e4567-e89b-12d3-A456-426614174000 "))
})
t.Run("ThumbSize", func(t *testing.T) {
assert.Equal(t, "left_224", IdString("left_224"))
})
t.Run("SHA1", func(t *testing.T) {
assert.Equal(t, "5c50ae14f339364eb8224f23c2d3abc7e79016f3readmemd", IdString("5c50ae14f339364eb8224f23c2d3abc7e79016f3 README.md"))
})
}
func TestIdUint(t *testing.T) {
t.Run("12334545", func(t *testing.T) {
assert.Equal(t, uint(12334545), IdUint("12334545"))
})
t.Run("ThumbSize", func(t *testing.T) {
assert.Equal(t, uint(0), IdUint("left_224"))
})
t.Run("SHA1", func(t *testing.T) {
assert.Equal(t, uint(0), IdUint("5c50ae14f339364eb8224f23c2d3abc7e79016f3 README.md"))
})
}

32
pkg/sanitize/sanitize.go Normal file
View File

@@ -0,0 +1,32 @@
/*
Package sanitize provides input value sanitation functions
Copyright (c) 2018 - 2021 Michael Mayer <hello@photoprism.app>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
PhotoPrism® is a registered trademark of Michael Mayer. You may use it as required
to describe our software, run your own server, for educational purposes, but not for
offering commercial goods, products, or services without prior written permission.
In other words, please ask.
Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
*/
package sanitize

23
pkg/sanitize/token.go Normal file
View File

@@ -0,0 +1,23 @@
package sanitize
import (
"strings"
)
// Token removes invalid character from a token string.
func Token(s string) string {
if s == "" {
return s
}
// Remove all invalid characters.
s = strings.Map(func(r rune) rune {
if (r < '0' || r > '9') && (r < 'a' || r > 'z') && (r < 'A' || r > 'Z') && r != '-' && r != '_' && r != ':' {
return -1
}
return r
}, s)
return s
}

View File

@@ -0,0 +1,19 @@
package sanitize
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestToken(t *testing.T) {
t.Run("UUID", func(t *testing.T) {
assert.Equal(t, "123e4567-e89b-12d3-A456-426614174000", Token("123e4567-e89b-12d3-A456-426614174000 "))
})
t.Run("ThumbSize", func(t *testing.T) {
assert.Equal(t, "left_224", Token("left_224"))
})
t.Run("SHA1", func(t *testing.T) {
assert.Equal(t, "5c50ae14f339364eb8224f23c2d3abc7e79016f3READMEmd", Token("5c50ae14f339364eb8224f23c2d3abc7e79016f3 README.md"))
})
}