diff options
| author | Mitchell Riedstra <mitch@riedstra.dev> | 2021-07-27 00:16:31 -0400 |
|---|---|---|
| committer | Mitchell Riedstra <mitch@riedstra.dev> | 2021-07-27 00:16:31 -0400 |
| commit | 830f43f467ad4f9dc0aef9335bb2756e57dfed4c (patch) | |
| tree | 67bcb19b398eb94df467fb125c20dca368f4a043 | |
| download | paste-830f43f467ad4f9dc0aef9335bb2756e57dfed4c.tar.gz paste-830f43f467ad4f9dc0aef9335bb2756e57dfed4c.tar.xz | |
Initial
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | error.tpl | 22 | ||||
| -rw-r--r-- | go.mod | 5 | ||||
| -rw-r--r-- | go.sum | 2 | ||||
| -rw-r--r-- | index.tpl | 32 | ||||
| -rw-r--r-- | main.go | 235 | ||||
| -rw-r--r-- | readme.md | 18 | ||||
| -rw-r--r-- | style.css | 71 | ||||
| -rw-r--r-- | view.tpl | 21 |
9 files changed, 407 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..cc10a06 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +bpaste diff --git a/error.tpl b/error.tpl new file mode 100644 index 0000000..c53fa4f --- /dev/null +++ b/error.tpl @@ -0,0 +1,22 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <link rel="stylesheet" href="/style.css" defer> + <title>Error {{.Short}}</title> +</head> +<body> +<nav> + <a href="/">Home</a> + <div style="display: block; float: right;"> + </div> +</nav> + +<h1>{{.Short}}</h1> + +{{.Long}} + +</body> +</html> + + @@ -0,0 +1,5 @@ +module riedstra.dev/mitch/bpaste + +go 1.16 + +require github.com/gorilla/mux v1.8.0 // indirect @@ -0,0 +1,2 @@ +github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= diff --git a/index.tpl b/index.tpl new file mode 100644 index 0000000..a117af9 --- /dev/null +++ b/index.tpl @@ -0,0 +1,32 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <link rel="stylesheet" href="/style.css" defer> + <title>Brutally Simple Pastebin</title> +</head> +<body> +<nav> + <a href="/">Home</a> + <div style="display: block; float: right;"> + </div> +</nav> + +<container> + <h1>Brutally simple pastebin</h1> + + <form action="/new" method="POST" id="postform"> + <label for="title">Title:</label> + <input id="title" name="title" type="text" /> + + <label for="content">Paste Content:</label> + + <input type="submit" value="Post"> + </form> + + <textarea id="content" name="content" cols="80" rows="24" form="postform"></textarea> +</container> + + +</body> +</html> @@ -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) + } +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..5b6f128 --- /dev/null +++ b/readme.md @@ -0,0 +1,18 @@ +# Brutally simple pastebin + +Decided to challenge myself to write a really simple pastebin in go, utilizing +almost nothing but the standard library in less than an hour. + +This is the result of about an hour and a half of work. + +It will create a binary with embedded templates. Simply point it at the +directory in which you'd like to store your pastes. + +Each paste is a file, a random id assigned at creation. There's no way to edit +or change a paste. The only way to delete it is to remove it from the +filesystem. + +There's no indexing function or search either. + +There's also absolutely no protection against people spamming the API until +your disk is full, no authentication either. diff --git a/style.css b/style.css new file mode 100644 index 0000000..472b18c --- /dev/null +++ b/style.css @@ -0,0 +1,71 @@ +nav { + border-bottom: 3px solid #000; + padding-bottom: 10px; +} +a { + text-decoration: none; + color: #268bd2; +} +a:visited { + color: #d22653; +} +a:hover { + text-decoration: underline; +} + +code { + color: #9672d5; + /* background-color: #ff000020; */ + /* padding: 2px; */ + font-size: .8em; + font-family: "Roboto Mono", "Monaco", "Lucida Console", "DejaVu Sans Mono", "monospace"; +} + +pre code { + color: #000; + background-color: #FFFFEA; + display: block; + padding: 10px; + border: 1px solid; + line-height: 1.1; + overflow: auto; +} + + +blockquote { + border-left: 4px solid #aaa; + padding-left: 1em; +} + +/* + * The following was shamelessly ripped from: + * http://bettermotherfuckingwebsite.com/ + * And subsequently modified to suit my needs + */ + +body { + margin: 40px auto; + max-width: 80%; + line-height: 1.6; + font-size: 1em; + color: #444; + padding: 0 10px; + /* Added because some browsers don't default to white */ + background-color: #fff; +} + +img { + width: 100%; + height: auto; +} + +h1,h2,h3 { + line-height: 1.2 +} + +@media screen and (min-width: 960px) { + body { + max-width: 768px; + } +} + diff --git a/view.tpl b/view.tpl new file mode 100644 index 0000000..84c5ebf --- /dev/null +++ b/view.tpl @@ -0,0 +1,21 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <link rel="stylesheet" href="/style.css" defer> + <title>Paste: {{.Title}}</title> +</head> +<body> +<nav> + <a href="/">Home</a> + <div style="display: block; float: right;"> + </div> +</nav> + +<h1>Paste: "{{.Title}}"</h1> + +<pre><code>{{.GetContent}}</pre></code> + +</body> +</html> + |
