✨ Response size and time limit - fixes #30
This commit is contained in:
parent
eae118faac
commit
7fe78f4d6b
1
NOTES.md
1
NOTES.md
|
@ -3,7 +3,6 @@
|
||||||
## Issues
|
## Issues
|
||||||
- URL for each tab should not be stored as a string - in the current code there's lots of reparsing the URL
|
- URL for each tab should not be stored as a string - in the current code there's lots of reparsing the URL
|
||||||
- Can't go back or do other things while page is loading - need a way to stop `handleURL`
|
- Can't go back or do other things while page is loading - need a way to stop `handleURL`
|
||||||
- dlChoiceModal doesn't go away when portal is selected, and freezes on Cancel
|
|
||||||
|
|
||||||
## Upstream Bugs
|
## Upstream Bugs
|
||||||
- Wrapping messes up on brackets
|
- Wrapping messes up on brackets
|
||||||
|
|
|
@ -149,6 +149,8 @@ func Init() error {
|
||||||
viper.SetDefault("a-general.left_margin", 0.15)
|
viper.SetDefault("a-general.left_margin", 0.15)
|
||||||
viper.SetDefault("a-general.max_width", 100)
|
viper.SetDefault("a-general.max_width", 100)
|
||||||
viper.SetDefault("a-general.downloads", "")
|
viper.SetDefault("a-general.downloads", "")
|
||||||
|
viper.SetDefault("a-general.page_max_size", 2097152)
|
||||||
|
viper.SetDefault("a-general.page_max_time", 10)
|
||||||
viper.SetDefault("cache.max_size", 0)
|
viper.SetDefault("cache.max_size", 0)
|
||||||
viper.SetDefault("cache.max_pages", 20)
|
viper.SetDefault("cache.max_pages", 20)
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
//go:generate ./default.sh
|
|
||||||
var defaultConf = []byte(`# This is the default config file.
|
var defaultConf = []byte(`# This is the default config file.
|
||||||
# It also shows all the default values, if you don't create the file.
|
# It also shows all the default values, if you don't create the file.
|
||||||
|
|
||||||
|
@ -29,6 +28,11 @@ max_width = 100 # The max number of columns to wrap a page's text to. Preformat
|
||||||
# An empty value means the code will find the default downloads folder for your system.
|
# An empty value means the code will find the default downloads folder for your system.
|
||||||
# If the path does not exist it will be created.
|
# If the path does not exist it will be created.
|
||||||
downloads = ""
|
downloads = ""
|
||||||
|
# Max size for displayable content in bytes - after that size a download window pops up
|
||||||
|
page_max_size = 2097152 # 2 MiB
|
||||||
|
# Max time it takes to load a page in seconds - after that a download window pops up
|
||||||
|
page_max_time = 10
|
||||||
|
|
||||||
# Options for page cache - which is only for text/gemini pages
|
# Options for page cache - which is only for text/gemini pages
|
||||||
# Increase the cache size to speed up browsing at the expense of memory
|
# Increase the cache size to speed up browsing at the expense of memory
|
||||||
[cache]
|
[cache]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
head -n 3 default.go | tee default.go > /dev/null
|
head -n 1 default.go | tee default.go > /dev/null
|
||||||
echo -n 'var defaultConf = []byte(`' >> default.go
|
echo -n 'var defaultConf = []byte(`' >> default.go
|
||||||
cat ../default-config.toml >> default.go
|
cat ../default-config.toml >> default.go
|
||||||
echo '`)' >> default.go
|
echo '`)' >> default.go
|
|
@ -26,6 +26,11 @@ max_width = 100 # The max number of columns to wrap a page's text to. Preformat
|
||||||
# An empty value means the code will find the default downloads folder for your system.
|
# An empty value means the code will find the default downloads folder for your system.
|
||||||
# If the path does not exist it will be created.
|
# If the path does not exist it will be created.
|
||||||
downloads = ""
|
downloads = ""
|
||||||
|
# Max size for displayable content in bytes - after that size a download window pops up
|
||||||
|
page_max_size = 2097152 # 2 MiB
|
||||||
|
# Max time it takes to load a page in seconds - after that a download window pops up
|
||||||
|
page_max_time = 10
|
||||||
|
|
||||||
# Options for page cache - which is only for text/gemini pages
|
# Options for page cache - which is only for text/gemini pages
|
||||||
# Increase the cache size to speed up browsing at the expense of memory
|
# Increase the cache size to speed up browsing at the expense of memory
|
||||||
[cache]
|
[cache]
|
||||||
|
|
|
@ -24,7 +24,6 @@ import (
|
||||||
// For choosing between download and the portal - copy of YesNo basically
|
// For choosing between download and the portal - copy of YesNo basically
|
||||||
var dlChoiceModal = cview.NewModal().
|
var dlChoiceModal = cview.NewModal().
|
||||||
SetTextColor(tcell.ColorWhite).
|
SetTextColor(tcell.ColorWhite).
|
||||||
SetText("That file could not be displayed. What would you like to do?").
|
|
||||||
AddButtons([]string{"Download", "Open in portal", "Cancel"})
|
AddButtons([]string{"Download", "Open in portal", "Cancel"})
|
||||||
|
|
||||||
// Channel to indicate what choice they made using the button text
|
// Channel to indicate what choice they made using the button text
|
||||||
|
@ -72,7 +71,7 @@ func dlInit() {
|
||||||
|
|
||||||
// dlChoice displays the download choice modal and acts on the user's choice.
|
// dlChoice displays the download choice modal and acts on the user's choice.
|
||||||
// It should run in a goroutine.
|
// It should run in a goroutine.
|
||||||
func dlChoice(u string, resp *gemini.Response) {
|
func dlChoice(text, u string, resp *gemini.Response) {
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
parsed, err := url.Parse(u)
|
parsed, err := url.Parse(u)
|
||||||
|
@ -81,6 +80,7 @@ func dlChoice(u string, resp *gemini.Response) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dlChoiceModal.SetText(text)
|
||||||
tabPages.ShowPage("dlChoice")
|
tabPages.ShowPage("dlChoice")
|
||||||
tabPages.SendToFront("dlChoice")
|
tabPages.SendToFront("dlChoice")
|
||||||
App.SetFocus(dlChoiceModal)
|
App.SetFocus(dlChoiceModal)
|
||||||
|
|
|
@ -309,11 +309,27 @@ func handleURL(t *tab, u string) (string, bool) {
|
||||||
return ret("", false)
|
return ret("", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
page.Width = termW
|
// Make new request for downloading purposes
|
||||||
|
res, clientErr := client.Fetch(u)
|
||||||
|
if clientErr != nil && clientErr != client.ErrTofu {
|
||||||
|
Error("URL Fetch Error", err.Error())
|
||||||
|
return ret("", false)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err == renderer.ErrTooLarge {
|
||||||
|
go dlChoice("That page is too large. What would you like to do?", u, res)
|
||||||
|
return ret("", false)
|
||||||
|
}
|
||||||
|
if err == renderer.ErrTimedOut {
|
||||||
|
go dlChoice("Loading that page timed out. What would you like to do?", u, res)
|
||||||
|
return ret("", false)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Error("Page Error", "Issuing creating page: "+err.Error())
|
Error("Page Error", "Issuing creating page: "+err.Error())
|
||||||
return ret("", false)
|
return ret("", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
page.Width = termW
|
||||||
go cache.Add(page)
|
go cache.Add(page)
|
||||||
setPage(t, page)
|
setPage(t, page)
|
||||||
return ret(u, true)
|
return ret(u, true)
|
||||||
|
@ -359,7 +375,7 @@ func handleURL(t *tab, u string) (string, bool) {
|
||||||
return ret("", false)
|
return ret("", false)
|
||||||
}
|
}
|
||||||
// Status code 20, but not a document that can be displayed
|
// Status code 20, but not a document that can be displayed
|
||||||
go dlChoice(u, res)
|
go dlChoice("That file could not be displayed. What would you like to do?", u, res)
|
||||||
return ret("", false)
|
return ret("", false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
package renderer
|
package renderer
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"mime"
|
"mime"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/makeworld-the-better-one/amfora/structs"
|
"github.com/makeworld-the-better-one/amfora/structs"
|
||||||
"github.com/makeworld-the-better-one/go-gemini"
|
"github.com/makeworld-the-better-one/go-gemini"
|
||||||
|
"github.com/spf13/viper"
|
||||||
"golang.org/x/text/encoding/ianaindex"
|
"golang.org/x/text/encoding/ianaindex"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var ErrTooLarge = errors.New("page content would be too large")
|
||||||
|
var ErrTimedOut = errors.New("page download timed out")
|
||||||
|
|
||||||
// isUTF8 returns true for charsets that are compatible with UTF-8 and don't need to be decoded.
|
// isUTF8 returns true for charsets that are compatible with UTF-8 and don't need to be decoded.
|
||||||
func isUTF8(charset string) bool {
|
func isUTF8(charset string) bool {
|
||||||
utfCharsets := []string{"", "utf-8", "us-ascii"}
|
utfCharsets := []string{"", "utf-8", "us-ascii"}
|
||||||
|
@ -53,11 +59,27 @@ func MakePage(url string, res *gemini.Response, width, leftMargin int) (*structs
|
||||||
return nil, errors.New("not valid content for a Page")
|
return nil, errors.New("not valid content for a Page")
|
||||||
}
|
}
|
||||||
|
|
||||||
rawText, err := ioutil.ReadAll(res.Body) // TODO: Don't use all memory on large pages
|
buf := new(bytes.Buffer)
|
||||||
if err != nil {
|
go func() {
|
||||||
|
time.Sleep(time.Duration(viper.GetInt("a-general.page_max_time")) * time.Second)
|
||||||
|
res.Body.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
_, err := io.CopyN(buf, res.Body, viper.GetInt64("a-general.page_max_size")) // 2 MiB max
|
||||||
|
res.Body.Close()
|
||||||
|
rawText := buf.Bytes()
|
||||||
|
if err == nil {
|
||||||
|
// Content was larger than 2 MiB
|
||||||
|
return nil, ErrTooLarge
|
||||||
|
} else if err != io.EOF {
|
||||||
|
if strings.HasSuffix(err.Error(), "use of closed network connection") {
|
||||||
|
// Timed out
|
||||||
|
return nil, ErrTimedOut
|
||||||
|
}
|
||||||
|
// Some other error
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
res.Body.Close()
|
// Otherwise, the error is EOF, which is what we want.
|
||||||
|
|
||||||
mediatype, params, _ := mime.ParseMediaType(res.Meta)
|
mediatype, params, _ := mime.ParseMediaType(res.Meta)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue