From 0d7caab5d8f8cd5f0ab5a9bb9f52f3211910e9b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Manuel=20R=C3=BCger?= Date: Fri, 13 Mar 2026 02:11:18 +0100 Subject: [PATCH] fix: close response body on all paths in newErrorStatusNotOK The 401 and 404 early-return paths returned without closing the HTTP response body, leaking the underlying connection. Move the defer body.Close() to the top of the function so it runs regardless of which code path is taken. fix: add HTTP status check to GetCurrentUser GetCurrentUser did not validate the HTTP response status code. A 401/403/500 response was silently ignored and returned a zero-value User pointer, causing callers (e.g. RestrictPageUpdatesCloud fallback) to use an empty accountId. fix: return nil on HTTP 204 from DeletePageLabel instead of panicking DeletePageLabel accepted both 200 OK and 204 No Content as success, but then unconditionally did request.Response.(*LabelInfo). On a 204 the response body is empty so request.Response is nil; the type assertion panics. Return (nil, nil) for 204 responses. fix: paginate GetPageLabels to handle pages with >50 labels A single request with the default page size silently truncated label lists longer than the API default (~50). Add a pagination loop matching the pattern used by GetAttachments. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- confluence/api.go | 64 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 48 insertions(+), 16 deletions(-) diff --git a/confluence/api.go b/confluence/api.go index bf06cc1..9275e9c 100644 --- a/confluence/api.go +++ b/confluence/api.go @@ -661,26 +661,54 @@ func (api *API) DeletePageLabel(page *PageInfo, label string) (*LabelInfo, error return nil, err } - if request.Raw.StatusCode != http.StatusOK && request.Raw.StatusCode != http.StatusNoContent { - return nil, newErrorStatusNotOK(request) + if request.Raw.StatusCode == http.StatusNoContent { + return nil, nil } return request.Response.(*LabelInfo), nil } func (api *API) GetPageLabels(page *PageInfo, prefix string) (*LabelInfo, error) { - - request, err := api.rest.Res( - "content/"+page.ID+"/label", &LabelInfo{}, - ).Get(map[string]string{"prefix": prefix}) - if err != nil { - return nil, err + type labelPage struct { + Links struct { + Next string `json:"next"` + } `json:"_links"` + Labels []Label `json:"results"` + Size int `json:"number"` } - if request.Raw.StatusCode != http.StatusOK { - return nil, newErrorStatusNotOK(request) + const pageSize = 50 + var all []Label + start := 0 + + for { + var result labelPage + + request, err := api.rest.Res( + "content/"+page.ID+"/label", &result, + ).Get(map[string]string{ + "prefix": prefix, + "limit": fmt.Sprintf("%d", pageSize), + "start": fmt.Sprintf("%d", start), + }) + if err != nil { + return nil, err + } + + if request.Raw.StatusCode != http.StatusOK { + return nil, newErrorStatusNotOK(request) + } + + all = append(all, result.Labels...) + + if len(result.Labels) < pageSize || result.Links.Next == "" { + break + } + + start += len(result.Labels) } - return request.Response.(*LabelInfo), nil + + return &LabelInfo{Labels: all, Size: len(all)}, nil } func (api *API) GetUserByName(name string) (*User, error) { @@ -734,7 +762,7 @@ func (api *API) GetUserByName(name string) (*User, error) { func (api *API) GetCurrentUser() (*User, error) { var user User - _, err := api.rest. + request, err := api.rest. Res("user"). Res("current", &user). Get() @@ -742,6 +770,10 @@ func (api *API) GetCurrentUser() (*User, error) { return nil, err } + if request.Raw.StatusCode != http.StatusOK { + return nil, newErrorStatusNotOK(request) + } + return &user, nil } @@ -845,6 +877,10 @@ func (api *API) RestrictPageUpdates( } func newErrorStatusNotOK(request *gopencils.Resource) error { + defer func() { + _ = request.Raw.Body.Close() + }() + if request.Raw.StatusCode == http.StatusUnauthorized { return errors.New( "the Confluence API returned unexpected status: 401 (Unauthorized)", @@ -857,10 +893,6 @@ func newErrorStatusNotOK(request *gopencils.Resource) error { ) } - defer func() { - _ = request.Raw.Body.Close() - }() - output, _ := io.ReadAll(request.Raw.Body) return fmt.Errorf(