统一严格解析管理接口 JSON
This commit is contained in:
parent
e178f9fcf0
commit
be84ecfb91
@ -49,9 +49,8 @@ type authLoginRequest struct {
|
||||
}
|
||||
|
||||
func (s *Server) authLogin(c *gin.Context) {
|
||||
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, authRequestBodyLimit)
|
||||
var request authLoginRequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
if err := bindJSON(c, &request, authRequestBodyLimit); err != nil {
|
||||
s.writeError(c, http.StatusBadRequest, "登录请求参数无效", err)
|
||||
return
|
||||
}
|
||||
|
||||
32
backend/internal/httpapi/json.go
Normal file
32
backend/internal/httpapi/json.go
Normal file
@ -0,0 +1,32 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// bindJSON ???? JSON ?????????????????? JSON?
|
||||
func bindJSON(c *gin.Context, target any, limit int64) error {
|
||||
if c == nil || c.Request == nil || c.Request.Body == nil {
|
||||
return errors.New("???????")
|
||||
}
|
||||
if limit > 0 {
|
||||
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, limit)
|
||||
}
|
||||
decoder := json.NewDecoder(c.Request.Body)
|
||||
decoder.DisallowUnknownFields()
|
||||
if err := decoder.Decode(target); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := decoder.Decode(&struct{}{}); err != io.EOF {
|
||||
if err == nil {
|
||||
return errors.New("????????? JSON ??")
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
61
backend/internal/httpapi/json_test.go
Normal file
61
backend/internal/httpapi/json_test.go
Normal file
@ -0,0 +1,61 @@
|
||||
package httpapi
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func TestBindJSONRejectsUnknownFieldsAndTrailingValues(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
body string
|
||||
want bool
|
||||
}{
|
||||
{name: "valid", body: "{\"value\":\"ok\"}\n", want: false},
|
||||
{name: "unknown field", body: "{\"value\":\"ok\",\"extra\":true}", want: true},
|
||||
{name: "trailing json", body: "{\"value\":\"ok\"}{\"value\":\"again\"}", want: true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
recorder := httptest.NewRecorder()
|
||||
context, _ := gin.CreateTestContext(recorder)
|
||||
context.Request = httptest.NewRequest(http.MethodPost, "/", bytes.NewBufferString(tt.body))
|
||||
var payload struct {
|
||||
Value string `json:"value"`
|
||||
}
|
||||
err := bindJSON(context, &payload, 1024)
|
||||
if (err != nil) != tt.want {
|
||||
t.Fatalf("bindJSON error = %v, want error %v", err, tt.want)
|
||||
}
|
||||
if !tt.want && payload.Value != "ok" {
|
||||
t.Fatalf("valid payload was not decoded: %+v", payload)
|
||||
}
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Fatalf("unexpected response status: %d", recorder.Code)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBindJSONHonorsBodyLimit(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
context, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
body := `{"value":"` + string(bytes.Repeat([]byte("x"), 128)) + `"}`
|
||||
context.Request = httptest.NewRequest(http.MethodPost, "/", bytes.NewBufferString(body))
|
||||
var payload struct {
|
||||
Value string `json:"value"`
|
||||
}
|
||||
err := bindJSON(context, &payload, 64)
|
||||
var tooLarge *http.MaxBytesError
|
||||
if !errors.As(err, &tooLarge) {
|
||||
t.Fatalf("bindJSON error = %v, want MaxBytesError", err)
|
||||
}
|
||||
}
|
||||
@ -332,9 +332,8 @@ func (s *Server) listDomains(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (s *Server) createDomain(c *gin.Context) {
|
||||
limitJSONBody(c)
|
||||
var request domainRequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
if err := bindJSON(c, &request, jsonRequestBodyLimit); err != nil {
|
||||
s.writeError(c, http.StatusBadRequest, "请求参数无效", err)
|
||||
return
|
||||
}
|
||||
@ -355,9 +354,8 @@ func (s *Server) createDomain(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (s *Server) updateDomain(c *gin.Context) {
|
||||
limitJSONBody(c)
|
||||
var request domainRequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
if err := bindJSON(c, &request, jsonRequestBodyLimit); err != nil {
|
||||
s.writeError(c, http.StatusBadRequest, "请求参数无效", err)
|
||||
return
|
||||
}
|
||||
@ -466,13 +464,12 @@ func (s *Server) getServer(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (s *Server) createServer(c *gin.Context) {
|
||||
limitJSONBody(c)
|
||||
if s.servers == nil {
|
||||
s.writeError(c, http.StatusNotImplemented, "服务器管理接口尚未配置", errors.New("服务器仓储未配置"))
|
||||
return
|
||||
}
|
||||
var request serverRequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
if err := bindJSON(c, &request, jsonRequestBodyLimit); err != nil {
|
||||
s.writeError(c, http.StatusBadRequest, "请求参数无效", err)
|
||||
return
|
||||
}
|
||||
@ -490,13 +487,12 @@ func (s *Server) createServer(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (s *Server) updateServer(c *gin.Context) {
|
||||
limitJSONBody(c)
|
||||
if s.servers == nil {
|
||||
s.writeError(c, http.StatusNotImplemented, "服务器管理接口尚未配置", errors.New("服务器仓储未配置"))
|
||||
return
|
||||
}
|
||||
var request serverRequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
if err := bindJSON(c, &request, jsonRequestBodyLimit); err != nil {
|
||||
s.writeError(c, http.StatusBadRequest, "请求参数无效", err)
|
||||
return
|
||||
}
|
||||
@ -570,7 +566,6 @@ func (s *Server) listDomainServers(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (s *Server) replaceDomainServers(c *gin.Context) {
|
||||
limitJSONBody(c)
|
||||
if s.servers == nil {
|
||||
s.writeError(c, http.StatusNotImplemented, "服务器管理接口尚未配置", errors.New("服务器仓储未配置"))
|
||||
return
|
||||
@ -584,7 +579,7 @@ func (s *Server) replaceDomainServers(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
var request domainServersRequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
if err := bindJSON(c, &request, jsonRequestBodyLimit); err != nil {
|
||||
s.writeError(c, http.StatusBadRequest, "请求参数无效", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -150,13 +150,12 @@ func (s *Server) getSettings(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (s *Server) updateSettings(c *gin.Context) {
|
||||
limitJSONBody(c)
|
||||
if strings.TrimSpace(s.configPath) == "" {
|
||||
s.writeError(c, http.StatusInternalServerError, "配置文件路径未配置", errors.New("配置文件路径为空"))
|
||||
return
|
||||
}
|
||||
var request settingsRequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
if err := bindJSON(c, &request, jsonRequestBodyLimit); err != nil {
|
||||
s.writeError(c, http.StatusBadRequest, "设置请求格式不正确", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -136,9 +136,8 @@ func (s *Server) testSetupDNS(c *gin.Context) {
|
||||
s.writeError(c, http.StatusServiceUnavailable, "DNS 在线验证服务尚未初始化", nil)
|
||||
return
|
||||
}
|
||||
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, setupRequestLimit)
|
||||
var request dnsTestRequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
if err := bindJSON(c, &request, setupRequestLimit); err != nil {
|
||||
s.writeError(c, http.StatusBadRequest, "DNS 测试请求参数无效", err)
|
||||
return
|
||||
}
|
||||
@ -184,9 +183,8 @@ func (s *Server) applySetup(c *gin.Context) {
|
||||
s.writeError(c, http.StatusConflict, "初始化已经完成,不能重复提交", nil)
|
||||
return
|
||||
}
|
||||
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, setupRequestLimit)
|
||||
var request setupRequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
if err := bindJSON(c, &request, setupRequestLimit); err != nil {
|
||||
s.writeError(c, http.StatusBadRequest, "初始化请求参数无效", err)
|
||||
return
|
||||
}
|
||||
|
||||
@ -79,13 +79,12 @@ func (s *Server) getUser(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (s *Server) createUser(c *gin.Context) {
|
||||
limitJSONBody(c)
|
||||
if s.users == nil {
|
||||
s.writeError(c, http.StatusNotImplemented, "用户管理接口尚未配置", errors.New("用户仓储未配置"))
|
||||
return
|
||||
}
|
||||
var request userRequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
if err := bindJSON(c, &request, jsonRequestBodyLimit); err != nil {
|
||||
s.writeError(c, http.StatusBadRequest, "用户参数无效", err)
|
||||
return
|
||||
}
|
||||
@ -103,7 +102,6 @@ func (s *Server) createUser(c *gin.Context) {
|
||||
}
|
||||
|
||||
func (s *Server) updateUser(c *gin.Context) {
|
||||
limitJSONBody(c)
|
||||
if s.users == nil {
|
||||
s.writeError(c, http.StatusNotImplemented, "用户管理接口尚未配置", errors.New("用户仓储未配置"))
|
||||
return
|
||||
@ -119,7 +117,7 @@ func (s *Server) updateUser(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
var request userRequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
if err := bindJSON(c, &request, jsonRequestBodyLimit); err != nil {
|
||||
s.writeError(c, http.StatusBadRequest, "用户参数无效", err)
|
||||
return
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user