137 lines
3.1 KiB
Go
137 lines
3.1 KiB
Go
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
|