- 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
+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=