1
0
Fork 0

🐛 Handle redirects for subscription URLs

Fixes #144
This commit is contained in:
makeworld 2020-12-17 21:48:23 -05:00
parent 8672b78d69
commit 008d713de3
2 changed files with 119 additions and 30 deletions

View File

@ -406,7 +406,7 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
return ret(u, true)
}
// Not displayable
// Could be a non 20 (or 21) status code, or a different kind of document
// Could be a non 20 status code, or a different kind of document
// Handle each status code
switch res.Status {
@ -414,7 +414,6 @@ func handleURL(t *tab, u string, numRedirects int) (string, bool) {
userInput, ok := Input(res.Meta)
if ok {
// Make another request with the query string added
// + chars are replaced because PathEscape doesn't do that
parsed.RawQuery = gemini.QueryEscape(userInput)
if len(parsed.String()) > gemini.URLMaxLength {
Error("Input Error", "URL for that input would be too long.")

View File

@ -8,6 +8,7 @@ import (
"io"
"io/ioutil"
"mime"
urlPkg "net/url"
"os"
"path"
"reflect"
@ -23,9 +24,10 @@ import (
)
var (
ErrSaving = errors.New("couldn't save JSON to disk")
ErrNotSuccess = errors.New("status 20 not returned")
ErrNotFeed = errors.New("not a valid feed")
ErrSaving = errors.New("couldn't save JSON to disk")
ErrNotSuccess = errors.New("status 20 not returned")
ErrNotFeed = errors.New("not a valid feed")
ErrTooManyRedirects = errors.New("redirected more than 5 times")
)
var writeMu = sync.Mutex{} // Prevent concurrent writes to subscriptions.json file
@ -115,7 +117,8 @@ func GetFeed(mediatype, filename string, r io.Reader) (*gofeed.Feed, bool) {
// Check mediatype and filename
if mediatype != "application/atom+xml" && mediatype != "application/rss+xml" && mediatype != "application/json+feed" &&
filename != "atom.xml" && filename != "feed.xml" && filename != "feed.json" &&
!strings.HasSuffix(filename, ".atom") && !strings.HasSuffix(filename, ".rss") && !strings.HasSuffix(filename, ".xml") {
!strings.HasSuffix(filename, ".atom") && !strings.HasSuffix(filename, ".rss") &&
!strings.HasSuffix(filename, ".xml") {
// No part of the above is true
return nil, false
}
@ -229,46 +232,133 @@ func AddPage(url string, r io.Reader) error {
return nil
}
func updateFeed(url string) error {
// getResource returns a URL and Response for the given URL.
// It will follow up to 5 redirects, and if there is a permanent
// redirect it will return the new URL. Otherwise the URL will
// stay the same. THe returned URL will never be empty.
//
// If there is over 5 redirects the error will be ErrTooManyRedirects.
// ErrNotSuccess, as well as other fetch errors will also be returned.
func getResource(url string) (string, *gemini.Response, error) {
res, err := client.Fetch(url)
if err != nil {
if res != nil {
res.Body.Close()
}
return err
return url, nil, err
}
defer res.Body.Close()
if res.Status != gemini.StatusSuccess {
return ErrNotSuccess
if res.Status == gemini.StatusSuccess {
// No redirects
return url, res, nil
}
mediatype, _, err := mime.ParseMediaType(res.Meta)
parsed, err := urlPkg.Parse(url)
if err != nil {
return err
return url, nil, err
}
filename := path.Base(url)
feed, ok := GetFeed(mediatype, filename, res.Body)
if !ok {
return ErrNotFeed
i := 0
redirs := make([]int, 0)
urls := make([]*urlPkg.URL, 0)
// Loop through redirects
for (res.Status == gemini.StatusRedirectPermanent || res.Status == gemini.StatusRedirectTemporary) && i < 5 {
redirs = append(redirs, res.Status)
urls = append(urls, parsed)
tmp, err := parsed.Parse(res.Meta)
if err != nil {
// Redirect URL returned by the server is invalid
return url, nil, err
}
parsed = tmp
// Make the new request
res, err := client.Fetch(parsed.String())
if err != nil {
if res != nil {
res.Body.Close()
}
return url, nil, err
}
i++
}
return AddFeed(url, feed)
// Two possible options here:
// - Never redirected, got error on start
// - No more redirects, other status code
// - Too many redirects
if i == 0 {
// Never redirected or succeeded
return url, res, ErrNotSuccess
}
if i < 5 {
// The server stopped redirecting after <5 redirects
if res.Status == gemini.StatusSuccess {
// It ended by succeeding
for j := range redirs {
if redirs[j] == gemini.StatusRedirectTemporary {
if j == 0 {
// First redirect is temporary
return url, res, nil
}
// There were permanent redirects before this one
// Return the URL of the latest permanent redirect
return urls[j-1].String(), res, nil
}
}
// They were all permanent redirects
return urls[len(urls)-1].String(), res, nil
}
// It stopped because there was a non-redirect, non-success response
return url, res, ErrNotSuccess
}
// Too many redirects, return original
return url, nil, ErrTooManyRedirects
}
func updatePage(url string) error {
res, err := client.Fetch(url)
func updateFeed(url string) {
newURL, res, err := getResource(url)
if err != nil {
if res != nil {
res.Body.Close()
}
return err
}
defer res.Body.Close()
if res.Status != gemini.StatusSuccess {
return ErrNotSuccess
return
}
return AddPage(url, res.Body)
mediatype, _, err := mime.ParseMediaType(res.Meta)
if err != nil {
return
}
filename := path.Base(newURL)
feed, ok := GetFeed(mediatype, filename, res.Body)
if !ok {
return
}
AddFeed(newURL, feed)
if url != newURL {
// URL has changed, remove old one
Remove(url)
}
}
func updatePage(url string) {
newURL, res, err := getResource(url)
if err != nil {
return
}
AddPage(newURL, res.Body)
if url != newURL {
// URL has changed, remove old one
Remove(url)
}
}
// updateAll updates all subscriptions using workers.