diff options
Diffstat (limited to 'modules/services/nixos/ganyupload/main.go')
| -rw-r--r-- | modules/services/nixos/ganyupload/main.go | 104 |
1 files changed, 104 insertions, 0 deletions
diff --git a/modules/services/nixos/ganyupload/main.go b/modules/services/nixos/ganyupload/main.go new file mode 100644 index 0000000..6aa219e --- /dev/null +++ b/modules/services/nixos/ganyupload/main.go @@ -0,0 +1,104 @@ +package main + +import ( + _ "embed" + "io" + "log" + "net/http" + "os" + "path/filepath" + "strings" +) + +//go:embed README +var readme string + +var uploadDir = "." + +func handleRoot(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.Write([]byte(readme)) +} + +func handleUpload(w http.ResponseWriter, r *http.Request) { + // Show README on GET / + if r.Method == http.MethodGet && r.URL.Path == "/" { + handleRoot(w, r) + return + } + + if r.Method != http.MethodPut { + http.Error(w, "method not allowed", http.StatusMethodNotAllowed) + return + } + + // Extract filename from URL path, preventing path traversal + filename := strings.TrimPrefix(r.URL.Path, "/") + if filename == "" { + http.Error(w, "filename required", http.StatusBadRequest) + return + } + + // Prevent path traversal + filename = filepath.Clean(filename) + if strings.HasPrefix(filename, "..") { + http.Error(w, "invalid filename", http.StatusBadRequest) + return + } + + fullPath := filepath.Join(uploadDir, filename) + + // Ensure the directory exists + dir := filepath.Dir(fullPath) + if err := os.MkdirAll(dir, 0755); err != nil { + log.Printf("failed to create directory: %v", err) + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + + // Create the file + f, err := os.Create(fullPath) + if err != nil { + log.Printf("failed to create file: %v", err) + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + defer f.Close() + + // Copy request body to file + written, err := io.Copy(f, r.Body) + if err != nil { + log.Printf("failed to write file: %v", err) + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "text/plain; charset=utf-8") + w.WriteHeader(http.StatusCreated) + w.Write([]byte("ok\n")) + + log.Printf("uploaded %s (%d bytes)", filename, written) +} + +func main() { + if envUploadDir := os.Getenv("UPLOAD_DIR"); envUploadDir != "" { + uploadDir = envUploadDir + } + + port := os.Getenv("PORT") + if port == "" { + port = "8080" + } + + http.HandleFunc("/", handleUpload) + + log.Printf("starting ganyupload on port %s, upload directory: %s", port, uploadDir) + if err := http.ListenAndServe(":"+port, nil); err != nil { + log.Fatalf("server error: %v", err) + } +} |
