Skip to content

Commit

Permalink
Merge pull request #173 from stripe/brandur-fix-double-slashes
Browse files Browse the repository at this point in the history
Deduplicate slashes in incoming paths
  • Loading branch information
brandur-stripe committed Jul 22, 2019
2 parents dd1e8e6 + 42ac9fd commit f999292
Show file tree
Hide file tree
Showing 3 changed files with 88 additions and 3 deletions.
16 changes: 13 additions & 3 deletions main.go
Expand Up @@ -88,7 +88,12 @@ func main() {
abort(fmt.Sprintf("Error initializing router: %v\n", err))
}

http.HandleFunc("/", stub.HandleRequest)
httpMux := http.NewServeMux()
httpMux.HandleFunc("/", stub.HandleRequest)

// Deduplicates doubled slashes in paths. e.g. `//v1/charges` becomes
// `/v1/charges`.
handler := &DoubleSlashFixHandler{httpMux}

httpListener, err := options.getHTTPListener()
if err != nil {
Expand All @@ -98,7 +103,9 @@ func main() {
// Only start HTTP if requested (it will activate by default with no arguments, but it won't start if
// HTTPS is explicitly requested and HTTP is not).
if httpListener != nil {
server := http.Server{}
server := http.Server{
Handler: handler,
}

// Listen in a new Goroutine that so we can start a simultaneous HTTPS
// listener if necessary.
Expand Down Expand Up @@ -138,7 +145,10 @@ func main() {
NextProtos: []string{"h2"},
}

server := http.Server{TLSConfig: tlsConfig}
server := http.Server{
Handler: handler,
TLSConfig: tlsConfig,
}
tlsListener := tls.NewListener(httpsListener, tlsConfig)

go func() {
Expand Down
19 changes: 19 additions & 0 deletions server.go
Expand Up @@ -21,6 +21,25 @@ import (
// Public types
//

// DoubleSlashFixHandler is a specialized handler that wraps an HTTP mux and
// deduplicates any doubled slashes that are included in an incoming path. So
// `//v1/charges` would become `/v1/charges`. This works around the standard Go
// behavior, which is to redirect the request with a 301 before it reaches the
// underlying handler.
//
// The reason we deduplicate is that in some API libraries occasionally
// generate paths with double slashes, and the real Stripe API responds to
// these requests normally, so stripe-mock emulates that behavior.
type DoubleSlashFixHandler struct {
Mux http.Handler
}

// ServeHTTP serves an HTTP request.
func (h *DoubleSlashFixHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
r.URL.Path = strings.Replace(r.URL.Path, "//", "/", -1)
h.Mux.ServeHTTP(w, r)
}

// ExpansionLevel represents expansions on a single "level" of resource. It may
// have subexpansions that are meant to take effect on resources that are
// nested below it (on other levels).
Expand Down
56 changes: 56 additions & 0 deletions server_test.go
Expand Up @@ -19,6 +19,62 @@ import (
// Tests
//

func TestDoubleSlashFixHandler(t *testing.T) {
var lastPath string

httpMux := http.NewServeMux()
httpMux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
lastPath = r.URL.Path
})

doubleSlashFixHandler := &DoubleSlashFixHandler{httpMux}

// Slash deduplication
{
lastPath = ""

req := httptest.NewRequest(
http.MethodGet, "http://example.com//v1/charges", nil)
w := httptest.NewRecorder()

doubleSlashFixHandler.ServeHTTP(w, req)

assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "/v1/charges", lastPath)
}

// Requests without duplicated slashes work normally
{
lastPath = ""

req := httptest.NewRequest(
http.MethodGet, "http://example.com/v1/charges", nil)
w := httptest.NewRecorder()

doubleSlashFixHandler.ServeHTTP(w, req)

assert.Equal(t, http.StatusOK, w.Code)
assert.Equal(t, "/v1/charges", lastPath)
}

// Demonstrates the (undesirable) standard Go behavior without the handler
{
lastPath = ""

req := httptest.NewRequest(
http.MethodGet, "http://example.com//v1/charges", nil)
w := httptest.NewRecorder()

// Note that we skip the double slash fix handler and have the mux
// serve directly
httpMux.ServeHTTP(w, req)

// This is the default Go behavior (301)
assert.Equal(t, http.StatusMovedPermanently, w.Code)
assert.Equal(t, "", lastPath)
}
}

func TestStubServer(t *testing.T) {
resp, body := sendRequest(t, "POST", "/v1/charges",
"amount=123", getDefaultHeaders(), nil)
Expand Down

0 comments on commit f999292

Please sign in to comment.