Skip to content

net/url: JoinPath may silently drop unescaped path #75799

@ShoshinNikita

Description

@ShoshinNikita

go version: go1.24, go1.25, dev

What did you do?

// Go Playground - https://go.dev/play/p/joiLsJ4cjNh?v=gotip

func main() {
  u, err := url.JoinPath("/qwerty", "a", "b", "100%")
  if err != nil {
    panic(err)
  }
  fmt.Println(u)
  // Expected: /qwerty/a/b/100%25
  // Got:      /qwerty
}

This behavior is caused by (*URL).JoinPath ignoring an error returned by (*URL).setPath - invalid URL escape "%" in this case.

func (u *URL) JoinPath(elem ...string) *URL {
	...
	url := *u
	url.setPath(p) // <--
	return &url
}

Potential fix

I have a potential fix for this bug:

func (u *URL) JoinPath(elem ...string) *URL {
+	for i := range elem {
+		if _, err := unescape(elem[i], encodePath); err != nil {
+			elem[i] = escape(elem[i], encodePath)
+		}
+	}
+
	elem = append([]string{u.EscapedPath()}, elem...)
	...
	return &url
}

It passes all existing tests in TestJoinPath, plus a new ones:

{
	base: "/",
	elem: []string{"a", "b", "100%"},
	out:  "/a/b/100%25",
},
{
	base: "/",
	elem: []string{"/a/b/100%"},
	out:  "/a/b/100%25",
},

Though, I am not sure whether this fix breaks any existing code or has any side-effects.

Metadata

Metadata

Assignees

No one assigned

    Labels

    BugReportIssues describing a possible bug in the Go implementation.NeedsInvestigationSomeone must examine and confirm this is a valid issue and not a duplicate of an existing one.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions