package webserv import ( "embed" _ "embed" "errors" "fmt" "net/http" "os" "path/filepath" "sync" "imuslab.com/zoraxy/mod/database" "imuslab.com/zoraxy/mod/info/logger" "imuslab.com/zoraxy/mod/utils" "imuslab.com/zoraxy/mod/webserv/filemanager" ) /* Static Web Server package This module host a static web server */ //go:embed templates/* var templates embed.FS /* WebServerOptions define the default option for the webserv might get override by user settings loaded from db Any changes in here might need to also update the StaticWebServerStatus struct in handler.go. See handler.go for more information. */ type WebServerOptions struct { Port string //Port for listening EnableDirectoryListing bool //Enable listing of directory WebRoot string //Folder for stroing the static web folders EnableWebDirManager bool //Enable web file manager to handle files in web directory DisableListenToAllInterface bool // Disable listening to all interfaces, only listen to localhost Logger *logger.Logger //System logger Sysdb *database.Database //Database for storing configs } type WebServer struct { FileManager *filemanager.FileManager mux *http.ServeMux server *http.Server option *WebServerOptions isRunning bool mu sync.Mutex } // NewWebServer creates a new WebServer instance. One instance only func NewWebServer(options *WebServerOptions) *WebServer { if options.Logger == nil { options.Logger, _ = logger.NewFmtLogger() } if !utils.FileExists(options.WebRoot) { //Web root folder not exists. Create one with default templates os.MkdirAll(filepath.Join(options.WebRoot, "html"), 0775) os.MkdirAll(filepath.Join(options.WebRoot, "templates"), 0775) indexTemplate, err := templates.ReadFile("templates/index.html") if err != nil { options.Logger.PrintAndLog("static-webserv", "Failed to read static wev server template file: ", err) } else { os.WriteFile(filepath.Join(options.WebRoot, "html", "index.html"), indexTemplate, 0775) } } //Create a new file manager if it is enabled var newDirManager *filemanager.FileManager if options.EnableWebDirManager { fm := filemanager.NewFileManager(filepath.Join(options.WebRoot, "/html")) newDirManager = fm } //Create new table to store the config options.Sysdb.NewTable("webserv") return &WebServer{ mux: http.NewServeMux(), FileManager: newDirManager, option: options, isRunning: false, mu: sync.Mutex{}, } } // Restore the configuration to previous config func (ws *WebServer) RestorePreviousState() { //Set the port port := ws.option.Port ws.option.Sysdb.Read("webserv", "port", &port) ws.option.Port = port //Set the enable directory list enableDirList := ws.option.EnableDirectoryListing ws.option.Sysdb.Read("webserv", "dirlist", &enableDirList) ws.option.EnableDirectoryListing = enableDirList //Set disable listen to all interface disableListenToAll := ws.option.DisableListenToAllInterface ws.option.Sysdb.Read("webserv", "disableListenToAllInterface", &disableListenToAll) ws.option.DisableListenToAllInterface = disableListenToAll //Check the running state webservRunning := true ws.option.Sysdb.Read("webserv", "enabled", &webservRunning) if webservRunning { ws.Start() } else { ws.Stop() } } // ChangePort changes the server's port. func (ws *WebServer) ChangePort(port string) error { if IsPortInUse(port) { return errors.New("selected port is used by another process") } if ws.isRunning { if err := ws.Stop(); err != nil { return err } } ws.option.Port = port ws.server.Addr = ":" + port err := ws.Start() if err != nil { return err } ws.option.Logger.PrintAndLog("static-webserv", "Listening port updated to "+port, nil) ws.option.Sysdb.Write("webserv", "port", port) return nil } // Get current using port in options func (ws *WebServer) GetListeningPort() string { return ws.option.Port } // Start starts the web server. func (ws *WebServer) Start() error { ws.mu.Lock() defer ws.mu.Unlock() //Check if server already running if ws.isRunning { return fmt.Errorf("web server is already running") } //Check if the port is usable if IsPortInUse(ws.option.Port) { return errors.New("port already in use or access denied by host OS") } //Dispose the old mux and create a new one ws.mux = http.NewServeMux() //Create a static web server fs := http.FileServer(http.Dir(filepath.Join(ws.option.WebRoot, "html"))) ws.mux.Handle("/", ws.fsMiddleware(fs)) listenAddr := ":" + ws.option.Port if ws.option.DisableListenToAllInterface { listenAddr = "127.0.0.1:" + ws.option.Port } ws.server = &http.Server{ Addr: listenAddr, Handler: ws.mux, } go func() { if err := ws.server.ListenAndServe(); err != nil { if err != http.ErrServerClosed { ws.option.Logger.PrintAndLog("static-webserv", "Web server failed to start", err) } } }() ws.option.Logger.PrintAndLog("static-webserv", "Static Web Server started. Listeing on :"+ws.option.Port, nil) ws.isRunning = true ws.option.Sysdb.Write("webserv", "enabled", true) return nil } // Stop stops the web server. func (ws *WebServer) Stop() error { ws.mu.Lock() defer ws.mu.Unlock() if !ws.isRunning { return fmt.Errorf("web server is not running") } if err := ws.server.Close(); err != nil { return err } ws.option.Logger.PrintAndLog("static-webserv", "Static Web Server stopped", nil) ws.isRunning = false ws.option.Sysdb.Write("webserv", "enabled", false) return nil } // UpdateDirectoryListing enables or disables directory listing. func (ws *WebServer) UpdateDirectoryListing(enable bool) { ws.option.EnableDirectoryListing = enable ws.option.Sysdb.Write("webserv", "dirlist", enable) } // Close stops the web server without returning an error. func (ws *WebServer) Close() { ws.Stop() }