goyave.dev/goyave/v5@v5.0.0-rc9.0.20240517145003-d3f977d0b9f3/middleware/parse/parse.go (about) 1 package parse 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "io" 7 "net/http" 8 "net/url" 9 "strings" 10 11 "goyave.dev/goyave/v5" 12 "goyave.dev/goyave/v5/util/fsutil" 13 ) 14 15 // Middleware reading the raw request query and body. 16 // 17 // First, the query is parsed using Go's standard `url.ParseQuery()`. After being flattened 18 // (single value arrays converted to non-array), the result is put in the request's `Query`. 19 // If the parsing fails, returns "400 Bad request". 20 // 21 // The body is read only if the "Content-Type" header is set. If 22 // the body exceeds the configured max upload size (in MiB), "413 Request Entity Too Large" 23 // is returned. 24 // If the content type is "application/json", the middleware will attempt 25 // to unmarshal the body and put the result in the request's `Data`. If it fails, returns "400 Bad request". 26 // If the content-type has another value, Go's standard `ParseMultipartForm` is called. The result 27 // is put inside the request's `Data` after being flattened. 28 // If the form is not a multipart form, attempts `ParseForm`. If `ParseMultipartForm` or `ParseForm` return 29 // an error, returns "400 Bad request". 30 // 31 // In `multipart/form-data`, all file parts are automatically converted to `[]fsutil.File`. 32 // Inside `request.Data`, a field of type "file" will therefore always be of type `[]fsutil.File`. 33 // It is a slice so it support multi-file uploads in a single field. 34 type Middleware struct { 35 goyave.Component 36 37 // MaxUpoadSize the maximum size of the request (in MiB). 38 // Defaults to the value provided in the config "server.maxUploadSize". 39 MaxUploadSize float64 40 } 41 42 // Handle reads the request query and body and parses it if necessary. 43 // If the request Data is not nil, the body is not parsed again and the 44 // middleware immediately passes after parsing the query. 45 func (m *Middleware) Handle(next goyave.Handler) goyave.Handler { 46 return func(response *goyave.Response, r *goyave.Request) { 47 if err := parseQuery(r); err != nil { 48 response.Status(http.StatusBadRequest) 49 return 50 } 51 52 if r.Data != nil { 53 next(response, r) 54 return 55 } 56 57 r.Data = nil 58 contentType := r.Header().Get("Content-Type") 59 if contentType != "" { 60 maxSize := int64(m.getMaxUploadSize() * 1024 * 1024) 61 maxValueBytes := maxSize 62 var bodyBuf bytes.Buffer 63 n, err := io.CopyN(&bodyBuf, r.Body(), maxValueBytes+1) 64 if err == nil || err == io.EOF { 65 maxValueBytes -= n 66 if maxValueBytes < 0 { 67 response.Status(http.StatusRequestEntityTooLarge) 68 return 69 } 70 71 bodyBytes := bodyBuf.Bytes() 72 if strings.HasPrefix(contentType, "application/json") { 73 var body any 74 if err := json.Unmarshal(bodyBytes, &body); err != nil { 75 response.Status(http.StatusBadRequest) 76 } 77 r.Data = body 78 } else { 79 req := r.Request() 80 req.Body = io.NopCloser(bytes.NewBuffer(bodyBytes)) 81 r.Data, err = generateFlatMap(req, maxSize) 82 if err != nil { 83 response.Status(http.StatusBadRequest) 84 } 85 } 86 } else { 87 response.Status(http.StatusBadRequest) 88 } 89 } 90 91 if response.GetStatus() != http.StatusBadRequest { 92 next(response, r) 93 } 94 } 95 } 96 97 func (m *Middleware) getMaxUploadSize() float64 { 98 if m.MaxUploadSize == 0 { 99 return m.Config().GetFloat("server.maxUploadSize") 100 } 101 102 return m.MaxUploadSize 103 } 104 105 func parseQuery(request *goyave.Request) error { 106 queryParams, err := url.ParseQuery(request.URL().RawQuery) 107 if err == nil { 108 request.Query = make(map[string]any, len(queryParams)) 109 flatten(request.Query, queryParams) 110 } 111 return err 112 } 113 114 func generateFlatMap(request *http.Request, maxSize int64) (map[string]any, error) { 115 flatMap := make(map[string]any) 116 request.Form = url.Values{} // Prevent Form from being parsed because it would be redundant with our parsing 117 err := request.ParseMultipartForm(maxSize) 118 119 if err != nil { 120 if err == http.ErrNotMultipart { 121 if err := request.ParseForm(); err != nil { 122 return nil, err 123 } 124 } else { 125 return nil, err 126 } 127 } 128 129 if request.PostForm != nil { 130 flatten(flatMap, request.PostForm) 131 } 132 if request.MultipartForm != nil { 133 flatten(flatMap, request.MultipartForm.Value) 134 135 for field, headers := range request.MultipartForm.File { 136 files, err := fsutil.ParseMultipartFiles(headers) 137 if err != nil { 138 return nil, err 139 } 140 flatMap[field] = files 141 } 142 } 143 144 // Source form is not needed anymore, clear it. 145 request.Form = nil 146 request.PostForm = nil 147 request.MultipartForm = nil 148 149 return flatMap, nil 150 } 151 152 func flatten(dst map[string]any, values url.Values) { 153 for field, value := range values { 154 if len(value) > 1 { 155 dst[field] = value 156 } else { 157 dst[field] = value[0] 158 } 159 } 160 }