AI: Reject API requests with content-type multipart/form-data #127 #1090

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer
2025-04-07 23:05:34 +02:00
parent d304509c0d
commit 0f76186663
5 changed files with 48 additions and 0 deletions

View File

@@ -8,6 +8,7 @@ import (
"github.com/photoprism/photoprism/internal/ai/vision"
"github.com/photoprism/photoprism/internal/auth/acl"
"github.com/photoprism/photoprism/internal/photoprism/get"
"github.com/photoprism/photoprism/pkg/media/http/header"
)
// PostVisionCaption returns a suitable caption for an image.
@@ -31,6 +32,12 @@ func PostVisionCaption(router *gin.RouterGroup) {
var request vision.ApiRequest
// File uploads are not currently supported for this API endpoint.
if header.HasContentType(&c.Request.Header, header.ContentTypeMultipart) {
c.JSON(http.StatusBadRequest, vision.NewApiError(request.GetId(), http.StatusBadRequest))
return
}
// Assign and validate request form values.
if err := c.BindJSON(&request); err != nil {
c.JSON(http.StatusBadRequest, vision.NewApiError(request.GetId(), http.StatusBadRequest))

View File

@@ -8,6 +8,7 @@ import (
"github.com/photoprism/photoprism/internal/ai/vision"
"github.com/photoprism/photoprism/internal/auth/acl"
"github.com/photoprism/photoprism/internal/photoprism/get"
"github.com/photoprism/photoprism/pkg/media/http/header"
)
// PostVisionFaces returns the positions and embeddings of detected faces.
@@ -31,6 +32,12 @@ func PostVisionFaces(router *gin.RouterGroup) {
var request vision.ApiRequest
// File uploads are not currently supported for this API endpoint.
if header.HasContentType(&c.Request.Header, header.ContentTypeMultipart) {
c.JSON(http.StatusBadRequest, vision.NewApiError(request.GetId(), http.StatusBadRequest))
return
}
// Assign and validate request form values.
if err := c.BindJSON(&request); err != nil {
c.JSON(http.StatusBadRequest, vision.NewApiError(request.GetId(), http.StatusBadRequest))

View File

@@ -8,6 +8,7 @@ import (
"github.com/photoprism/photoprism/internal/ai/vision"
"github.com/photoprism/photoprism/internal/auth/acl"
"github.com/photoprism/photoprism/internal/photoprism/get"
"github.com/photoprism/photoprism/pkg/media/http/header"
)
// PostVisionLabels returns suitable labels for an image.
@@ -32,6 +33,12 @@ func PostVisionLabels(router *gin.RouterGroup) {
var request vision.ApiRequest
// File uploads are not currently supported for this API endpoint.
if header.HasContentType(&c.Request.Header, header.ContentTypeMultipart) {
c.JSON(http.StatusBadRequest, vision.NewApiError(request.GetId(), http.StatusBadRequest))
return
}
// Assign and validate request form values.
if err := c.BindJSON(&request); err != nil {
c.JSON(http.StatusBadRequest, vision.NewApiError(request.GetId(), http.StatusBadRequest))

View File

@@ -1,6 +1,7 @@
package header
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
@@ -36,3 +37,13 @@ func TestContent(t *testing.T) {
assert.Equal(t, "video/mp4; codecs=\"hvc1\"", ContentTypeMp4Hvc)
})
}
func TestHasContentType(t *testing.T) {
t.Run("True", func(t *testing.T) {
assert.True(t, HasContentType(&http.Header{"Content-Type": []string{"multipart/form-data"}}, ContentTypeMultipart))
})
t.Run("False", func(t *testing.T) {
assert.False(t, HasContentType(nil, ContentTypeMultipart))
assert.False(t, HasContentType(&http.Header{"Content-Type": []string{"application/json"}}, ContentTypeMultipart))
})
}

View File

@@ -1,5 +1,10 @@
package header
import (
"mime"
"net/http"
)
/*
Standard content types for use in HTTP headers and the web interface, see:
- https://developer.mozilla.org/en-US/docs/Web/Media/Guides/Formats/Video_codecs
@@ -101,3 +106,14 @@ const (
ContentTypePDF = "application/pdf"
ContentTypeZip = "application/zip"
)
// HasContentType checks weather the Content-Type header has the specified type.
func HasContentType(header *http.Header, contentType string) bool {
if header == nil || contentType == "" {
return false
} else if ct, _, err := mime.ParseMediaType(header.Get("Content-Type")); err == nil && ct == contentType {
return true
}
return false
}