func (rh *RouteHandler) UpdateBlobUpload(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	name, ok := vars["name"]
	if !ok || name == "" {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	is := rh.getImageStore(name)
	sessionID, ok := vars["session_id"]
	if !ok || sessionID == "" {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	digests, ok := r.URL.Query()["digest"]
	if !ok || len(digests) != 1 {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	digest := digests[0]
	rh.c.Log.Info().Int64("r.ContentLength", r.ContentLength).Msg("DEBUG")
	contentPresent := true
	contentLen, err := strconv.ParseInt(r.Header.Get("Content-Length"), 10, 64)
	if err != nil {
		contentPresent = false
	}
	contentRangePresent := true
	if r.Header.Get("Content-Range") == "" {
		contentRangePresent = false
	}
	if !contentPresent && !contentRangePresent {
		w.WriteHeader(http.StatusBadRequest)
		return
	}
	var from, to int64
	if contentPresent {
		contentRange := r.Header.Get("Content-Range")
		if contentRange == "" {  
			from = 0
			if contentLen == 0 {
				goto finish  
			}
			to = contentLen
		} else if from, to, err = getContentRange(r); err != nil {  
			w.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
			return
		}
		_, err = is.PutBlobChunk(name, sessionID, from, to, r.Body)
		if err != nil {
			switch err {
			case io.ErrUnexpectedEOF:
				rh.c.Log.Warn().Msg("received unexpected EOF, removing .uploads/ files")
				if err = is.DeleteBlobUpload(name, sessionID); err != nil {
					rh.c.Log.Error().Err(err).Msgf("couldn't remove blobUpload %s in repo %s", sessionID, name)
				}
				w.WriteHeader(http.StatusInternalServerError)
			case errors.ErrBadUploadRange:
				WriteJSON(w, http.StatusBadRequest,
					NewErrorList(NewError(BLOB_UPLOAD_INVALID, map[string]string{"session_id": sessionID})))
			case errors.ErrRepoNotFound:
				WriteJSON(w, http.StatusNotFound,
					NewErrorList(NewError(NAME_UNKNOWN, map[string]string{"name": name})))
			case errors.ErrUploadNotFound:
				WriteJSON(w, http.StatusNotFound,
					NewErrorList(NewError(BLOB_UPLOAD_UNKNOWN, map[string]string{"session_id": sessionID})))
			default:
				rh.c.Log.Error().Err(err).Msg("unexpected error")
				w.WriteHeader(http.StatusInternalServerError)
			}
			return
		}
	}
finish:
	if err := is.FinishBlobUpload(name, sessionID, r.Body, digest); err != nil {
		switch err {
		case errors.ErrBadBlobDigest:
			WriteJSON(w, http.StatusBadRequest,
				NewErrorList(NewError(DIGEST_INVALID, map[string]string{"digest": digest})))
		case errors.ErrBadUploadRange:
			WriteJSON(w, http.StatusBadRequest,
				NewErrorList(NewError(BLOB_UPLOAD_INVALID, map[string]string{"session_id": sessionID})))
		case errors.ErrRepoNotFound:
			WriteJSON(w, http.StatusNotFound,
				NewErrorList(NewError(NAME_UNKNOWN, map[string]string{"name": name})))
		case errors.ErrUploadNotFound:
			WriteJSON(w, http.StatusNotFound,
				NewErrorList(NewError(BLOB_UPLOAD_UNKNOWN, map[string]string{"session_id": sessionID})))
		default:
			rh.c.Log.Error().Err(err).Msg("unexpected error")
			w.WriteHeader(http.StatusInternalServerError)
		}
		return
	}
	w.Header().Set("Location", fmt.Sprintf("/v2/%s/blobs/%s", name, digest))
	w.Header().Set("Content-Length", "0")
	w.Header().Set(DistContentDigestKey, digest)
	w.WriteHeader(http.StatusCreated)
}
UpdateBlobUpload godoc
@Summary Update image blob/layer upload
@Description Update and finish an image's blob/layer upload given a digest
@Accept  json
@Produce json
@Param   name     path    string     true        "repository name"
@Param   session_id     path    string     true        "upload session_id"
@Param 	digest	 query 	 string 		true				"blob/layer digest"
@Success 201 {string} string	"created"
@Header  202 {string} Location "/v2/{name}/blobs/uploads/{digest}"
@Header  200 {object} api.DistContentDigestKey
@Failure 404 {string} string "not found"
@Failure 500 {string} string "internal server error"
@Router /v2/{name}/blobs/uploads/{session_id} [put].
package api
import (
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"path"
	"sort"
	"strconv"
	"strings"
	"github.com/anuvu/zot/errors"
	ext "github.com/anuvu/zot/pkg/extensions"
	"github.com/anuvu/zot/pkg/log"
	"github.com/anuvu/zot/pkg/storage"
	_ "github.com/anuvu/zot/swagger"  
	"github.com/gorilla/mux"
	jsoniter "github.com/json-iterator/go"
	ispec "github.com/opencontainers/image-spec/specs-go/v1"
	httpSwagger "github.com/swaggo/http-swagger"
)
const (
	RoutePrefix          = "/v2"
	DistAPIVersion       = "Docker-Distribution-API-Version"
	DistContentDigestKey = "Docker-Content-Digest"
	BlobUploadUUID       = "Blob-Upload-UUID"
	DefaultMediaType     = "application/json"
	BinaryMediaType      = "application/octet-stream"
)
type RouteHandler struct {
	c *Controller
}
func NewRouteHandler(c *Controller) *RouteHandler {
	rh := &RouteHandler{c: c}
	rh.SetupRoutes()
	return rh
}
func (rh *RouteHandler) SetupRoutes() {
	rh.c.Router.Use(AuthHandler(rh.c))
	if rh.c.Config.AccessControl != nil && !isBearerAuthEnabled(rh.c.Config) && isAuthnEnabled(rh.c.Config) {
		rh.c.Log.Info().Msg("access control is being enabled")
		rh.c.Router.Use(AuthzHandler(rh.c))
	}
	g := rh.c.Router.PathPrefix(RoutePrefix).Subrouter()
	{
		g.HandleFunc(fmt.Sprintf("/{name:%s}/tags/list", NameRegexp.String()),
			rh.ListTags).Methods("GET")
		g.HandleFunc(fmt.Sprintf("/{name:%s}/manifests/{reference}", NameRegexp.String()),
			rh.CheckManifest).Methods("HEAD")
		g.HandleFunc(fmt.Sprintf("/{name:%s}/manifests/{reference}", NameRegexp.String()),
			rh.GetManifest).Methods("GET")
		g.HandleFunc(fmt.Sprintf("/{name:%s}/manifests/{reference}", NameRegexp.String()),
			rh.UpdateManifest).Methods("PUT")
		g.HandleFunc(fmt.Sprintf("/{name:%s}/manifests/{reference}", NameRegexp.String()),
			rh.DeleteManifest).Methods("DELETE")
		g.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/{digest}", NameRegexp.String()),
			rh.CheckBlob).Methods("HEAD")
		g.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/{digest}", NameRegexp.String()),
			rh.GetBlob).Methods("GET")
		g.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/{digest}", NameRegexp.String()),
			rh.DeleteBlob).Methods("DELETE")
		g.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/uploads/", NameRegexp.String()),
			rh.CreateBlobUpload).Methods("POST")
		g.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/uploads/{session_id}", NameRegexp.String()),
			rh.GetBlobUpload).Methods("GET")
		g.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/uploads/{session_id}", NameRegexp.String()),
			rh.PatchBlobUpload).Methods("PATCH")
		g.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/uploads/{session_id}", NameRegexp.String()),
			rh.UpdateBlobUpload).Methods("PUT")
		g.HandleFunc(fmt.Sprintf("/{name:%s}/blobs/uploads/{session_id}", NameRegexp.String()),
			rh.DeleteBlobUpload).Methods("DELETE")
		g.HandleFunc("/_catalog",
			rh.ListRepositories).Methods("GET")
		g.HandleFunc("/",
			rh.CheckVersionSupport).Methods("GET")
	}
	rh.c.Router.PathPrefix("/swagger/v2/").Methods("GET").Handler(httpSwagger.WrapHandler)
	if rh.c.Config != nil && rh.c.Config.Extensions != nil {
		ext.SetupRoutes(rh.c.Config, rh.c.Router, rh.c.StoreController, rh.c.Log)
	}
}
func (rh *RouteHandler) CheckVersionSupport(w http.ResponseWriter, r *http.Request) {
	w.Header().Set(DistAPIVersion, "registry/2.0")
	if rh.c.Config.HTTP.AllowReadAccess {
		if rh.c.Config.HTTP.Auth != nil {
			if rh.c.Config.HTTP.Auth.Bearer != nil {
				w.Header().Set("WWW-Authenticate", fmt.Sprintf("bearer realm=%s", rh.c.Config.HTTP.Auth.Bearer.Realm))
			} else {
				w.Header().Set("WWW-Authenticate", fmt.Sprintf("basic realm=%s", rh.c.Config.HTTP.Realm))
			}
		}
	}
	WriteData(w, http.StatusOK, "application/json", []byte{})
}
type ImageTags struct {
	Name string   `json:"name"`
	Tags []string `json:"tags"`
}
func (rh *RouteHandler) ListTags(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	name, ok := vars["name"]
	if !ok || name == "" {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	is := rh.getImageStore(name)
	paginate := false
	n := -1
	nQuery, ok := r.URL.Query()["n"]
	if ok {
		if len(nQuery) != 1 {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		var n1 int64
		var err error
		if n1, err = strconv.ParseInt(nQuery[0], 10, 0); err != nil {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		n = int(n1)
		paginate = true
	}
	last := ""
	lastQuery, ok := r.URL.Query()["last"]
	if ok {
		if len(lastQuery) != 1 {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		last = lastQuery[0]
	}
	tags, err := is.GetImageTags(name)
	if err != nil {
		WriteJSON(w, http.StatusNotFound, NewErrorList(NewError(NAME_UNKNOWN, map[string]string{"name": name})))
		return
	}
	if paginate && (n < len(tags)) {
		sort.Strings(tags)
		pTags := ImageTags{Name: name}
		if last == "" {
			pTags.Tags = tags[:n]
		} else {
			i := -1
			tag := ""
			found := false
			for i, tag = range tags {
				if tag == last {
					found = true
					break
				}
			}
			if !found {
				w.WriteHeader(http.StatusNotFound)
				return
			}
			if n >= len(tags)-i {
				pTags.Tags = tags[i+1:]
				WriteJSON(w, http.StatusOK, pTags)
				return
			}
			pTags.Tags = tags[i+1 : i+1+n]
		}
		if len(pTags.Tags) == 0 {
			last = ""
		} else {
			last = pTags.Tags[len(pTags.Tags)-1]
		}
		w.Header().Set("Link", fmt.Sprintf("/v2/%s/tags/list?n=%d&last=%s; rel=\"next\"", name, n, last))
		WriteJSON(w, http.StatusOK, pTags)
		return
	}
	WriteJSON(w, http.StatusOK, ImageTags{Name: name, Tags: tags})
}
func (rh *RouteHandler) CheckManifest(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	name, ok := vars["name"]
	if !ok || name == "" {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	is := rh.getImageStore(name)
	reference, ok := vars["reference"]
	if !ok || reference == "" {
		WriteJSON(w, http.StatusNotFound, NewErrorList(NewError(MANIFEST_INVALID, map[string]string{"reference": reference})))
		return
	}
	_, digest, mediaType, err := getImageManifest(rh, is, name, reference)
	if err != nil {
		switch err {
		case errors.ErrRepoNotFound:
			WriteJSON(w, http.StatusNotFound,
				NewErrorList(NewError(NAME_UNKNOWN, map[string]string{"reference": reference})))
		case errors.ErrManifestNotFound:
			WriteJSON(w, http.StatusNotFound,
				NewErrorList(NewError(MANIFEST_UNKNOWN, map[string]string{"reference": reference})))
		default:
			rh.c.Log.Error().Err(err).Msg("unexpected error")
			WriteJSON(w, http.StatusInternalServerError,
				NewErrorList(NewError(MANIFEST_INVALID, map[string]string{"reference": reference})))
		}
		return
	}
	w.Header().Set(DistContentDigestKey, digest)
	w.Header().Set("Content-Length", "0")
	w.Header().Set("Content-Type", mediaType)
	w.WriteHeader(http.StatusOK)
}
type ImageManifest struct {
	ispec.Manifest
}
func (rh *RouteHandler) GetManifest(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	name, ok := vars["name"]
	if !ok || name == "" {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	is := rh.getImageStore(name)
	reference, ok := vars["reference"]
	if !ok || reference == "" {
		WriteJSON(w, http.StatusNotFound, NewErrorList(NewError(MANIFEST_UNKNOWN, map[string]string{"reference": reference})))
		return
	}
	content, digest, mediaType, err := getImageManifest(rh, is, name, reference)
	if err != nil {
		switch err {
		case errors.ErrRepoNotFound:
			WriteJSON(w, http.StatusNotFound,
				NewErrorList(NewError(NAME_UNKNOWN, map[string]string{"name": name})))
		case errors.ErrRepoBadVersion:
			WriteJSON(w, http.StatusNotFound,
				NewErrorList(NewError(NAME_UNKNOWN, map[string]string{"name": name})))
		case errors.ErrManifestNotFound:
			WriteJSON(w, http.StatusNotFound,
				NewErrorList(NewError(MANIFEST_UNKNOWN, map[string]string{"reference": reference})))
		default:
			rh.c.Log.Error().Err(err).Msg("unexpected error")
			w.WriteHeader(http.StatusInternalServerError)
		}
		return
	}
	w.Header().Set(DistContentDigestKey, digest)
	WriteData(w, http.StatusOK, mediaType, content)
}
func (rh *RouteHandler) UpdateManifest(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	name, ok := vars["name"]
	if !ok || name == "" {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	is := rh.getImageStore(name)
	reference, ok := vars["reference"]
	if !ok || reference == "" {
		WriteJSON(w, http.StatusNotFound, NewErrorList(NewError(MANIFEST_INVALID, map[string]string{"reference": reference})))
		return
	}
	mediaType := r.Header.Get("Content-Type")
	if mediaType != ispec.MediaTypeImageManifest {
		w.WriteHeader(http.StatusUnsupportedMediaType)
		return
	}
	body, err := ioutil.ReadAll(r.Body)
	if err != nil {
		rh.c.Log.Error().Err(err).Msg("unexpected error")
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	digest, err := is.PutImageManifest(name, reference, mediaType, body)
	if err != nil {
		switch err {
		case errors.ErrRepoNotFound:
			WriteJSON(w, http.StatusNotFound,
				NewErrorList(NewError(NAME_UNKNOWN, map[string]string{"name": name})))
		case errors.ErrManifestNotFound:
			WriteJSON(w, http.StatusNotFound,
				NewErrorList(NewError(MANIFEST_UNKNOWN, map[string]string{"reference": reference})))
		case errors.ErrBadManifest:
			WriteJSON(w, http.StatusBadRequest,
				NewErrorList(NewError(MANIFEST_INVALID, map[string]string{"reference": reference})))
		case errors.ErrBlobNotFound:
			WriteJSON(w, http.StatusBadRequest,
				NewErrorList(NewError(BLOB_UNKNOWN, map[string]string{"blob": digest})))
		default:
			rh.c.Log.Error().Err(err).Msg("unexpected error")
			w.WriteHeader(http.StatusInternalServerError)
		}
		return
	}
	w.Header().Set("Location", fmt.Sprintf("/v2/%s/manifests/%s", name, digest))
	w.Header().Set(DistContentDigestKey, digest)
	w.WriteHeader(http.StatusCreated)
}
func (rh *RouteHandler) DeleteManifest(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	name, ok := vars["name"]
	if !ok || name == "" {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	is := rh.getImageStore(name)
	reference, ok := vars["reference"]
	if !ok || reference == "" {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	err := is.DeleteImageManifest(name, reference)
	if err != nil {
		switch err {
		case errors.ErrRepoNotFound:
			WriteJSON(w, http.StatusBadRequest,
				NewErrorList(NewError(NAME_UNKNOWN, map[string]string{"name": name})))
		case errors.ErrManifestNotFound:
			WriteJSON(w, http.StatusNotFound,
				NewErrorList(NewError(MANIFEST_UNKNOWN, map[string]string{"reference": reference})))
		case errors.ErrBadManifest:
			WriteJSON(w, http.StatusBadRequest,
				NewErrorList(NewError(UNSUPPORTED, map[string]string{"reference": reference})))
		default:
			rh.c.Log.Error().Err(err).Msg("unexpected error")
			w.WriteHeader(http.StatusInternalServerError)
		}
		return
	}
	w.WriteHeader(http.StatusAccepted)
}
func (rh *RouteHandler) CheckBlob(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	name, ok := vars["name"]
	if !ok || name == "" {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	is := rh.getImageStore(name)
	digest, ok := vars["digest"]
	if !ok || digest == "" {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	ok, blen, err := is.CheckBlob(name, digest)
	if err != nil {
		switch err {
		case errors.ErrBadBlobDigest:
			WriteJSON(w, http.StatusBadRequest, NewErrorList(NewError(DIGEST_INVALID, map[string]string{"digest": digest})))
		case errors.ErrRepoNotFound:
			WriteJSON(w, http.StatusNotFound, NewErrorList(NewError(NAME_UNKNOWN, map[string]string{"name": name})))
		case errors.ErrBlobNotFound:
			WriteJSON(w, http.StatusNotFound, NewErrorList(NewError(BLOB_UNKNOWN, map[string]string{"digest": digest})))
		default:
			rh.c.Log.Error().Err(err).Msg("unexpected error")
			w.WriteHeader(http.StatusInternalServerError)
		}
		return
	}
	if !ok {
		WriteJSON(w, http.StatusNotFound, NewErrorList(NewError(BLOB_UNKNOWN, map[string]string{"digest": digest})))
		return
	}
	w.Header().Set("Content-Length", fmt.Sprintf("%d", blen))
	w.Header().Set(DistContentDigestKey, digest)
	w.WriteHeader(http.StatusOK)
}
func (rh *RouteHandler) GetBlob(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	name, ok := vars["name"]
	if !ok || name == "" {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	is := rh.getImageStore(name)
	digest, ok := vars["digest"]
	if !ok || digest == "" {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	mediaType := r.Header.Get("Accept")
	br, blen, err := is.GetBlob(name, digest, mediaType)
	if err != nil {
		switch err {
		case errors.ErrBadBlobDigest:
			WriteJSON(w, http.StatusBadRequest, NewErrorList(NewError(DIGEST_INVALID, map[string]string{"digest": digest})))
		case errors.ErrRepoNotFound:
			WriteJSON(w, http.StatusNotFound, NewErrorList(NewError(NAME_UNKNOWN, map[string]string{"name": name})))
		case errors.ErrBlobNotFound:
			WriteJSON(w, http.StatusNotFound, NewErrorList(NewError(BLOB_UNKNOWN, map[string]string{"digest": digest})))
		default:
			rh.c.Log.Error().Err(err).Msg("unexpected error")
			w.WriteHeader(http.StatusInternalServerError)
		}
		return
	}
	w.Header().Set("Content-Length", fmt.Sprintf("%d", blen))
	w.Header().Set(DistContentDigestKey, digest)
	WriteDataFromReader(w, http.StatusOK, blen, mediaType, br, rh.c.Log)
}
func (rh *RouteHandler) DeleteBlob(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	name, ok := vars["name"]
	if !ok || name == "" {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	digest, ok := vars["digest"]
	if !ok || digest == "" {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	is := rh.getImageStore(name)
	err := is.DeleteBlob(name, digest)
	if err != nil {
		switch err {
		case errors.ErrBadBlobDigest:
			WriteJSON(w, http.StatusBadRequest, NewErrorList(NewError(DIGEST_INVALID, map[string]string{"digest": digest})))
		case errors.ErrRepoNotFound:
			WriteJSON(w, http.StatusNotFound, NewErrorList(NewError(NAME_UNKNOWN, map[string]string{"name": name})))
		case errors.ErrBlobNotFound:
			WriteJSON(w, http.StatusNotFound, NewErrorList(NewError(BLOB_UNKNOWN, map[string]string{"digest": digest})))
		default:
			rh.c.Log.Error().Err(err).Msg("unexpected error")
			w.WriteHeader(http.StatusInternalServerError)
		}
		return
	}
	w.WriteHeader(http.StatusAccepted)
}
func (rh *RouteHandler) CreateBlobUpload(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	name, ok := vars["name"]
	if !ok || name == "" {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	is := rh.getImageStore(name)
	if mountDigests, ok := r.URL.Query()["mount"]; ok {
		if len(mountDigests) != 1 {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		from, ok := r.URL.Query()["from"]
		if !ok || len(from) != 1 {
			w.WriteHeader(http.StatusMethodNotAllowed)
			return
		}
		_, _, err := is.CheckBlob(name, mountDigests[0])
		if err != nil {
			u, err := is.NewBlobUpload(name)
			if err != nil {
				switch err {
				case errors.ErrRepoNotFound:
					WriteJSON(w, http.StatusNotFound, NewErrorList(NewError(NAME_UNKNOWN, map[string]string{"name": name})))
				default:
					rh.c.Log.Error().Err(err).Msg("unexpected error")
					w.WriteHeader(http.StatusInternalServerError)
				}
				return
			}
			w.Header().Set("Location", path.Join(r.URL.String(), u))
			w.Header().Set("Range", "bytes=0-0")
			w.WriteHeader(http.StatusAccepted)
			return
		}
		w.Header().Set("Location", fmt.Sprintf("/v2/%s/blobs/%s", name, mountDigests[0]))
		w.WriteHeader(http.StatusCreated)
		return
	}
	if _, ok := r.URL.Query()["from"]; ok {
		w.WriteHeader(http.StatusMethodNotAllowed)
		return
	}
	digests, ok := r.URL.Query()["digest"]
	if ok {
		if len(digests) != 1 {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		digest := digests[0]
		if contentType := r.Header.Get("Content-Type"); contentType != BinaryMediaType {
			rh.c.Log.Warn().Str("actual", contentType).Str("expected", BinaryMediaType).Msg("invalid media type")
			w.WriteHeader(http.StatusUnsupportedMediaType)
			return
		}
		rh.c.Log.Info().Int64("r.ContentLength", r.ContentLength).Msg("DEBUG")
		var contentLength int64
		var err error
		if contentLength, err = strconv.ParseInt(r.Header.Get("Content-Length"), 10, 64); err != nil || contentLength <= 0 {
			rh.c.Log.Warn().Str("actual", r.Header.Get("Content-Length")).Msg("invalid content length")
			WriteJSON(w, http.StatusBadRequest,
				NewErrorList(NewError(BLOB_UPLOAD_INVALID, map[string]string{"digest": digest})))
			return
		}
		sessionID, size, err := is.FullBlobUpload(name, r.Body, digest)
		if err != nil {
			rh.c.Log.Error().Err(err).Int64("actual", size).Int64("expected", contentLength).Msg("failed full upload")
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		if size != contentLength {
			rh.c.Log.Warn().Int64("actual", size).Int64("expected", contentLength).Msg("invalid content length")
			w.WriteHeader(http.StatusInternalServerError)
			return
		}
		w.Header().Set("Location", fmt.Sprintf("/v2/%s/blobs/%s", name, digest))
		w.Header().Set(BlobUploadUUID, sessionID)
		w.WriteHeader(http.StatusCreated)
		return
	}
	u, err := is.NewBlobUpload(name)
	if err != nil {
		switch err {
		case errors.ErrRepoNotFound:
			WriteJSON(w, http.StatusNotFound, NewErrorList(NewError(NAME_UNKNOWN, map[string]string{"name": name})))
		default:
			rh.c.Log.Error().Err(err).Msg("unexpected error")
			w.WriteHeader(http.StatusInternalServerError)
		}
		return
	}
	w.Header().Set("Location", path.Join(r.URL.String(), u))
	w.Header().Set("Range", "bytes=0-0")
	w.WriteHeader(http.StatusAccepted)
}
func (rh *RouteHandler) GetBlobUpload(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	name, ok := vars["name"]
	if !ok || name == "" {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	is := rh.getImageStore(name)
	sessionID, ok := vars["session_id"]
	if !ok || sessionID == "" {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	size, err := is.GetBlobUpload(name, sessionID)
	if err != nil {
		switch err {
		case errors.ErrBadUploadRange:
			WriteJSON(w, http.StatusBadRequest,
				NewErrorList(NewError(BLOB_UPLOAD_INVALID, map[string]string{"session_id": sessionID})))
		case errors.ErrBadBlobDigest:
			WriteJSON(w, http.StatusBadRequest,
				NewErrorList(NewError(BLOB_UPLOAD_INVALID, map[string]string{"session_id": sessionID})))
		case errors.ErrRepoNotFound:
			WriteJSON(w, http.StatusNotFound,
				NewErrorList(NewError(NAME_UNKNOWN, map[string]string{"name": name})))
		case errors.ErrUploadNotFound:
			WriteJSON(w, http.StatusNotFound,
				NewErrorList(NewError(BLOB_UPLOAD_UNKNOWN, map[string]string{"session_id": sessionID})))
		default:
			rh.c.Log.Error().Err(err).Msg("unexpected error")
			w.WriteHeader(http.StatusInternalServerError)
		}
		return
	}
	w.Header().Set("Location", path.Join(r.URL.String(), sessionID))
	w.Header().Set("Range", fmt.Sprintf("bytes=0-%d", size-1))
	w.WriteHeader(http.StatusNoContent)
}
func (rh *RouteHandler) PatchBlobUpload(w http.ResponseWriter, r *http.Request) {
	vars := mux.Vars(r)
	name, ok := vars["name"]
	if !ok || name == "" {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	is := rh.getImageStore(name)
	sessionID, ok := vars["session_id"]
	if !ok || sessionID == "" {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	var clen int64
	var err error
	if r.Header.Get("Content-Length") == "" || r.Header.Get("Content-Range") == "" {
		clen, err = is.PutBlobChunkStreamed(name, sessionID, r.Body)
	} else {
		var contentLength int64
		if contentLength, err = strconv.ParseInt(r.Header.Get("Content-Length"), 10, 64); err != nil {
			rh.c.Log.Warn().Str("actual", r.Header.Get("Content-Length")).Msg("invalid content length")
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		contentRange := r.Header.Get("Content-Range")
		if contentRange == "" {
			rh.c.Log.Warn().Str("actual", r.Header.Get("Content-Range")).Msg("invalid content range")
			w.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
			return
		}
		var from, to int64
		if from, to, err = getContentRange(r); err != nil || (to-from)+1 != contentLength {
			w.WriteHeader(http.StatusRequestedRangeNotSatisfiable)
			return
		}
		clen, err = is.PutBlobChunk(name, sessionID, from, to, r.Body)
	}
	if err != nil {
		switch err {
		case io.ErrUnexpectedEOF:
			rh.c.Log.Warn().Msg("received unexpected EOF, removing .uploads/ files")
			if err = is.DeleteBlobUpload(name, sessionID); err != nil {
				rh.c.Log.Error().Err(err).Msgf("couldn't remove blobUpload %s in repo %s", sessionID, name)
			}
			w.WriteHeader(http.StatusInternalServerError)
		case errors.ErrBadUploadRange:
			WriteJSON(w, http.StatusRequestedRangeNotSatisfiable,
				NewErrorList(NewError(BLOB_UPLOAD_INVALID, map[string]string{"session_id": sessionID})))
		case errors.ErrRepoNotFound:
			WriteJSON(w, http.StatusNotFound,
				NewErrorList(NewError(NAME_UNKNOWN, map[string]string{"name": name})))
		case errors.ErrUploadNotFound:
			WriteJSON(w, http.StatusNotFound,
				NewErrorList(NewError(BLOB_UPLOAD_UNKNOWN, map[string]string{"session_id": sessionID})))
		default:
			rh.c.Log.Error().Err(err).Msg("unexpected error")
			w.WriteHeader(http.StatusInternalServerError)
		}
		return
	}
	w.Header().Set("Location", r.URL.String())
	w.Header().Set("Range", fmt.Sprintf("bytes=0-%d", clen-1))
	w.Header().Set("Content-Length", "0")
	w.Header().Set(BlobUploadUUID, sessionID)
	w.WriteHeader(http.StatusAccepted)
}