mirror of
https://github.com/tobychui/zoraxy.git
synced 2025-08-11 07:37:51 +02:00
Added web directory manager
+ Added web directory manager + Added dummy service expose proxy page + Moved ACME and renew to a new section in TLS management page
This commit is contained in:
@@ -10,7 +10,6 @@ import (
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
@@ -164,12 +163,12 @@ func (a *ACMEHandler) ObtainCert(domains []string, certificateName string, email
|
||||
|
||||
// Each certificate comes back with the cert bytes, the bytes of the client's
|
||||
// private key, and a certificate URL.
|
||||
err = ioutil.WriteFile("./conf/certs/"+certificateName+".crt", certificates.Certificate, 0777)
|
||||
err = os.WriteFile("./conf/certs/"+certificateName+".crt", certificates.Certificate, 0777)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return false, err
|
||||
}
|
||||
err = ioutil.WriteFile("./conf/certs/"+certificateName+".key", certificates.PrivateKey, 0777)
|
||||
err = os.WriteFile("./conf/certs/"+certificateName+".key", certificates.PrivateKey, 0777)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return false, err
|
||||
|
406
src/mod/webserv/filemanager/filemanager.go
Normal file
406
src/mod/webserv/filemanager/filemanager.go
Normal file
@@ -0,0 +1,406 @@
|
||||
package filemanager
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
)
|
||||
|
||||
/*
|
||||
File Manager
|
||||
|
||||
This is a simple package that handles file management
|
||||
under the web server directory
|
||||
*/
|
||||
|
||||
type FileManager struct {
|
||||
Directory string
|
||||
}
|
||||
|
||||
// Create a new file manager with directory as root
|
||||
func NewFileManager(directory string) *FileManager {
|
||||
return &FileManager{
|
||||
Directory: directory,
|
||||
}
|
||||
}
|
||||
|
||||
// Handle listing of a given directory
|
||||
func (fm *FileManager) HandleList(w http.ResponseWriter, r *http.Request) {
|
||||
directory, err := utils.GetPara(r, "dir")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid directory given")
|
||||
return
|
||||
}
|
||||
|
||||
// Construct the absolute path to the target directory
|
||||
targetDir := filepath.Join(fm.Directory, directory)
|
||||
|
||||
// Open the target directory
|
||||
dirEntries, err := os.ReadDir(targetDir)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "unable to open directory")
|
||||
return
|
||||
}
|
||||
|
||||
// Create a slice to hold the file information
|
||||
var files []map[string]interface{} = []map[string]interface{}{}
|
||||
|
||||
// Iterate through the directory entries
|
||||
for _, dirEntry := range dirEntries {
|
||||
fileInfo := make(map[string]interface{})
|
||||
fileInfo["filename"] = dirEntry.Name()
|
||||
fileInfo["filepath"] = filepath.Join(directory, dirEntry.Name())
|
||||
fileInfo["isDir"] = dirEntry.IsDir()
|
||||
|
||||
// Get file size and last modified time
|
||||
finfo, err := dirEntry.Info()
|
||||
if err != nil {
|
||||
//unable to load its info. Skip this file
|
||||
continue
|
||||
}
|
||||
fileInfo["lastModified"] = finfo.ModTime().Unix()
|
||||
if !dirEntry.IsDir() {
|
||||
// If it's a file, get its size
|
||||
fileInfo["size"] = finfo.Size()
|
||||
} else {
|
||||
// If it's a directory, set size to 0
|
||||
fileInfo["size"] = 0
|
||||
}
|
||||
|
||||
// Append file info to the list
|
||||
files = append(files, fileInfo)
|
||||
}
|
||||
|
||||
// Serialize the file info slice to JSON
|
||||
jsonData, err := json.Marshal(files)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "unable to marshal JSON")
|
||||
return
|
||||
}
|
||||
|
||||
// Set response headers and send the JSON response
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(jsonData)
|
||||
}
|
||||
|
||||
// Handle upload of a file (multi-part), 25MB max
|
||||
func (fm *FileManager) HandleUpload(w http.ResponseWriter, r *http.Request) {
|
||||
dir, err := utils.PostPara(r, "dir")
|
||||
if err != nil {
|
||||
log.Println("no dir given")
|
||||
utils.SendErrorResponse(w, "invalid dir given")
|
||||
return
|
||||
}
|
||||
|
||||
// Parse the multi-part form data
|
||||
err = r.ParseMultipartForm(25 << 20)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "unable to parse form data")
|
||||
return
|
||||
}
|
||||
|
||||
// Get the uploaded file
|
||||
file, fheader, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
log.Println(err.Error())
|
||||
utils.SendErrorResponse(w, "unable to get uploaded file")
|
||||
return
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Specify the directory where you want to save the uploaded file
|
||||
uploadDir := filepath.Join(fm.Directory, dir)
|
||||
if !utils.FileExists(uploadDir) {
|
||||
utils.SendErrorResponse(w, "upload target directory not exists")
|
||||
return
|
||||
}
|
||||
|
||||
filename := sanitizeFilename(fheader.Filename)
|
||||
if !isValidFilename(filename) {
|
||||
utils.SendErrorResponse(w, "filename contain invalid or reserved characters")
|
||||
return
|
||||
}
|
||||
|
||||
// Create the file on the server
|
||||
filePath := filepath.Join(uploadDir, filepath.Base(filename))
|
||||
out, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "unable to create file on the server")
|
||||
return
|
||||
}
|
||||
defer out.Close()
|
||||
|
||||
// Copy the uploaded file to the server
|
||||
_, err = io.Copy(out, file)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "unable to copy file to server")
|
||||
return
|
||||
}
|
||||
|
||||
// Respond with a success message or appropriate response
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
// Handle download of a selected file, serve with content dispose header
|
||||
func (fm *FileManager) HandleDownload(w http.ResponseWriter, r *http.Request) {
|
||||
filename, err := utils.GetPara(r, "file")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid filepath given")
|
||||
return
|
||||
}
|
||||
|
||||
previewMode, _ := utils.GetPara(r, "preview")
|
||||
if previewMode == "true" {
|
||||
// Serve the file using http.ServeFile
|
||||
filePath := filepath.Join(fm.Directory, filename)
|
||||
http.ServeFile(w, r, filePath)
|
||||
} else {
|
||||
// Trigger a download with content disposition headers
|
||||
filePath := filepath.Join(fm.Directory, filename)
|
||||
w.Header().Set("Content-Disposition", "attachment; filename="+filepath.Base(filename))
|
||||
http.ServeFile(w, r, filePath)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleNewFolder creates a new folder in the specified directory
|
||||
func (fm *FileManager) HandleNewFolder(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse the directory name from the request
|
||||
dirName, err := utils.GetPara(r, "path")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid directory name")
|
||||
return
|
||||
}
|
||||
|
||||
//Prevent path escape
|
||||
dirName = strings.ReplaceAll(dirName, "\\", "/")
|
||||
dirName = strings.ReplaceAll(dirName, "../", "")
|
||||
|
||||
// Specify the directory where you want to create the new folder
|
||||
newFolderPath := filepath.Join(fm.Directory, dirName)
|
||||
|
||||
// Check if the folder already exists
|
||||
if _, err := os.Stat(newFolderPath); os.IsNotExist(err) {
|
||||
// Create the new folder
|
||||
err := os.Mkdir(newFolderPath, os.ModePerm)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "unable to create the new folder")
|
||||
return
|
||||
}
|
||||
|
||||
// Respond with a success message or appropriate response
|
||||
utils.SendOK(w)
|
||||
} else {
|
||||
// If the folder already exists, respond with an error
|
||||
utils.SendErrorResponse(w, "folder already exists")
|
||||
}
|
||||
}
|
||||
|
||||
// HandleFileCopy copies a file or directory from the source path to the destination path
|
||||
func (fm *FileManager) HandleFileCopy(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse the source and destination paths from the request
|
||||
srcPath, err := utils.PostPara(r, "srcpath")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid source path")
|
||||
return
|
||||
}
|
||||
|
||||
destPath, err := utils.PostPara(r, "destpath")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid destination path")
|
||||
return
|
||||
}
|
||||
|
||||
// Validate and sanitize the source and destination paths
|
||||
srcPath = filepath.Clean(srcPath)
|
||||
destPath = filepath.Clean(destPath)
|
||||
|
||||
// Construct the absolute paths
|
||||
absSrcPath := filepath.Join(fm.Directory, srcPath)
|
||||
absDestPath := filepath.Join(fm.Directory, destPath)
|
||||
|
||||
// Check if the source path exists
|
||||
if _, err := os.Stat(absSrcPath); os.IsNotExist(err) {
|
||||
utils.SendErrorResponse(w, "source path does not exist")
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the destination path exists
|
||||
if _, err := os.Stat(absDestPath); os.IsNotExist(err) {
|
||||
utils.SendErrorResponse(w, "destination path does not exist")
|
||||
return
|
||||
}
|
||||
|
||||
//Join the name to create final paste filename
|
||||
absDestPath = filepath.Join(absDestPath, filepath.Base(absSrcPath))
|
||||
//Reject opr if already exists
|
||||
if utils.FileExists(absDestPath) {
|
||||
utils.SendErrorResponse(w, "target already exists")
|
||||
return
|
||||
}
|
||||
|
||||
// Perform the copy operation based on whether the source is a file or directory
|
||||
if isDir(absSrcPath) {
|
||||
// Recursive copy for directories
|
||||
err := copyDirectory(absSrcPath, absDestPath)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, fmt.Sprintf("error copying directory: %v", err))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// Copy a single file
|
||||
err := copyFile(absSrcPath, absDestPath)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, fmt.Sprintf("error copying file: %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func (fm *FileManager) HandleFileMove(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse the source and destination paths from the request
|
||||
srcPath, err := utils.GetPara(r, "srcpath")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid source path")
|
||||
return
|
||||
}
|
||||
|
||||
destPath, err := utils.GetPara(r, "destpath")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid destination path")
|
||||
return
|
||||
}
|
||||
|
||||
// Validate and sanitize the source and destination paths
|
||||
srcPath = filepath.Clean(srcPath)
|
||||
destPath = filepath.Clean(destPath)
|
||||
|
||||
// Construct the absolute paths
|
||||
absSrcPath := filepath.Join(fm.Directory, srcPath)
|
||||
absDestPath := filepath.Join(fm.Directory, destPath)
|
||||
|
||||
// Check if the source path exists
|
||||
if _, err := os.Stat(absSrcPath); os.IsNotExist(err) {
|
||||
utils.SendErrorResponse(w, "source path does not exist")
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the destination path exists
|
||||
if _, err := os.Stat(absDestPath); !os.IsNotExist(err) {
|
||||
utils.SendErrorResponse(w, "destination path already exists")
|
||||
return
|
||||
}
|
||||
|
||||
// Rename the source to the destination
|
||||
err = os.Rename(absSrcPath, absDestPath)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, fmt.Sprintf("error moving file/directory: %v", err))
|
||||
return
|
||||
}
|
||||
utils.SendOK(w)
|
||||
}
|
||||
|
||||
func (fm *FileManager) HandleFileProperties(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse the target file or directory path from the request
|
||||
filePath, err := utils.GetPara(r, "file")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid file path")
|
||||
return
|
||||
}
|
||||
|
||||
// Construct the absolute path to the target file or directory
|
||||
absPath := filepath.Join(fm.Directory, filePath)
|
||||
|
||||
// Check if the target path exists
|
||||
_, err = os.Stat(absPath)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "file or directory does not exist")
|
||||
return
|
||||
}
|
||||
|
||||
// Initialize a map to hold file properties
|
||||
fileProps := make(map[string]interface{})
|
||||
fileProps["filename"] = filepath.Base(absPath)
|
||||
fileProps["filepath"] = filePath
|
||||
fileProps["isDir"] = isDir(absPath)
|
||||
|
||||
// Get file size and last modified time
|
||||
finfo, err := os.Stat(absPath)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "unable to retrieve file properties")
|
||||
return
|
||||
}
|
||||
fileProps["lastModified"] = finfo.ModTime().Unix()
|
||||
if !isDir(absPath) {
|
||||
// If it's a file, get its size
|
||||
fileProps["size"] = finfo.Size()
|
||||
} else {
|
||||
// If it's a directory, calculate its total size containing all child files and folders
|
||||
totalSize, err := calculateDirectorySize(absPath)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "unable to calculate directory size")
|
||||
return
|
||||
}
|
||||
fileProps["size"] = totalSize
|
||||
}
|
||||
|
||||
// Count the number of sub-files and sub-folders
|
||||
numSubFiles, numSubFolders, err := countSubFilesAndFolders(absPath)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "unable to count sub-files and sub-folders")
|
||||
return
|
||||
}
|
||||
fileProps["fileCounts"] = numSubFiles
|
||||
fileProps["folderCounts"] = numSubFolders
|
||||
|
||||
// Serialize the file properties to JSON
|
||||
jsonData, err := json.Marshal(fileProps)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "unable to marshal JSON")
|
||||
return
|
||||
}
|
||||
|
||||
// Set response headers and send the JSON response
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(jsonData)
|
||||
}
|
||||
|
||||
// HandleFileDelete deletes a file or directory
|
||||
func (fm *FileManager) HandleFileDelete(w http.ResponseWriter, r *http.Request) {
|
||||
// Parse the target file or directory path from the request
|
||||
filePath, err := utils.PostPara(r, "target")
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "invalid file path")
|
||||
return
|
||||
}
|
||||
|
||||
// Construct the absolute path to the target file or directory
|
||||
absPath := filepath.Join(fm.Directory, filePath)
|
||||
|
||||
// Check if the target path exists
|
||||
_, err = os.Stat(absPath)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "file or directory does not exist")
|
||||
return
|
||||
}
|
||||
|
||||
// Delete the file or directory
|
||||
err = os.RemoveAll(absPath)
|
||||
if err != nil {
|
||||
utils.SendErrorResponse(w, "error deleting file or directory")
|
||||
return
|
||||
}
|
||||
|
||||
// Respond with a success message or appropriate response
|
||||
utils.SendOK(w)
|
||||
}
|
156
src/mod/webserv/filemanager/utils.go
Normal file
156
src/mod/webserv/filemanager/utils.go
Normal file
@@ -0,0 +1,156 @@
|
||||
package filemanager
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// isValidFilename checks if a given filename is safe and valid.
|
||||
func isValidFilename(filename string) bool {
|
||||
// Define a list of disallowed characters and reserved names
|
||||
disallowedChars := []string{"/", "\\", ":", "*", "?", "\"", "<", ">", "|"} // Add more if needed
|
||||
reservedNames := []string{"CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9"} // Add more if needed
|
||||
|
||||
// Check for disallowed characters
|
||||
for _, char := range disallowedChars {
|
||||
if strings.Contains(filename, char) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check for reserved names (case-insensitive)
|
||||
lowerFilename := strings.ToUpper(filename)
|
||||
for _, reserved := range reservedNames {
|
||||
if lowerFilename == reserved {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Check for empty filename
|
||||
if filename == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
// The filename is considered valid
|
||||
return true
|
||||
}
|
||||
|
||||
// sanitizeFilename sanitizes a given filename by removing disallowed characters.
|
||||
func sanitizeFilename(filename string) string {
|
||||
disallowedChars := []string{"/", "\\", ":", "*", "?", "\"", "<", ">", "|"} // Add more if needed
|
||||
|
||||
// Replace disallowed characters with underscores
|
||||
for _, char := range disallowedChars {
|
||||
filename = strings.ReplaceAll(filename, char, "_")
|
||||
}
|
||||
|
||||
return filename
|
||||
}
|
||||
|
||||
// copyFile copies a single file from source to destination
|
||||
func copyFile(srcPath, destPath string) error {
|
||||
srcFile, err := os.Open(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer srcFile.Close()
|
||||
|
||||
destFile, err := os.Create(destPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer destFile.Close()
|
||||
|
||||
_, err = io.Copy(destFile, srcFile)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyDirectory recursively copies a directory and its contents from source to destination
|
||||
func copyDirectory(srcPath, destPath string) error {
|
||||
// Create the destination directory
|
||||
err := os.MkdirAll(destPath, os.ModePerm)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
entries, err := os.ReadDir(srcPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
srcEntryPath := filepath.Join(srcPath, entry.Name())
|
||||
destEntryPath := filepath.Join(destPath, entry.Name())
|
||||
|
||||
if entry.IsDir() {
|
||||
err := copyDirectory(srcEntryPath, destEntryPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := copyFile(srcEntryPath, destEntryPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// isDir checks if the given path is a directory
|
||||
func isDir(path string) bool {
|
||||
fileInfo, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return fileInfo.IsDir()
|
||||
}
|
||||
|
||||
// calculateDirectorySize calculates the total size of a directory and its contents
|
||||
func calculateDirectorySize(dirPath string) (int64, error) {
|
||||
var totalSize int64
|
||||
err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
totalSize += info.Size()
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return totalSize, nil
|
||||
}
|
||||
|
||||
// countSubFilesAndFolders counts the number of sub-files and sub-folders within a directory
|
||||
func countSubFilesAndFolders(dirPath string) (int, int, error) {
|
||||
var numSubFiles, numSubFolders int
|
||||
|
||||
err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
numSubFolders++
|
||||
} else {
|
||||
numSubFiles++
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
|
||||
// Subtract 1 from numSubFolders to exclude the root directory itself
|
||||
return numSubFiles, numSubFolders - 1, nil
|
||||
}
|
@@ -13,6 +13,7 @@ import (
|
||||
|
||||
"imuslab.com/zoraxy/mod/database"
|
||||
"imuslab.com/zoraxy/mod/utils"
|
||||
"imuslab.com/zoraxy/mod/webserv/filemanager"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -33,6 +34,8 @@ type WebServerOptions struct {
|
||||
}
|
||||
|
||||
type WebServer struct {
|
||||
FileManager *filemanager.FileManager
|
||||
|
||||
mux *http.ServeMux
|
||||
server *http.Server
|
||||
option *WebServerOptions
|
||||
@@ -55,13 +58,21 @@ func NewWebServer(options *WebServerOptions) *WebServer {
|
||||
|
||||
}
|
||||
|
||||
//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(),
|
||||
option: options,
|
||||
isRunning: false,
|
||||
mu: sync.Mutex{},
|
||||
mux: http.NewServeMux(),
|
||||
FileManager: newDirManager,
|
||||
option: options,
|
||||
isRunning: false,
|
||||
mu: sync.Mutex{},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -90,6 +101,10 @@ func (ws *WebServer) RestorePreviousState() {
|
||||
|
||||
// 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
|
||||
|
Reference in New Issue
Block a user