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  }