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 ) }