From 830f43f467ad4f9dc0aef9335bb2756e57dfed4c Mon Sep 17 00:00:00 2001 From: Mitchell Riedstra Date: Tue, 27 Jul 2021 00:16:31 -0400 Subject: Initial --- main.go | 235 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 235 insertions(+) create mode 100644 main.go (limited to 'main.go') diff --git a/main.go b/main.go new file mode 100644 index 0000000..ee53f7e --- /dev/null +++ b/main.go @@ -0,0 +1,235 @@ +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) + } +} -- cgit v1.2.3