package main import ( "compress/gzip" "crypto/rand" _ "embed" "encoding/base64" "encoding/gob" "errors" "flag" "html/template" "log" "net/http" "os" "time" "github.com/gorilla/mux" ) var logger = log.New(os.Stderr, "", 0) // const ID_BYTES = 5 const ID_BYTES = 16 //go:embed index.tpl var indexTemplateContent string var indexTemplate = template.Must(template.New("index").Parse(indexTemplateContent)) //go:embed view.tpl var viewTemplateContent string var viewTemplate = template.Must(template.New("view").Parse(viewTemplateContent)) //go:embed error.tpl var errorTemplateContent string var errorTemplate = template.Must(template.New("error").Parse(errorTemplateContent)) //go:embed style.css var stylesheetContent []byte type Paste struct { Id string Title string Content []byte } func (p Paste) GetContent() string { return string(p.Content) } func (p *Paste) Load() error { fh, err := os.Open(p.Id) if err != nil { return err } zrdr, err := gzip.NewReader(fh) if err != nil { return err } dec := gob.NewDecoder(zrdr) err = dec.Decode(&p) if err != nil { return err } zrdr.Close() return fh.Close() } func (p Paste) Save() error { fh, err := os.OpenFile(p.Id, os.O_CREATE|os.O_RDWR, 0666) if err != nil { return err } zwr := gzip.NewWriter(fh) enc := gob.NewEncoder(zwr) err = enc.Encode(&p) if err != nil { return err } zwr.Close() return fh.Close() } func GenId() string { r := make([]byte, ID_BYTES) _, err := rand.Read(r) if err != nil { logger.Fatal(err) } return base64.RawURLEncoding.EncodeToString(r) } func main() { fl := flag.NewFlagSet("Brutally simple pastebin", flag.ExitOnError) listen := fl.String("listen", ":6130", "Address to bind to, LISTEN_ADDR environment variable overrides") debug := fl.Bool("d", false, "debugging add information to the logging output DEBUG=true|false controls this as well") storage := fl.String("s", "", "Directory to serve, must be supplied via flag or STORAGE_DIR environment variable") _ = fl.Parse(os.Args[1:]) if addr := os.Getenv("LISTEN_ADDR"); addr != "" { *listen = addr } if d := os.Getenv("DEBUG"); d == "true" || *debug { logger.SetFlags(log.LstdFlags | log.Llongfile) } if d := os.Getenv("STORAGE_DIR"); d != "" { *storage = d } if *storage == "" { logger.Fatal("Cannot continue without storage directory, set `-s` flag or STORAGE_DIR environment variable") } err := os.Chdir(*storage) if err != nil { logger.Fatal(err) } mux := mux.NewRouter() mux.HandleFunc("/new", newPaste) mux.HandleFunc("/view/{id}", loadPaste) mux.HandleFunc("/style.css", stylesheet) mux.HandleFunc("/", index) logger.Println("listening on: ", *listen) srv := &http.Server{ Handler: mux, Addr: *listen, WriteTimeout: 15 * time.Second, ReadTimeout: 15 * time.Second, } logger.Fatal(srv.ListenAndServe()) } func newPaste(w http.ResponseWriter, r *http.Request) { err := r.ParseForm() if err != nil { logger.Println(err) http.Error(w, "Internal server error", http.StatusInternalServerError) return } title := r.FormValue("title") content := r.FormValue("content") if title == "" || content == "" { logger.Println("Empty title or content") http.Error(w, "Internal server error", http.StatusInternalServerError) return } p := &Paste{ Id: GenId(), Title: title, Content: []byte(content), } err = p.Save() if err != nil { logger.Println(err) http.Error(w, "Internal server error", http.StatusInternalServerError) return } http.Redirect(w, r, "/view/"+p.Id, http.StatusFound) } func loadPaste(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id, ok := vars["id"] if !ok { w.WriteHeader(http.StatusBadRequest) err := errorTemplate.Execute(w, map[string]string{ "Short": "ID Not found", "Long": "There was no ID supplied", }) if err != nil { logger.Println(err) } return } p := &Paste{Id: id} err := p.Load() if err != nil { logger.Println(err) if errors.Is(err, os.ErrNotExist) { w.WriteHeader(http.StatusNotFound) err = errorTemplate.Execute(w, map[string]string{ "Short": "ID Not found", "Long": "ID: " + p.Id + "Was not found", }) } else { w.WriteHeader(http.StatusInternalServerError) err = errorTemplate.Execute(w, map[string]string{ "Short": "ID Not found", "Long": "There was an issue reading ID: " + p.Id, }) } if err != nil { logger.Println(err) } return } err = viewTemplate.Execute(w, p) if err != nil { logger.Println(err) } } func stylesheet(w http.ResponseWriter, r *http.Request) { w.Header().Add("Content-type", "text/css") _, _ = w.Write(stylesheetContent) } func index(w http.ResponseWriter, r *http.Request) { err := indexTemplate.Execute(w, nil) if err != nil { logger.Println(err) http.Error(w, "Internal server error", http.StatusInternalServerError) } }