package main
import (
"fmt"
"net/http"
"io"
"regexp"
"strings"
"errors"
"encoding/json"
)
// Allowed operations mapping
var allowedOperations = map[string]func(string) (string, error){
"count_lines": countLines,
"sort_lines": sortLines,
"uppercase": toUppercase,
"word_count": wordCount,
}
type ScriptRequest struct {
Operation string `json:"operation"`
Data string `json:"data"`
}
type ScriptResponse struct {
Result string `json:"result"`
Error string `json:"error,omitempty"`
}
// SECURE: Structured API instead of arbitrary script execution
func executeOperationHandler(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// Parse JSON request
var req ScriptRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
sendErrorResponse(w, "Invalid JSON format", http.StatusBadRequest)
return
}
// Validate operation
operation, exists := allowedOperations[req.Operation]
if !exists {
sendErrorResponse(w, "Operation not allowed", http.StatusBadRequest)
return
}
// Validate input data
if !isValidInputData(req.Data) {
sendErrorResponse(w, "Invalid input data", http.StatusBadRequest)
return
}
// Execute safe operation
result, err := operation(req.Data)
if err != nil {
sendErrorResponse(w, err.Error(), http.StatusInternalServerError)
return
}
// Send successful response
response := ScriptResponse{Result: result}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
func sendErrorResponse(w http.ResponseWriter, message string, statusCode int) {
response := ScriptResponse{Error: message}
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(statusCode)
json.NewEncoder(w).Encode(response)
}
// SECURE: Native Go operations instead of shell commands
func countLines(data string) (string, error) {
lines := strings.Split(data, "\n")
count := 0
for _, line := range lines {
if strings.TrimSpace(line) != "" {
count++
}
}
return fmt.Sprintf("%d", count), nil
}
func sortLines(data string) (string, error) {
lines := strings.Split(data, "\n")
// Filter and validate lines
validLines := make([]string, 0)
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if len(trimmed) > 0 && isValidLine(trimmed) {
validLines = append(validLines, trimmed)
}
}
// Sort using Go's built-in sort
import "sort"
sort.Strings(validLines)
return strings.Join(validLines, "\n"), nil
}
func toUppercase(data string) (string, error) {
return strings.ToUpper(data), nil
}
func wordCount(data string) (string, error) {
words := strings.Fields(data)
count := 0
for _, word := range words {
if isValidWord(word) {
count++
}
}
return fmt.Sprintf("%d", count), nil
}
// Input validation functions
func isValidInputData(data string) bool {
// Size limit
if len(data) > 100000 {
return false
}
// Character allowlist
pattern := `^[a-zA-Z0-9\s.,!?\n\r-]+$`
matched, err := regexp.MatchString(pattern, data)
if err != nil || !matched {
return false
}
// No control characters or suspicious patterns
return !strings.Contains(data, "\x00") &&
!strings.Contains(data, "$(")
}
func isValidLine(line string) bool {
return len(line) <= 1000 && !strings.Contains(line, "..")
}
func isValidWord(word string) bool {
pattern := `^[a-zA-Z0-9]+$`
matched, _ := regexp.MatchString(pattern, word)
return matched && len(word) <= 50
}
func main() {
http.HandleFunc("/execute", executeOperationHandler)
fmt.Println("Secure server starting on :8080")
fmt.Println("Allowed operations: count_lines, sort_lines, uppercase, word_count")
http.ListenAndServe(":8080", nil)
}
// Secure usage example:
// curl -X POST -H "Content-Type: application/json" \
// -d '{"operation":"count_lines","data":"line1\nline2\nline3"}' \
// http://localhost:8080/execute