divided in different files

This commit is contained in:
uan
2025-08-03 15:04:16 +02:00
parent 5d7f13e04e
commit 1f6742762c
4 changed files with 265 additions and 232 deletions

62
logger.go Normal file
View File

@@ -0,0 +1,62 @@
package main
import (
"fmt"
"log"
"os"
)
const (
FLAGS_LOG_OPEN int = os.O_APPEND | os.O_WRONLY | os.O_CREATE
FLAGS_CONFIG_OPEN int = os.O_RDONLY | os.O_CREATE
PERMS_LOG_OPEN os.FileMode = os.ModeType | os.ModePerm
PERMS_CONFIG_OPEN os.FileMode = os.ModeType | os.ModePerm
PERMS_MKDIR os.FileMode = os.ModeDir | os.ModePerm
)
type Logger struct {
logFolder string
}
func NewLogger(logFolder_ string) *Logger {
return &Logger{
logFolder: logFolder_,
}
}
// returns error != nil
func (l *Logger) logIfError(err error) bool {
if err != nil {
l.logError(err.Error())
return true
}
return false
}
func (l *Logger) logAccess(
remoteAddr, identifier, authuser, timestamp, request,
status, bytesSent, referer, user_agent string,
) {
out := fmt.Sprintf("%v %v %v [%v] \"%v\" %v %v \"%v\" \"%v\"\n",
remoteAddr, identifier, authuser, timestamp, request, status, bytesSent, referer, user_agent,
)
os.Mkdir(l.logFolder, os.ModeDir|os.ModePerm)
file, err := os.OpenFile(ensureSlashSuffix(l.logFolder)+FILENAME_ACCESSLOG, FLAGS_LOG_OPEN, PERMS_LOG_OPEN)
if err != nil {
log.Println("couldn't open log access file at", ensureSlashSuffix(l.logFolder)+FILENAME_ACCESSLOG)
}
defer file.Close()
file.WriteString(out)
}
func (l *Logger) logError(str string) {
os.Mkdir(l.logFolder, PERMS_MKDIR)
file, err := os.OpenFile(ensureSlashSuffix(l.logFolder)+FILENAME_ERRORLOG, FLAGS_LOG_OPEN, PERMS_LOG_OPEN)
if err != nil {
log.Println("couldn't open log error file at", ensureSlashSuffix(l.logFolder)+FILENAME_ERRORLOG)
}
defer file.Close()
file.WriteString(str + "\n")
}

242
main.go
View File

@@ -1,48 +1,26 @@
package main
import (
"errors"
"fmt"
"log"
"net/http"
"os"
"slices"
"strconv"
"strings"
"time"
"github.com/akamensky/argparse"
)
const (
PATH_ACCESSLOG string = "access.log"
PATH_ERRORLOG string = "error.log"
PATH_IPFILTER string = "ipfilter.conf"
PATH_USERAGENTFILTER string = "useragentfilter.conf"
FLAGS_LOG_OPEN int = os.O_APPEND | os.O_WRONLY | os.O_CREATE
FLAGS_CONFIG_OPEN int = os.O_RDONLY | os.O_CREATE
PERMS_LOG_OPEN os.FileMode = os.ModeType | os.ModePerm
PERMS_CONFIG_OPEN os.FileMode = os.ModeType | os.ModePerm
PERMS_MKDIR os.FileMode = os.ModeDir | os.ModePerm
FILTER_MODE_WHITELIST = 0
FILTER_MODE_BLACKLIST = 1
)
var (
logFolder string = ""
configFolder string = ""
ipFilter []string = make([]string, 0)
ipFilterMode = FILTER_MODE_WHITELIST
userAgentFilter []string = make([]string, 0)
userAgentFilterMode = FILTER_MODE_WHITELIST
FILENAME_ACCESSLOG string = "access.log"
FILENAME_ERRORLOG string = "error.log"
FILENAME_IPFILTER string = "ipfilter.conf"
FILENAME_USERAGENTFILTER string = "useragentfilter.conf"
)
func main() {
parser := argparse.NewParser("miniws", "")
port := parser.String("p", "port", &argparse.Options{Default: "8040"})
_logFolder := parser.String("l", "logs-folder", &argparse.Options{Default: "logs"})
_configFolder := parser.String("c", "config-folder", &argparse.Options{Default: "config"})
port := parser.Int("p", "port", &argparse.Options{Default: 8040})
logFolder := parser.String("l", "logs-folder", &argparse.Options{Default: "logs"})
configFolder := parser.String("c", "config-folder", &argparse.Options{Default: "config"})
err := parser.Parse(os.Args)
if err != nil {
@@ -52,207 +30,7 @@ func main() {
return
}
logFolder = *_logFolder
configFolder = *_configFolder
parseIpFilter()
parseUserAgentFilter()
http.HandleFunc("/", get)
log.Println("Server started on port " + *port)
http.ListenAndServe(":"+*port, nil)
}
func get(writer http.ResponseWriter, req *http.Request) {
respStatusCode := http.StatusOK
if !isIpValid(req.RemoteAddr) || !isUserAgentValid(req.UserAgent()) {
writer.WriteHeader(http.StatusForbidden)
return
}
fetchedData, fetchErr := fetchFileContents(req.URL.Path)
sentBytes := 0
if logIfError(fetchErr) {
respStatusCode = http.StatusNotFound
writer.WriteHeader(respStatusCode)
} else {
sentBytesCount, _ := writer.Write(fetchedData)
sentBytes = sentBytesCount
}
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
)
}
func 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)
webserver := NewWebServer(*port, *logFolder, *configFolder)
webserver.Run()
}
// returns error != nil
func logIfError(err error) bool {
if err != nil {
logError(err.Error())
return true
}
return false
}
func logAccess(
remoteAddr, identifier, authuser, timestamp, request,
status, bytesSent, referer, user_agent string,
) {
out := fmt.Sprintf("%v %v %v [%v] \"%v\" %v %v \"%v\" \"%v\"\n",
remoteAddr, identifier, authuser, timestamp, request, status, bytesSent, referer, user_agent,
)
os.Mkdir(logFolder, os.ModeDir|os.ModePerm)
file, err := os.OpenFile(ensureSlashSuffix(logFolder)+PATH_ACCESSLOG, FLAGS_LOG_OPEN, PERMS_LOG_OPEN)
if err != nil {
log.Println("couldn't open log access file at", ensureSlashSuffix(logFolder)+PATH_ACCESSLOG)
}
defer file.Close()
file.WriteString(out)
}
func logError(str string) {
os.Mkdir(logFolder, PERMS_MKDIR)
file, err := os.OpenFile(ensureSlashSuffix(logFolder)+PATH_ERRORLOG, FLAGS_LOG_OPEN, PERMS_LOG_OPEN)
if err != nil {
log.Println("couldn't open log error file at", ensureSlashSuffix(logFolder)+PATH_ERRORLOG)
}
defer file.Close()
file.WriteString(str + "\n")
}
func getHttpVersionString(major, minor int) string {
return "HTTP/" + strconv.Itoa(major) + "." + strconv.Itoa(minor)
}
func getOrDash(str string) string {
if str == "" {
return "-"
}
return str
}
func ensureSlashSuffix(str string) string {
return strings.TrimSuffix(str, "/") + "/"
}
func isIpValid(ip string) bool {
ip = strings.Split(ip, ":")[0] // remove port
switch ipFilterMode {
case FILTER_MODE_WHITELIST:
return slices.Contains(ipFilter, ip)
case FILTER_MODE_BLACKLIST:
return !slices.Contains(ipFilter, ip)
default:
return false
}
}
func isUserAgentValid(userAgent string) bool {
switch userAgentFilterMode {
case FILTER_MODE_WHITELIST:
return slices.Contains(userAgentFilter, userAgent)
case FILTER_MODE_BLACKLIST:
return !slices.Contains(userAgentFilter, userAgent)
default:
return false
}
}
func parseIpFilter() {
os.Mkdir(configFolder, PERMS_MKDIR)
fileinfo, err := os.Stat(ensureSlashSuffix(configFolder) + PATH_IPFILTER)
if errors.Is(err, os.ErrNotExist) {
os.Create(ensureSlashSuffix(configFolder) + PATH_IPFILTER)
}
if fileinfo.Size() == 0 { // empty config
ipFilterMode = FILTER_MODE_BLACKLIST
ipFilter = make([]string, 0)
return
}
ipFilterContent, err := os.ReadFile(ensureSlashSuffix(configFolder) + PATH_IPFILTER)
if logIfError(err) {
return
}
ipFilterLines := strings.Split(string(ipFilterContent), "\n")
_ipFilterMode := ipFilterLines[0]
switch _ipFilterMode {
case "allow":
ipFilterMode = FILTER_MODE_WHITELIST
case "deny":
ipFilterMode = FILTER_MODE_BLACKLIST
default:
logError("invalid ip filter mode, use allow|deny")
}
ipFilter = ipFilterLines[1:]
}
func parseUserAgentFilter() {
os.Mkdir(configFolder, PERMS_MKDIR)
fileinfo, err := os.Stat(ensureSlashSuffix(configFolder) + PATH_USERAGENTFILTER)
if errors.Is(err, os.ErrNotExist) {
os.Create(ensureSlashSuffix(configFolder) + PATH_USERAGENTFILTER)
}
if fileinfo.Size() == 0 { // empty config
userAgentFilterMode = FILTER_MODE_BLACKLIST
userAgentFilter = make([]string, 0)
return
}
userAgentFilterContent, err := os.ReadFile(ensureSlashSuffix(configFolder) + PATH_USERAGENTFILTER)
if logIfError(err) {
return
}
userAgentFilterLines := strings.Split(string(userAgentFilterContent), "\n")
_userAgentFilterMode := userAgentFilterLines[0]
println(len(userAgentFilterLines))
switch _userAgentFilterMode {
case "allow":
userAgentFilterMode = FILTER_MODE_WHITELIST
case "deny":
userAgentFilterMode = FILTER_MODE_BLACKLIST
default:
logError("invalid userAgent filter mode, use allow|deny")
}
userAgentFilter = userAgentFilterLines[1:]
}

21
utils.go Normal file
View File

@@ -0,0 +1,21 @@
package main
import (
"strconv"
"strings"
)
func getHttpVersionString(major, minor int) string {
return "HTTP/" + strconv.Itoa(major) + "." + strconv.Itoa(minor)
}
func getOrDash(str string) string {
if str == "" {
return "-"
}
return str
}
func ensureSlashSuffix(str string) string {
return strings.TrimSuffix(str, "/") + "/"
}

172
webserver.go Normal file
View File

@@ -0,0 +1,172 @@
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
)
}