commit 4f8fe007ad7dea84da1375a5d89962cadcb5d466 Author: Kaustubh Maske Patil Date: Tue Jul 2 14:55:33 2024 +0530 Initial commit diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..110d2d9 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.kaustubh.page/fun-with-files + +go 1.22.4 diff --git a/main.go b/main.go new file mode 100644 index 0000000..d3dd8fa --- /dev/null +++ b/main.go @@ -0,0 +1,133 @@ +package main + +import ( + "crypto/rand" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "regexp" + "strings" +) + +var uploadDir string + +func initConfig() { + if envUploadDir := os.Getenv("UPLOAD_DIR"); envUploadDir != "" { + uploadDir = envUploadDir + } else { + uploadDir = "./uploads/" + } +} + +func main() { + initConfig() + + if len(os.Args) < 2 { + fmt.Println("Usage: go run main.go ") + return + } + + port := os.Args[1] + + uploadEndpoint := "upload" + if len(os.Args) >= 3 { + uploadEndpoint = os.Args[2] + } + + http.HandleFunc(fmt.Sprintf("/%s/", uploadEndpoint), uploadHandler) + fmt.Printf("Server started at :%s\n", port) + fmt.Printf("Endpoint: /%s/\n", uploadEndpoint) + fmt.Printf("Upload Directory: %s\n", uploadDir) + http.ListenAndServe(":"+port, nil) +} + +func uploadHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Only POST method is allowed", http.StatusMethodNotAllowed) + return + } + + // Parse the multipart form + err := r.ParseMultipartForm(10 << 20) // 10 MB + if err != nil { + http.Error(w, "Error parsing form data", http.StatusBadRequest) + return + } + + file, handler, err := r.FormFile("file") + if err != nil { + http.Error(w, "Error retrieving the file", http.StatusBadRequest) + return + } + defer file.Close() + + // Sanitize the filename + sanitizedFilename := sanitizeFilename(handler.Filename) + if sanitizedFilename == "" { + sanitizedFilename = generateRandomFilename() + } + + filePath := filepath.Join(uploadDir, sanitizedFilename) + + // Ensure unique filename + for fileExists(filePath) { + sanitizedFilename = generateRandomFilename() + filePath = filepath.Join(uploadDir, sanitizedFilename) + } + + // Create the uploads directory if it doesn't exist + if _, err := os.Stat(uploadDir); os.IsNotExist(err) { + os.MkdirAll(uploadDir, os.ModePerm) + } + + // Save the file + out, err := os.Create(filePath) + if err != nil { + http.Error(w, "Unable to save the file", http.StatusInternalServerError) + return + } + defer out.Close() + + _, err = io.Copy(out, file) + if err != nil { + http.Error(w, "Error saving the file", http.StatusInternalServerError) + return + } + + w.Write([]byte("File uploaded successfully")) +} + +func sanitizeFilename(filename string) string { + // Allow only alphanumeric, dashes, underscores, and dots + re := regexp.MustCompile(`[^a-zA-Z0-9._-]`) + sanitized := re.ReplaceAllString(filename, "") + sanitized = strings.TrimLeft(sanitized, ".") + + return sanitized +} + +func generateRandomFilename() string { + const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + b := make([]byte, 16) + for i := range b { + b[i] = letters[randInt(len(letters))] + } + return string(b) + ".file" +} + +func randInt(n int) int { + b := make([]byte, 1) + rand.Read(b) + return int(b[0]) % n +} + +func fileExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() +} +