Misusing go's `fmt.Sscanf`
Recently I was building a new website in go at $work and needed to do some URL parsing to grab some expected parameters in the URL.
The URL was expected to look something like the following: /path/:id1/:id2
, where I was trying to grab id1
and id2
out of the URL.
In the past, I’ve done something along the lines of:
splitPath := strings.Split(r.URL.Path, "/")
// error handling, etc.
// splitPath is ["", "path", ":id1", ":id2"],
// because of the way `strings.Split` works
id1 := splitPath[2]
id2 := splitPath[3]
However, I had come across the following code, from the source code of builds.sr.ht:
var (
jobId int
op string
)
_, err := fmt.Sscanf(r.URL.Path, "/job/%d/%s", &jobId, &op)
fmt.Sscanf
? Haven’t seen that in use often! But it seemed like a great fit for the exact problem I was solving.
So, I tried it:
var id1, id2 string
matchCount, err := fmt.Sscanf(r.URL.Path, "/path/%s/%s", &id1, &id2)
if err != nil {
fmt.Printf("err: %s, match count: %d\n", err, matchCount)
fmt.Printf("id1: %s, id2: %s", id1, id2)
} else {
fmt.Printf("no err, id1: %s, id2: %s", id1, id2)
}
Here’s a slightly modified version on play.golang.org.
Spoiler: here's the output.
err: unexpected EOF, match count: 1id1: abc/def, id2:
So, the first match hit by the scan, because it’s %s
, continues through the /
character and picks up id2
’s value as well, leaving nothing for id2
.
So why did it work in the line of code shown above?
Because the %d
format specifier only captures numeric characters, so the /
character breaks up the scan.
I messed around with this approach for awhile, but ultimately gave up and went back to my approach using strings.Split
.
But, it’s a nice reminder that format strings in go can be used both for formatting output and for scanning input, even if there are some footguns attached.
🔫