Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3577 sec fix #3597

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
55 changes: 48 additions & 7 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
package gin

import (
"encoding/json"
"errors"
"io"
"log"
Expand All @@ -15,6 +16,7 @@
"net/url"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -956,15 +958,54 @@
// JSONP serializes the given struct as JSON into the response body.
// It adds padding to response body to request data from a server residing in a different domain than the client.
// It also sets the Content-Type as "application/javascript".
func (c *Context) JSONP(code int, obj any) {
callback := c.DefaultQuery("callback", "")
if callback == "" {
c.Render(code, render.JSON{Data: obj})
return
}
c.Render(code, render.JsonpJSON{Callback: callback, Data: obj})
func (c *Context) JSONP(code int, obj interface{}) {
// Get the callback query parameter from the request or use an empty string as the default value

Check failure on line 962 in context.go

View workflow job for this annotation

GitHub Actions / lint

File is not `gci`-ed with --skip-generated -s standard,default (gci)
callback := c.DefaultQuery("callback", "")

// If the callback query parameter is empty, respond with a JSON object
if callback == "" {
c.Render(code, render.JSON{Data: obj})
return
}

// Add type checking for the callback function name

Check failure on line 971 in context.go

View workflow job for this annotation

GitHub Actions / lint

File is not `gci`-ed with --skip-generated -s standard,default (gci)
// Use a Unicode-aware pattern for alphanumeric characters and underscores
callbackPattern := `^[\p{L}\p{N}_]+$`
isValidCallback := regexp.MustCompile(callbackPattern).MatchString(callback)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should move the callbackPattern and the regex MustCompile in a package level variable to avoid the cost of rebuilding it everytime.


// If the callback function name is not valid, respond with an error message
if !isValidCallback {
c.JSON(http.StatusBadRequest, H{"error": "Invalid callback function name"})
return
}

// Convert the input object to a slice of H (map[string]interface{}) values

Check failure on line 982 in context.go

View workflow job for this annotation

GitHub Actions / lint

File is not `gci`-ed with --skip-generated -s standard,default (gci)
var data []H
if d, ok := obj.([]H); ok {
data = d
} else if d, ok := obj.(H); ok {
data = []H{d}
} else {
data = []H{{"message": obj}}
}

// Convert the H slice to a slice of empty interface values
var anyData []interface{}
for _, item := range data {
anyData = append(anyData, item)
}

// Marshal the anyData slice to a JSON string
jsonString, _ := json.Marshal(anyData)

// Respond with a JavaScript callback function call that includes the JSON data
c.Render(code, render.String{Format: "/**/ typeof " + callback + " === 'function' && " + callback + "(%s);", Data: []interface{}{string(jsonString)}})

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The render logic should probably be moved to the render package by leveraging the JsonpJSON struct (in particular https://github.com/gin-gonic/gin/blob/master/render/json.go#LL116C13-L116C13).

}





// JSON serializes the given struct as JSON into the response body.
// It also sets the Content-Type as "application/json".
func (c *Context) JSON(code int, obj any) {
Expand Down
50 changes: 50 additions & 0 deletions context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2413,3 +2413,53 @@ func TestInterceptedHeader(t *testing.T) {
assert.Equal(t, "", w.Result().Header.Get("X-Test"))
assert.Equal(t, "present", w.Result().Header.Get("X-Test-2"))
}


func TestJSONPCallbackTypeChecking(t *testing.T) {
router := New()
router.GET("/jsonp", func(c *Context) {
c.JSONP(http.StatusOK, H{"message": "success"})
})
testCases := []struct {
callback string
expected string
statusCode int
description string
}{
{
callback: "validCallback",
expected: `/**/ typeof validCallback === 'function' && validCallback([{"message":"success"}]);`,
statusCode: http.StatusOK,
description: "Valid callback function name",
},

{
callback: url.QueryEscape("invalidCallback();"),
expected: "{\"error\":\"Invalid callback function name\"}",
statusCode: http.StatusBadRequest,
description: "Invalid callback function name",
},
}

for _, tc := range testCases {
t.Run(tc.description, func(t *testing.T) {
req, _ := http.NewRequest("GET", "/jsonp?callback="+tc.callback, nil)
resp := httptest.NewRecorder()

router.ServeHTTP(resp, req)

if resp.Code != tc.statusCode {
t.Errorf("Expected status code %d, got %d", tc.statusCode, resp.Code)
}

actualBody := resp.Body.String()
expectedBody := tc.expected
if actualBody != expectedBody {
t.Errorf("Expected response body %q, got %q", expectedBody, actualBody)
}
})
}
}



6 changes: 4 additions & 2 deletions docs/doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -1071,7 +1071,9 @@ func main() {

#### JSONP

Using JSONP to request data from a server in a different domain. Add callback to response body if the query parameter callback exists.
Using JSONP to request data from a server in a different domain. Add callback to response body if the query parameter `callback` exists and contains a valid callback function name. Valid callback function names consist of alphanumeric characters and underscores.

Note: To enhance security, Gin performs type checking on the JSONP callback function names. Only alphanumeric characters and underscores are allowed in the callback function names. If an invalid callback function name is provided, Gin will return an error. This type checking mechanism helps prevent attackers from exploiting the JSONP endpoint to bypass content security headers and execute malicious scripts.

```go
func main() {
Expand All @@ -1093,7 +1095,7 @@ func main() {
// client
// curl http://127.0.0.1:8080/JSONP?callback=x
}
```


#### AsciiJSON

Expand Down