forms
This commit is contained in:
parent
39723f8331
commit
3d61501eb9
136
core/http/forms.go
Normal file
136
core/http/forms.go
Normal 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
|
Loading…
Reference in New Issue
Block a user