aboutsummaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'main.go')
-rw-r--r--main.go235
1 files changed, 235 insertions, 0 deletions
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)
+ }
+}