CVE-2020-36627: URL Redirection to Untrusted Site ('Open Redirect') (CWE-601)
value: A vulnerability was found in Macaron i18n. It has been declared as problematic. Affected by this vulnerability is an unknown functionality of the file i18n.go. The manipulation leads to open redirect. The attack can be launched remotely. Upgrading to version 0.5.0 is able to address this issue. The name of the patch is 329b0c4844cc16a5a253c011b55180598e707735. It is recommended to upgrade the affected component. The identifier VDB-216745 was assigned to this vulnerability.
CWE description: A web application accepts a user-controlled input that specifies a link to an external site, and uses that link in a Redirect. This simplifies phishing attacks.
CVSS v3 base score: 6.1
Published: 2022-12-25T11:15Z
Repository: https://github.com/go-macaron/i18n
Language: Go
Files changed: .gitignore, go.mod, go.sum, i18n.go, i18n_test.go
Commit date: 2020-02-17 00:13:52 +0800
Commit message: security: fix Open Redirection vulnerability

Diff: diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a09c56d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+/.idea
diff --git a/go.mod b/go.mod
index c0e4f8f..e958e8d 100644
--- a/go.mod
+++ b/go.mod
@@ -1,11 +1,10 @@
 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
 )
diff --git a/go.sum b/go.sum
index 8447b40..1d9a316 100644
--- a/go.sum
+++ b/go.sum
@@ -1,23 +1,28 @@
+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=
 github.com/unknwon/i18n v0.0.0-20190805065654-5c6446a380b6/go.mod h1:+5rDk6sDGpl3azws3O+f+GpFSyN9GVr0K8cvQLQM2ZQ=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
@@ -35,9 +40,13 @@ golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
 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=
diff --git a/i18n.go b/i18n.go
index 33cc0ef..a6daed6 100644
--- a/i18n.go
+++ b/i18n.go
@@ -15,23 +15,27 @@
 // Package i18n is a middleware that provides app Internationalization and Localization of Macaron.
 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.
 func initLocales(opt Options) language.Matcher {
 	tags := make([]language.Tag, len(opt.Langs))
@@ -39,11 +43,11 @@ func initLocales(opt Options) language.Matcher {
 		tags[i] = language.Raw.Make(lang)
 		fname := fmt.Sprintf(opt.Format, lang)
 		// Append custom locale file.
 		custom := []interface{}{}
 		customPath := path.Join(opt.CustomDirectory, fname)
-		if com.IsFile(customPath) {
+		if isFile(customPath) {
 			custom = append(custom, customPath)
 		}
 
 		var locale interface{}
 		if data, ok := opt.Files[fname]; ok {
@@ -219,9 +223,9 @@ func I18n(options ...Options) macaron.Handler {
 		ctx.Data["LangName"] = curLang.Name
 		ctx.Data["AllLangs"] = append([]LangType{curLang}, restLangs...)
 		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, "?")]))
 		}
 	}
 }
diff --git a/i18n_test.go b/i18n_test.go
index eb1d5dd..33f8325 100644
--- a/i18n_test.go
+++ b/i18n_test.go
@@ -13,120 +13,146 @@
 // under the License.
 
 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())
 		})
-	})
+	}
 }

Vulnerable code: 	github.com/smartystreets/goconvey v0.0.0-20190731233626-505e41936337
	github.com/unknwon/com v0.0.0-20190804042917-757f69c95f3e
github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg=
github.com/smartystreets/assertions v0.0.0-20190116191733-b6c0e53d7304 h1:Jpy1PXuP99tXNrhbq2BaPz9B+jNAvH1JPQQpG/9GCXY=
	"github.com/unknwon/com"
const _VERSION = "0.4.1"

func Version() string {
	return _VERSION
		if com.IsFile(customPath) {
			ctx.Redirect(opt.SubURL + ctx.Req.RequestURI[:strings.Index(ctx.Req.RequestURI, "?")])
	. "github.com/smartystreets/goconvey/convey"
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)
			}()
			m := macaron.New()
			m.Use(I18n(Options{}))
		})
		Convey("Languages and names not match", func() {
			defer func() {
				So(recover(), ShouldNotBeNil)
			}()
			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"},
			}))
		})
		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() {})
			resp := httptest.NewRecorder()
			req, err := http.NewRequest("GET", "/", nil)
			So(err, ShouldBeNil)
			m.ServeHTTP(resp, req)
		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", "/?lang=en-US", nil)
			So(err, ShouldBeNil)
			req.RequestURI = "/?lang=en-US"
			m.ServeHTTP(resp, req)
		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")
			})
			req, err := http.NewRequest("GET", "/", nil)
			So(err, ShouldBeNil)
			req.Header.Set("Accept-Language", "it")
		})

		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)
	})
Fixed code: /.idea
	github.com/stretchr/testify v1.4.0
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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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=
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.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
	"os"
// 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()
		if isFile(customPath) {
			ctx.Redirect(opt.SubURL + path.Clean(ctx.Req.RequestURI[:strings.Index(ctx.Req.RequestURI, "?")]))
	"errors"
	"github.com/stretchr/testify/assert"
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{}))
	})
	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"},
		}))
	})
	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"},
		}))
	})
	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)
	})
	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())
		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)
	})
	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())
		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) {
			req, err := http.NewRequest("GET", test.url, nil)
			if err != nil {
				t.Fatal(err)
			}
			req.RequestURI = test.url
			assert.Equal(t, http.StatusFound, resp.Code)
			assert.Equal(t, "<a href=\""+test.expURL+"\">Found</a>.\n\n", resp.Body.String())
	}