pico

created pr with 41.1 on 2025-01-10T15:10:28Z · by c8ef7d19
added 41.2 on 2025-01-10T15:14:05Z · by c8ef7d19
1: 5d5b1f5 ! 1: a6f10fc chore(feeds): better error handling for invalid feed files
cmds
checkout latest patchset:
ssh pr.pico.sh print 41 | git am -3
checkout any patchset in a patch request:
ssh pr.pico.sh print 41.[rev] | git am -3
add changes to patch request:
git format-patch main --stdout | ssh pr.pico.sh pr add 41

Patchset 41.2 on 2025-01-10T15:14:05Z · commit a6f10fc

When a user uploads an invalid feed file we do not have any mechanism to
notify that user.  Further will continue to attempt to run the file
through our rss-to-email cron.

This commit makes it so we perform validation on the feed files as they
are uploaded and refused to save files that we know will eventually
fail.

Further, we don't want to continuously try files that we know will not
succeed so we are pushing those known issues into our retry-then-delete
mechanism inside our cron.
Semantic diff summary
0 added, 6 modified, 0 signature changed, 0 removed across 2 analyzed files
+20 -9 feeds/cron.go #
......@@ -9,6 +9,7 @@ import (
99 "log/slog"
1010 "math"
1111 "net/http"
12+ "net/url"
1213 "strings"
1314 "text/template"
1415 "time"
......@@ -161,23 +162,29 @@ func (f *Fetcher) RunPost(logger *slog.Logger, user *db.User, post *db.Post) err
161162
162163 urls := []string{}
163164 for _, item := range parsed.Items {
164- url := ""
165- if item.IsText {
166- url = item.Value
165+ u := ""
166+ if item.IsText || item.IsURL {
167+ u = item.Value
167168 } else if item.IsURL {
168- url = string(item.URL)
169+ u = string(item.Value)
169170 }
170171
171- if url == "" {
172+ if u == "" {
172173 continue
173174 }
174175
175- urls = append(urls, url)
176+ _, err := url.Parse(string(item.URL))
177+ if err != nil {
178+ logger.Info("invalid url", "url", string(item.URL))
179+ continue
180+ }
181+
182+ urls = append(urls, u)
176183 }
177184
178185 now := time.Now().UTC()
179186 if post.ExpiresAt == nil {
180- expiresAt := time.Now().AddDate(0, 6, 0)
187+ expiresAt := time.Now().AddDate(0, 12, 0)
181188 post.ExpiresAt = &expiresAt
182189 }
183190 _, err = f.db.UpdatePost(post)
......@@ -199,7 +206,7 @@ func (f *Fetcher) RunPost(logger *slog.Logger, user *db.User, post *db.Post) err
199206 post.Data.Attempts += 1
200207 logger.Error("could not fetch urls", "err", err, "attempts", post.Data.Attempts)
201208
202- errBody := fmt.Sprintf(`There was an error attempting to fetch your feeds (%d) times. After (3) attempts we remove the file from our system. Please check all the URLs and re-upload.
209+ errBody := fmt.Sprintf(`There was an error attempting to fetch your feeds (%d) times. After (5) attempts we remove the file from our system. Please check all the URLs and re-upload.
203210 Also, we have centralized logs in our pico.sh TUI that will display realtime feed errors so you can debug.
204211
205212
......@@ -217,7 +224,7 @@ Also, we have centralized logs in our pico.sh TUI that will display realtime fee
217224 return err
218225 }
219226
220- if post.Data.Attempts >= 3 {
227+ if post.Data.Attempts >= 5 {
221228 err = f.db.RemovePosts([]string{post.ID})
222229 if err != nil {
223230 return err
......@@ -410,6 +417,10 @@ func (f *Fetcher) FetchAll(logger *slog.Logger, urls []string, inlineContent boo
410417 return nil, err
411418 }
412419
420+ if len(urls) == 0 {
421+ return nil, fmt.Errorf("feed file does not contain any urls")
422+ }
423+
413424 var allErrors error
414425 for _, url := range urls {
415426 feedTmpl, err := f.Fetch(logger, fp, url, username, feedItems)
+33 -14 feeds/scp_hooks.go #
......@@ -1,12 +1,13 @@
11 package feeds
22
33 import (
4+ "errors"
45 "fmt"
6+ "net/url"
7+
58 "strings"
69 "time"
710
8- "slices"
9-
1011 "github.com/charmbracelet/ssh"
1112 "github.com/picosh/pico/db"
1213 "github.com/picosh/pico/filehandlers"
......@@ -38,23 +39,41 @@ func (p *FeedHooks) FileValidate(s ssh.Session, data *filehandlers.PostMetaData)
3839 return false, err
3940 }
4041
41- return true, nil
42-}
43-
44-func (p *FeedHooks) FileMeta(s ssh.Session, data *filehandlers.PostMetaData) error {
45- parsedText := shared.ListParseText(string(data.Text))
42+ // Because we need to support sshfs, sftp runs our Write handler twice
43+ // and on the first pass we do not have access to the file data.
44+ // In that case we should skip the parsing validation
45+ if data.Text == "" {
46+ return true, nil
47+ }
4648
47- if parsedText.Title == "" {
48- data.Title = utils.ToUpper(data.Slug)
49- } else {
50- data.Title = parsedText.Title
49+ parsed := shared.ListParseText(string(data.Text))
50+ if parsed.Email == "" {
51+ return false, fmt.Errorf("ERROR: no email variable detected for %s, check the format of your file, skipping", data.Filename)
5152 }
5253
53- data.Description = parsedText.Description
54- data.Tags = parsedText.Tags
54+ var allErr error
55+ for _, txt := range parsed.Items {
56+ u := ""
57+ if txt.IsText {
58+ u = txt.Value
59+ } else if txt.IsURL {
60+ u = string(txt.URL)
61+ }
5562
56- data.Hidden = slices.Contains(p.Cfg.HiddenPosts, data.Filename)
63+ _, err := url.Parse(u)
64+ if err != nil {
65+ allErr = errors.Join(allErr, fmt.Errorf("%s: %w", u, err))
66+ continue
67+ }
68+ }
69+ if allErr != nil {
70+ return false, fmt.Errorf("ERROR: some urls provided were invalid check the format of your file, skipping: %w", allErr)
71+ }
72+
73+ return true, nil
74+}
5775
76+func (p *FeedHooks) FileMeta(s ssh.Session, data *filehandlers.PostMetaData) error {
5877 if data.Data.LastDigest == nil {
5978 now := time.Now()
6079 data.Data.LastDigest = &now
Back to top