Skip to content
Permalink
Browse files Browse the repository at this point in the history
security: fix Open Redirection vulnerability
  • Loading branch information
unknwon committed Feb 16, 2020
1 parent 326bc77 commit 329b0c4
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 102 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
/.idea
3 changes: 1 addition & 2 deletions go.mod
Expand Up @@ -3,8 +3,7 @@ module github.com/go-macaron/i18n
go 1.12

require (
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337
github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e
github.com/stretchr/testify v1.4.0
github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6
golang.org/x/text v0.3.2
gopkg.in/macaron.v1 v1.3.4
Expand Down
13 changes: 11 additions & 2 deletions go.sum
@@ -1,21 +1,26 @@
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/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191 h1:NjHlg70DuOkcAMqgt0+XA+NHwtu66MkTVVgR4fFWbcI=
github.com/go-macaron/inject v0.0.0-20160627170012-d8a0b8677191/go.mod h1:VFI2o2q9kYsC4o7VP1HrEVosiZZTd+MVT3YZx4gqvJw=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c h1:7lF+Vz0LqiRidnzC1Oq86fpX1q/iEv2KJdrCtttYjT4=
github.com/gopherjs/gopherjs v0.0.0-20190430165422-3e4dfb77656c/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
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/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/assertions v1.0.1 h1:voD4ITNjPL5jjBfgR/r8fPIIBrliWrWHeiJApdr3r4w=
github.com/smartystreets/assertions v1.0.1/go.mod h1:kHHU4qYBaI3q23Pp3VPrmWhuIUrLW/7eUrw0BU5VaoM=
github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337 h1:WN9BUFbdyOsSH/XohnWpXOlq9NBD5sGAB2FciQMUEe8=
github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e h1:GSGeB9EAKY2spCABz6xOX5DbxZEXolK+nBSvmsQwRjM=
github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e/go.mod h1:tOOxU81rwgoCLoOVVPHb6T/wt8HZygqH5id+GNnlCXM=
github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6 h1:sRrkJEHtNoaSvyXMbRgofEOX4/3gMiraevQKJdIBhYE=
Expand All @@ -37,7 +42,11 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190802220118-1d1727260058/go.mod h1:jcCCGcm9btYwXyDqrUWc6MKQKKGJCWEQ3AfLSRIbEuI=
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/ini.v1 v1.46.0 h1:VeDZbLYGaupuvIrsYCEOe/L/2Pcs5n7hdO1ZTjporag=
gopkg.in/ini.v1 v1.46.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/macaron.v1 v1.3.4 h1:HvIscOwxhFhx3swWM/979wh2QMYyuXrNmrF9l+j3HZs=
gopkg.in/macaron.v1 v1.3.4/go.mod h1:/RoHTdC8ALpyJ3+QR36mKjwnT1F1dyYtsGM9Ate6ZFI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
18 changes: 11 additions & 7 deletions i18n.go
Expand Up @@ -17,19 +17,23 @@ package i18n

import (
"fmt"
"os"
"path"
"strings"

"github.com/unknwon/com"
"github.com/unknwon/i18n"
"golang.org/x/text/language"
"gopkg.in/macaron.v1"
)

const _VERSION = "0.4.1"

func Version() string {
return _VERSION
// isFile returns true if given path is a file,
// or returns false when it's a directory or does not exist.
func isFile(filePath string) bool {
f, e := os.Stat(filePath)
if e != nil {
return false
}
return !f.IsDir()
}

// initLocales initializes language type list and Accept-Language header matcher.
Expand All @@ -41,7 +45,7 @@ func initLocales(opt Options) language.Matcher {
// Append custom locale file.
custom := []interface{}{}
customPath := path.Join(opt.CustomDirectory, fname)
if com.IsFile(customPath) {
if isFile(customPath) {
custom = append(custom, customPath)
}

Expand Down Expand Up @@ -221,7 +225,7 @@ func I18n(options ...Options) macaron.Handler {
ctx.Data["RestLangs"] = restLangs

if opt.Redirect && isNeedRedir {
ctx.Redirect(opt.SubURL + ctx.Req.RequestURI[:strings.Index(ctx.Req.RequestURI, "?")])
ctx.Redirect(opt.SubURL + path.Clean(ctx.Req.RequestURI[:strings.Index(ctx.Req.RequestURI, "?")]))
}
}
}
208 changes: 117 additions & 91 deletions i18n_test.go
Expand Up @@ -15,118 +15,144 @@
package i18n

import (
"errors"
"net/http"
"net/http/httptest"
"testing"

. "github.com/smartystreets/goconvey/convey"
"github.com/stretchr/testify/assert"
"gopkg.in/macaron.v1"
)

func Test_Version(t *testing.T) {
Convey("Check package version", t, func() {
So(Version(), ShouldEqual, _VERSION)
})
}

func Test_I18n(t *testing.T) {
Convey("Use i18n middleware", t, func() {
Convey("No langauge", func() {
defer func() {
So(recover(), ShouldNotBeNil)
}()
func TestI18n(t *testing.T) {
t.Run("no language", func(t *testing.T) {
defer func() {
assert.Equal(t, "no language is specified", recover())
}()

m := macaron.New()
m.Use(I18n(Options{}))
})
m := macaron.New()
m.Use(I18n(Options{}))
})

Convey("Languages and names not match", func() {
defer func() {
So(recover(), ShouldNotBeNil)
}()
t.Run("languages and names not match", func(t *testing.T) {
defer func() {
assert.Equal(t, "length of langs is not same as length of names", recover())
}()

m := macaron.New()
m.Use(I18n(Options{
Langs: []string{"en-US"},
}))
})
m := macaron.New()
m.Use(I18n(Options{
Langs: []string{"en-US"},
}))
})

Convey("Invalid directory", func() {
defer func() {
So(recover(), ShouldNotBeNil)
}()

m := macaron.New()
m.Use(I18n(Options{
Directory: "404",
Langs: []string{"en-US"},
Names: []string{"English"},
}))
})
t.Run("invalid directory", func(t *testing.T) {
defer func() {
assert.Equal(t, errors.New("fail to set message file(en-US): open 404/locale_en-US.ini: no such file or directory"), recover())
}()

m := macaron.New()
m.Use(I18n(Options{
Directory: "404",
Langs: []string{"en-US"},
Names: []string{"English"},
}))
})

Convey("With correct options", func() {
m := macaron.New()
m.Use(I18n(Options{
Files: map[string][]byte{"locale_en-US.ini": []byte("")},
Langs: []string{"en-US"},
Names: []string{"English"},
}))
m.Get("/", func() {})
t.Run("with correct options", func(t *testing.T) {
m := macaron.New()
m.Use(I18n(Options{
Files: map[string][]byte{"locale_en-US.ini": []byte("")},
Langs: []string{"en-US"},
Names: []string{"English"},
}))
m.Get("/", func() {})

resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
m.ServeHTTP(resp, req)
})

resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
m.ServeHTTP(resp, req)
t.Run("set by Accept-Language", func(t *testing.T) {
m := macaron.New()
m.Use(I18n(Options{
Langs: []string{"en-US", "zh-CN", "it-IT"},
Names: []string{"English", "简体中文", "Italiano"},
}))
m.Get("/", func(l Locale) {
assert.Equal(t, "it-IT", l.Language())
})

Convey("Set by redirect of URL parameter", func() {
m := macaron.New()
m.Use(I18n(Options{
Langs: []string{"en-US"},
Names: []string{"English"},
Redirect: true,
}))
m.Get("/", func() {})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
req.Header.Set("Accept-Language", "it")
m.ServeHTTP(resp, req)
})

resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/?lang=en-US", nil)
So(err, ShouldBeNil)
req.RequestURI = "/?lang=en-US"
m.ServeHTTP(resp, req)
t.Run("set to default language", func(t *testing.T) {
m := macaron.New()
m.Use(I18n(Options{
Langs: []string{"en-US", "zh-CN", "it-IT"},
Names: []string{"English", "简体中文", "Italiano"},
}))
m.Get("/", func(l Locale) {
assert.Equal(t, "en-US", l.Language())
})

Convey("Set by Accept-Language", func() {
m := macaron.New()
m.Use(I18n(Options{
Langs: []string{"en-US", "zh-CN", "it-IT"},
Names: []string{"English", "简体中文", "Italiano"},
}))
m.Get("/", func(l Locale) {
So(l.Language(), ShouldEqual, "it-IT")
})
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/", nil)
if err != nil {
t.Fatal(err)
}
req.Header.Set("Accept-Language", "ru")
m.ServeHTTP(resp, req)
})
}

func TestRedirect(t *testing.T) {
m := macaron.New()
m.Use(I18n(Options{
Langs: []string{"en-US"},
Names: []string{"English"},
Redirect: true,
}))
m.Get("/", func() {})

tests := []struct {
url string
expURL string
}{
{
url: "/?lang=en-US",
expURL: "/",
}, {
url: "//example.com?lang=en-US",
expURL: "/example.com",
}, {
url: "/abc/../../../example.com?lang=en-US",
expURL: "/example.com",
}, {
url: "/../abc/../example.com?lang=en-US",
expURL: "/example.com",
},
}
for _, test := range tests {
t.Run("", func(t *testing.T) {
resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
req.Header.Set("Accept-Language", "it")
req, err := http.NewRequest("GET", test.url, nil)
if err != nil {
t.Fatal(err)
}
req.RequestURI = test.url
m.ServeHTTP(resp, req)
})

Convey("Set to default language", func() {
m := macaron.New()
m.Use(I18n(Options{
Langs: []string{"en-US", "zh-CN", "it-IT"},
Names: []string{"English", "简体中文", "Italiano"},
}))
m.Get("/", func(l Locale) {
So(l.Language(), ShouldEqual, "en-US")
})

resp := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/", nil)
So(err, ShouldBeNil)
req.Header.Set("Accept-Language", "ru")
m.ServeHTTP(resp, req)
assert.Equal(t, http.StatusFound, resp.Code)
assert.Equal(t, "<a href=\""+test.expURL+"\">Found</a>.\n\n", resp.Body.String())
})
})
}
}

0 comments on commit 329b0c4

Please sign in to comment.