Skip to content

Commit 367f02e

Browse files
authored
Merge pull request #4 from zerodha/feat-redirect-uri
Add method for relative redirect and update fashttp+router
2 parents 59e046b + c2b65c7 commit 367f02e

File tree

5 files changed

+96
-26
lines changed

5 files changed

+96
-26
lines changed

.github/workflows/go-test.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ jobs:
2121
go-version: ${{ matrix.go }}
2222

2323
- name: Run Test
24-
run: go test -v ./...
24+
run: go test -p 1 -v ./...
2525

2626
- name: Run Coverage
27-
run: go test -v -cover ./...
27+
run: go test -p 1 -v -cover ./...

fastglue.go

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"encoding/xml"
88
"errors"
99
"fmt"
10+
"strings"
1011

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

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

344345
// Copy current url before mutating.
345346
rURI := &fasthttp.URI{}
346347
r.RequestCtx.URI().CopyTo(rURI)
347-
rURI.Update(uri)
348+
rURI.Update(url)
348349

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

391+
// RedirectURI redirects to the given URI. If URI contains hostname, scheme etc
392+
// then its stripped and only path is used for the redirect.
393+
// Used for internal app redirect and to prevent open redirect vulnerabilities.
394+
func (r *Request) RedirectURI(uri string, code int, args map[string]interface{}, anchor string) error {
395+
// Parse URI to check if its a valid URI.
396+
u := &fasthttp.URI{}
397+
err := u.Parse(nil, []byte(uri))
398+
if err != nil {
399+
return err
400+
}
401+
402+
// Use only the rquest URI from the parsed URL.
403+
// This makes sure we only redirect to relative path.
404+
rURI := string(u.RequestURI())
405+
hash := string(u.Hash())
406+
if len(hash) > 0 {
407+
rURI = rURI + "#" + hash
408+
}
409+
410+
// If path starts with more than one forward slash then its considerd
411+
// as full URL and leads to open redirect vulnerability.
412+
// So here we strip out all leading forward slashes and replace it
413+
// with one forward slash so its always considered as a path.
414+
fURI := "/" + strings.TrimLeft(rURI, "/")
415+
416+
return r.Redirect(fURI, code, args, anchor)
417+
}
418+
390419
// ParseAuthHeader parses the Authorization header and returns an api_key and access_token
391420
// based on the auth schemes passed as bit flags (eg: AuthBasic, AuthBasic | AuthToken etc.).
392421
func (r *Request) ParseAuthHeader(schemes uint8) ([]byte, []byte, error) {

fastglue_test.go

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ func init() {
5555

5656
srv.GET("/get", myGEThandler)
5757
srv.GET("/next", myNextRedirectHandler)
58+
srv.GET("/next-uri", myNextRedirectURIHandler)
5859
srv.GET("/redirect", myRedirectHandler)
5960
srv.DELETE("/delete", myGEThandler)
6061
srv.POST("/post", myPOSThandler)
@@ -180,6 +181,15 @@ func myNextRedirectHandler(r *Request) error {
180181
}
181182
}
182183

184+
func myNextRedirectURIHandler(r *Request) error {
185+
next := r.RequestCtx.QueryArgs().Peek("next")
186+
if len(next) > 0 {
187+
return r.RedirectURI(string(next), fasthttp.StatusFound, nil, "")
188+
} else {
189+
return r.SendErrorEnvelope(fasthttp.StatusBadRequest, "Invalid value for param `next`", nil, "InputException")
190+
}
191+
}
192+
183193
func myPOSThandler(r *Request) error {
184194
var p Person
185195
if err := r.DecodeFail(&p, "json"); err != nil {
@@ -594,7 +604,7 @@ func TestRedirectScheme(t *testing.T) {
594604
}
595605
if tErr, ok := err.(net.Error); !ok {
596606
t.Fatalf("Expected timeout error on https redirect but got: %v", err)
597-
} else if !tErr.Timeout() {
607+
} else if !tErr.Timeout() && !strings.Contains(tErr.Error(), "server gave HTTP response to HTTPS client") {
598608
t.Fatalf("Expected timeout error on https redirect but got: %v", err)
599609
}
600610
}
@@ -619,6 +629,32 @@ func TestNextRedirectRequest(t *testing.T) {
619629
}
620630
}
621631

632+
func TestNextRedirectURIRequest(t *testing.T) {
633+
// Test relative url with query args and hash fragment.
634+
resp := GETrequest(srvRoot+"/next-uri?param=123&next=%2Ffoo%2Fbar%3Fabc%3D123%2312345", t)
635+
if resp.Request.URL.String() != "http://127.0.0.1:10200/foo/bar?abc=123#12345" {
636+
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())
637+
}
638+
639+
// Test relative url with single forward slash.
640+
resp = GETrequest(srvRoot+"/next-uri?param=123&next=%2Fexample.com%2F%2F", t)
641+
if resp.Request.URL.String() != srvRoot+"/example.com/" {
642+
t.Fatalf("Incorrect redirect. Expected redirect %s and got redirect %s", srvRoot+"/example.com/", resp.Request.URL.String())
643+
}
644+
645+
// Test relative url with double forward slash.
646+
resp = GETrequest(srvRoot+"/next-uri?param=123&next=%2F%2Fexample.com%2F%2F", t)
647+
if resp.Request.URL.String() != srvRoot+"/" {
648+
t.Fatalf("Incorrect redirect. Expected redirect %s and got redirect %s", srvRoot+"/", resp.Request.URL.String())
649+
}
650+
651+
// Test absolute redirect url.
652+
resp = GETrequest(srvRoot+"/next-uri?param=123&next=https%3A%2F%2Fzerodha.com%3Fabc%3D123%23xyz", t)
653+
if resp.Request.URL.String() != srvRoot+"/?abc=123#xyz" {
654+
t.Fatalf("Incorrect redirect. Expected redirect %s and got redirect %s", srvRoot+"/?abc=123#xyz", resp.Request.URL.String())
655+
}
656+
}
657+
622658
func TestAnyHandler(t *testing.T) {
623659
c := http.Client{
624660
Timeout: time.Second * 3,

go.mod

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,8 @@ module github.com/zerodha/fastglue
33
go 1.14
44

55
require (
6-
github.com/fasthttp/router v1.0.2
6+
github.com/fasthttp/router v1.4.5
77
github.com/gorilla/schema v1.1.0
8-
github.com/klauspost/compress v1.10.6 // indirect
9-
github.com/savsgio/gotils v0.0.0-20200413113635-8c468ce75cca // indirect
108
github.com/stretchr/testify v1.6.0
11-
github.com/valyala/fasthttp v1.9.0
9+
github.com/valyala/fasthttp v1.32.0
1210
)

go.sum

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

0 commit comments

Comments
 (0)