package http import ( "errors" "io" "mime" "net/http" "net/url" "strings" ) // Maximum form parse size (16MB) const maxFormSize = 16 << 20 // Common errors var ( ErrFormSizeTooLarge = errors.New("form size too large") ErrInvalidFormType = errors.New("invalid form content type") ) // ParseForm parses a POST request body into a map of values // Supports both application/x-www-form-urlencoded and multipart/form-data content types func ParseForm(r *http.Request) (map[string]any, error) { // Only handle POST, PUT, PATCH if r.Method != http.MethodPost && r.Method != http.MethodPut && r.Method != http.MethodPatch { return make(map[string]any), nil } // Check content type contentType := r.Header.Get("Content-Type") if contentType == "" { return make(map[string]any), nil } // Parse the media type mediaType, params, err := mime.ParseMediaType(contentType) if err != nil { return nil, ErrInvalidFormType } result := make(map[string]any) switch { case mediaType == "application/x-www-form-urlencoded": // Handle URL-encoded form if err := parseURLEncodedForm(r, result); err != nil { return nil, err } case strings.HasPrefix(mediaType, "multipart/form-data"): // Handle multipart form boundary := params["boundary"] if boundary == "" { return nil, ErrInvalidFormType } if err := parseMultipartForm(r, boundary, result); err != nil { return nil, err } default: // Unrecognized content type return make(map[string]any), nil } return result, nil } // parseURLEncodedForm handles application/x-www-form-urlencoded forms func parseURLEncodedForm(r *http.Request, result map[string]any) error { // Enforce size limit r.Body = http.MaxBytesReader(nil, r.Body, maxFormSize) // Read the entire body body, err := io.ReadAll(r.Body) if err != nil { if strings.Contains(err.Error(), "http: request body too large") { return ErrFormSizeTooLarge } return err } // Parse form values form, err := url.ParseQuery(string(body)) if err != nil { return err } // Convert to map[string]any for key, values := range form { if len(values) == 1 { // Single value result[key] = values[0] } else if len(values) > 1 { // Multiple values result[key] = values } } return nil } // parseMultipartForm handles multipart/form-data forms func parseMultipartForm(r *http.Request, boundary string, result map[string]any) error { // Limit the form size if err := r.ParseMultipartForm(maxFormSize); err != nil { if strings.Contains(err.Error(), "http: request body too large") { return ErrFormSizeTooLarge } return err } // Process form values for key, values := range r.MultipartForm.Value { if len(values) == 1 { // Single value result[key] = values[0] } else if len(values) > 1 { // Multiple values result[key] = values } } // We don't handle file uploads here - could be extended in the future // if needed to support file uploads to Lua return nil } // Usage: // After parsing the form with ParseForm, you can add it to the context with: // ctx.Set("form", formData) // // This makes the form data accessible in Lua as ctx.form.field_name