* 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>
140 lines
3.7 KiB
Go
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)
|
|
}
|
|
}
|