mirror of
https://github.com/shlldev/miniws.git
synced 2025-09-02 19:00:59 +02:00
173 lines
4.5 KiB
Go
173 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"errors"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
const (
|
|
FILTER_MODE_WHITELIST FilterMode = 0
|
|
FILTER_MODE_BLACKLIST FilterMode = 1
|
|
)
|
|
|
|
type FilterMode int
|
|
|
|
type WebServer struct {
|
|
logger *Logger
|
|
port int
|
|
configFolder string
|
|
ipFilter []string
|
|
userAgentFilter []string
|
|
ipFilterMode FilterMode
|
|
userAgentFilterMode FilterMode
|
|
}
|
|
|
|
func NewWebServer(port_ int, logFolder_, configFolder_ string) *WebServer {
|
|
|
|
return &WebServer{
|
|
logger: NewLogger(logFolder_),
|
|
port: port_,
|
|
configFolder: configFolder_,
|
|
ipFilter: make([]string, 0),
|
|
userAgentFilter: make([]string, 0),
|
|
ipFilterMode: FILTER_MODE_BLACKLIST,
|
|
userAgentFilterMode: FILTER_MODE_BLACKLIST,
|
|
}
|
|
}
|
|
|
|
func (ws *WebServer) Run() {
|
|
|
|
ws.ipFilterMode, ws.ipFilter = ws.parseFilterPanics(FILENAME_IPFILTER)
|
|
ws.userAgentFilterMode, ws.userAgentFilter = ws.parseFilterPanics(FILENAME_USERAGENTFILTER)
|
|
|
|
http.HandleFunc("/", ws.get)
|
|
log.Println("Server started on port " + strconv.Itoa(ws.port))
|
|
http.ListenAndServe(":"+strconv.Itoa(ws.port), nil)
|
|
}
|
|
|
|
func (ws *WebServer) parseFilterPanics(filename string) (FilterMode, []string) {
|
|
|
|
filterMode := FILTER_MODE_BLACKLIST
|
|
filter := make([]string, 0)
|
|
|
|
os.Mkdir(ws.configFolder, PERMS_MKDIR)
|
|
fileinfo, err := os.Stat(ensureSlashSuffix(ws.configFolder) + filename)
|
|
|
|
if errors.Is(err, os.ErrNotExist) {
|
|
os.Create(ensureSlashSuffix(ws.configFolder) + filename)
|
|
fileinfo, err = os.Stat(ensureSlashSuffix(ws.configFolder) + filename)
|
|
}
|
|
|
|
if err != nil {
|
|
panic("Error opening " + filename + ": " + err.Error())
|
|
}
|
|
|
|
if fileinfo.Size() == 0 { // empty config
|
|
return filterMode, filter
|
|
}
|
|
|
|
filterContent, err := os.ReadFile(ensureSlashSuffix(ws.configFolder) + filename)
|
|
|
|
if ws.logger.logIfError(err) {
|
|
panic("Error reading " + filename + ": " + err.Error())
|
|
}
|
|
|
|
filterLines := strings.Split(string(filterContent), "\n")
|
|
readFilterMode := filterLines[0]
|
|
|
|
switch readFilterMode {
|
|
case "allow":
|
|
filterMode = FILTER_MODE_WHITELIST
|
|
case "deny":
|
|
filterMode = FILTER_MODE_BLACKLIST
|
|
default:
|
|
panic("invalid filter mode for " + filename + ": use allow|deny")
|
|
}
|
|
|
|
filter = filterLines[1:]
|
|
|
|
return filterMode, filter
|
|
|
|
}
|
|
|
|
func (ws *WebServer) isIpValid(ip string) bool {
|
|
ip = strings.Split(ip, ":")[0] // remove port
|
|
switch ws.ipFilterMode {
|
|
case FILTER_MODE_WHITELIST:
|
|
return slices.Contains(ws.ipFilter, ip)
|
|
case FILTER_MODE_BLACKLIST:
|
|
return !slices.Contains(ws.ipFilter, ip)
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (ws *WebServer) isUserAgentValid(userAgent string) bool {
|
|
switch ws.userAgentFilterMode {
|
|
case FILTER_MODE_WHITELIST:
|
|
return slices.Contains(ws.userAgentFilter, userAgent)
|
|
case FILTER_MODE_BLACKLIST:
|
|
return !slices.Contains(ws.userAgentFilter, userAgent)
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func (ws *WebServer) fetchFileContents(filepath string) ([]byte, error) {
|
|
if filepath == "/" {
|
|
filepath = "."
|
|
} else {
|
|
filepath_relative, _ := strings.CutPrefix(filepath, "/")
|
|
filepath = filepath_relative
|
|
}
|
|
fileinfo, err := os.Stat(filepath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if fileinfo.IsDir() {
|
|
filepath += "/index.html"
|
|
}
|
|
return os.ReadFile(filepath)
|
|
|
|
}
|
|
|
|
func (ws *WebServer) get(writer http.ResponseWriter, req *http.Request) {
|
|
|
|
respStatusCode := http.StatusOK
|
|
|
|
if !ws.isIpValid(req.RemoteAddr) || !ws.isUserAgentValid(req.UserAgent()) {
|
|
writer.WriteHeader(http.StatusForbidden)
|
|
return
|
|
}
|
|
|
|
fetchedData, fetchErr := ws.fetchFileContents(req.URL.Path)
|
|
|
|
sentBytes := 0
|
|
|
|
if ws.logger.logIfError(fetchErr) {
|
|
respStatusCode = http.StatusNotFound
|
|
writer.WriteHeader(respStatusCode)
|
|
} else {
|
|
sentBytesCount, _ := writer.Write(fetchedData)
|
|
sentBytes = sentBytesCount
|
|
}
|
|
|
|
ws.logger.logAccess(
|
|
strings.Split(req.RemoteAddr, ":")[0], //remote address
|
|
"-", //identifier (can't get)
|
|
getOrDash(req.URL.User.Username()), //username
|
|
time.Now().Format("02/Jan/2006:15:04:05 -0700"), //timestamp
|
|
req.Method+" "+req.URL.Path+" "+getHttpVersionString(req.ProtoMajor, req.ProtoMinor), //HTTP version
|
|
strconv.Itoa(respStatusCode), //response code
|
|
strconv.Itoa(sentBytes), //# of sent bytes
|
|
req.Referer(), //Referer
|
|
req.UserAgent(), //User Agent
|
|
)
|
|
}
|