@@ -1844,16 +1844,16 @@ func (rh *RouteHandler) CreateBlobUpload(response http.ResponseWriter, request *
18441844
18451845 var contentLength int64
18461846
1847- contentLength , err = strconv . ParseInt ( request .Header . Get ( "Content-Length" ), 10 , 64 )
1848- if err != nil || contentLength <= 0 {
1849- rh . c . Log . Warn (). Str ( "actual" , request . Header . Get ( " Content-Length" )). Msg ( "invalid content length" )
1850-
1851- details := map [ string ] string { "digest" : digest . String ()}
1852-
1853- if err != nil {
1854- details [ "conversion error" ] = err . Error ()
1855- } else {
1856- details [ "Content-Length" ] = request . Header . Get ( "Content-Length" )
1847+ // request.ContentLength is pre-parsed by net/http: 0 for an explicit empty
1848+ // body, -1 when unknown/chunked. Reject only the unknown case (< 0); a
1849+ // Content-Length of 0 is a valid empty-blob upload.
1850+ contentLength = request . ContentLength
1851+ if contentLength < 0 {
1852+ rh . c . Log . Warn (). Int64 ( "actual" , contentLength ). Msg ( "invalid content length" )
1853+
1854+ details := map [ string ] string {
1855+ "digest" : digest . String (),
1856+ "Content-Length" : strconv . FormatInt ( contentLength , 10 ),
18571857 }
18581858
18591859 e := apiErr .NewError (apiErr .BLOB_UPLOAD_INVALID ).AddDetail (details )
@@ -2134,39 +2134,44 @@ func (rh *RouteHandler) UpdateBlobUpload(response http.ResponseWriter, request *
21342134 return
21352135 }
21362136
2137- contentPresent := true
2137+ contentLenHeader := request .Header .Get ("Content-Length" )
2138+ contentRange := request .Header .Get ("Content-Range" )
2139+ // Header.Get cannot distinguish missing headers from present-but-empty headers.
2140+ contentLenHeaderPresent := len (request .Header .Values ("Content-Length" )) > 0
2141+ contentRangePresent := len (request .Header .Values ("Content-Range" )) > 0
21382142
2139- contentLen , err := strconv .ParseInt (request .Header .Get ("Content-Length" ), 10 , 64 )
2140- if err != nil {
2141- contentPresent = false
2142- }
2143+ shouldPutBlobChunk := contentLenHeaderPresent || contentRangePresent
21432144
2144- contentRangePresent := true
2145+ var from , to int64
21452146
2146- if request .Header .Get ("Content-Range" ) == "" {
2147- contentRangePresent = false
2148- }
2147+ if shouldPutBlobChunk {
2148+ contentLen , err := strconv .ParseInt (contentLenHeader , 10 , 64 )
2149+ if err != nil || contentLen < 0 {
2150+ rh .c .Log .Warn ().Str ("actual" , contentLenHeader ).Msg ("invalid content length" )
21492151
2150- // we expect at least one of "Content-Length" or "Content-Range" to be
2151- // present
2152- if ! contentPresent && ! contentRangePresent {
2153- response .WriteHeader (http .StatusBadRequest )
2152+ details := map [string ]string {"digest" : digest .String ()}
2153+ if err != nil {
2154+ details ["conversion error" ] = err .Error ()
2155+ } else {
2156+ details ["Content-Length" ] = contentLenHeader
2157+ }
21542158
2155- return
2156- }
2159+ e := apiErr . NewError ( apiErr . BLOB_UPLOAD_INVALID ). AddDetail ( details )
2160+ zcommon . WriteJSON ( response , http . StatusBadRequest , apiErr . NewErrorList ( e ))
21572161
2158- var from , to int64
2162+ return
2163+ }
21592164
2160- if contentPresent {
2161- contentRange := request .Header .Get ("Content-Range" )
21622165 if contentRange == "" { // monolithic upload
21632166 from = 0
21642167
2165- if contentLen == 0 {
2166- goto finish
2168+ if contentLen > 0 {
2169+ to = contentLen
2170+ } else {
2171+ // Zero-length monolithic upload (e.g. empty blob): skip
2172+ // PutBlobChunk and go straight to FinishBlobUpload below.
2173+ shouldPutBlobChunk = false
21672174 }
2168-
2169- to = contentLen
21702175 } else if from , to , err = getContentRange (request ); err != nil { // finish chunked upload
21712176 details := zerr .GetDetails (err )
21722177 details ["session_id" ] = sessionID
@@ -2175,7 +2180,9 @@ func (rh *RouteHandler) UpdateBlobUpload(response http.ResponseWriter, request *
21752180
21762181 return
21772182 }
2183+ }
21782184
2185+ if shouldPutBlobChunk {
21792186 _ , err = imgStore .PutBlobChunk (ctx , name , sessionID , from , to , request .Body )
21802187 if err != nil { //nolint:dupl
21812188 details := zerr .GetDetails (err )
@@ -2207,7 +2214,6 @@ func (rh *RouteHandler) UpdateBlobUpload(response http.ResponseWriter, request *
22072214 }
22082215 }
22092216
2210- finish:
22112217 // blob chunks already transferred, just finish
22122218 if err := imgStore .FinishBlobUpload (name , sessionID , request .Body , digest ); err != nil {
22132219 details := zerr .GetDetails (err )
0 commit comments