Added static web server and black / whitelist template

- Added static web server
- Added static web server default index
- Added embeded templates to blacklist / whitelist
- Added wip Web Directory Manager

Place the templates at ./www/templates/blacklist.html or whitelist.html to replace the build in embedded template for access control 403 error
-
This commit is contained in:
Toby Chui
2023-09-14 16:15:40 +08:00
parent ed92cccf0e
commit b63a0fc246
34 changed files with 6991 additions and 26 deletions

View File

@@ -7,6 +7,7 @@ import (
"net/http"
"net/url"
"os"
"path/filepath"
"strings"
"imuslab.com/zoraxy/mod/geodb"
@@ -192,9 +193,9 @@ func (h *ProxyHandler) handleAccessRouting(w http.ResponseWriter, r *http.Reques
if h.Parent.Option.GeodbStore.IsBlacklisted(clientIpAddr) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusForbidden)
template, err := os.ReadFile("./web/forbidden.html")
template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/blacklist.html"))
if err != nil {
w.Write([]byte("403 - Forbidden"))
w.Write(page_forbidden)
} else {
w.Write(template)
}
@@ -206,9 +207,9 @@ func (h *ProxyHandler) handleAccessRouting(w http.ResponseWriter, r *http.Reques
if !h.Parent.Option.GeodbStore.IsWhitelisted(clientIpAddr) {
w.Header().Set("Content-Type", "text/html; charset=utf-8")
w.WriteHeader(http.StatusForbidden)
template, err := os.ReadFile("./web/forbidden.html")
template, err := os.ReadFile(filepath.Join(h.Parent.Option.WebDirectory, "templates/whitelist.html"))
if err != nil {
w.Write([]byte("403 - Forbidden"))
w.Write(page_forbidden)
} else {
w.Write(template)
}

View File

@@ -0,0 +1,55 @@
<html>
<head>
<!-- Zoraxy Forbidden Template -->
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.5.0/semantic.min.css">
<script type="text/javascript" src="https://code.jquery.com/jquery-3.6.4.min.js"></script>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.5.0/semantic.min.js"></script>
<title>Forbidden</title>
<style>
#msg{
position: absolute;
top: calc(50% - 150px);
left: calc(50% - 250px);
width: 500px;
height: 300px;
text-align: center;
}
#footer{
position: fixed;
padding: 2em;
padding-left: 5em;
padding-right: 5em;
bottom: 0px;
left: 0px;
width: 100%;
}
small{
word-break: break-word;
}
</style>
</head>
<body>
<div id="msg">
<h1 style="font-size: 6em; margin-bottom: 0px;"><i class="red ban icon"></i></h1>
<div>
<h3 style="margin-top: 1em;">403 - Forbidden</h3>
<div class="ui divider"></div>
<p>You do not have permission to view this directory or page. <br>
This might cause by the region limit setting of this site.</p>
<div class="ui divider"></div>
<div style="text-align: left;">
<small>Request time: <span id="reqtime"></span></small><br>
<small id="reqURLDisplay">Request URI: <span id="requrl"></span></small>
</div>
</div>
</div>
<script>
$("#reqtime").text(new Date().toLocaleString(undefined, {year: 'numeric', month: '2-digit', day: '2-digit', weekday:"long", hour: '2-digit', hour12: false, minute:'2-digit', second:'2-digit'}));
$("#requrl").text(window.location.href);
</script>
</body>
</html>

View File

@@ -1,6 +1,7 @@
package dynamicproxy
import (
_ "embed"
"net"
"net/http"
"sync"
@@ -31,6 +32,7 @@ type RouterOption struct {
RedirectRuleTable *redirection.RuleTable
GeodbStore *geodb.Store //GeoIP blacklist and whitelist
StatisticCollector *statistic.Collector
WebDirectory string //The static web server directory containing the templates folder
}
type Router struct {
@@ -123,3 +125,11 @@ type SubdOptions struct {
BasicAuthCredentials []*BasicAuthCredentials
BasicAuthExceptionRules []*BasicAuthExceptionRule
}
/*
Web Templates
*/
var (
//go:embed templates/forbidden.html
page_forbidden []byte
)

View File

@@ -5,6 +5,7 @@ import (
"log"
"net/http"
"os"
"strconv"
"strings"
"time"
)
@@ -76,6 +77,22 @@ func PostBool(r *http.Request, key string) (bool, error) {
return false, errors.New("invalid boolean given")
}
// Get POST paramter as int
func PostInt(r *http.Request, key string) (int, error) {
x, err := PostPara(r, key)
if err != nil {
return 0, err
}
x = strings.TrimSpace(x)
rx, err := strconv.Atoi(x)
if err != nil {
return 0, err
}
return rx, nil
}
func FileExists(filename string) bool {
_, err := os.Stat(filename)
if os.IsNotExist(err) {

View File

@@ -1,5 +1,13 @@
package webserv
import (
"encoding/json"
"net/http"
"strconv"
"imuslab.com/zoraxy/mod/utils"
)
/*
Handler.go
@@ -7,3 +15,74 @@ package webserv
web server is directly listening to the TCP port
handlers in this script are for setting change only
*/
type StaticWebServerStatus struct {
ListeningPort int
EnableDirectoryListing bool
WebRoot string
Running bool
EnableWebDirManager bool
}
// Handle getting current static web server status
func (ws *WebServer) HandleGetStatus(w http.ResponseWriter, r *http.Request) {
listeningPortInt, _ := strconv.Atoi(ws.option.Port)
currentStatus := StaticWebServerStatus{
ListeningPort: listeningPortInt,
EnableDirectoryListing: ws.option.EnableDirectoryListing,
WebRoot: ws.option.WebRoot,
Running: ws.isRunning,
EnableWebDirManager: ws.option.EnableWebDirManager,
}
js, _ := json.Marshal(currentStatus)
utils.SendJSONResponse(w, string(js))
}
// Handle request for starting the static web server
func (ws *WebServer) HandleStartServer(w http.ResponseWriter, r *http.Request) {
err := ws.Start()
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}
// Handle request for stopping the static web server
func (ws *WebServer) HandleStopServer(w http.ResponseWriter, r *http.Request) {
err := ws.Stop()
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}
// Handle change server listening port request
func (ws *WebServer) HandlePortChange(w http.ResponseWriter, r *http.Request) {
newPort, err := utils.PostInt(r, "port")
if err != nil {
utils.SendErrorResponse(w, "invalid port number given")
return
}
err = ws.ChangePort(strconv.Itoa(newPort))
if err != nil {
utils.SendErrorResponse(w, err.Error())
return
}
utils.SendOK(w)
}
// Change enable directory listing settings
func (ws *WebServer) SetEnableDirectoryListing(w http.ResponseWriter, r *http.Request) {
enableList, err := utils.PostBool(r, "enable")
if err != nil {
utils.SendErrorResponse(w, "invalid setting given")
return
}
ws.option.EnableDirectoryListing = enableList
utils.SendOK(w)
}

View File

@@ -22,7 +22,7 @@ func (ws *WebServer) resolveFileDiskPath(requestPath string) string {
// File server middleware to handle directory listing (and future expansion)
func (ws *WebServer) fsMiddleware(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if ws.option.EnableDirectoryListing {
if !ws.option.EnableDirectoryListing {
if strings.HasSuffix(r.URL.Path, "/") {
//This is a folder. Let check if index exists
if utils.FileExists(filepath.Join(ws.resolveFileDiskPath(r.URL.Path), "index.html")) {

View File

@@ -0,0 +1,61 @@
<html>
<head>
<title>Hello Zoraxy</title>
<style>
body {
font-family: Tahoma, sans-serif;
background-color: #f6f6f6;
color: #2d2e30;
}
.sectionHeader{
background-color: #c4d0d9;
padding: 0.1em;
}
.sectionHeader h3{
text-align: center;
}
.container{
margin: 4em;
margin-left: 10em;
margin-right: 10em;
background-color: #fefefe;
}
@media (max-width:960px) {
.container{
margin-left: 1em;
margin-right: 1em;
}
.sectionHeader{
padding-left: 1em;
padding-right: 1em;
}
}
.textcontainer{
padding: 1em;
}
</style>
</head>
<body>
<div class="container">
<div class="sectionHeader">
<h3>Welcome to Zoraxy Static Web Server!</h3>
</div>
<div class="textcontainer">
<p>If you see this page, that means your static web server is running.<br>
By default, all the html files are stored under <code>./web/html/</code>
relative to the zoraxy runtime directory.<br>
You can upload your html files to your web directory via the <b>Web Directory Manager</b>.
</p>
<p>
For online documentation, please refer to <a href="//zoraxy.arozos.com">zoraxy.arozos.com</a> or the <a href="https://github.com/tobychui/zoraxy/wiki">project wiki</a>.<br>
Thank you for using Zoraxy!
</p>
</div>
</div>
</body>
</html>

View File

@@ -1,6 +1,8 @@
package webserv
import (
"embed"
_ "embed"
"errors"
"fmt"
"log"
@@ -9,6 +11,7 @@ import (
"path/filepath"
"sync"
"imuslab.com/zoraxy/mod/database"
"imuslab.com/zoraxy/mod/utils"
)
@@ -18,11 +21,17 @@ import (
This module host a static web server
*/
//go:embed templates/*
var templates embed.FS
type WebServerOptions struct {
Port string //Port for listening
EnableDirectoryListing bool //Enable listing of directory
WebRoot string //Folder for stroing the static web folders
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
Sysdb *database.Database //Database for storing configs
}
type WebServer struct {
mux *http.ServeMux
server *http.Server
@@ -31,13 +40,23 @@ type WebServer struct {
mu sync.Mutex
}
// NewWebServer creates a new WebServer instance.
// NewWebServer creates a new WebServer instance. One instance only
func NewWebServer(options *WebServerOptions) *WebServer {
if !utils.FileExists(options.WebRoot) {
//Web root folder not exists. Create one
//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 {
log.Println("Failed to read static wev server template file: ", err.Error())
} else {
os.WriteFile(filepath.Join(options.WebRoot, "html", "index.html"), indexTemplate, 0775)
}
}
//Create new table to store the config
options.Sysdb.NewTable("webserv")
return &WebServer{
mux: http.NewServeMux(),
option: options,
@@ -46,11 +65,31 @@ func NewWebServer(options *WebServerOptions) *WebServer {
}
}
// 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
//Check the running state
webservRunning := false
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 {
ws.mu.Lock()
defer ws.mu.Unlock()
if ws.isRunning {
if err := ws.Stop(); err != nil {
return err
@@ -60,6 +99,13 @@ func (ws *WebServer) ChangePort(port string) error {
ws.option.Port = port
ws.server.Addr = ":" + port
err := ws.Start()
if err != nil {
return err
}
ws.option.Sysdb.Write("webserv", "port", port)
return nil
}
@@ -100,7 +146,7 @@ func (ws *WebServer) Start() error {
log.Println("Static Web Server started. Listeing on :" + ws.option.Port)
ws.isRunning = true
ws.option.Sysdb.Write("webserv", "enabled", true)
return nil
}
@@ -118,17 +164,14 @@ func (ws *WebServer) Stop() error {
}
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.mu.Lock()
defer ws.mu.Unlock()
ws.option.EnableDirectoryListing = enable
ws.option.Sysdb.Write("webserv", "dirlist", enable)
}
// Close stops the web server without returning an error.