- commit
- ec83e1d
- parent
- 24df143
- author
- Mac Chaffee
- date
- 2024-10-14 15:15:31 +0000 UTC
chore(pgs): limit size of _redirects and _headers files
8 files changed,
+84,
-43
M
db/db.go
+14,
-5
1@@ -224,13 +224,14 @@ type FeatureFlag struct {
2 Data FeatureFlagData `json:"data"`
3 }
4
5-func NewFeatureFlag(userID, name string, storageMax uint64, fileMax int64) *FeatureFlag {
6+func NewFeatureFlag(userID, name string, storageMax uint64, fileMax int64, specialFileMax int64) *FeatureFlag {
7 return &FeatureFlag{
8 UserID: userID,
9 Name: name,
10 Data: FeatureFlagData{
11- StorageMax: storageMax,
12- FileMax: fileMax,
13+ StorageMax: storageMax,
14+ FileMax: fileMax,
15+ SpecialFileMax: specialFileMax,
16 },
17 }
18 }
19@@ -249,6 +250,13 @@ func (ff *FeatureFlag) FindFileMax(defaultSize int64) int64 {
20 return ff.Data.FileMax
21 }
22
23+func (ff *FeatureFlag) FindSpecialFileMax(defaultSize int64) int64 {
24+ if ff.Data.SpecialFileMax == 0 {
25+ return defaultSize
26+ }
27+ return ff.Data.SpecialFileMax
28+}
29+
30 func (ff *FeatureFlag) IsValid() bool {
31 if ff.ExpiresAt.IsZero() {
32 return false
33@@ -257,8 +265,9 @@ func (ff *FeatureFlag) IsValid() bool {
34 }
35
36 type FeatureFlagData struct {
37- StorageMax uint64 `json:"storage_max"`
38- FileMax int64 `json:"file_max"`
39+ StorageMax uint64 `json:"storage_max"`
40+ FileMax int64 `json:"file_max"`
41+ SpecialFileMax int64 `json:"special_file_max"`
42 }
43
44 // Make the Attrs struct implement the driver.Valuer interface. This method
M
dev.md
+1,
-1
1@@ -16,7 +16,7 @@ echo dotenv > .envrc && direnv allow
2 Boot up database (or bring your own)
3
4 ```bash
5-docker compose up -f docker-compose.yml -f docker-compose.override.yml --profile db -d
6+docker compose -f docker-compose.yml -f docker-compose.override.yml --profile db up -d
7 ```
8
9 Create db and migrate
+12,
-1
1@@ -363,6 +363,11 @@ func (h *UploadAssetHandler) Write(s ssh.Session, entry *sendutils.FileEntry) (s
2 "sizeRemaining", sizeRemaining,
3 )
4
5+ specialFileMax := featureFlag.Data.SpecialFileMax
6+ if isSpecialFile(entry) {
7+ sizeRemaining = min(sizeRemaining, specialFileMax)
8+ }
9+
10 fsize, err := h.writeAsset(
11 utils.NewMaxBytesReader(data.Reader, int64(sizeRemaining)),
12 data,
13@@ -370,11 +375,12 @@ func (h *UploadAssetHandler) Write(s ssh.Session, entry *sendutils.FileEntry) (s
14 if err != nil {
15 logger.Error("could not write asset", "err", err.Error())
16 cerr := fmt.Errorf(
17- "%s: storage size %.2fmb, storage max %.2fmb, file max %.2fmb",
18+ "%s: storage size %.2fmb, storage max %.2fmb, file max %.2fmb, special file max %.2fmb",
19 err,
20 utils.BytesToMB(int(curStorageSize)),
21 utils.BytesToMB(int(storageMax)),
22 utils.BytesToMB(int(fileMax)),
23+ utils.BytesToMB(int(specialFileMax)),
24 )
25 return "", cerr
26 }
27@@ -400,6 +406,11 @@ func (h *UploadAssetHandler) Write(s ssh.Session, entry *sendutils.FileEntry) (s
28 return str, nil
29 }
30
31+func isSpecialFile(entry *sendutils.FileEntry) bool {
32+ fname := filepath.Base(entry.Filepath)
33+ return fname == "_headers" || fname == "_redirects"
34+}
35+
36 func (h *UploadAssetHandler) Delete(s ssh.Session, entry *sendutils.FileEntry) error {
37 user, err := shared.GetUser(s.Context())
38 if err != nil {
+18,
-4
1@@ -170,11 +170,18 @@ func hasProtocol(url string) bool {
2
3 func (h *AssetHandler) handle(logger *slog.Logger, w http.ResponseWriter, r *http.Request) {
4 var redirects []*RedirectRule
5- redirectFp, _, err := h.Storage.GetObject(h.Bucket, filepath.Join(h.ProjectDir, "_redirects"))
6+ redirectFp, redirectInfo, err := h.Storage.GetObject(h.Bucket, filepath.Join(h.ProjectDir, "_redirects"))
7 if err == nil {
8 defer redirectFp.Close()
9+ if redirectInfo != nil && redirectInfo.Size > h.Cfg.MaxSpecialFileSize {
10+ errMsg := fmt.Sprintf("_redirects file is too large (%d > %d)", redirectInfo.Size, h.Cfg.MaxSpecialFileSize)
11+ logger.Error(errMsg)
12+ http.Error(w, errMsg, http.StatusInternalServerError)
13+ return
14+ }
15 buf := new(strings.Builder)
16- _, err := io.Copy(buf, redirectFp)
17+ lr := io.LimitReader(redirectFp, h.Cfg.MaxSpecialFileSize)
18+ _, err := io.Copy(buf, lr)
19 if err != nil {
20 logger.Error("io copy", "err", err.Error())
21 http.Error(w, "cannot read _redirects file", http.StatusInternalServerError)
22@@ -297,11 +304,18 @@ func (h *AssetHandler) handle(logger *slog.Logger, w http.ResponseWriter, r *htt
23 }
24
25 var headers []*HeaderRule
26- headersFp, _, err := h.Storage.GetObject(h.Bucket, filepath.Join(h.ProjectDir, "_headers"))
27+ headersFp, headersInfo, err := h.Storage.GetObject(h.Bucket, filepath.Join(h.ProjectDir, "_headers"))
28 if err == nil {
29 defer headersFp.Close()
30+ if headersInfo != nil && headersInfo.Size > h.Cfg.MaxSpecialFileSize {
31+ errMsg := fmt.Sprintf("_headers file is too large (%d > %d)", headersInfo.Size, h.Cfg.MaxSpecialFileSize)
32+ logger.Error(errMsg)
33+ http.Error(w, errMsg, http.StatusInternalServerError)
34+ return
35+ }
36 buf := new(strings.Builder)
37- _, err := io.Copy(buf, headersFp)
38+ lr := io.LimitReader(headersFp, h.Cfg.MaxSpecialFileSize)
39+ _, err := io.Copy(buf, lr)
40 if err != nil {
41 logger.Error("io copy", "err", err.Error())
42 http.Error(w, "cannot read _headers file", http.StatusInternalServerError)
+1,
-1
1@@ -302,7 +302,7 @@ func (c *Cmd) statsSites() error {
2 func (c *Cmd) stats(cfgMaxSize uint64) error {
3 ff, err := c.Dbpool.FindFeatureForUser(c.User.ID, "plus")
4 if err != nil {
5- ff = db.NewFeatureFlag(c.User.ID, "plus", cfgMaxSize, 0)
6+ ff = db.NewFeatureFlag(c.User.ID, "plus", cfgMaxSize, 0, 0)
7 }
8 // this is jank
9 ff.Data.StorageMax = ff.FindStorageMax(cfgMaxSize)
+17,
-13
1@@ -8,6 +8,9 @@ import (
2 var maxSize = uint64(25 * utils.MB)
3 var maxAssetSize = int64(10 * utils.MB)
4
5+// Needs to be small for caching files like _headers and _redirects.
6+var maxSpecialFileSize = int64(5 * utils.KB)
7+
8 func NewConfigSite() *shared.ConfigSite {
9 domain := utils.GetEnv("PGS_DOMAIN", "pgs.sh")
10 port := utils.GetEnv("PGS_WEB_PORT", "3000")
11@@ -23,19 +26,20 @@ func NewConfigSite() *shared.ConfigSite {
12 }
13
14 cfg := shared.ConfigSite{
15- Secret: secret,
16- Domain: domain,
17- Port: port,
18- Protocol: protocol,
19- DbURL: dbURL,
20- StorageDir: storageDir,
21- MinioURL: minioURL,
22- MinioUser: minioUser,
23- MinioPass: minioPass,
24- Space: "pgs",
25- MaxSize: maxSize,
26- MaxAssetSize: maxAssetSize,
27- Logger: shared.CreateLogger("pgs"),
28+ Secret: secret,
29+ Domain: domain,
30+ Port: port,
31+ Protocol: protocol,
32+ DbURL: dbURL,
33+ StorageDir: storageDir,
34+ MinioURL: minioURL,
35+ MinioUser: minioUser,
36+ MinioPass: minioPass,
37+ Space: "pgs",
38+ MaxSize: maxSize,
39+ MaxAssetSize: maxAssetSize,
40+ MaxSpecialFileSize: maxSpecialFileSize,
41+ Logger: shared.CreateLogger("pgs"),
42 }
43
44 return &cfg
1@@ -28,24 +28,25 @@ type PageData struct {
2 }
3
4 type ConfigSite struct {
5- Debug bool
6- SendgridKey string
7- Secret string
8- Domain string
9- Port string
10- PortOverride string
11- Protocol string
12- DbURL string
13- StorageDir string
14- MinioURL string
15- MinioUser string
16- MinioPass string
17- Space string
18- AllowedExt []string
19- HiddenPosts []string
20- MaxSize uint64
21- MaxAssetSize int64
22- Logger *slog.Logger
23+ Debug bool
24+ SendgridKey string
25+ Secret string
26+ Domain string
27+ Port string
28+ PortOverride string
29+ Protocol string
30+ DbURL string
31+ StorageDir string
32+ MinioURL string
33+ MinioUser string
34+ MinioPass string
35+ Space string
36+ AllowedExt []string
37+ HiddenPosts []string
38+ MaxSize uint64
39+ MaxAssetSize int64
40+ MaxSpecialFileSize int64
41+ Logger *slog.Logger
42 }
43
44 func NewConfigSite() *ConfigSite {
1@@ -93,11 +93,13 @@ func (r *SshAuthHandler) PubkeyAuthHandler(ctx ssh.Context, key ssh.PublicKey) b
2 "plus",
3 r.Cfg.MaxSize,
4 r.Cfg.MaxAssetSize,
5+ r.Cfg.MaxSpecialFileSize,
6 )
7 }
8 // this is jank
9 ff.Data.StorageMax = ff.FindStorageMax(r.Cfg.MaxSize)
10 ff.Data.FileMax = ff.FindFileMax(r.Cfg.MaxAssetSize)
11+ ff.Data.SpecialFileMax = ff.FindSpecialFileMax(r.Cfg.MaxSpecialFileSize)
12
13 SetUser(ctx, user)
14 SetFeatureFlag(ctx, ff)