package routes import ( "archive/tar" "crypto/sha256" "encoding/base64" "fmt" "io" "io/ioutil" "net/http" "os" "path/filepath" "strconv" "strings" "github.com/gorilla/mux" "go.sour.is/paste/src/pkg/readutil" "sour.is/x/toolbox/httpsrv" "sour.is/x/toolbox/log" "github.com/gomarkdown/markdown" "github.com/gomarkdown/markdown/html" "github.com/gomarkdown/markdown/parser" ) func init() { a := &Artifact{} httpsrv.RegisterModule("artifact", a.config) httpsrv.HttpRegister("artifact", httpsrv.HttpRoutes{ {Name: "get-path", Method: "GET", Pattern: "/a/{name}/{path:.*}", HandlerFunc: a.get}, {Name: "get", Method: "GET", Pattern: "/a/{name}", HandlerFunc: a.get}, {Name: "put", Method: "PUT", Pattern: "/a", HandlerFunc: a.put}, {Name: "get", Method: "GET", Pattern: "/a", HandlerFunc: a.list}, }) } // Artifact stores items to disk type Artifact struct { store string maxSize int64 } func (a *Artifact) config(config map[string]string) { a.store = "data/" if config["store"] != "" { a.store = config["store"] } if !chkStore(a.store) { log.Criticalf("Artifact Store location [%s] does not exist or is not writable.", a.store) } log.Noticef("Artifact Store location set to [%s]", a.store) a.maxSize = 50 * 1024 * 1024 if s, ok := config["max-size"]; ok { if i, err := strconv.Atoi(s); err == nil { a.maxSize = int64(i) } } log.Noticef("Artifact: store max-size set to [%d]", a.maxSize) } func (a *Artifact) get(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) name := vars["name"] path, hasPath := vars["path"] ext := filepath.Ext(name) id := strings.TrimSuffix(name, ext) fname := filepath.Join(a.store, id) if !chkFile(fname) { httpsrv.WriteError(w, http.StatusNotFound, "Not Found") return } if chkGone(fname) { httpsrv.WriteError(w, http.StatusGone, "Gone") return } f, err := os.Open(fname) if err != nil { log.Error(err) } defer f.Close() if !hasPath { pr := readutil.NewPreviewReader(f) mime, err := readutil.ReadMIME(pr, name) if err != nil { httpsrv.WriteError(w, http.StatusInternalServerError, err.Error()) return } w.Header().Set("Content-Type", mime) w.Header().Set("X-Content-Type-Options", "nosniff") _, _ = io.Copy(w, pr.Drain()) return } rdr, err := readutil.Decompress(f) if err != nil && err != readutil.ErrUnsupportedType { httpsrv.WriteError(w, http.StatusInternalServerError, err.Error()) return } if cl, ok := rdr.(io.ReadCloser); ok { defer cl.Close() } tr := tar.NewReader(rdr) if path == "@" { var paths []string for { hdr, err := tr.Next() if err == io.EOF { break // End of archive } if err != nil { log.Error(err) } paths = append(paths, hdr.Name) } httpsrv.WriteObject(w, http.StatusOK, paths) return } for { hdr, err := tr.Next() if err == io.EOF { break // End of archive } if err != nil { log.Error(err) } if path == "~" && hdr.Name == "index.html" { path = hdr.Name } if path == "~" && hdr.Name == "index.md" { path = hdr.Name } if hdr.Name == path { if strings.HasSuffix(hdr.Name, ".md") { md, err := ioutil.ReadAll(tr) if err != nil { httpsrv.WriteError(w, http.StatusInternalServerError, err.Error()) return } w.Header().Set("Content-Type", "text/html") extensions := parser.CommonExtensions | parser.AutoHeadingIDs p := parser.NewWithExtensions(extensions) htmlFlags := html.CommonFlags | html.HrefTargetBlank opts := html.RendererOptions{Flags: htmlFlags} renderer := html.NewRenderer(opts) b := markdown.ToHTML(md, p, renderer) _, _ = w.Write(b) w.WriteHeader(http.StatusOK) return } pr := readutil.NewPreviewReader(tr) mime, err := readutil.ReadMIME(pr, hdr.Name) if err != nil { httpsrv.WriteError(w, http.StatusInternalServerError, err.Error()) return } w.Header().Set("Content-Type", mime) if _, err := io.Copy(w, pr.Drain()); err != nil { log.Error(err) } w.WriteHeader(http.StatusOK) return } } httpsrv.WriteError(w, http.StatusNotFound, "Not Found in Archive") } func (a *Artifact) put(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() if h := r.Header.Get("Content-Length"); h != "" { if i, err := strconv.Atoi(h); err == nil { if int64(i) > a.maxSize { http.Error(w, "ERR Size Too Large", http.StatusRequestEntityTooLarge) return } } } rdr := io.LimitReader(r.Body, 500*1024*1024) pr := readutil.NewPreviewReader(rdr) rdr = pr.Drain() s256 := sha256.New() tmp, err := ioutil.TempFile(a.store, "arch-") if err != nil { httpsrv.WriteError(w, 400, err.Error()) return } defer os.Remove(tmp.Name()) m := io.MultiWriter(s256, tmp) if _, err := io.Copy(m, rdr); err != nil { httpsrv.WriteError(w, 400, err.Error()) } tmp.Close() id := base64.RawURLEncoding.EncodeToString(s256.Sum(nil)[12:]) fname := filepath.Join(a.store, id) log.Debugs("Artifact: moving file", "src", tmp.Name(), "dst", fname) _ = os.Rename(tmp.Name(), fname) httpsrv.WriteText(w, http.StatusCreated, id) } func (a *Artifact) list(w http.ResponseWriter, r *http.Request) { err := filepath.Walk(a.store, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if info.IsDir() { return nil } _, err = fmt.Fprintln(w, "FILE: ", info.Name()) return err }) if err != nil { httpsrv.WriteError(w, http.StatusInternalServerError, err.Error()) return } }