debugged function actually working, passed tests

This commit is contained in:
KDreynolds 2023-05-07 22:46:01 -06:00
parent 961513d2c1
commit c716cb94b7
3 changed files with 100 additions and 17 deletions

View File

@ -5,6 +5,7 @@
package gin package gin
import ( import (
"encoding/json"
"errors" "errors"
"io" "io"
"log" "log"
@ -15,6 +16,7 @@ import (
"net/url" "net/url"
"os" "os"
"path/filepath" "path/filepath"
"regexp"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -956,25 +958,54 @@ func (c *Context) SecureJSON(code int, obj any) {
// JSONP serializes the given struct as JSON into the response body. // 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 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". // It also sets the Content-Type as "application/javascript".
func (c *Context) JSONP(code int, obj any) { 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
callback := c.DefaultQuery("callback", "") callback := c.DefaultQuery("callback", "")
// If the callback query parameter is empty, respond with a JSON object
if callback == "" { if callback == "" {
c.Render(code, render.JSON{Data: obj}) c.Render(code, render.JSON{Data: obj})
return return
} }
// Add type checking for the callback function name // Add type checking for the callback function name
callbackPattern := `^[\p{L}\p{N}_]+$` // Unicode-aware pattern for alphanumeric characters and underscores // Use a Unicode-aware pattern for alphanumeric characters and underscores
callbackPattern := `^[\p{L}\p{N}_]+$`
isValidCallback := regexp.MustCompile(callbackPattern).MatchString(callback) isValidCallback := regexp.MustCompile(callbackPattern).MatchString(callback)
// If the callback function name is not valid, respond with an error message
if !isValidCallback { if !isValidCallback {
// Handle the invalid callback function name, e.g., return an error or set a default callback function name c.JSON(http.StatusBadRequest, H{"error": "Invalid callback function name"})
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid callback function name"})
return return
} }
c.Render(code, render.JsonpJSON{Callback: callback, Data: obj}) // Convert the input object to a slice of H (map[string]interface{}) values
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)}})
}
// 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 any) { func (c *Context) JSON(code int, obj any) {

View File

@ -2413,3 +2413,53 @@ func TestInterceptedHeader(t *testing.T) {
assert.Equal(t, "", w.Result().Header.Get("X-Test")) assert.Equal(t, "", w.Result().Header.Get("X-Test"))
assert.Equal(t, "present", w.Result().Header.Get("X-Test-2")) 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)
}
})
}
}

View File

@ -1071,7 +1071,9 @@ func main() {
#### JSONP #### 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 ```go
func main() { func main() {
@ -1093,7 +1095,7 @@ func main() {
// client // client
// curl http://127.0.0.1:8080/JSONP?callback=x // curl http://127.0.0.1:8080/JSONP?callback=x
} }
```
#### AsciiJSON #### AsciiJSON