sophuwu.site > myweb   
              131
            
             package main

import (
	"context"
	"errors"
	"fmt"
	"golang.org/x/crypto/bcrypt"
	"golang.org/x/sys/unix"
	"log"
	"net/http"
	"os"
	"os/signal"
	"sophuwu.site/myweb/config"
	"sophuwu.site/myweb/template"
	"strings"
)

// CheckHttpErr will check if err is not nil. It will then handle the HTTP
// response and return true if an error occurred.
func CheckHttpErr(err error, w http.ResponseWriter, r *http.Request, code int) bool {
	if err != nil {
		HttpErr(w, r, code)
		log.Printf("err: %v: HTTP %d: %s %s\n", err, code, r.Method, r.URL.Path)
		return true
	}
	return false
}

// HttpErrs is a map of HTTP error codes to error messages.
var HttpErrs = map[int]string{
	400: "Bad request: the server could not understand your request. Please check the URL and try again.",
	401: "Unauthorized: You must log in to access this page.",
	403: "Forbidden: You do not have permission to access this page.",
	404: "Not found: the requested page does not exist. Please check the URL and try again.",
	405: "Method not allowed: the requested method is not allowed on this page.",
	500: "Internal server error: the server encountered an error while processing your request. Please try again later.",
}

// HttpErr will write an HTTP error response with the given status code.
func HttpErr(w http.ResponseWriter, r *http.Request, code int) {
	w.WriteHeader(code)
	var ErrTxt string
	if t, ok := HttpErrs[code]; ok {
		ErrTxt = t
	} else {
		ErrTxt = "An error occurred. Please try again later."
	}
	data := template.Data("An error occurred", fmt.Sprintf("%d: %s", code, ErrTxt))
	data.Set("ErrText", ErrTxt)
	data.Set("ErrCode", code)
	err := template.Use(w, r, "err", data)
	if err != nil {
		log.Printf("error writing error page: %v", err)
	}
}

// HttpIndex is the handler for the index page.
func HttpIndex(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path != "/" {
		HttpErr(w, r, 404)
		return
	}
	d, err := GetPageData("index")
	if CheckHttpErr(err, w, r, 500) {
		AddRequiredData()
		return
	}
	d.Set("Image", strings.TrimSuffix(config.URL, "/")+d["ImagePath"].(string))
	err = template.Use(w, r, "index", d)
	_ = CheckHttpErr(err, w, r, 500)
}

// Profile is a struct that holds information about profiles on
// social media or other external websites.
// Icon is used for a rune to display from the Sophuwu iconfont.
type Profile struct {
	Icon    string
	Website string
	URL     string
	User    string
}

// Authenticate is a middleware that checks for basic authentication.
// Passwords are hashed with bcrypt, stored in the userpass file in the
// webhome directory. The file only contains one line, the bcrypt hash.
// The hash is generated hashing the string "user:password" with bcrypt.
func Authenticate(next http.HandlerFunc) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		user, apass, authOK := r.BasicAuth()
		if !authOK || bcrypt.CompareHashAndPassword(config.PassHash().Bytes(), []byte(user+":"+apass)) != nil {
			w.Header().Set("WWW-Authenticate", `Basic realm="Restricted"`)
			http.Error(w, "Unauthorized.", http.StatusUnauthorized)
			return
		}
		next.ServeHTTP(w, r)
	})
}

// main is the entry point for the web server.
func main() {
	OpenDB()
	err := template.Init(config.Templates)
	if err != nil {
		log.Fatalf("Error initializing templates: %v", err)
	}

	http.HandleFunc("/", HttpIndex)
	http.HandleFunc("/blog/", BlogHandler)
	http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir(config.StaticPath))))
	http.HandleFunc("/media/", MediaHandler)
	http.HandleFunc("/animations/", AnimHandler)
	http.Handle("/manage/", Authenticate(ManagerHandler))

	server := http.Server{Addr: config.ListenAddr, Handler: nil}
	go func() {
		err = server.ListenAndServe()
		if err != nil && !errors.Is(err, http.ErrServerClosed) {
			log.Fatalf("Error starting server: %v", err)
		}
	}()
	sigchan := make(chan os.Signal)
	signal.Notify(sigchan, unix.SIGINT, unix.SIGTERM, unix.SIGQUIT, unix.SIGKILL, unix.SIGSTOP)
	s := <-sigchan
	println("stopping: got signal", s.String())
	err = server.Shutdown(context.Background())
	if err != nil {
		log.Println("Error stopping server: %v", err)
	}
	CloseDB()
	println("stopped")
}