From 74054c0f71d3be7f3768855dffee833ea11b542b Mon Sep 17 00:00:00 2001 From: "Babak K. Shandiz" Date: Wed, 22 Apr 2026 15:55:02 +0100 Subject: [PATCH] fix: url escape path components Signed-off-by: Babak K. Shandiz --- internal/codespaces/api/api.go | 14 +++++++------- pkg/cmd/label/create.go | 3 ++- pkg/cmd/label/delete.go | 3 ++- pkg/cmd/repo/create/http.go | 5 +++-- pkg/cmd/ruleset/view/http.go | 3 ++- pkg/cmd/secret/delete/delete.go | 9 +++++---- pkg/cmd/secret/list/list.go | 5 +++-- pkg/cmd/secret/set/http.go | 11 ++++++----- pkg/cmd/variable/delete/delete.go | 7 ++++--- pkg/cmd/variable/get/get.go | 7 ++++--- pkg/cmd/variable/list/list.go | 5 +++-- 11 files changed, 41 insertions(+), 31 deletions(-) diff --git a/internal/codespaces/api/api.go b/internal/codespaces/api/api.go index 0d1eaf5b3..1632cfd46 100644 --- a/internal/codespaces/api/api.go +++ b/internal/codespaces/api/api.go @@ -374,10 +374,10 @@ func (a *API) ListCodespaces(ctx context.Context, opts ListCodespacesOptions) (c orgName := opts.OrgName if opts.UserName != "" { userName := opts.UserName - listURL = fmt.Sprintf("%s/orgs/%s/members/%s/codespaces?per_page=%d", a.githubAPI, orgName, userName, perPage) + listURL = fmt.Sprintf("%s/orgs/%s/members/%s/codespaces?per_page=%d", a.githubAPI, url.PathEscape(orgName), url.PathEscape(userName), perPage) spanName = "/orgs/*/members/*/codespaces" } else { - listURL = fmt.Sprintf("%s/orgs/%s/codespaces?per_page=%d", a.githubAPI, orgName, perPage) + listURL = fmt.Sprintf("%s/orgs/%s/codespaces?per_page=%d", a.githubAPI, url.PathEscape(orgName), perPage) spanName = "/orgs/*/codespaces" } } else { @@ -445,7 +445,7 @@ func findNextPage(linkValue string) string { func (a *API) GetOrgMemberCodespace(ctx context.Context, orgName string, userName string, codespaceName string) (*Codespace, error) { perPage := 100 - listURL := fmt.Sprintf("%s/orgs/%s/members/%s/codespaces?per_page=%d", a.githubAPI, orgName, userName, perPage) + listURL := fmt.Sprintf("%s/orgs/%s/members/%s/codespaces?per_page=%d", a.githubAPI, url.PathEscape(orgName), url.PathEscape(userName), perPage) for { req, err := http.NewRequest(http.MethodGet, listURL, nil) @@ -569,10 +569,10 @@ func (a *API) StopCodespace(ctx context.Context, codespaceName string, orgName s var spanName string if orgName != "" { - stopURL = fmt.Sprintf("%s/orgs/%s/members/%s/codespaces/%s/stop", a.githubAPI, orgName, userName, codespaceName) + stopURL = fmt.Sprintf("%s/orgs/%s/members/%s/codespaces/%s/stop", a.githubAPI, url.PathEscape(orgName), url.PathEscape(userName), url.PathEscape(codespaceName)) spanName = "/orgs/*/members/*/codespaces/*/stop" } else { - stopURL = fmt.Sprintf("%s/user/codespaces/%s/stop", a.githubAPI, codespaceName) + stopURL = fmt.Sprintf("%s/user/codespaces/%s/stop", a.githubAPI, url.PathEscape(codespaceName)) spanName = "/user/codespaces/*/stop" } @@ -976,10 +976,10 @@ func (a *API) DeleteCodespace(ctx context.Context, codespaceName string, orgName var spanName string if orgName != "" && userName != "" { - deleteURL = fmt.Sprintf("%s/orgs/%s/members/%s/codespaces/%s", a.githubAPI, orgName, userName, codespaceName) + deleteURL = fmt.Sprintf("%s/orgs/%s/members/%s/codespaces/%s", a.githubAPI, url.PathEscape(orgName), url.PathEscape(userName), url.PathEscape(codespaceName)) spanName = "/orgs/*/members/*/codespaces/*" } else { - deleteURL = a.githubAPI + "/user/codespaces/" + codespaceName + deleteURL = a.githubAPI + "/user/codespaces/" + url.PathEscape(codespaceName) spanName = "/user/codespaces/*" } diff --git a/pkg/cmd/label/create.go b/pkg/cmd/label/create.go index 9d6b2e9ee..b4442786d 100644 --- a/pkg/cmd/label/create.go +++ b/pkg/cmd/label/create.go @@ -7,6 +7,7 @@ import ( "fmt" "math/rand" "net/http" + "net/url" "strings" "time" @@ -156,7 +157,7 @@ func createLabel(client *http.Client, repo ghrepo.Interface, opts *createOptions } func updateLabel(apiClient *api.Client, repo ghrepo.Interface, opts *editOptions) error { - path := fmt.Sprintf("repos/%s/%s/labels/%s", repo.RepoOwner(), repo.RepoName(), opts.Name) + path := fmt.Sprintf("repos/%s/%s/labels/%s", repo.RepoOwner(), repo.RepoName(), url.PathEscape(opts.Name)) properties := map[string]string{} if opts.Description != "" { properties["description"] = opts.Description diff --git a/pkg/cmd/label/delete.go b/pkg/cmd/label/delete.go index c9d8f4cae..f76d011de 100644 --- a/pkg/cmd/label/delete.go +++ b/pkg/cmd/label/delete.go @@ -3,6 +3,7 @@ package label import ( "fmt" "net/http" + "net/url" "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/internal/ghrepo" @@ -94,7 +95,7 @@ func deleteRun(opts *deleteOptions) error { func deleteLabel(client *http.Client, repo ghrepo.Interface, name string) error { apiClient := api.NewClientFromHTTP(client) - path := fmt.Sprintf("repos/%s/%s/labels/%s", repo.RepoOwner(), repo.RepoName(), name) + path := fmt.Sprintf("repos/%s/%s/labels/%s", repo.RepoOwner(), repo.RepoName(), url.PathEscape(name)) return apiClient.REST(repo.RepoHost(), "DELETE", path, nil, nil) } diff --git a/pkg/cmd/repo/create/http.go b/pkg/cmd/repo/create/http.go index 725fc48c5..c99b7bdea 100644 --- a/pkg/cmd/repo/create/http.go +++ b/pkg/cmd/repo/create/http.go @@ -5,6 +5,7 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "strings" "github.com/cli/cli/v2/api" @@ -254,7 +255,7 @@ func (r *ownerResponse) IsOrganization() bool { func resolveOwner(client *api.Client, hostname, orgName string) (*ownerResponse, error) { var response ownerResponse - err := client.REST(hostname, "GET", fmt.Sprintf("users/%s", orgName), nil, &response) + err := client.REST(hostname, "GET", fmt.Sprintf("users/%s", url.PathEscape(orgName)), nil, &response) return &response, err } @@ -268,7 +269,7 @@ type teamResponse struct { func resolveOrganizationTeam(client *api.Client, hostname, orgName, teamSlug string) (*teamResponse, error) { var response teamResponse - err := client.REST(hostname, "GET", fmt.Sprintf("orgs/%s/teams/%s", orgName, teamSlug), nil, &response) + err := client.REST(hostname, "GET", fmt.Sprintf("orgs/%s/teams/%s", url.PathEscape(orgName), url.PathEscape(teamSlug)), nil, &response) return &response, err } diff --git a/pkg/cmd/ruleset/view/http.go b/pkg/cmd/ruleset/view/http.go index d0b26f530..6427a5b65 100644 --- a/pkg/cmd/ruleset/view/http.go +++ b/pkg/cmd/ruleset/view/http.go @@ -3,6 +3,7 @@ package view import ( "fmt" "net/http" + "net/url" "github.com/cli/cli/v2/api" "github.com/cli/cli/v2/internal/ghrepo" @@ -15,7 +16,7 @@ func viewRepoRuleset(httpClient *http.Client, repo ghrepo.Interface, databaseId } func viewOrgRuleset(httpClient *http.Client, orgLogin string, databaseId string, host string) (*shared.RulesetREST, error) { - path := fmt.Sprintf("orgs/%s/rulesets/%s", orgLogin, databaseId) + path := fmt.Sprintf("orgs/%s/rulesets/%s", url.PathEscape(orgLogin), databaseId) return viewRuleset(httpClient, host, path) } diff --git a/pkg/cmd/secret/delete/delete.go b/pkg/cmd/secret/delete/delete.go index b73b59848..26b7ce68e 100644 --- a/pkg/cmd/secret/delete/delete.go +++ b/pkg/cmd/secret/delete/delete.go @@ -3,6 +3,7 @@ package delete import ( "fmt" "net/http" + "net/url" "os" "github.com/MakeNowJust/heredoc" @@ -127,16 +128,16 @@ func removeRun(opts *DeleteOptions) error { var host string switch secretEntity { case shared.Organization: - path = fmt.Sprintf("orgs/%s/%s/secrets/%s", orgName, secretApp, opts.SecretName) + path = fmt.Sprintf("orgs/%s/%s/secrets/%s", url.PathEscape(orgName), secretApp, url.PathEscape(opts.SecretName)) host, _ = cfg.Authentication().DefaultHost() case shared.Environment: - path = fmt.Sprintf("repos/%s/environments/%s/secrets/%s", ghrepo.FullName(baseRepo), envName, opts.SecretName) + path = fmt.Sprintf("repos/%s/environments/%s/secrets/%s", ghrepo.FullName(baseRepo), url.PathEscape(envName), url.PathEscape(opts.SecretName)) host = baseRepo.RepoHost() case shared.User: - path = fmt.Sprintf("user/codespaces/secrets/%s", opts.SecretName) + path = fmt.Sprintf("user/codespaces/secrets/%s", url.PathEscape(opts.SecretName)) host, _ = cfg.Authentication().DefaultHost() case shared.Repository: - path = fmt.Sprintf("repos/%s/%s/secrets/%s", ghrepo.FullName(baseRepo), secretApp, opts.SecretName) + path = fmt.Sprintf("repos/%s/%s/secrets/%s", ghrepo.FullName(baseRepo), secretApp, url.PathEscape(opts.SecretName)) host = baseRepo.RepoHost() } diff --git a/pkg/cmd/secret/list/list.go b/pkg/cmd/secret/list/list.go index 06476a86d..422beda90 100644 --- a/pkg/cmd/secret/list/list.go +++ b/pkg/cmd/secret/list/list.go @@ -3,6 +3,7 @@ package list import ( "fmt" "net/http" + "net/url" "os" "slices" "strings" @@ -248,7 +249,7 @@ func fmtVisibility(s Secret) string { } func getOrgSecrets(client *http.Client, host, orgName string, showSelectedRepoInfo bool, app shared.App) ([]Secret, error) { - secrets, err := getSecrets(client, host, fmt.Sprintf("orgs/%s/%s/secrets", orgName, app)) + secrets, err := getSecrets(client, host, fmt.Sprintf("orgs/%s/%s/secrets", url.PathEscape(orgName), app)) if err != nil { return nil, err } @@ -279,7 +280,7 @@ func getUserSecrets(client *http.Client, host string, showSelectedRepoInfo bool) } func getEnvSecrets(client *http.Client, repo ghrepo.Interface, envName string) ([]Secret, error) { - path := fmt.Sprintf("repos/%s/environments/%s/secrets", ghrepo.FullName(repo), envName) + path := fmt.Sprintf("repos/%s/environments/%s/secrets", ghrepo.FullName(repo), url.PathEscape(envName)) return getSecrets(client, repo.RepoHost(), path) } diff --git a/pkg/cmd/secret/set/http.go b/pkg/cmd/secret/set/http.go index 7d623be16..11477ecab 100644 --- a/pkg/cmd/secret/set/http.go +++ b/pkg/cmd/secret/set/http.go @@ -4,6 +4,7 @@ import ( "bytes" "encoding/json" "fmt" + "net/url" "strconv" "github.com/cli/cli/v2/api" @@ -40,7 +41,7 @@ func getPubKey(client *api.Client, host, path string) (*PubKey, error) { } func getOrgPublicKey(client *api.Client, host, orgName string, app shared.App) (*PubKey, error) { - return getPubKey(client, host, fmt.Sprintf("orgs/%s/%s/secrets/public-key", orgName, app)) + return getPubKey(client, host, fmt.Sprintf("orgs/%s/%s/secrets/public-key", url.PathEscape(orgName), app)) } func getUserPublicKey(client *api.Client, host string) (*PubKey, error) { @@ -54,7 +55,7 @@ func getRepoPubKey(client *api.Client, repo ghrepo.Interface, app shared.App) (* func getEnvPubKey(client *api.Client, repo ghrepo.Interface, envName string) (*PubKey, error) { return getPubKey(client, repo.RepoHost(), fmt.Sprintf("repos/%s/environments/%s/secrets/public-key", - ghrepo.FullName(repo), envName)) + ghrepo.FullName(repo), url.PathEscape(envName))) } func putSecret(client *api.Client, host, path string, payload interface{}) error { @@ -68,7 +69,7 @@ func putSecret(client *api.Client, host, path string, payload interface{}) error } func putOrgSecret(client *api.Client, host string, pk *PubKey, orgName, visibility, secretName, eValue string, repositoryIDs []int64, app shared.App) error { - path := fmt.Sprintf("orgs/%s/%s/secrets/%s", orgName, app, secretName) + path := fmt.Sprintf("orgs/%s/%s/secrets/%s", url.PathEscape(orgName), app, url.PathEscape(secretName)) if app == shared.Dependabot { repos := make([]string, len(repositoryIDs)) @@ -111,7 +112,7 @@ func putEnvSecret(client *api.Client, pk *PubKey, repo ghrepo.Interface, envName EncryptedValue: eValue, KeyID: pk.ID, } - path := fmt.Sprintf("repos/%s/environments/%s/secrets/%s", ghrepo.FullName(repo), envName, secretName) + path := fmt.Sprintf("repos/%s/environments/%s/secrets/%s", ghrepo.FullName(repo), url.PathEscape(envName), url.PathEscape(secretName)) return putSecret(client, repo.RepoHost(), path, payload) } @@ -120,6 +121,6 @@ func putRepoSecret(client *api.Client, pk *PubKey, repo ghrepo.Interface, secret EncryptedValue: eValue, KeyID: pk.ID, } - path := fmt.Sprintf("repos/%s/%s/secrets/%s", ghrepo.FullName(repo), app, secretName) + path := fmt.Sprintf("repos/%s/%s/secrets/%s", ghrepo.FullName(repo), app, url.PathEscape(secretName)) return putSecret(client, repo.RepoHost(), path, payload) } diff --git a/pkg/cmd/variable/delete/delete.go b/pkg/cmd/variable/delete/delete.go index d51320167..bd030ca9b 100644 --- a/pkg/cmd/variable/delete/delete.go +++ b/pkg/cmd/variable/delete/delete.go @@ -3,6 +3,7 @@ package delete import ( "fmt" "net/http" + "net/url" "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/api" @@ -100,13 +101,13 @@ func removeRun(opts *DeleteOptions) error { var host string switch variableEntity { case shared.Organization: - path = fmt.Sprintf("orgs/%s/actions/variables/%s", orgName, opts.VariableName) + path = fmt.Sprintf("orgs/%s/actions/variables/%s", url.PathEscape(orgName), url.PathEscape(opts.VariableName)) host, _ = cfg.Authentication().DefaultHost() case shared.Environment: - path = fmt.Sprintf("repos/%s/environments/%s/variables/%s", ghrepo.FullName(baseRepo), envName, opts.VariableName) + path = fmt.Sprintf("repos/%s/environments/%s/variables/%s", ghrepo.FullName(baseRepo), url.PathEscape(envName), url.PathEscape(opts.VariableName)) host = baseRepo.RepoHost() case shared.Repository: - path = fmt.Sprintf("repos/%s/actions/variables/%s", ghrepo.FullName(baseRepo), opts.VariableName) + path = fmt.Sprintf("repos/%s/actions/variables/%s", ghrepo.FullName(baseRepo), url.PathEscape(opts.VariableName)) host = baseRepo.RepoHost() } diff --git a/pkg/cmd/variable/get/get.go b/pkg/cmd/variable/get/get.go index e4def5a03..d9a89d2e9 100644 --- a/pkg/cmd/variable/get/get.go +++ b/pkg/cmd/variable/get/get.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "net/http" + "net/url" "github.com/MakeNowJust/heredoc" "github.com/cli/cli/v2/api" @@ -101,13 +102,13 @@ func getRun(opts *GetOptions) error { var host string switch variableEntity { case shared.Organization: - path = fmt.Sprintf("orgs/%s/actions/variables/%s", orgName, opts.VariableName) + path = fmt.Sprintf("orgs/%s/actions/variables/%s", url.PathEscape(orgName), url.PathEscape(opts.VariableName)) host, _ = cfg.Authentication().DefaultHost() case shared.Environment: - path = fmt.Sprintf("repos/%s/environments/%s/variables/%s", ghrepo.FullName(baseRepo), envName, opts.VariableName) + path = fmt.Sprintf("repos/%s/environments/%s/variables/%s", ghrepo.FullName(baseRepo), url.PathEscape(envName), url.PathEscape(opts.VariableName)) host = baseRepo.RepoHost() case shared.Repository: - path = fmt.Sprintf("repos/%s/actions/variables/%s", ghrepo.FullName(baseRepo), opts.VariableName) + path = fmt.Sprintf("repos/%s/actions/variables/%s", ghrepo.FullName(baseRepo), url.PathEscape(opts.VariableName)) host = baseRepo.RepoHost() } diff --git a/pkg/cmd/variable/list/list.go b/pkg/cmd/variable/list/list.go index 764c0af4d..3daf5f4fe 100644 --- a/pkg/cmd/variable/list/list.go +++ b/pkg/cmd/variable/list/list.go @@ -3,6 +3,7 @@ package list import ( "fmt" "net/http" + "net/url" "slices" "strings" "time" @@ -197,12 +198,12 @@ func getRepoVariables(client *http.Client, repo ghrepo.Interface) ([]shared.Vari } func getEnvVariables(client *http.Client, repo ghrepo.Interface, envName string) ([]shared.Variable, error) { - path := fmt.Sprintf("repos/%s/environments/%s/variables", ghrepo.FullName(repo), envName) + path := fmt.Sprintf("repos/%s/environments/%s/variables", ghrepo.FullName(repo), url.PathEscape(envName)) return getVariables(client, repo.RepoHost(), path) } func getOrgVariables(client *http.Client, host, orgName string, showSelectedRepoInfo bool) ([]shared.Variable, error) { - variables, err := getVariables(client, host, fmt.Sprintf("orgs/%s/actions/variables", orgName)) + variables, err := getVariables(client, host, fmt.Sprintf("orgs/%s/actions/variables", url.PathEscape(orgName))) if err != nil { return nil, err }