repos / pico

pico services - prose.sh, pastes.sh, imgs.sh, feeds.sh, pgs.sh
git clone https://github.com/picosh/pico.git

commit
8074dfb
parent
c84a932
author
Eric Bower
date
2024-04-06 15:48:20 +0000 UTC
feat(pgs): override denylist
3 files changed,  +99, -33
M go.mod
M go.sum
M filehandlers/assets/handler.go
+95, -33
  1@@ -8,7 +8,6 @@ import (
  2 	"log/slog"
  3 	"os"
  4 	"path/filepath"
  5-	"regexp"
  6 	"strings"
  7 	"time"
  8 
  9@@ -20,11 +19,30 @@ import (
 10 	"github.com/picosh/pobj"
 11 	sst "github.com/picosh/pobj/storage"
 12 	"github.com/picosh/send/send/utils"
 13+	"github.com/sabhiram/go-gitignore"
 14 )
 15 
 16 type ctxBucketKey struct{}
 17 type ctxStorageSizeKey struct{}
 18 type ctxProjectKey struct{}
 19+type ctxDenylistKey struct{}
 20+
 21+type DenyList struct {
 22+	Denylist string
 23+}
 24+
 25+func getDenylist(s ssh.Session) *DenyList {
 26+	v := s.Context().Value(ctxDenylistKey{})
 27+	if v == nil {
 28+		return nil
 29+	}
 30+	denylist := s.Context().Value(ctxDenylistKey{}).(*DenyList)
 31+	return denylist
 32+}
 33+
 34+func setDenylist(s ssh.Session, denylist string) {
 35+	s.Context().SetValue(ctxDenylistKey{}, &DenyList{Denylist: denylist})
 36+}
 37 
 38 func getProject(s ssh.Session) *db.Project {
 39 	v := s.Context().Value(ctxProjectKey{})
 40@@ -35,6 +53,10 @@ func getProject(s ssh.Session) *db.Project {
 41 	return project
 42 }
 43 
 44+func setProject(s ssh.Session, project *db.Project) {
 45+	s.Context().SetValue(ctxProjectKey{}, project)
 46+}
 47+
 48 func getBucket(s ssh.Session) (sst.Bucket, error) {
 49 	bucket := s.Context().Value(ctxBucketKey{}).(sst.Bucket)
 50 	if bucket.Name == "" {
 51@@ -59,6 +81,11 @@ func incrementStorageSize(s ssh.Session, fileSize int64) uint64 {
 52 	return nextStorageSize
 53 }
 54 
 55+func shouldIgnoreFile(fp, ignoreStr string) bool {
 56+	object := ignore.CompileIgnoreLines(strings.Split(ignoreStr, "\n")...)
 57+	return object.MatchesPath(fp)
 58+}
 59+
 60 type FileData struct {
 61 	*utils.FileEntry
 62 	Text          []byte
 63@@ -67,6 +94,8 @@ type FileData struct {
 64 	StorageSize   uint64
 65 	FeatureFlag   *db.FeatureFlag
 66 	DeltaFileSize int64
 67+	Project       *db.Project
 68+	DenyList      string
 69 }
 70 
 71 type UploadAssetHandler struct {
 72@@ -224,12 +253,34 @@ func (h *UploadAssetHandler) Validate(s ssh.Session) error {
 73 	return nil
 74 }
 75 
 76+func (h *UploadAssetHandler) findDenylist(bucket sst.Bucket, project *db.Project, logger *slog.Logger) (string, error) {
 77+	fp, _, _, err := h.Storage.GetObject(bucket, filepath.Join(project.ProjectDir, "_pgs_ignore"))
 78+	if err != nil {
 79+		return "", fmt.Errorf("_pgs_ignore not found")
 80+	}
 81+
 82+	defer fp.Close()
 83+	buf := new(strings.Builder)
 84+	_, err = io.Copy(buf, fp)
 85+	if err != nil {
 86+		logger.Error("io copy", "err", err.Error())
 87+		return "", err
 88+	}
 89+
 90+	str := buf.String()
 91+	return str, nil
 92+}
 93+
 94 func (h *UploadAssetHandler) Write(s ssh.Session, entry *utils.FileEntry) (string, error) {
 95 	user, err := futil.GetUser(s)
 96 	if err != nil {
 97-		h.Cfg.Logger.Error(err.Error())
 98+		h.Cfg.Logger.Error("user not found in ctx", "err", err.Error())
 99 		return "", err
100 	}
101+	logger := h.GetLogger().With(
102+		"user", user.Name,
103+		"file", entry.Filepath,
104+	)
105 
106 	var origText []byte
107 	if b, err := io.ReadAll(entry.Reader); err == nil {
108@@ -242,35 +293,36 @@ func (h *UploadAssetHandler) Write(s ssh.Session, entry *utils.FileEntry) (strin
109 
110 	bucket, err := getBucket(s)
111 	if err != nil {
112-		h.Cfg.Logger.Error(err.Error())
113+		logger.Error("could not find bucket in ctx", "err", err.Error())
114 		return "", err
115 	}
116 
117-	hasProject := getProject(s)
118+	project := getProject(s)
119 	projectName := shared.GetProjectName(entry)
120+	logger = logger.With("project", projectName)
121 
122 	// find, create, or update project if we haven't already done it
123-	if hasProject == nil {
124-		project, err := h.DBPool.FindProjectByName(user.ID, projectName)
125+	if project == nil {
126+		project, err = h.DBPool.FindProjectByName(user.ID, projectName)
127 		if err == nil {
128 			err = h.DBPool.UpdateProject(user.ID, projectName)
129 			if err != nil {
130-				h.Cfg.Logger.Error("could not update project", "err", err.Error())
131+				logger.Error("could not update project", "err", err.Error())
132 				return "", err
133 			}
134 		} else {
135 			_, err = h.DBPool.InsertProject(user.ID, projectName, projectName)
136 			if err != nil {
137-				h.Cfg.Logger.Error("could not create project", "err", err.Error())
138+				logger.Error("could not create project", "err", err.Error())
139 				return "", err
140 			}
141 			project, err = h.DBPool.FindProjectByName(user.ID, projectName)
142 			if err != nil {
143-				h.Cfg.Logger.Error("could not find project", "err", err.Error())
144+				logger.Error("could not find project", "err", err.Error())
145 				return "", err
146 			}
147 		}
148-		s.Context().SetValue(ctxProjectKey{}, project)
149+		setProject(s, project)
150 	}
151 
152 	storageSize := getStorageSize(s)
153@@ -284,6 +336,17 @@ func (h *UploadAssetHandler) Write(s ssh.Session, entry *utils.FileEntry) (strin
154 	curFileSize, _ := h.Storage.GetObjectSize(bucket, assetFilename)
155 	deltaFileSize := curFileSize - entry.Size
156 
157+	denylist := getDenylist(s)
158+	if denylist == nil {
159+		dlist, err := h.findDenylist(bucket, project, logger)
160+		if err != nil {
161+			logger.Info("failed to get denylist, setting default (.*)", "err", err.Error())
162+			dlist = ".*"
163+		}
164+		setDenylist(s, dlist)
165+		denylist = &DenyList{Denylist: dlist}
166+	}
167+
168 	data := &FileData{
169 		FileEntry:     entry,
170 		User:          user,
171@@ -292,10 +355,18 @@ func (h *UploadAssetHandler) Write(s ssh.Session, entry *utils.FileEntry) (strin
172 		StorageSize:   storageSize,
173 		FeatureFlag:   featureFlag,
174 		DeltaFileSize: deltaFileSize,
175+		DenyList:      denylist.Denylist,
176+		Project:       project,
177 	}
178+
179+	valid, err := h.validateAsset(data)
180+	if !valid {
181+		return "", err
182+	}
183+
184 	err = h.writeAsset(data)
185 	if err != nil {
186-		h.Cfg.Logger.Error(err.Error())
187+		logger.Error(err.Error())
188 		return "", err
189 	}
190 	nextStorageSize := incrementStorageSize(s, deltaFileSize)
191@@ -356,37 +427,28 @@ func (h *UploadAssetHandler) validateAsset(data *FileData) (bool, error) {
192 		}
193 	}
194 
195-	// special file we use for custom routing
196-	if fname == "_redirects" || fname == "_headers" || fname == "LICENSE" {
197+	// special files we use for custom routing
198+	if fname == "_pgs_ignore" || fname == "_redirects" || fname == "_headers" {
199 		return true, nil
200 	}
201 
202-	dotFileRe := regexp.MustCompile(`/\..+`)
203-	// TODO: let user control this list somehow
204-	denylist := []*regexp.Regexp{dotFileRe}
205-	for _, denyRe := range denylist {
206-		if denyRe.MatchString(data.Filepath) {
207-			err := fmt.Errorf(
208-				"ERROR: (%s) file rejected, https://pico.sh/pgs#file-denylist",
209-				data.Filepath,
210-			)
211-			return false, err
212-		}
213+	fpath := strings.Replace(data.Filepath, "/"+projectName, "", 1)
214+	if shouldIgnoreFile(fpath, data.DenyList) {
215+		err := fmt.Errorf(
216+			"ERROR: (%s) file rejected, https://pico.sh/pgs#file-denylist",
217+			data.Filepath,
218+		)
219+		return false, err
220 	}
221 
222 	return true, nil
223 }
224 
225 func (h *UploadAssetHandler) writeAsset(data *FileData) error {
226-	valid, err := h.validateAsset(data)
227-	if !valid {
228-		return err
229-	}
230-
231-	assetFilename := shared.GetAssetFileName(data.FileEntry)
232+	assetFilepath := shared.GetAssetFileName(data.FileEntry)
233 
234 	if data.Size == 0 {
235-		err = h.Storage.DeleteObject(data.Bucket, assetFilename)
236+		err := h.Storage.DeleteObject(data.Bucket, assetFilepath)
237 		if err != nil {
238 			return err
239 		}
240@@ -397,12 +459,12 @@ func (h *UploadAssetHandler) writeAsset(data *FileData) error {
241 			"uploading file to bucket",
242 			"user", data.User.Name,
243 			"bucket", data.Bucket.Name,
244-			"filename", assetFilename,
245+			"filename", assetFilepath,
246 		)
247 
248 		_, err := h.Storage.PutObject(
249 			data.Bucket,
250-			assetFilename,
251+			assetFilepath,
252 			utils.NopReaderAtCloser(reader),
253 			data.FileEntry,
254 		)
M go.mod
+1, -0
1@@ -31,6 +31,7 @@ require (
2 	github.com/picosh/pobj v0.0.0-20240218150308-1dc70e819bbf
3 	github.com/picosh/ptun v0.0.0-20240313192814-d0ca401937fe
4 	github.com/picosh/send v0.0.0-20240217194807-77b972121e63
5+	github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06
6 	github.com/sendgrid/sendgrid-go v3.13.0+incompatible
7 	github.com/simplesurance/go-ip-anonymizer v0.0.0-20200429124537-35a880f8e87d
8 	github.com/stripe/stripe-go/v75 v75.11.0
M go.sum
+3, -0
 1@@ -219,6 +219,8 @@ github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjR
 2 github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
 3 github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc=
 4 github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
 5+github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06 h1:OkMGxebDjyw0ULyrTYWeN0UNCCkmCWfjPnIA2W6oviI=
 6+github.com/sabhiram/go-gitignore v0.0.0-20210923224102-525f6e181f06/go.mod h1:+ePHsJ1keEjQtpvf9HHw0f4ZeJ0TLRsxhunSI2hYJSs=
 7 github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP0=
 8 github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
 9 github.com/scylladb/termtables v0.0.0-20191203121021-c4c0b6d42ff4/go.mod h1:C1a7PQSMz9NShzorzCiG2fk9+xuCgLkPeCvMHYR2OWg=
10@@ -242,6 +244,7 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
11 github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
12 github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
13 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
14+github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
15 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
16 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
17 github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=