repos / pico

pico services mono repo
git clone https://github.com/picosh/pico.git

pico / pkg / apps / prose
Eric Bower  ·  2026-01-25

scp_hooks.go

  1package prose
  2
  3import (
  4	"fmt"
  5	"path/filepath"
  6	"strings"
  7
  8	"slices"
  9
 10	"github.com/picosh/pico/pkg/db"
 11	"github.com/picosh/pico/pkg/filehandlers"
 12	"github.com/picosh/pico/pkg/pssh"
 13	"github.com/picosh/pico/pkg/shared"
 14	pipeUtil "github.com/picosh/utils/pipe"
 15)
 16
 17type MarkdownHooks struct {
 18	Cfg  *shared.ConfigSite
 19	Db   db.DB
 20	Pipe *pipeUtil.ReconnectReadWriteCloser
 21}
 22
 23func (p *MarkdownHooks) FileValidate(s *pssh.SSHServerConnSession, data *filehandlers.PostMetaData) (bool, error) {
 24	if !shared.IsTextFile(data.Text) {
 25		err := fmt.Errorf(
 26			"ERROR: (%s) invalid file must be plain text (utf-8), skipping",
 27			data.Filename,
 28		)
 29		return false, err
 30	}
 31
 32	fp := strings.Replace(data.Filename, "/", "", 1)
 33	// special styles css file we want to permit but no other css file.
 34	// sometimes the directory is provided in the filename, so we want to
 35	// remove that before we perform this check.
 36	if fp == "_styles.css" {
 37		return true, nil
 38	}
 39	// allow users to upload robots file
 40	if fp == "robots.txt" {
 41		return true, nil
 42	}
 43
 44	if !shared.IsExtAllowed(data.Filename, p.Cfg.AllowedExt) {
 45		extStr := strings.Join(p.Cfg.AllowedExt, ",")
 46		err := fmt.Errorf(
 47			"ERROR: (%s) invalid file, format must be (%s), skipping",
 48			data.Filename,
 49			extStr,
 50		)
 51		return false, err
 52	}
 53
 54	if data.FileSize > MAX_FILE_SIZE {
 55		return false, fmt.Errorf(
 56			"ERROR: file (%s) has exceeded maximum file size (%d bytes)",
 57			data.Filename,
 58			MAX_FILE_SIZE,
 59		)
 60	}
 61
 62	return true, nil
 63}
 64
 65func (p *MarkdownHooks) metaLxt(data *filehandlers.PostMetaData) error {
 66	parsedText := shared.ListParseText(data.Text)
 67
 68	if parsedText.Title == "" {
 69		data.Title = shared.ToUpper(data.Slug)
 70	} else {
 71		data.Title = parsedText.Title
 72	}
 73
 74	data.Aliases = parsedText.Aliases
 75	data.Tags = parsedText.Tags
 76	data.Description = parsedText.Description
 77
 78	if parsedText.PublishAt != nil && !parsedText.PublishAt.IsZero() {
 79		data.PublishAt = parsedText.PublishAt
 80	}
 81	data.Hidden = parsedText.Hidden
 82
 83	return nil
 84}
 85
 86func (p *MarkdownHooks) metaMd(data *filehandlers.PostMetaData) error {
 87	parsedText, err := shared.ParseText(data.Text)
 88	if err != nil {
 89		return fmt.Errorf("%s: %w", data.Filename, err)
 90	}
 91
 92	if parsedText.Title == "" {
 93		data.Title = shared.ToUpper(data.Slug)
 94	} else {
 95		data.Title = parsedText.Title
 96	}
 97
 98	data.Aliases = parsedText.Aliases
 99	data.Tags = parsedText.Tags
100	data.Description = parsedText.Description
101
102	if parsedText.PublishAt != nil && !parsedText.PublishAt.IsZero() {
103		data.PublishAt = parsedText.PublishAt
104	}
105	data.Hidden = parsedText.Hidden
106
107	return nil
108}
109
110func (p *MarkdownHooks) FileMeta(s *pssh.SSHServerConnSession, data *filehandlers.PostMetaData) error {
111	ext := filepath.Ext(data.Filename)
112	switch ext {
113	case ".lxt":
114		err := p.metaLxt(data)
115		if err != nil {
116			return fmt.Errorf("%s: %w", data.Filename, err)
117		}
118	case ".md":
119		err := p.metaMd(data)
120		if err != nil {
121			return fmt.Errorf("%s: %w", data.Filename, err)
122		}
123	}
124
125	isHiddenFilename := slices.Contains(p.Cfg.HiddenPosts, data.Filename)
126	data.Hidden = data.Hidden || isHiddenFilename
127
128	return nil
129}