Merge pull request #987 from easonlin404/secure-json
feat(render): add SecureJSON func to prevent json hijacking
This commit is contained in:
		@ -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) {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										7
									
								
								gin.go
									
									
									
									
									
								
							
							
						
						
									
										7
									
								
								gin.go
									
									
									
									
									
								
							@ -45,6 +45,7 @@ type RoutesInfo []RouteInfo
 | 
				
			|||||||
type Engine struct {
 | 
					type Engine struct {
 | 
				
			||||||
	RouterGroup
 | 
						RouterGroup
 | 
				
			||||||
	delims           render.Delims
 | 
						delims           render.Delims
 | 
				
			||||||
 | 
						secureJsonPrefix string
 | 
				
			||||||
	HTMLRender       render.HTMLRender
 | 
						HTMLRender       render.HTMLRender
 | 
				
			||||||
	FuncMap          template.FuncMap
 | 
						FuncMap          template.FuncMap
 | 
				
			||||||
	allNoRoute       HandlersChain
 | 
						allNoRoute       HandlersChain
 | 
				
			||||||
@ -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
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user