go-cdn/main.go
2023-06-03 21:14:43 +03:00

238 lines
6.1 KiB
Go

package main
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path/filepath"
"regexp"
"runtime"
"strings"
"github.com/go-chi/chi"
"github.com/go-chi/chi/middleware"
"github.com/sirupsen/logrus"
)
type Config struct {
Username string `json:"username"`
Password string `json:"password"`
ServicePort string `json:"service_port"`
UploadsDir string `json:"uploads_dir"`
}
var (
config Config
logger *logrus.Logger
filenameRegex *regexp.Regexp
)
func main() {
// Initialize logger
logger = logrus.New()
logger.Formatter = &logrus.TextFormatter{
DisableTimestamp: false,
FullTimestamp: true,
}
// Load configuration
err := loadConfig("config.json")
if err != nil {
logger.Fatalf("Error loading config file: %s", err)
}
// Initialize filename regular expression
filenameRegex = regexp.MustCompile(`^[a-zA-Z0-9-_\.]+$`)
// Initialize router
router := chi.NewRouter()
router.Use(middleware.Logger)
router.HandleFunc("/", serveCDN)
router.HandleFunc("/upload", handleUpload)
// Start server
address := fmt.Sprintf(":%s", config.ServicePort)
logger.Infof("Starting CDN server on port %s...", config.ServicePort)
logger.Infof("Serving files from %s", config.UploadsDir)
// Print upload path and list files
uploadPath := filepath.Join(".", config.UploadsDir)
logger.Infof("Upload path: %s", uploadPath)
listFiles(uploadPath)
err = http.ListenAndServe(address, router)
if err != nil {
logger.Fatalf("Server error: %s", err)
}
}
func loadConfig(filename string) error {
// Get the absolute path of the main.go file
_, currentFile, _, _ := runtime.Caller(1)
currentDir := filepath.Dir(currentFile)
// Construct the absolute path for the config file
configPath := filepath.Join(currentDir, filename)
// Open the config file
file, err := os.Open(configPath)
if err != nil {
return fmt.Errorf("error opening config file: %s", err)
}
defer file.Close()
// Decode the config file into the config variable
decoder := json.NewDecoder(file)
err = decoder.Decode(&config)
if err != nil {
return fmt.Errorf("error decoding config file: %s", err)
}
// Set the relative path for the uploads directory
config.UploadsDir = filepath.Join(currentDir, filepath.FromSlash(config.UploadsDir))
// Create the uploads directory if it doesn't exist
if _, err := os.Stat(config.UploadsDir); os.IsNotExist(err) {
err := os.MkdirAll(config.UploadsDir, os.ModePerm)
if err != nil {
return fmt.Errorf("error creating uploads directory: %s", err)
}
}
return nil
}
func serveCDN(w http.ResponseWriter, r *http.Request) {
// Log the request information
logger.Infof("Received request: %s %s", r.Method, r.URL.Path)
// Check if the request has valid authentication
if !checkAuthentication(r) {
logger.Warn("Authentication failed")
w.Header().Set("WWW-Authenticate", `Basic realm="CDN Authentication"`)
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprintf(w, "401 Unauthorized\n")
return
}
// Get the requested file path
filePath := filepath.Join(config.UploadsDir, filepath.Clean(r.URL.Path))
// Check if the file exists
_, err := os.Stat(filePath)
if err != nil {
if os.IsNotExist(err) {
logger.Infof("File not found: %s", r.URL.Path)
http.NotFound(w, r)
} else {
logger.Error("Internal Server Error:", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
}
return
}
// Serve the file
logger.Infof("Serving file: %s", r.URL.Path)
http.ServeFile(w, r, filePath)
}
func handleUpload(w http.ResponseWriter, r *http.Request) {
// Log the request information
logger.Infof("Received upload request: %s %s", r.Method, r.URL.Path)
// Check if the request has valid authentication
if !checkAuthentication(r) {
logger.Warn("Authentication failed")
w.Header().Set("WWW-Authenticate", `Basic realm="CDN Authentication"`)
w.WriteHeader(http.StatusUnauthorized)
fmt.Fprintf(w, "401 Unauthorized\n")
return
}
// Parse the uploaded file
err := r.ParseMultipartForm(32 << 20) // Max file size: 32MB
if err != nil {
logger.Error("Bad Request:", err)
http.Error(w, "Bad Request", http.StatusBadRequest)
return
}
file, handler, err := r.FormFile("file")
if err != nil {
logger.Error("No file provided in the request:", err)
http.Error(w, "No file provided in the request", http.StatusBadRequest)
return
}
defer file.Close()
// Validate filename
filename := sanitizeFilename(handler.Filename)
if !isValidFilename(filename) {
logger.Errorf("Invalid filename: %s", handler.Filename)
http.Error(w, "Invalid filename", http.StatusBadRequest)
return
}
// Create the uploads directory if it doesn't exist
err = os.MkdirAll(config.UploadsDir, os.ModePerm)
if err != nil {
logger.Error("Error creating uploads directory:", err)
http.Error(w, "Error creating uploads directory", http.StatusInternalServerError)
return
}
// Create a new file in the uploads directory
dst, err := os.Create(filepath.Join(config.UploadsDir, filename))
if err != nil {
logger.Error("Internal Server Error:", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
defer dst.Close()
// Copy the uploaded file to the destination
_, err = io.Copy(dst, file)
if err != nil {
logger.Error("Internal Server Error:", err)
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
return
}
logger.Infof("File uploaded successfully: %s", filename)
fmt.Fprintf(w, "File uploaded successfully!")
}
func checkAuthentication(r *http.Request) bool {
username, password, ok := r.BasicAuth()
return ok && username == config.Username && password == config.Password
}
func sanitizeFilename(filename string) string {
return strings.TrimSpace(filename)
}
func isValidFilename(filename string) bool {
return filenameRegex.MatchString(filename)
}
func listFiles(dirPath string) {
logger.Infof("Files in %s:", dirPath)
err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
if err != nil {
logger.Errorf("Error accessing file: %s", err)
return nil
}
if !info.IsDir() {
logger.Infof("- %s", path)
}
return nil
})
if err != nil {
logger.Errorf("Error listing files: %s", err)
}
}