aboutsummaryrefslogtreecommitdiff
path: root/main.go
diff options
context:
space:
mode:
Diffstat (limited to 'main.go')
-rw-r--r--main.go773
1 files changed, 292 insertions, 481 deletions
diff --git a/main.go b/main.go
index df0711a..c3f5613 100644
--- a/main.go
+++ b/main.go
@@ -1,649 +1,460 @@
package main
import (
- "crypto/ed25519"
+ "bytes"
"crypto/rand"
"embed"
+ _ "embed"
"encoding/base64"
"encoding/json"
- "encoding/pem"
"errors"
"flag"
"fmt"
- "html/template"
"io"
+ "io/fs"
"log"
"net/http"
"os"
"path/filepath"
+ "strconv"
"strings"
"time"
- "github.com/golang-jwt/jwt/v4"
- "github.com/gorilla/mux"
"golang.org/x/crypto/bcrypt"
- "gopkg.in/yaml.v3"
+ "golang.org/x/term"
)
-var Version = "Development"
+// Default to the system umask
+const OPEN_MODE = 0777
-var logger = log.New(os.Stderr, "", 0)
+var (
+ Version = "git or development"
+ logger = log.New(os.Stderr, "", 0)
+ ID_BYTES = 8
-var ID_BYTES = 8
-
-//go:embed templates/*
-var templateFS embed.FS
-
-var tpls = map[string]*template.Template{}
+ //go:embed static/*
+ staticEmbedded embed.FS
+)
-func init() {
- // List of templates that will be pre-rendered and toss into
- // the tpls map above
- tplList := []string{"index", "view", "error"}
- for _, tpl := range tplList {
- tpls[tpl] = template.Must(template.ParseFS(templateFS,
- "templates/"+tpl+".tpl",
- "templates/base.tpl",
- ))
- }
+type App struct {
+ static fs.FS
+ users map[string]string
+ storage string // path to where the paste files are actually stored
}
-//go:embed static/*
-var staticFS embed.FS
-
-type Paste struct {
- Id string
- Title string
- Tags map[string]struct{}
- Content string
+// EnvFlagString is a convent way to set value from environment variable,
+// and allow override when a command line flag is set. It's assumed `p` is
+// not nil.
+func EnvFlagString(fl *flag.FlagSet, p *string, name, envvar, usage string) {
+ if v := os.Getenv(envvar); v != "" {
+ *p = v
+ }
+ fl.StringVar(p, name, *p, fmt.Sprintf("%s (Environ: '%s')", usage, envvar))
}
-func (p Paste) GetContent() string {
- return string(p.Content)
+type errResp struct {
+ w http.ResponseWriter
+ Code int
+ Msg string
}
-func (p *Paste) Load() error {
- fh, err := os.Open(filepath.Join("p", p.Id))
- if err != nil {
- return err
- }
+func main() {
+ var (
+ listen = ":6130"
+ idBytes = "8"
+ debug = "false"
+ genhash = false
+ storage = ""
+ fsdir = ""
+ static fs.FS
+ err error
+ )
- dec := json.NewDecoder(fh)
- err = dec.Decode(&p)
+ static, err = fs.Sub(staticEmbedded, "static")
if err != nil {
- return err
- }
+ logger.Fatal("Embedding failed no static directory")
+ }
+
+ fl := flag.NewFlagSet("Simple pastebin", flag.ExitOnError)
+ EnvFlagString(fl, &listen, "listen", "LISTEN_ADDR",
+ "Address to bind to")
+ EnvFlagString(fl, &idBytes, "b", "ID_BYTES",
+ "How many bytes long should the IDs be")
+ EnvFlagString(fl, &debug, "d", "DEBUG",
+ "Additional logger flags for debugging")
+ EnvFlagString(fl, &storage, "s", "STORAGE_DIR",
+ "Path of directory to serve")
+ EnvFlagString(fl, &fsdir, "fs", "FS_DIR",
+ "Path which static assets are located, empty to use embedded")
+ fl.BoolVar(&genhash, "genhash", genhash,
+ "Interactively prompt for a password and spit out a hash\n")
+ version := fl.Bool("v", false, "Print version then exit")
- return fh.Close()
-}
+ _ = fl.Parse(os.Args[1:])
-func (p Paste) Save() error {
- fh, err := os.OpenFile(filepath.Join("p", p.Id), os.O_CREATE|os.O_RDWR, 0666)
- if err != nil {
- return err
+ if *version {
+ log.Println(Version)
+ os.Exit(0)
}
- enc := json.NewEncoder(fh)
- enc.SetIndent("", " ")
- err = enc.Encode(&p)
- if err != nil {
- return err
+ if genhash {
+ interactiveHashGen()
+ os.Exit(0)
}
- return fh.Close()
-}
+ if d, err := strconv.ParseBool(debug); err != nil && d {
+ logger.SetFlags(log.LstdFlags | log.Llongfile)
+ }
-func GenId() string {
- r := make([]byte, ID_BYTES)
- _, err := rand.Read(r)
- if err != nil {
- logger.Fatal(err)
+ if fsdir != "" {
+ static = os.DirFS(fsdir)
}
- return base64.RawURLEncoding.EncodeToString(r)
-}
+ if b, err := strconv.Atoi(idBytes); err != nil && b > 4 {
+ ID_BYTES = b
+ }
-type User struct {
- Username string `yaml:"Username"`
- Password string `yaml:"Password"`
- HashedPassword string `yaml:"HashedPassword"`
-}
+ if storage == "" {
+ logger.Fatal("Cannot continue without storage directory, set " +
+ "`-s` flag or STORAGE_DIR environment variable")
+ }
-// CheckPass returns true if passwords match
-func (u *User) CheckPass(pass string) bool {
- err := bcrypt.CompareHashAndPassword([]byte(u.HashedPassword), []byte(pass))
- if err != nil {
- return false
+ app := &App{
+ static: static,
+ storage: storage,
+ users: getUsersFromEnviron(),
}
- return true
-}
+ logger.Println("listening on: ", listen)
-type Conf struct {
- // Populated after read used for lookups
- Users map[string]*User `yaml:"Users"`
+ srv := &http.Server{
+ Handler: logRequests(app.Handler()),
+ Addr: listen,
+ WriteTimeout: 15 * time.Second,
+ ReadTimeout: 15 * time.Second,
+ }
+ logger.Fatal(srv.ListenAndServe())
}
-func hashPasswd(pass string) (string, error) {
- b, err := bcrypt.GenerateFromPassword([]byte(pass), bcrypt.DefaultCost)
- return string(b), err
+func logRequests(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ logger.Printf("%s %s %s \"%s\" \"%s\"\n",
+ r.RemoteAddr, r.Method, r.URL.Path, r.UserAgent(), r.Referer())
+ next.ServeHTTP(w, r)
+ })
}
-func writeSampleConf(fn string) (*os.File, error) {
- c := &Conf{
- Users: map[string]*User{
- "admin": &User{
- Password: "admin",
- },
- },
+func interactiveHashGen() {
+ fmt.Print("Enter password: ")
+ passwd, err := term.ReadPassword(0)
+ if err != nil {
+ logger.Fatal("\nFailed: ", err)
}
- fh, err := os.OpenFile(fn, os.O_CREATE|os.O_RDWR, 0600)
+ fmt.Printf("\nAgain: ")
+ passwd2, err := term.ReadPassword(0)
if err != nil {
- return nil, err
+ logger.Fatal("\nFailed: ", err)
}
- enc := yaml.NewEncoder(fh)
- enc.SetIndent(2)
-
- err = enc.Encode(c)
- if err != nil {
- return nil, err
+ fmt.Println("")
+ if !bytes.Equal(passwd, passwd2) {
+ logger.Fatal("Passwords do not match")
}
- err = fh.Close()
+ passwd, err = bcrypt.GenerateFromPassword(passwd, bcrypt.DefaultCost)
if err != nil {
- return nil, err
+ logger.Fatal("Failed: ", err)
}
- return os.Open(fn)
+ fmt.Printf("hash: %s\n", string(passwd))
}
-func readConf(fn string) (*Conf, error) {
- fh, err := os.Open(fn)
- if errors.Is(err, os.ErrNotExist) {
- fh, err = writeSampleConf(fn)
- if err != nil {
- return nil, err
- }
- } else if err != nil {
- return nil, err
- }
+func (a *App) Handler() http.Handler {
+ mux := http.NewServeMux()
- dec := yaml.NewDecoder(fh)
- dec.KnownFields(true)
+ secHandlers := map[string]http.Handler{
+ "/new": a.HandleNew(),
+ "/list": a.HandleList(),
- c := &Conf{}
- err = dec.Decode(c)
- if err != nil {
- return nil, err
+ "/api/v1/new": a.HandleNewJSON(),
+ "/api/v1/list": a.HandleListJSON(),
}
- fh.Close()
-
- changed := false
- // Convert any plain passwords to HashedPasswords
- for n, u := range c.Users {
- u.Username = n
- if u.Password != "" {
- u.HashedPassword, err = hashPasswd(u.Password)
- if err != nil {
- return nil, err
- }
- u.Password = ""
- changed = true
- }
- }
-
- if changed {
- fh, err := os.OpenFile(fn, os.O_TRUNC|os.O_RDWR, 0600)
- if err != nil {
- return nil, err
- }
- enc := yaml.NewEncoder(fh)
- enc.SetIndent(2)
- err = enc.Encode(c)
- if err != nil {
- return nil, err
- }
- fh.Close()
- }
+ handlers := map[string]http.Handler{
+ "/api/v1/view": http.StripPrefix(
+ "/api/v1/view/",
+ a.HandleViewJSON()),
- return c, nil
-}
+ "/view": http.StripPrefix(
+ "/view/",
+ a.HandleViewPlain()),
-func loadOrGenKeys() (ed25519.PublicKey, ed25519.PrivateKey, error) {
- var (
- key ed25519.PrivateKey
- pub ed25519.PublicKey
- err error
- )
- if _, err = os.Stat("key"); err != nil {
- pub, key, err = ed25519.GenerateKey(rand.Reader)
- if err != nil {
- return nil, nil, err
- }
+ "/": http.FileServer(http.FS(a.static)),
+ }
- fh, err := os.OpenFile("key", os.O_CREATE|os.O_RDWR, 0600)
- if err != nil {
- return nil, nil, err
+ if len(a.users) > 0 {
+ for user := range a.users {
+ logger.Println("Found user:", user)
}
- err = pem.Encode(fh, &pem.Block{
- Type: "ED25519 PRIVATE KEY",
- Bytes: key,
- })
- if err != nil {
- return nil, nil, err
+ for pth, handler := range secHandlers {
+ mux.Handle(pth, authHandler(
+ handler,
+ a.users,
+ ))
}
-
- fh.Close()
} else {
- fh, err := os.Open("key")
- if err != nil {
- return nil, nil, err
- }
+ _, _ = fmt.Fprintf(os.Stderr,
+ "\033[1;31mWARNING: RUNNING WITH NO AUTHENTICATION\033[0m\n")
- b, err := io.ReadAll(fh)
- if err != nil {
- return nil, nil, err
- }
-
- blk, _ := pem.Decode(b)
- if blk == nil || blk.Type != "ED25519 PRIVATE KEY" {
- return nil, nil, errors.New("Failed to decode PEM file on disk")
+ for pth, handler := range secHandlers {
+ mux.Handle(pth, handler)
}
+ }
- key = ed25519.PrivateKey(blk.Bytes)
- pub = key.Public().(ed25519.PublicKey)
- fh.Close()
+ for pth, handler := range handlers {
+ mux.Handle(pth, handler)
}
- return pub, key, err
+ return mux
}
-func main() {
- fl := flag.NewFlagSet("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.IntVar(&ID_BYTES, "b", ID_BYTES, "How many random bytes for the id?")
- version := fl.Bool("v", false, "Print version and exit")
-
- _ = fl.Parse(os.Args[1:])
-
- if *version {
- logger.Fatal(Version)
- }
-
- 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
- }
+func getUsersFromEnviron() map[string]string {
+ users := map[string]string{}
+ for _, entry := range os.Environ() {
+ if !strings.HasPrefix(entry, "USER_") {
+ continue
+ }
- if *storage == "" {
- logger.Fatal("Cannot continue without storage directory, set `-s` flag or STORAGE_DIR environment variable")
- }
+ e := strings.SplitN(entry, "=", 2)
+ key := e[0]
+ val := e[1]
- err := os.MkdirAll(filepath.Join(*storage, "p"), 0755)
- if err != nil {
- logger.Fatal(err)
- }
+ username := strings.TrimPrefix(key, "USER_")
- err = os.Chdir(*storage)
- if err != nil {
- logger.Fatal(err)
+ users[username] = val
}
+ return users
+}
- c, err := readConf("config.yml")
- if err != nil {
- logger.Fatal(err)
- }
- logger.Println("Config:")
- b, _ := json.MarshalIndent(c, "", " ")
- logger.Println(string(b))
+func sendErr(er errResp) {
+ er.w.WriteHeader(er.Code)
+ er.w.Header().Add("Content-type", "application/json")
+ enc := json.NewEncoder(er.w)
+ _ = enc.Encode(er)
+}
- pubKey, key, err := loadOrGenKeys()
+func GenId() string {
+ r := make([]byte, ID_BYTES)
+ _, err := rand.Read(r)
if err != nil {
logger.Fatal(err)
}
- r := mux.NewRouter()
-
- r.Handle("/new", handlerRequireJWT(pubKey, c.Users, handlerNewPasteJSON()))
- r.HandleFunc("/view/{id}", handlerFuncLoadPaste)
- r.HandleFunc("/view/json/{id}", handlerFuncLoadPasteJson)
- r.Handle("/list", handlerRequireJWT(pubKey, c.Users, handlerList()))
- r.PathPrefix("/static").Handler(http.FileServer(http.FS(staticFS)))
- r.Handle("/login", handlerLogin(key, c.Users))
- r.Handle("/", handlerIndex())
-
- logger.Println("listening on: ", *listen)
-
- srv := &http.Server{
- Handler: r,
- Addr: *listen,
- WriteTimeout: 15 * time.Second,
- ReadTimeout: 15 * time.Second,
- }
- logger.Fatal(srv.ListenAndServe())
-}
-
-func jsonResp(w http.ResponseWriter, code int, data interface{}) {
- w.Header().Add("Content-type", "application/json")
- w.WriteHeader(code)
-
- enc := json.NewEncoder(w)
- err := enc.Encode(data)
- if err != nil {
- logger.Println("While jsonResp: ", err)
- }
+ return base64.RawURLEncoding.EncodeToString(r)
}
-func jsonErr(logMsg string, msg string,
- w http.ResponseWriter, statusCode int) {
+func authHandler(next http.Handler, users map[string]string) http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ respUnAuth := func() {
+ logger.Println("Unauthed")
+ w.Header().Add("Content-type", "text/plain")
+ w.Header().Add("WWW-Authenticate",
+ "Basic realm=\"Login\", charset=\"UTF-8\"")
+ w.WriteHeader(http.StatusUnauthorized)
+ _, _ = w.Write([]byte("Unauthorized\n"))
+ }
- logger.Println(logMsg)
+ username, passwd, ok := r.BasicAuth()
+ if _, haveUser := users[username]; !ok || !haveUser {
+ respUnAuth()
+ return
+ }
- w.Header().Add("Content-type", "application/json")
- w.WriteHeader(statusCode)
+ err := bcrypt.CompareHashAndPassword(
+ []byte(users[username]),
+ []byte(passwd),
+ )
- enc := json.NewEncoder(w)
- err := enc.Encode(map[string]string{"error": msg})
- if err != nil {
- logger.Println("While logMsg: ", err)
- }
-}
-
-func handlerLogin(key ed25519.PrivateKey, users map[string]*User) http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- reqU := &User{}
- dec := json.NewDecoder(r.Body)
+ if errors.Is(err, bcrypt.ErrHashTooShort) {
+ logger.Println("Hash too short for username: ",
+ username)
+ }
- err := dec.Decode(reqU)
if err != nil {
- jsonErr(
- fmt.Sprintf("Encountered error decoding user: %s", err),
- "invalid json",
- w, http.StatusBadRequest)
+ respUnAuth()
return
}
- u, ok := users[reqU.Username]
+ next.ServeHTTP(w, r)
+ })
+}
- if !ok || reqU.Username == "" || reqU.Password == "" {
- jsonErr(
- "Invalid username or password",
- "invalid username or password",
- w, http.StatusBadRequest)
- return
- }
+func (a *App) HandleNew() http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ id := GenId()
- if !u.CheckPass(reqU.Password) {
- jsonErr(
- fmt.Sprintf("Bad password for: %s", u.Username),
- "bad username or password",
- w, http.StatusBadRequest)
+ fh, err := os.OpenFile(id, os.O_CREATE|os.O_EXCL|os.O_RDWR, OPEN_MODE)
+ if err != nil {
+ sendErr(errResp{w, http.StatusInternalServerError, "Internal server error"})
return
}
- t := jwt.NewWithClaims(jwt.SigningMethodEdDSA, &jwt.StandardClaims{
- ExpiresAt: time.Now().Unix() + (12 * 60 * 60),
- Subject: u.Username,
- })
-
- s, err := t.SignedString(key)
+ _, err = io.Copy(fh, r.Body)
if err != nil {
- jsonErr(
- fmt.Sprintf("Failed to sign: %s", err),
- "internal server error",
- w, http.StatusInternalServerError)
+ sendErr(errResp{w, http.StatusInternalServerError, "Internal server error"})
return
}
- w.Header().Add("Content-type", "application/json")
- w.Header().Add("Authorization", "Bearer "+s)
+ w.WriteHeader(http.StatusOK)
+ w.Header().Add("Content-type", "text/plain")
enc := json.NewEncoder(w)
- _ = enc.Encode(map[string]string{
- "token": s,
- })
+ _ = enc.Encode(struct {
+ Id string
+ }{id})
})
}
-func handlerRequireJWT(key ed25519.PublicKey, users map[string]*User,
- next http.Handler) http.Handler {
+type NewPost struct {
+ Content string `json:"content"`
+}
+func (a *App) HandleNewJSON() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ id := GenId()
- tokenS := r.Header.Get("Authorization")
- if tokenS == "" {
- jsonErr("Empty token received", "Unauthorized: empty token",
- w, http.StatusUnauthorized)
- return
- }
-
- tokenS = strings.TrimPrefix(tokenS, "Bearer ")
+ p := &NewPost{}
- claims := &jwt.StandardClaims{}
-
- token, err := jwt.ParseWithClaims(tokenS, claims,
- func(token *jwt.Token) (interface{}, error) {
- return key, nil
- })
+ dec := json.NewDecoder(r.Body)
- if err != nil {
- jsonErr(fmt.Sprintf("Error parsing token: %s", err),
- "Unauthorized: invalid token", w, http.StatusUnauthorized)
+ err := dec.Decode(p)
+ if err != nil || p.Content == "" {
+ sendErr(errResp{w, http.StatusBadRequest, "Malformed JSON"})
return
}
- if !token.Valid {
- jsonErr(
- fmt.Sprintf("Token for %s expires at: %v", claims.Subject,
- claims.ExpiresAt),
- "Unauthorized: invalid token", w, http.StatusUnauthorized)
+ fh, err := os.OpenFile(id, os.O_CREATE|os.O_EXCL|os.O_RDWR, OPEN_MODE)
+ if err != nil {
+ sendErr(errResp{w, http.StatusInternalServerError, "Internal server error"})
return
}
- u, ok := users[claims.Subject]
-
- if !ok {
- jsonErr(
- fmt.Sprintf("User %s not valid", claims.Subject),
- "invalid user", w, http.StatusUnauthorized)
+ _, err = io.Copy(fh, bytes.NewBufferString(p.Content))
+ if err != nil {
+ sendErr(errResp{w, http.StatusInternalServerError, "Internal server error"})
return
}
- logger.Printf("%s -> authed user: %s", r.URL.Path, u.Username)
-
- next.ServeHTTP(w, r)
+ sendErr(errResp{w, http.StatusOK, "OK"})
})
}
-func handlerNewPasteJSON() http.Handler {
+func (a *App) HandleViewJSON() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- dec := json.NewDecoder(r.Body)
- dec.DisallowUnknownFields()
-
- paste := &Paste{}
-
- err := dec.Decode(paste)
+ pth := filepath.Join(a.storage, strings.ReplaceAll(filepath.Clean(r.URL.Path), "/", ""))
+ fh, err := os.Open(pth)
if err != nil {
- jsonErr(fmt.Sprintf("Encountered error decoding: %s", err),
- "unable to decode input", w, http.StatusBadRequest)
+ sendErr(errResp{w, http.StatusNotFound, "ID not found"})
return
}
- if paste.Content == "" {
- jsonErr("Not saving paste with empty content",
- "empty content", w, http.StatusBadRequest)
+ b, err := io.ReadAll(fh)
+ if err != nil {
+ sendErr(errResp{w, http.StatusInternalServerError, "Internal server error"})
return
}
- paste.Id = GenId()
-
- err = paste.Save()
+ w.WriteHeader(http.StatusOK)
+ w.Header().Add("Content-type", "application/json")
+ enc := json.NewEncoder(w)
+ _ = enc.Encode(struct {
+ Content string
+ }{string(b)})
+ })
+}
+func (a *App) HandleViewPlain() http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ pth := filepath.Join(a.storage, strings.ReplaceAll(filepath.Clean(r.URL.Path), "/", ""))
+ fh, err := os.Open(pth)
if err != nil {
- jsonErr(fmt.Sprintf("Encountered error saving paste: %s", err),
- "internal server error", w, http.StatusInternalServerError)
+ sendErr(errResp{w, http.StatusNotFound, "ID not found"})
return
}
- jsonResp(w, http.StatusOK, map[string]string{
- "status": "ok",
- "id": paste.Id,
- })
+ w.WriteHeader(http.StatusOK)
+ w.Header().Add("Content-type", "text/plain")
+ _, _ = io.Copy(w, fh)
})
}
-func handlerFuncNewPaste(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: 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 handlerFuncLoadPasteJson(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
-
- id, ok := vars["id"]
- if !ok {
- jsonErr("No ID supplied",
- "ID must be supplied", w, http.StatusBadRequest)
- return
- }
-
- p := &Paste{Id: id}
+func (a *App) HandleList() http.Handler {
+ return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+ buf := &bytes.Buffer{}
- err := p.Load()
- if err != nil {
- logger.Println(err)
- if errors.Is(err, os.ErrNotExist) {
- jsonErr(
- fmt.Sprintf("Snip with id: %s not found", id),
- "ID not found", w, http.StatusNotFound)
- } else {
- jsonErr(
- fmt.Sprintf("Snip with id faild loading: %s", err),
- "Failed to load snippet", w, http.StatusInternalServerError)
+ de, err := os.ReadDir(a.storage)
+ if err != nil {
+ sendErr(errResp{w, http.StatusInternalServerError, "Internal server error"})
+ return
}
- return
- }
- jsonResp(w, http.StatusOK, p)
-}
+ for _, e := range de {
+ if e.IsDir() {
+ continue
+ }
-func handlerFuncLoadPaste(w http.ResponseWriter, r *http.Request) {
- vars := mux.Vars(r)
+ info, err := e.Info()
+ if err != nil {
+ sendErr(errResp{w, http.StatusInternalServerError, "Internal server error"})
+ return
+ }
- id, ok := vars["id"]
- if !ok {
- w.WriteHeader(http.StatusBadRequest)
- err := tpls["error"].Execute(w, map[string]string{
- "Short": "ID Not found",
- "Long": "There was no ID supplied",
- })
- if err != nil {
- logger.Println(err)
+ _, _ = buf.Write([]byte(fmt.Sprintf("%d\t%s\t%s\n",
+ info.Size(),
+ info.ModTime().Format("2006-01-02 15:04 MST"),
+ e.Name())))
}
- 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 = tpls["error"].Execute(w, map[string]string{
- "Short": "ID Not found",
- "Long": "ID: " + p.Id + "Was not found",
- })
- } else {
- w.WriteHeader(http.StatusInternalServerError)
- err = tpls["error"].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
- }
+ _, _ = io.Copy(w, buf)
+ })
+}
- err = tpls["view"].Execute(w, p)
- if err != nil {
- logger.Println(err)
- }
+type PasteListing struct {
+ Id string `json:"id"`
+ Created time.Time `json:"created"`
+ Size int64 `json:"size"`
}
-func handlerList() http.Handler {
+func (a *App) HandleListJSON() http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- lst, err := os.ReadDir("p")
+ enc := json.NewEncoder(w)
+ enc.SetIndent("", " ")
+ out := []*PasteListing{}
+
+ de, err := os.ReadDir(a.storage)
if err != nil {
- jsonErr(
- fmt.Sprintf("Failed reading directory: %s", err),
- "Internal server error",
- w, http.StatusInternalServerError)
+ sendErr(errResp{w, http.StatusInternalServerError, "Internal server error"})
return
}
- out := struct {
- Pastes []string
- }{
- Pastes: []string{},
- }
-
- for _, e := range lst {
+ for _, e := range de {
if e.IsDir() {
continue
}
- out.Pastes = append(out.Pastes, e.Name())
- }
- jsonResp(w, http.StatusOK, out)
- })
-}
+ info, err := e.Info()
+ if err != nil {
+ sendErr(errResp{w, http.StatusInternalServerError, "Internal server error"})
+ return
+ }
-func handlerIndex() http.Handler {
- return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
- err := tpls["index"].Execute(w, nil)
- if err != nil {
- logger.Println(err)
- http.Error(w, "Internal server error", http.StatusInternalServerError)
+ out = append(out, &PasteListing{
+ e.Name(),
+ info.ModTime(),
+ info.Size(),
+ })
}
+
+ _ = enc.Encode(out)
})
}