⇠ back to blog posts

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: 1
id1: 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.


Thoughts? Corrections? Feel free to reply via email!