package main import ( "embed" "encoding/gob" "encoding/hex" "encoding/json" "errors" "flag" "fmt" "html/template" "io" "net/http" "net/url" "os" "strings" _ "embed" "git.milar.in/milarin/slices" "github.com/gorilla/sessions" ) var ( intf = flag.String("i", "", "interface") port = flag.Uint("p", 80, "port") ) var ( //go:embed templates/* TemplateFS embed.FS //go:embed static/* StaticFS embed.FS Templates *template.Template SessionStore = sessions.NewCookieStore(Must(hex.DecodeString(os.Getenv("SESSION_KEY")))) ) func main() { gob.Register([]Bookmark{}) gob.Register(&Settings{}) flag.Parse() if tmpl, err := template.New("homepage").ParseFS(TemplateFS, "templates/*"); err == nil { Templates = tmpl } else { panic(err) } http.HandleFunc("/", handler) http.HandleFunc("/customize", customize) http.HandleFunc("/save-changes", saveChanges) http.HandleFunc("/search", search) http.HandleFunc("/style.css", ProvideFile("static/style.css", "text/css")) http.HandleFunc("/customize.js", ProvideFile("static/customize.js", "application/javascript")) http.HandleFunc("/icons.ttf", ProvideFile("static/icons.ttf", "font/ttf")) http.HandleFunc("/transmission.png", ProvideFile("static/transmission.png", "image/png")) http.HandleFunc("/pihole.svg", ProvideFile("static/pihole.svg", "image/svg+xml")) http.HandleFunc("/gitea.png", ProvideFile("static/gitea.png", "image/png")) if err := http.ListenAndServe(fmt.Sprintf("%s:%d", *intf, *port), nil); err != nil { panic(err) } } func handler(w http.ResponseWriter, r *http.Request) { session, _ := SessionStore.Get(r, "settings") data := &TmplData{ text: GetText(r), Bookmarks: GetValueDefault(session, "bookmarks", DefaultBookmarks()), Settings: GetValueDefault(session, "settings", DefaultSettings()), } if err := Templates.ExecuteTemplate(w, "index.html", data); err != nil { panic(err) } } func ProvideFile(path, contentType string) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { file, err := StaticFS.Open(path) if err != nil { fmt.Println(err) w.WriteHeader(http.StatusInternalServerError) return } defer file.Close() w.Header().Add("Content-Type", contentType) io.Copy(w, file) } } func search(w http.ResponseWriter, r *http.Request) { session, _ := SessionStore.Get(r, "settings") settings := GetValueDefault(session, "settings", DefaultSettings()) if err := r.ParseForm(); err != nil { fmt.Println(err) w.WriteHeader(http.StatusInternalServerError) return } query := r.Form.Get("query") if uri, err := ParseURI(query); err == nil { // url w.Header().Add("Location", uri.String()) w.WriteHeader(http.StatusMovedPermanently) } else { // search string w.Header().Add("Location", fmt.Sprintf(settings.Search, query)) w.WriteHeader(http.StatusMovedPermanently) } } func customize(w http.ResponseWriter, r *http.Request) { session, _ := SessionStore.Get(r, "settings") text := GetText(r) bookmarks := slices.Map(GetValueDefault(session, "bookmarks", DefaultBookmarks()), func(b Bookmark) BookmarkData { return BookmarkData{ Bookmark: &b, text: text, } }) data := &CustomizeData{ text: text, Bookmarks: bookmarks, Settings: GetValueDefault(session, "settings", DefaultSettings()), } if err := Templates.ExecuteTemplate(w, "customize.html", data); err != nil { panic(err) } } func saveChanges(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { w.WriteHeader(http.StatusMethodNotAllowed) return } session, _ := SessionStore.Get(r, "settings") sessionData := &SessionData{} err := json.NewDecoder(r.Body).Decode(&sessionData) if err != nil { w.WriteHeader(http.StatusInternalServerError) fmt.Println(err) return } Reorder(sessionData.Bookmarks) session.Values["bookmarks"] = sessionData.Bookmarks session.Values["settings"] = sessionData.Settings if err := session.Save(r, w); err != nil { fmt.Println(err) w.WriteHeader(http.StatusInternalServerError) return } } func ParseURI(uri string) (*url.URL, error) { if !strings.HasPrefix(uri, "http://") && !strings.HasPrefix(uri, "https://") { uri = "https://" + uri } ret, err := url.ParseRequestURI(uri) if err != nil { return nil, err } splits := strings.Split(ret.Hostname(), ".") if len(splits) <= 1 { return nil, errors.New("hostname doesn't have a TLD") } if _, ok := TLDs[splits[len(splits)-1]]; !ok { return nil, errors.New("invalid top level domain") } return ret, nil }