forked from mirror/gin
Merge pull request #987 from easonlin404/secure-json
feat(render): add SecureJSON func to prevent json hijacking
This commit is contained in:
commit
c4249f923f
|
@ -616,6 +616,13 @@ func (c *Context) IndentedJSON(code int, obj interface{}) {
|
||||||
c.Render(code, render.IndentedJSON{Data: obj})
|
c.Render(code, render.IndentedJSON{Data: obj})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SecureJSON serializes the given struct as Secure JSON into the response body.
|
||||||
|
// Default prepends "while(1)," to response body if the given struct is array values.
|
||||||
|
// It also sets the Content-Type as "application/json".
|
||||||
|
func (c *Context) SecureJSON(code int, obj interface{}) {
|
||||||
|
c.Render(code, render.SecureJSON{Prefix: c.engine.secureJsonPrefix, Data: obj})
|
||||||
|
}
|
||||||
|
|
||||||
// JSON serializes the given struct as JSON into the response body.
|
// JSON serializes the given struct as JSON into the response body.
|
||||||
// It also sets the Content-Type as "application/json".
|
// It also sets the Content-Type as "application/json".
|
||||||
func (c *Context) JSON(code int, obj interface{}) {
|
func (c *Context) JSON(code int, obj interface{}) {
|
||||||
|
|
|
@ -598,6 +598,32 @@ func TestContextRenderNoContentIndentedJSON(t *testing.T) {
|
||||||
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8")
|
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tests that the response is serialized as Secure JSON
|
||||||
|
// and Content-Type is set to application/json
|
||||||
|
func TestContextRenderSecureJSON(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, router := CreateTestContext(w)
|
||||||
|
|
||||||
|
router.SecureJsonPrefix("&&&START&&&")
|
||||||
|
c.SecureJSON(201, []string{"foo", "bar"})
|
||||||
|
|
||||||
|
assert.Equal(t, w.Code, 201)
|
||||||
|
assert.Equal(t, w.Body.String(), "&&&START&&&[\"foo\",\"bar\"]")
|
||||||
|
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests that no Custom JSON is rendered if code is 204
|
||||||
|
func TestContextRenderNoContentSecureJSON(t *testing.T) {
|
||||||
|
w := httptest.NewRecorder()
|
||||||
|
c, _ := CreateTestContext(w)
|
||||||
|
|
||||||
|
c.SecureJSON(204, []string{"foo", "bar"})
|
||||||
|
|
||||||
|
assert.Equal(t, 204, w.Code)
|
||||||
|
assert.Equal(t, "", w.Body.String())
|
||||||
|
assert.Equal(t, w.HeaderMap.Get("Content-Type"), "application/json; charset=utf-8")
|
||||||
|
}
|
||||||
|
|
||||||
// Tests that the response executes the templates
|
// Tests that the response executes the templates
|
||||||
// and responds with Content-Type set to text/html
|
// and responds with Content-Type set to text/html
|
||||||
func TestContextRenderHTML(t *testing.T) {
|
func TestContextRenderHTML(t *testing.T) {
|
||||||
|
|
25
gin.go
25
gin.go
|
@ -44,15 +44,16 @@ type RoutesInfo []RouteInfo
|
||||||
// Create an instance of Engine, by using New() or Default()
|
// Create an instance of Engine, by using New() or Default()
|
||||||
type Engine struct {
|
type Engine struct {
|
||||||
RouterGroup
|
RouterGroup
|
||||||
delims render.Delims
|
delims render.Delims
|
||||||
HTMLRender render.HTMLRender
|
secureJsonPrefix string
|
||||||
FuncMap template.FuncMap
|
HTMLRender render.HTMLRender
|
||||||
allNoRoute HandlersChain
|
FuncMap template.FuncMap
|
||||||
allNoMethod HandlersChain
|
allNoRoute HandlersChain
|
||||||
noRoute HandlersChain
|
allNoMethod HandlersChain
|
||||||
noMethod HandlersChain
|
noRoute HandlersChain
|
||||||
pool sync.Pool
|
noMethod HandlersChain
|
||||||
trees methodTrees
|
pool sync.Pool
|
||||||
|
trees methodTrees
|
||||||
|
|
||||||
// Enables automatic redirection if the current route can't be matched but a
|
// Enables automatic redirection if the current route can't be matched but a
|
||||||
// handler for the path with (without) the trailing slash exists.
|
// handler for the path with (without) the trailing slash exists.
|
||||||
|
@ -121,6 +122,7 @@ func New() *Engine {
|
||||||
UnescapePathValues: true,
|
UnescapePathValues: true,
|
||||||
trees: make(methodTrees, 0, 9),
|
trees: make(methodTrees, 0, 9),
|
||||||
delims: render.Delims{"{{", "}}"},
|
delims: render.Delims{"{{", "}}"},
|
||||||
|
secureJsonPrefix: "while(1);",
|
||||||
}
|
}
|
||||||
engine.RouterGroup.engine = engine
|
engine.RouterGroup.engine = engine
|
||||||
engine.pool.New = func() interface{} {
|
engine.pool.New = func() interface{} {
|
||||||
|
@ -145,6 +147,11 @@ func (engine *Engine) Delims(left, right string) *Engine {
|
||||||
return engine
|
return engine
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) SecureJsonPrefix(prefix string) *Engine {
|
||||||
|
engine.secureJsonPrefix = prefix
|
||||||
|
return engine
|
||||||
|
}
|
||||||
|
|
||||||
func (engine *Engine) LoadHTMLGlob(pattern string) {
|
func (engine *Engine) LoadHTMLGlob(pattern string) {
|
||||||
if IsDebugging() {
|
if IsDebugging() {
|
||||||
debugPrintLoadTemplate(template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseGlob(pattern)))
|
debugPrintLoadTemplate(template.Must(template.New("").Delims(engine.delims.Left, engine.delims.Right).Funcs(engine.FuncMap).ParseGlob(pattern)))
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
package render
|
package render
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
@ -17,6 +18,13 @@ type IndentedJSON struct {
|
||||||
Data interface{}
|
Data interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SecureJSON struct {
|
||||||
|
Prefix string
|
||||||
|
Data interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type SecureJSONPrefix string
|
||||||
|
|
||||||
var jsonContentType = []string{"application/json; charset=utf-8"}
|
var jsonContentType = []string{"application/json; charset=utf-8"}
|
||||||
|
|
||||||
func (r JSON) Render(w http.ResponseWriter) (err error) {
|
func (r JSON) Render(w http.ResponseWriter) (err error) {
|
||||||
|
@ -53,3 +61,21 @@ func (r IndentedJSON) Render(w http.ResponseWriter) error {
|
||||||
func (r IndentedJSON) WriteContentType(w http.ResponseWriter) {
|
func (r IndentedJSON) WriteContentType(w http.ResponseWriter) {
|
||||||
writeContentType(w, jsonContentType)
|
writeContentType(w, jsonContentType)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r SecureJSON) Render(w http.ResponseWriter) error {
|
||||||
|
r.WriteContentType(w)
|
||||||
|
jsonBytes, err := json.Marshal(r.Data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// if the jsonBytes is array values
|
||||||
|
if bytes.HasPrefix(jsonBytes, []byte("[")) && bytes.HasSuffix(jsonBytes, []byte("]")) {
|
||||||
|
w.Write([]byte(r.Prefix))
|
||||||
|
}
|
||||||
|
w.Write(jsonBytes)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r SecureJSON) WriteContentType(w http.ResponseWriter) {
|
||||||
|
writeContentType(w, jsonContentType)
|
||||||
|
}
|
||||||
|
|
|
@ -14,6 +14,7 @@ type Render interface {
|
||||||
var (
|
var (
|
||||||
_ Render = JSON{}
|
_ Render = JSON{}
|
||||||
_ Render = IndentedJSON{}
|
_ Render = IndentedJSON{}
|
||||||
|
_ Render = SecureJSON{}
|
||||||
_ Render = XML{}
|
_ Render = XML{}
|
||||||
_ Render = String{}
|
_ Render = String{}
|
||||||
_ Render = Redirect{}
|
_ Render = Redirect{}
|
||||||
|
|
|
@ -66,6 +66,31 @@ func TestRenderIndentedJSON(t *testing.T) {
|
||||||
assert.Equal(t, w.Header().Get("Content-Type"), "application/json; charset=utf-8")
|
assert.Equal(t, w.Header().Get("Content-Type"), "application/json; charset=utf-8")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRenderSecureJSON(t *testing.T) {
|
||||||
|
w1 := httptest.NewRecorder()
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"foo": "bar",
|
||||||
|
}
|
||||||
|
|
||||||
|
err1 := (SecureJSON{"while(1);", data}).Render(w1)
|
||||||
|
|
||||||
|
assert.NoError(t, err1)
|
||||||
|
assert.Equal(t, "{\"foo\":\"bar\"}", w1.Body.String())
|
||||||
|
assert.Equal(t, "application/json; charset=utf-8", w1.Header().Get("Content-Type"))
|
||||||
|
|
||||||
|
w2 := httptest.NewRecorder()
|
||||||
|
datas := []map[string]interface{}{{
|
||||||
|
"foo": "bar",
|
||||||
|
}, {
|
||||||
|
"bar": "foo",
|
||||||
|
}}
|
||||||
|
|
||||||
|
err2 := (SecureJSON{"while(1);", datas}).Render(w2)
|
||||||
|
assert.NoError(t, err2)
|
||||||
|
assert.Equal(t, "while(1);[{\"foo\":\"bar\"},{\"bar\":\"foo\"}]", w2.Body.String())
|
||||||
|
assert.Equal(t, "application/json; charset=utf-8", w2.Header().Get("Content-Type"))
|
||||||
|
}
|
||||||
|
|
||||||
type xmlmap map[string]interface{}
|
type xmlmap map[string]interface{}
|
||||||
|
|
||||||
// Allows type H to be used with xml.Marshal
|
// Allows type H to be used with xml.Marshal
|
||||||
|
|
Loading…
Reference in New Issue