package http import ( "crypto/rand" "encoding/base64" "fmt" "mime/multipart" "strings" "time" "Moonshark/core/utils/logger" "github.com/valyala/fasthttp" ) // LogRequest logs an HTTP request with its status code and duration func LogRequest(statusCode int, method, path string, duration time.Duration) { var statusColor, resetColor, methodColor string // Status code colors if statusCode >= 200 && statusCode < 300 { statusColor = "\u001b[32m" // Green for 2xx } else if statusCode >= 300 && statusCode < 400 { statusColor = "\u001b[36m" // Cyan for 3xx } else if statusCode >= 400 && statusCode < 500 { statusColor = "\u001b[33m" // Yellow for 4xx } else { statusColor = "\u001b[31m" // Red for 5xx and others } // Method colors switch method { case "GET": methodColor = "\u001b[32m" // Green case "POST": methodColor = "\u001b[34m" // Blue case "PUT": methodColor = "\u001b[33m" // Yellow case "DELETE": methodColor = "\u001b[31m" // Red default: methodColor = "\u001b[35m" // Magenta for others } resetColor = "\u001b[0m" // Format duration var durationStr string if duration.Milliseconds() < 1 { durationStr = fmt.Sprintf("%.2fµs", float64(duration.Microseconds())) } else if duration.Milliseconds() < 1000 { durationStr = fmt.Sprintf("%.2fms", float64(duration.Microseconds())/1000) } else { durationStr = fmt.Sprintf("%.2fs", duration.Seconds()) } // Log with colors logger.Server("%s%d%s %s%s%s %s %s", statusColor, statusCode, resetColor, methodColor, method, resetColor, path, durationStr) } // QueryToLua converts HTTP query args to a Lua-friendly map func QueryToLua(ctx *fasthttp.RequestCtx) map[string]any { queryMap := make(map[string]any) // Visit all query parameters ctx.QueryArgs().VisitAll(func(key, value []byte) { // Convert to string k := string(key) v := string(value) // Check if this key already exists as an array if existing, ok := queryMap[k]; ok { // If it's already an array, append to it if arr, ok := existing.([]string); ok { queryMap[k] = append(arr, v) } else if str, ok := existing.(string); ok { // Convert existing string to array and append new value queryMap[k] = []string{str, v} } } else { // New key, store as string queryMap[k] = v } }) return queryMap } // ParseForm extracts form data from a request func ParseForm(ctx *fasthttp.RequestCtx) (map[string]any, error) { formData := make(map[string]any) // Check if multipart form if strings.Contains(string(ctx.Request.Header.ContentType()), "multipart/form-data") { return parseMultipartForm(ctx) } // Regular form ctx.PostArgs().VisitAll(func(key, value []byte) { k := string(key) v := string(value) // Check if this key already exists if existing, ok := formData[k]; ok { // If it's already an array, append to it if arr, ok := existing.([]string); ok { formData[k] = append(arr, v) } else if str, ok := existing.(string); ok { // Convert existing string to array and append new value formData[k] = []string{str, v} } } else { // New key, store as string formData[k] = v } }) return formData, nil } // parseMultipartForm handles multipart/form-data requests func parseMultipartForm(ctx *fasthttp.RequestCtx) (map[string]any, error) { formData := make(map[string]any) // Parse multipart form form, err := ctx.MultipartForm() if err != nil { return nil, err } // Process form values for key, values := range form.Value { if len(values) == 1 { formData[key] = values[0] } else if len(values) > 1 { formData[key] = values } } // Process files (store file info, not the content) if len(form.File) > 0 { files := make(map[string]any) for fieldName, fileHeaders := range form.File { if len(fileHeaders) == 1 { files[fieldName] = fileInfoToMap(fileHeaders[0]) } else if len(fileHeaders) > 1 { fileInfos := make([]map[string]any, 0, len(fileHeaders)) for _, fh := range fileHeaders { fileInfos = append(fileInfos, fileInfoToMap(fh)) } files[fieldName] = fileInfos } } formData["_files"] = files } return formData, nil } // fileInfoToMap converts a FileHeader to a map for Lua func fileInfoToMap(fh *multipart.FileHeader) map[string]any { return map[string]any{ "filename": fh.Filename, "size": fh.Size, "mimetype": getMimeType(fh), } } // getMimeType gets the mime type from a file header func getMimeType(fh *multipart.FileHeader) string { if fh.Header != nil { contentType := fh.Header.Get("Content-Type") if contentType != "" { return contentType } } // Fallback to basic type detection from filename if strings.HasSuffix(fh.Filename, ".pdf") { return "application/pdf" } else if strings.HasSuffix(fh.Filename, ".png") { return "image/png" } else if strings.HasSuffix(fh.Filename, ".jpg") || strings.HasSuffix(fh.Filename, ".jpeg") { return "image/jpeg" } else if strings.HasSuffix(fh.Filename, ".gif") { return "image/gif" } else if strings.HasSuffix(fh.Filename, ".svg") { return "image/svg+xml" } return "application/octet-stream" } // GenerateSecureToken creates a cryptographically secure random token func GenerateSecureToken(length int) (string, error) { b := make([]byte, length) if _, err := rand.Read(b); err != nil { return "", err } return base64.URLEncoding.EncodeToString(b)[:length], nil }