package http import ( "errors" "mime/multipart" "strings" "github.com/valyala/fasthttp" ) // 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(ctx *fasthttp.RequestCtx) (map[string]any, error) { // Only handle POST, PUT, PATCH method := string(ctx.Method()) if method != "POST" && method != "PUT" && method != "PATCH" { return make(map[string]any), nil } // Check content type contentType := string(ctx.Request.Header.ContentType()) if contentType == "" { return make(map[string]any), nil } result := make(map[string]any) // Check for content length to prevent DOS if len(ctx.Request.Body()) > maxFormSize { return nil, ErrFormSizeTooLarge } // Handle by content type if strings.HasPrefix(contentType, "application/x-www-form-urlencoded") { return parseURLEncodedForm(ctx) } else if strings.HasPrefix(contentType, "multipart/form-data") { return parseMultipartForm(ctx) } // Unrecognized content type return result, nil } // parseURLEncodedForm handles application/x-www-form-urlencoded forms func parseURLEncodedForm(ctx *fasthttp.RequestCtx) (map[string]any, error) { result := make(map[string]any) // Process form values directly from PostArgs() ctx.PostArgs().VisitAll(func(key, value []byte) { keyStr := string(key) valStr := string(value) // Check if we already have this key if existing, ok := result[keyStr]; ok { // If it's already a slice, append if existingSlice, ok := existing.([]string); ok { result[keyStr] = append(existingSlice, valStr) } else { // Convert to slice and append result[keyStr] = []string{existing.(string), valStr} } } else { // New key result[keyStr] = valStr } }) return result, nil } // parseMultipartForm handles multipart/form-data forms func parseMultipartForm(ctx *fasthttp.RequestCtx) (map[string]any, error) { result := make(map[string]any) // Parse multipart form form, err := ctx.MultipartForm() if err != nil { if err == multipart.ErrMessageTooLarge || strings.Contains(err.Error(), "too large") { return nil, ErrFormSizeTooLarge } return nil, err } // Process form values for key, values := range form.Value { if len(values) == 1 { // Single value result[key] = values[0] } else if len(values) > 1 { // Multiple values - store as string slice strValues := make([]string, len(values)) copy(strValues, values) result[key] = strValues } } // We don't handle file uploads here - could be extended in the future // if needed to support file uploads to Lua return result, 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