package main import ( "fmt" "html/template" "log" "net/http" "os" "slices" "regexp" "database/sql" _ "modernc.org/sqlite" ) type Entry struct { os string name string section int path string } type Database struct { DB *sql.DB } func (d *Database) GetOS(w http.ResponseWriter, r *http.Request) { rows, err := d.DB.Query("SELECT DISTINCT os FROM manpages;") if err != nil { log.Println(err) ErrorPage(w, 500) return } defer rows.Close() for rows.Next() { var osName string if err := rows.Scan(&osName); err != nil { log.Println(err) ErrorPage(w, 500) return } fmt.Fprintf(w, "", osName, osName) } } func (d *Database) Search(w http.ResponseWriter, r *http.Request) { query := r.URL.Query() log.Println("Request for:", r.URL.String()) name := query["name"][0] section := query["section"][0] osName := query["os"][0] log.Println(name) rows, err := d.DB.Query("SELECT * FROM manpages WHERE name == ? AND section == ? AND os == ?;", name, section, osName) if err != nil { log.Println(err) ErrorPage(w, 500) return } defer rows.Close() var entries []Entry for rows.Next() { var entry Entry if err := rows.Scan(&entry.os, &entry.name, &entry.section, &entry.path); err != nil { log.Println(err) ErrorPage(w, 500) return } entries = append(entries, entry) } if len(entries) > 0 { fmt.Fprintf(w, "
") for _, e := range entries { data, err := os.ReadFile(e.path) if err != nil { log.Println(err) ErrorPage(w, 500) return } fmt.Fprintf(w, "%s", data) } fmt.Fprintf(w, "
") } else { fmt.Fprintf(w, "

No results found for %s %s(%s)

", osName, name, section) } } type AllowedFiles struct { allowedFiles []string } func (a *AllowedFiles) GetFiles(w http.ResponseWriter, r *http.Request) { log.Println("Request for:", r.URL.String()) url := fmt.Sprintf("static%s", r.URL.String()) htmlRegex, err := regexp.Compile("^static/.*\\.html$") if err != nil { ErrorPage(w, 500) return } if url == "static/" { url = "static/index.html" } if !slices.Contains(a.allowedFiles, url) { ErrorPage(w, 404) return } if htmlRegex.MatchString(url) { t := template.Must(template.ParseFiles("templates/template.html")) html, err := os.ReadFile(url) if err != nil { ErrorPage(w, 500) return } t.Execute(w, template.HTML(html)) } else { http.ServeFile(w, r, url) } } func ErrorPage(w http.ResponseWriter, code int) { log.Println(code, http.StatusText(code)) t := template.Must(template.ParseFiles("templates/error.html")) t.Execute(w, struct { Code int Message string }{ Code: code, Message: http.StatusText(code), }) } func Usage() { fmt.Fprintf(os.Stderr, "%s [--port port]\n", os.Args[0]) } func main() { port := ":8000" for i := 1; i < len(os.Args); i++ { switch os.Args[i] { case "--port": if i+1 >= len(os.Args) { Usage() } else { port = ":" + os.Args[i+1] i++ } default: log.Println("Unknown option: ", os.Args[i]) Usage() } } db, err := sql.Open("sqlite", "man.db") if err != nil { log.Fatal(err) } if err := db.Ping(); err != nil { log.Fatal(err) } dbWrapper := &Database{ DB: db } allowed := &AllowedFiles { allowedFiles: []string{ "static/htmx.min.js", "static/index.html", "static/css/main.css", }, } http.HandleFunc("/search", dbWrapper.Search) http.HandleFunc("/os", dbWrapper.GetOS) http.HandleFunc("/", allowed.GetFiles) log.Fatal(http.ListenAndServe(port, nil)) err = db.Close() if err != nil { log.Fatal(err) } }