This commit is contained in:
Mathias Gebbe 2025-05-23 12:20:46 +02:00 committed by GitHub
commit 22a55d9387
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 45 additions and 12 deletions

View File

@ -44,7 +44,6 @@ func parseHTTPErrorResponse(resp *http.Response) error {
if err != nil { if err != nil {
return err return err
} }
statusCode := resp.StatusCode statusCode := resp.StatusCode
// A HEAD request for example validly does not contain any body, while // A HEAD request for example validly does not contain any body, while
@ -53,6 +52,10 @@ func parseHTTPErrorResponse(resp *http.Response) error {
return makeError(statusCode, "") return makeError(statusCode, "")
} }
if statusCode == 401 {
return makeError(statusCode, string(body))
}
ctHeader := resp.Header.Get("Content-Type") ctHeader := resp.Header.Get("Content-Type")
if ctHeader == "" { if ctHeader == "" {
return makeError(statusCode, string(body)) return makeError(statusCode, string(body))
@ -101,13 +104,13 @@ func parseHTTPErrorResponse(resp *http.Response) error {
func makeError(statusCode int, details string) error { func makeError(statusCode int, details string) error {
switch statusCode { switch statusCode {
case http.StatusUnauthorized: case http.StatusUnauthorized:
return errcode.ErrorCodeUnauthorized.WithMessage(details) return errcode.ErrorCodeUnauthorized.WithMessageStatusCode(details, statusCode)
case http.StatusForbidden: case http.StatusForbidden:
return errcode.ErrorCodeDenied.WithMessage(details) return errcode.ErrorCodeDenied.WithMessageStatusCode(details, statusCode)
case http.StatusTooManyRequests: case http.StatusTooManyRequests:
return errcode.ErrorCodeTooManyRequests.WithMessage(details) return errcode.ErrorCodeTooManyRequests.WithMessageStatusCode(details, statusCode)
default: default:
return errcode.ErrorCodeUnknown.WithMessage(details) return errcode.ErrorCodeUnknown.WithMessageStatusCode(details, statusCode)
} }
} }
@ -138,6 +141,7 @@ func HandleHTTPResponseError(resp *http.Response) error {
for _, c := range challenge.ResponseChallenges(resp) { for _, c := range challenge.ResponseChallenges(resp) {
if c.Scheme == "bearer" { if c.Scheme == "bearer" {
var err errcode.Error var err errcode.Error
err.StatusCode = resp.StatusCode
// codes defined at https://tools.ietf.org/html/rfc6750#section-3.1 // codes defined at https://tools.ietf.org/html/rfc6750#section-3.1
switch c.Parameters["error"] { switch c.Parameters["error"] {
case "invalid_token": case "invalid_token":

View File

@ -78,6 +78,16 @@ func (ec ErrorCode) WithMessage(message string) Error {
} }
} }
// WithMessage creates a new Error struct based on the passed-in info and
// overrides the Message property.
func (ec ErrorCode) WithMessageStatusCode(message string, statusCode int) Error {
return Error{
Code: ec,
Message: message,
StatusCode: statusCode,
}
}
// WithDetail creates a new Error struct based on the passed-in info and // WithDetail creates a new Error struct based on the passed-in info and
// set the Detail property appropriately // set the Detail property appropriately
func (ec ErrorCode) WithDetail(detail interface{}) Error { func (ec ErrorCode) WithDetail(detail interface{}) Error {
@ -100,7 +110,7 @@ type Error struct {
Code ErrorCode `json:"code"` Code ErrorCode `json:"code"`
Message string `json:"message"` Message string `json:"message"`
Detail interface{} `json:"detail,omitempty"` Detail interface{} `json:"detail,omitempty"`
StatusCode int `json:"statusCode"`
// TODO(duglin): See if we need an "args" property so we can do the // TODO(duglin): See if we need an "args" property so we can do the
// variable substitution right before showing the message to the user // variable substitution right before showing the message to the user
} }
@ -122,6 +132,7 @@ func (e Error) Error() string {
func (e Error) WithDetail(detail interface{}) Error { func (e Error) WithDetail(detail interface{}) Error {
return Error{ return Error{
Code: e.Code, Code: e.Code,
StatusCode: e.StatusCode,
Message: e.Message, Message: e.Message,
Detail: detail, Detail: detail,
} }

View File

@ -2,6 +2,7 @@ package handlers
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"mime" "mime"
"net/http" "net/http"
@ -142,6 +143,8 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request)
if err != nil { if err != nil {
if _, ok := err.(distribution.ErrManifestUnknownRevision); ok { if _, ok := err.(distribution.ErrManifestUnknownRevision); ok {
imh.Errors = append(imh.Errors, errcode.ErrorCodeManifestUnknown.WithDetail(err)) imh.Errors = append(imh.Errors, errcode.ErrorCodeManifestUnknown.WithDetail(err))
} else if checkAllErrorsStatusCode(err, 401) {
imh.Errors = append(imh.Errors, errcode.ErrorCodeUnauthorized.WithDetail(err))
} else { } else {
imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err)) imh.Errors = append(imh.Errors, errcode.ErrorCodeUnknown.WithDetail(err))
} }
@ -225,6 +228,21 @@ func (imh *manifestHandler) GetManifest(w http.ResponseWriter, r *http.Request)
} }
} }
func checkAllErrorsStatusCode(err error, statusCode int) bool {
var errs errcode.Errors
if !errors.As(err, &errs) {
return false
}
for _, e := range errs {
var specificErr errcode.Error
ok := errors.As(e, &specificErr)
if !ok || specificErr.StatusCode != statusCode {
return false
}
}
return true
}
func etagMatch(r *http.Request, etag string) bool { func etagMatch(r *http.Request, etag string) bool {
for _, headerVal := range r.Header["If-None-Match"] { for _, headerVal := range r.Header["If-None-Match"] {
if headerVal == etag || headerVal == fmt.Sprintf(`"%s"`, etag) { // allow quoted or unquoted if headerVal == etag || headerVal == fmt.Sprintf(`"%s"`, etag) { // allow quoted or unquoted