Files
kubesphere/pkg/kapis/static/handler.go
KubeSphere CI Bot 447a51f08b feat: kubesphere 4.0 (#6115)
* feat: kubesphere 4.0

Signed-off-by: ci-bot <ci-bot@kubesphere.io>

* feat: kubesphere 4.0

Signed-off-by: ci-bot <ci-bot@kubesphere.io>

---------

Signed-off-by: ci-bot <ci-bot@kubesphere.io>
Co-authored-by: ks-ci-bot <ks-ci-bot@example.com>
Co-authored-by: joyceliu <joyceliu@yunify.com>
2024-09-06 11:05:52 +08:00

140 lines
3.7 KiB
Go

/*
* Please refer to the LICENSE file in the root directory of the project.
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
*/
package static
import (
"crypto/md5"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"io"
"mime"
"net/http"
"path/filepath"
"time"
"github.com/emicklei/go-restful/v3"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/klog/v2"
ksapi "kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/apiserver/rest"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
)
const (
imageCacheKeyPrefix = "static:images:"
mimePNG = "image/png"
mimeJPG = "image/jpeg"
mimeSVG = "image/svg+xml"
// https://mimesniff.spec.whatwg.org/#matching-an-image-type-pattern
mimeICON = "image/x-icon"
maxUploadSize = 2 * 1024 * 1024 // 2M
)
var supportedMIMETypes = sets.NewString(mimePNG, mimeJPG, mimeSVG, mimeICON)
type handler struct {
cache cache.Interface
}
func init() {
_ = mime.AddExtensionType(".ico", mimeICON)
}
func NewHandler(cache cache.Interface) rest.Handler {
return &handler{cache: cache}
}
func NewFakeHandler() rest.Handler {
return &handler{}
}
func (h *handler) uploadImage(req *restful.Request, resp *restful.Response) {
req.Request.Body = http.MaxBytesReader(resp, req.Request.Body, maxUploadSize)
if err := req.Request.ParseMultipartForm(maxUploadSize); err != nil {
ksapi.HandleBadRequest(resp, req, fmt.Errorf("failed to parse multipart form: %s", err))
return
}
imagePart, existed := req.Request.MultipartForm.File["image"]
if !existed || len(imagePart) == 0 {
ksapi.HandleBadRequest(resp, req, fmt.Errorf("invalid form data"))
return
}
header := imagePart[0]
ext := filepath.Ext(header.Filename)
contentType := mime.TypeByExtension(ext)
if contentType == "" {
contentType = header.Header.Get("Content-Type")
}
if !supportedMIMETypes.Has(contentType) {
ksapi.HandleBadRequest(resp, req, fmt.Errorf("the provided file format is not allowed"))
return
}
file, err := header.Open()
if err != nil {
ksapi.HandleInternalError(resp, req, err)
return
}
defer file.Close()
rawContent, err := io.ReadAll(file)
if err != nil {
ksapi.HandleInternalError(resp, req, err)
return
}
hasher := md5.New()
hasher.Write(rawContent)
hash := hasher.Sum(nil)
md5Hex := hex.EncodeToString(hash)
fileName := fmt.Sprintf("%s%s", md5Hex, ext)
key := fmt.Sprintf("%s%s", imageCacheKeyPrefix, fileName)
base64EncodedContent := base64.StdEncoding.EncodeToString(rawContent)
if err = h.cache.Set(key, base64EncodedContent, cache.NeverExpire); err != nil {
ksapi.HandleInternalError(resp, req, err)
return
}
result := map[string]string{"image": fileName}
_ = resp.WriteEntity(result)
}
func (h *handler) getImage(req *restful.Request, resp *restful.Response) {
fileName := req.PathParameter("file")
base64EncodedData, err := h.cache.Get(fmt.Sprintf("%s%s", imageCacheKeyPrefix, fileName))
if err != nil {
if errors.Is(err, cache.ErrNoSuchKey) {
ksapi.HandleNotFound(resp, req, fmt.Errorf("image %s not found", fileName))
return
}
ksapi.HandleInternalError(resp, req, err)
return
}
rawContent, err := base64.StdEncoding.DecodeString(base64EncodedData)
if err != nil {
klog.Warningf("failed to decode base64 encoded image data: %s, %v", fileName, err)
ksapi.HandleInternalError(resp, req, err)
return
}
contentType := mime.TypeByExtension(filepath.Ext(fileName))
resp.Header().Set("Content-Type", contentType)
resp.Header().Set("Cache-Control", "public, max-age=86400")
resp.Header().Set("Expires", time.Now().Add(86400*time.Second).Format(time.RFC1123))
_, err = resp.Write(rawContent)
if err != nil {
klog.Warningf("failed to write image data: %s, %v", fileName, err)
}
}