This commit is contained in:
Sky Johnson 2025-03-06 20:07:32 -06:00
parent 39723f8331
commit 3d61501eb9

136
core/http/forms.go Normal file
View File

@ -0,0 +1,136 @@
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