repos / pico

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

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 dev.md
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
M filehandlers/assets/handler.go
+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 {
M pgs/api.go
+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)
M pgs/cli.go
+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)
M pgs/config.go
+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
M shared/config.go
+19, -18
 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 {
M shared/ssh.go
+2, -0
 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)