upload blob by chunk should return 416

Signed-off-by: yminer <yminer@vmware.com>
This commit is contained in:
yminer 2024-03-29 07:20:47 +00:00
parent aa4a142bc1
commit a4f05e0fc8
4 changed files with 81 additions and 1 deletions

View File

@ -19,6 +19,8 @@ const (
NotFoundCode = "NOT_FOUND"
// ConflictCode ...
ConflictCode = "CONFLICT"
// RangeUnsatisfy = "RequestRange_Unsatisfy"
RangeUnsatisfy = "RequestRange_Unsatisfy"
// UnAuthorizedCode ...
UnAuthorizedCode = "UNAUTHORIZED"
// BadRequestCode ...

View File

@ -43,6 +43,7 @@ var (
errors.ViolateForeignKeyConstraintCode: http.StatusPreconditionFailed,
errors.PROJECTPOLICYVIOLATION: http.StatusPreconditionFailed,
errors.GeneralCode: http.StatusInternalServerError,
errors.RangeUnsatisfy: http.StatusRequestedRangeNotSatisfiable,
}
)

View File

@ -15,15 +15,79 @@
package blob
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"time"
"github.com/goharbor/harbor/src/lib/errors"
"github.com/goharbor/harbor/src/pkg/distribution"
"github.com/goharbor/harbor/src/server/middleware"
)
type blobUploadState struct {
// name is the primary repository under which the blob will be linked.
Name string
// UUID identifies the upload.
UUID string
// offset contains the current progress of the upload.
Offset int64
// StartedAt is the original start time of the upload.
StartedAt time.Time
}
// /v2/conformance/testrepo/blobs/uploads/96a6fe7e-6683-4dce-a0d5-80fdc50f3822?_state=PwxpQahplvWdosoKCWat7zK2PtCo_4pUEEAmWzV2YOl7Ik5hbWUiOiJjb25mb3JtYW5jZS90ZXN0cmVwbyIsIlVVSUQiOiI5NmE2ZmU3ZS02NjgzLTRkY2UtYTBkNS04MGZkYzUwZjM4MjIiLCJPZmZzZXQiOjAsIlN0YXJ0ZWRBdCI6IjIwMjQtMDMtMTBUMTU6MzA6MjMuMjE1MjU1MzVaIn0%3D
func unpackUploadState(r *http.Request) (blobUploadState, error) {
var state blobUploadState
token := r.FormValue("_state")
tokenBytes, err := base64.URLEncoding.DecodeString(token)
if err != nil {
return state, err
}
secret := strings.TrimPrefix(r.Header.Get("Authorization"), "Bearer")
mac := hmac.New(sha256.New, []byte(secret))
if len(tokenBytes) < mac.Size() {
return state, err
}
macBytes := tokenBytes[:mac.Size()]
messageBytes := tokenBytes[mac.Size():]
mac.Write(messageBytes)
if !hmac.Equal(mac.Sum(nil), macBytes) {
return state, err
}
if err := json.Unmarshal(messageBytes, &state); err != nil {
return state, err
}
return state, nil
}
func isDisorder(state *blobUploadState, r *http.Request) (bool, error) {
cntRange := r.Header.Get("Content-Range")
startstr := strings.Split(cntRange, "-")[0]
offset := state.Offset
start, err := strconv.ParseInt(startstr, 10, 64)
if err != nil {
return false, err
}
if start > offset {
return true, nil
}
return false, nil
}
// PatchBlobUploadMiddleware middleware to record the accepted blob size for stream blob upload
func PatchBlobUploadMiddleware() func(http.Handler) http.Handler {
return middleware.AfterResponse(func(w http.ResponseWriter, r *http.Request, statusCode int) error {
@ -31,6 +95,18 @@ func PatchBlobUploadMiddleware() func(http.Handler) http.Handler {
if statusCode != http.StatusAccepted {
return nil
}
//check if disorder when upload by chunk
state, err := unpackUploadState(r)
if err != nil {
return err
}
disorder, err := isDisorder(&state, r)
if err != nil {
return err
}
if disorder {
return errors.New(nil).WithCode(errors.RangeUnsatisfy).WithMessage("Request Range is disordered")
}
size, err := parseAcceptedBlobSize(w.Header().Get("Range"))
if err != nil {

View File

@ -2,7 +2,7 @@
set -e
echo "get the conformance testing code..."
git clone https://github.com/opencontainers/distribution-spec.git
# git clone https://github.com/opencontainers/distribution-spec.git
function createPro {
echo "create testing project: $2"
@ -31,4 +31,5 @@ export OCI_CROSSMOUNT_NAMESPACE="crossmount/testrepo"
export OCI_AUTOMATIC_CROSSMOUNT="false"
cd ./distribution-spec/conformance
git checkout tags/v1.1.0
go test .