Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add FormatURLPath helper to allow safer URL path building #573

Merged
merged 3 commits into from
Jun 7, 2018

Conversation

brandur
Copy link
Contributor

@brandur brandur commented Jun 6, 2018

Adds a helper to the top-level package called FormatURLPath. It's
basically a pass-through to Sprintf, except that it only takes string
parameters. This is designed to make invocations slightly more safe —
we've had a little trouble in the past where a string pointer was
accidentally passed instead of a string, and because Sprintf doesn't
automatically dereference or anything, the result is an invalid URL.

It also gives us the opportunity to call url.QueryEscape on every
parameter for a little additional safety. Previously, this was being
invoked manually only in certain packages like plan.

I changed all uses of %v in invocations to %s for consistency and
because we won't need any types beyond strings. In the same spirit, I
also changed all the places that we were concatenating strings (like
"/skus/"+id) to invoke FormatURLPath.

This patch is fairly noisy, but you can mostly just examine the added
function in stripe.go and test in stripe_test.go:

func FormatURLPath(format string, params ...string) string {
    untypedParams := make([]interface{}, len(params))
    for i, param := range params {
	    untypedParams[i] = interface{}(url.QueryEscape(param))
    }

    return fmt.Sprintf(format, untypedParams...)
}

r? @remi-stripe If you wouldn't mind.
cc @stripe/api-libraries

Adds a helper to the top-level package called `FormatURLPath`. It's
basically a pass-through to `Sprintf`, except that it only takes string
parameters. This is designed to make invocations slightly more safe --
we've had a little trouble in the past where a string pointer was
accidentally passed instead of a string, and because `Sprintf` doesn't
automatically dereference or anything, the result is an invalid URL.

It also gives us the opportunity to call `url.QueryEscape` on every
parameter for a little additional safety. Previously, this was being
invoked manually only in certain packages like `plan`.

I changed all uses of `%v` in invocations to `%s` for consistency and
because we won't need any types beyond strings. In the same spirit, I
also changed all the places that we were concatenating strings (like
`"/skus/"+id`) to invoke `FormatURLPath`.

This patch is fairly noisy, but you can mostly just examine the added
function in `stripe.go` and test in `stripe_test.go`:

``` go
func FormatURLPath(format string, params ...string) string {
    // Parameters must be converted to interface{} before we send them to
    // Sprintf.
    untypedParams := make([]interface{}, len(params))
    for i, param := range params {
	    untypedParams[i] = interface{}(url.QueryEscape(param))
    }

    return fmt.Sprintf(format, untypedParams...)
}
```
Copy link
Contributor

@remi-stripe remi-stripe left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor comments but lgtm

Thank you so much for fixing that one!

// Tests that each parameter is escaped for use in URLs
assert.Equal(t, "/resources/%25",
stripe.FormatURLPath("/resources/%s", "%"))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we test/handle cases where we're missing a param like

stripe.FormatURLPath("/resources/%s/subresource/%v", "%")

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I added some tests (and an implementation) for this. See 78d772a.

Just a heads up though: this does add a little bit of runtime overhead because we have to check the response from Sprintf for bad values (see 78d772a for details there). There's also a minute chance it could cause a problem if a user has coupon or plan ID that contains (MISSING) or %!(EXTRA . That's hopefully quite unlikely, but still unfortunately possible.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Gotcha, I did not think about the overhead. Maybe it's better to skip this then since it would mostly be our own mistake and it is unlikely to happen and not be caught by another crash or stripe-mock right?
Sorry for asking you to do the work and then undo it.

Copy link
Contributor

@brandur-stripe brandur-stripe Jun 7, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No worries! It was an interesting experiment.

Alright, I'm going to revert the commit, but leave it in the history so that we can recover it if we want to. I don't think this has been a huge problem so far so it's probably slightly better to leave it out, but we can bring it back if mismatches in number of parameters is ever a problem in the future.

@@ -61,7 +61,7 @@ func (c Client) GetByID(id string, params *stripe.AccountParams) (*stripe.Accoun
}

account := &stripe.Account{}
err := c.B.Call("GET", "/accounts/"+id, c.Key, body, commonParams, account)
err := c.B.Call("GET", stripe.FormatURLPath("/accounts/%s", id), c.Key, body, commonParams, account)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a bit minor but would it be cleaner to use a temporary variable? That's my personal dislike of methods called as params

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I could see this being set as path := stripe.FormatURLPath(...). Given this is fairly minor and quite a few lines to change though, would you mind if we did that as a follow up?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

totally. Especially if we end up changing most of that code to be a helper function globally too.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent! Thanks.

@stripe-ci stripe-ci removed the approved label Jun 7, 2018
Reverts 78d772a which added some
protections against mismatches in number of parameters.

This was an interesting experiment, but it adds some runtime overhead,
and so far this hasn't been a huge problem. I'm taking it back out but
leaving it in Git history in case we want to resurrect it at some point.
@brandur-stripe brandur-stripe merged commit 41c20dd into master Jun 7, 2018
@brandur-stripe
Copy link
Contributor

Released as 32.2.0.

@brandur-stripe brandur-stripe deleted the brandur-safer-path-building branch June 7, 2018 00:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants