go-paste/src/routes/artifact.go
xuu 392341cf38
Some checks failed
Deploy / deploy (push) Failing after 45s
build: use go install
2023-10-15 16:03:34 -06:00

247 lines
5.4 KiB
Go

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
}
}