Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/lib/errors/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const (
NotFoundCode = "NOT_FOUND"
// ConflictCode ...
ConflictCode = "CONFLICT"
// RangeUnsatisfy = "RequestRange_Unsatisfy"
RangeUnsatisfy = "RequestRange_Unsatisfy"
// UnAuthorizedCode ...
UnAuthorizedCode = "UNAUTHORIZED"
// BadRequestCode ...
Expand Down
1 change: 1 addition & 0 deletions src/lib/http/error.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ var (
errors.ViolateForeignKeyConstraintCode: http.StatusPreconditionFailed,
errors.PROJECTPOLICYVIOLATION: http.StatusPreconditionFailed,
errors.GeneralCode: http.StatusInternalServerError,
errors.RangeUnsatisfy: http.StatusRequestedRangeNotSatisfiable,
}
)

Expand Down
76 changes: 76 additions & 0 deletions src/server/middleware/blob/patch_blob_upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,98 @@
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 {
// Only record when patch blob upload success
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 {
Expand Down
3 changes: 2 additions & 1 deletion tests/ci/conformance_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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 .