package routes import ( "net/http" "sour.is/x/httpsrv" "encoding/json" "github.com/gorilla/mux" "io/ioutil" "io" "sour.is/x/log" "os" "golang.org/x/sys/unix" "crypto/rand" "strconv" "bufio" "crypto/sha256" "encoding/base64" "strings" "time" ) var store string var randBytes int func init() { httpsrv.RegisterModule("paste", setConfig) httpsrv.HttpRegister("paste", httpsrv.HttpRoutes{ { "getRandom", "GET", "/paste/rng", getRandom, }, { "getPaste", "GET", "/paste/{id}", getPaste, }, { "getPaste", "GET", "/paste/get/{id}", getPaste, }, { "postPaste", "POST", "/paste", postPaste, }, { "getRandom", "GET", "/api/rng", getRandom, }, { "getPaste", "GET", "/api/{id}", getPaste, }, { "getPaste", "GET", "/api/get/{id}", getPaste, }, { "postPaste", "POST", "/api", postPaste, }, }) httpsrv.AssetRegister("paste", httpsrv.AssetRoutes{ { "Assets", "/", httpsrv.FsHtml5( assetFS() ) }, }) } func setConfig (config map[string]string) { store = "data/" if config["store"] != "" { store = config["store"] } if !chkStore(store) { log.Criticalf("[routes::Paste] Store location [%s] does not exist or is not writable.", store) } log.Noticef("[paste::getPaste] Store location set to [%s]", store) randBytes = 1024 if config["random"] != "" { randBytes, _ = strconv.Atoi(config["random"]) log.Noticef("[paste:getRandom] set random size to %d bytes", randBytes) } } func chkStore(path string) bool { file, err := os.Stat(path) if err == nil { return true } if os.IsNotExist(err) { return false } if !file.IsDir() { return false } if unix.Access(path, unix.W_OK & unix.R_OK) != nil { return false } return true } func chkFile(path string) bool { file, err := os.Stat(path) if err == nil { return true } if os.IsNotExist(err) { return false } if file.IsDir() { return false } if unix.Access(path, unix.W_OK & unix.R_OK) != nil { return false } return true } func chkGone(path string) bool { file, err := os.Stat(path) if err != nil { return true } if file.Size() == 0 { return true } return false } func getRandom(w http.ResponseWriter, r *http.Request) { s := make([]byte, randBytes) rand.Read(s) w.Header().Set("content-type","application/octet-stream") w.WriteHeader(http.StatusOK) w.Write(s) } func getPaste(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) id := vars["id"] if !chkFile(store + id) { w.WriteHeader(http.StatusNotFound) w.Write([]byte("ERR Not Found")) return } if chkGone(store + id) { w.WriteHeader(http.StatusGone) w.Write([]byte("ERR Gone")) return } head, err := os.Open(store + id) if err != nil { log.Fatal(err) } defer head.Close() keep := true scanner := bufio.NewScanner(head) for scanner.Scan() { txt := scanner.Text() log.Debug(txt) if (txt == "") { break } if strings.HasPrefix(txt, "exp:") { now := time.Now().Unix() exp, err := strconv.ParseInt(strings.TrimSpace(strings.TrimPrefix(txt, "exp:")), 10, 64) if err != nil { log.Warning(err) } log.Debugf("%d > %d", now, exp) if now > exp { w.WriteHeader(http.StatusGone) w.Write([]byte("ERR Gone")) deleteFile(store + id) return } } if strings.HasPrefix(txt, "burn:") { burn := strings.TrimSpace(strings.TrimPrefix(txt, "burn:")) if burn == "true" { keep = false } } } file, _ := os.Open(store + id) defer file.Close() w.WriteHeader(http.StatusOK) scanner = bufio.NewScanner(file) for scanner.Scan() { w.Write(scanner.Bytes()) w.Write([]byte("\n")) } if !keep { deleteFile(store + id) } } func postPaste(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() body, err := ioutil.ReadAll(io.LimitReader(r.Body, 1048576)) checkErr(err, w) s256 := sha256.Sum256(body) id := base64.RawURLEncoding.EncodeToString(s256[12:]) ioutil.WriteFile(store + id, body, 0644) w.WriteHeader(http.StatusCreated) w.Write([]byte("OK " + id)) w.WriteHeader(http.StatusCreated) } func checkErr(err error, w http.ResponseWriter) { if err != nil { w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(err); panic(err) } } func deleteFile(path string) { ioutil.WriteFile(path, []byte(""), 0644) }