117 lines
3.0 KiB
Go
117 lines
3.0 KiB
Go
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
|