Skip to content

Commit

Permalink
Merge pull request #4 from zerodha/feat-redirect-uri
Browse files Browse the repository at this point in the history
Add method for relative redirect and update fashttp+router
  • Loading branch information
knadh authored Jan 21, 2022
2 parents 59e046b + c2b65c7 commit 367f02e
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 26 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/go-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
go-version: ${{ matrix.go }}

- name: Run Test
run: go test -v ./...
run: go test -p 1 -v ./...

- name: Run Coverage
run: go test -v -cover ./...
run: go test -p 1 -v -cover ./...
35 changes: 32 additions & 3 deletions fastglue.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"encoding/xml"
"errors"
"fmt"
"strings"

fasthttprouter "github.com/fasthttp/router"
"github.com/valyala/fasthttp"
Expand Down Expand Up @@ -335,16 +336,16 @@ func (r *Request) SendJSON(code int, v interface{}) error {
return nil
}

// Redirect redirects to the given URI.
// Redirect redirects to the given URL.
// Accepts optional query args and anchor tags.
// Test : curl -I -L -X GET "localhost:8000/redirect"
func (r *Request) Redirect(uri string, code int, args map[string]interface{}, anchor string) error {
func (r *Request) Redirect(url string, code int, args map[string]interface{}, anchor string) error {
var redirectURI string

// Copy current url before mutating.
rURI := &fasthttp.URI{}
r.RequestCtx.URI().CopyTo(rURI)
rURI.Update(uri)
rURI.Update(url)

// This avoids a redirect vulnerability when `uri` is relative and contains double slash.
// For example: if the `uri` is `/bing.com//` which is a relative path passed from client side,
Expand Down Expand Up @@ -387,6 +388,34 @@ func (r *Request) Redirect(uri string, code int, args map[string]interface{}, an
return nil
}

// RedirectURI redirects to the given URI. If URI contains hostname, scheme etc
// then its stripped and only path is used for the redirect.
// Used for internal app redirect and to prevent open redirect vulnerabilities.
func (r *Request) RedirectURI(uri string, code int, args map[string]interface{}, anchor string) error {
// Parse URI to check if its a valid URI.
u := &fasthttp.URI{}
err := u.Parse(nil, []byte(uri))
if err != nil {
return err
}

// Use only the rquest URI from the parsed URL.
// This makes sure we only redirect to relative path.
rURI := string(u.RequestURI())
hash := string(u.Hash())
if len(hash) > 0 {
rURI = rURI + "#" + hash
}

// If path starts with more than one forward slash then its considerd
// as full URL and leads to open redirect vulnerability.
// So here we strip out all leading forward slashes and replace it
// with one forward slash so its always considered as a path.
fURI := "/" + strings.TrimLeft(rURI, "/")

return r.Redirect(fURI, code, args, anchor)
}

// ParseAuthHeader parses the Authorization header and returns an api_key and access_token
// based on the auth schemes passed as bit flags (eg: AuthBasic, AuthBasic | AuthToken etc.).
func (r *Request) ParseAuthHeader(schemes uint8) ([]byte, []byte, error) {
Expand Down
38 changes: 37 additions & 1 deletion fastglue_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ func init() {

srv.GET("/get", myGEThandler)
srv.GET("/next", myNextRedirectHandler)
srv.GET("/next-uri", myNextRedirectURIHandler)
srv.GET("/redirect", myRedirectHandler)
srv.DELETE("/delete", myGEThandler)
srv.POST("/post", myPOSThandler)
Expand Down Expand Up @@ -180,6 +181,15 @@ func myNextRedirectHandler(r *Request) error {
}
}

func myNextRedirectURIHandler(r *Request) error {
next := r.RequestCtx.QueryArgs().Peek("next")
if len(next) > 0 {
return r.RedirectURI(string(next), fasthttp.StatusFound, nil, "")
} else {
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, "Invalid value for param `next`", nil, "InputException")
}
}

func myPOSThandler(r *Request) error {
var p Person
if err := r.DecodeFail(&p, "json"); err != nil {
Expand Down Expand Up @@ -594,7 +604,7 @@ func TestRedirectScheme(t *testing.T) {
}
if tErr, ok := err.(net.Error); !ok {
t.Fatalf("Expected timeout error on https redirect but got: %v", err)
} else if !tErr.Timeout() {
} else if !tErr.Timeout() && !strings.Contains(tErr.Error(), "server gave HTTP response to HTTPS client") {
t.Fatalf("Expected timeout error on https redirect but got: %v", err)
}
}
Expand All @@ -619,6 +629,32 @@ func TestNextRedirectRequest(t *testing.T) {
}
}

func TestNextRedirectURIRequest(t *testing.T) {
// Test relative url with query args and hash fragment.
resp := GETrequest(srvRoot+"/next-uri?param=123&next=%2Ffoo%2Fbar%3Fabc%3D123%2312345", t)
if resp.Request.URL.String() != "http://127.0.0.1:10200/foo/bar?abc=123#12345" {
t.Fatalf("Incorrect redirect. Expected redirect %s and got redirect %s", "http://127.0.0.1:10200/foo/bar?abc=123#12345", resp.Request.URL.String())
}

// Test relative url with single forward slash.
resp = GETrequest(srvRoot+"/next-uri?param=123&next=%2Fexample.com%2F%2F", t)
if resp.Request.URL.String() != srvRoot+"/example.com/" {
t.Fatalf("Incorrect redirect. Expected redirect %s and got redirect %s", srvRoot+"/example.com/", resp.Request.URL.String())
}

// Test relative url with double forward slash.
resp = GETrequest(srvRoot+"/next-uri?param=123&next=%2F%2Fexample.com%2F%2F", t)
if resp.Request.URL.String() != srvRoot+"/" {
t.Fatalf("Incorrect redirect. Expected redirect %s and got redirect %s", srvRoot+"/", resp.Request.URL.String())
}

// Test absolute redirect url.
resp = GETrequest(srvRoot+"/next-uri?param=123&next=https%3A%2F%2Fzerodha.com%3Fabc%3D123%23xyz", t)
if resp.Request.URL.String() != srvRoot+"/?abc=123#xyz" {
t.Fatalf("Incorrect redirect. Expected redirect %s and got redirect %s", srvRoot+"/?abc=123#xyz", resp.Request.URL.String())
}
}

func TestAnyHandler(t *testing.T) {
c := http.Client{
Timeout: time.Second * 3,
Expand Down
6 changes: 2 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ module github.com/zerodha/fastglue
go 1.14

require (
github.com/fasthttp/router v1.0.2
github.com/fasthttp/router v1.4.5
github.com/gorilla/schema v1.1.0
github.com/klauspost/compress v1.10.6 // indirect
github.com/savsgio/gotils v0.0.0-20200413113635-8c468ce75cca // indirect
github.com/stretchr/testify v1.6.0
github.com/valyala/fasthttp v1.9.0
github.com/valyala/fasthttp v1.32.0
)
39 changes: 23 additions & 16 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,30 +1,37 @@
github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E=
github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fasthttp/router v1.0.2 h1:rdYdcAmwOLqWuFgc4afa409SYmuw4t0A66K5Ib+GT3I=
github.com/fasthttp/router v1.0.2/go.mod h1:Myk/ofrwtfiLSCIfbE44+e+PyP3mR6JhZg3AYzqwJI0=
github.com/fasthttp/router v1.4.5 h1:YZonsKCssEwEi3veDMhL6okIx550qegAiuXAK8NnM3Y=
github.com/fasthttp/router v1.4.5/go.mod h1:UYExWhCy7pUmavRZ0XfjEgHwzxyKwyS8uzXhaTRDG9Y=
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/gorilla/schema v1.1.0 h1:CamqUDOFUBqzrvxuz2vEwo8+SUdwsluFh7IlzJh30LY=
github.com/gorilla/schema v1.1.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU=
github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A=
github.com/klauspost/compress v1.10.6 h1:SP6zavvTG3YjOosWePXFDlExpKIWMTO4SE/Y8MZB2vI=
github.com/klauspost/compress v1.10.6/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek=
github.com/klauspost/compress v1.13.4 h1:0zhec2I8zGnjWcKyLl6i3gPqKANCCn5e9xmviEEeX6s=
github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/savsgio/gotils v0.0.0-20200319105752-a9cc718f6a3f/go.mod h1:lHhJedqxCoHN+zMtwGNTXWmF0u9Jt363FYRhV6g0CdY=
github.com/savsgio/gotils v0.0.0-20200413113635-8c468ce75cca h1:Qe7Mtuhjkk38HVpRtvWdziZJcwG3Qup1mfyvyOrcnyM=
github.com/savsgio/gotils v0.0.0-20200413113635-8c468ce75cca/go.mod h1:TWNAOTaVzGOXq8RbEvHnhzA/A2sLZzgn0m6URjnukY8=
github.com/savsgio/gotils v0.0.0-20211223103454-d0aaa54c5899 h1:Orn7s+r1raRTBKLSc9DmbktTT04sL+vkzsbRD2Q8rOI=
github.com/savsgio/gotils v0.0.0-20211223103454-d0aaa54c5899/go.mod h1:oejLrk1Y/5zOF+c/aHtXqn3TFlzzbAgPWg8zBiAHDas=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.0 h1:jlIyCplCJFULU/01vCkhKuTyc3OorI3bJFuw6obfgho=
github.com/stretchr/testify v1.6.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.9.0 h1:hNpmUdy/+ZXYpGy0OBfm7K0UQTzb73W0T0U4iJIVrMw=
github.com/valyala/fasthttp v1.9.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
github.com/valyala/fasthttp v1.32.0 h1:keswgWzyKyNIIjz2a7JmCYHOOIkRp6HMx9oTV6QrZWY=
github.com/valyala/fasthttp v1.32.0/go.mod h1:2rsYD01CKFrjjsvFxx75KlEUNpWNBY9JWD3K/7o2Cus=
github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
Expand Down

0 comments on commit 367f02e

Please sign in to comment.