Skip to content

Commit 7fe78f4

Browse files
committed
✨ Response size and time limit - fixes #30
1 parent eae118f commit 7fe78f4

File tree

8 files changed

+59
-11
lines changed

8 files changed

+59
-11
lines changed

NOTES.md

-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
## Issues
44
- URL for each tab should not be stored as a string - in the current code there's lots of reparsing the URL
55
- Can't go back or do other things while page is loading - need a way to stop `handleURL`
6-
- dlChoiceModal doesn't go away when portal is selected, and freezes on Cancel
76

87
## Upstream Bugs
98
- Wrapping messes up on brackets

config/config.go

+2
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,8 @@ func Init() error {
149149
viper.SetDefault("a-general.left_margin", 0.15)
150150
viper.SetDefault("a-general.max_width", 100)
151151
viper.SetDefault("a-general.downloads", "")
152+
viper.SetDefault("a-general.page_max_size", 2097152)
153+
viper.SetDefault("a-general.page_max_time", 10)
152154
viper.SetDefault("cache.max_size", 0)
153155
viper.SetDefault("cache.max_pages", 20)
154156

config/default.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package config
22

3-
//go:generate ./default.sh
43
var defaultConf = []byte(`# This is the default config file.
54
# It also shows all the default values, if you don't create the file.
65
@@ -29,6 +28,11 @@ max_width = 100 # The max number of columns to wrap a page's text to. Preformat
2928
# An empty value means the code will find the default downloads folder for your system.
3029
# If the path does not exist it will be created.
3130
downloads = ""
31+
# Max size for displayable content in bytes - after that size a download window pops up
32+
page_max_size = 2097152 # 2 MiB
33+
# Max time it takes to load a page in seconds - after that a download window pops up
34+
page_max_time = 10
35+
3236
# Options for page cache - which is only for text/gemini pages
3337
# Increase the cache size to speed up browsing at the expense of memory
3438
[cache]

config/default.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/usr/bin/env bash
22

3-
head -n 3 default.go | tee default.go > /dev/null
3+
head -n 1 default.go | tee default.go > /dev/null
44
echo -n 'var defaultConf = []byte(`' >> default.go
55
cat ../default-config.toml >> default.go
66
echo '`)' >> default.go

default-config.toml

+5
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ max_width = 100 # The max number of columns to wrap a page's text to. Preformat
2626
# An empty value means the code will find the default downloads folder for your system.
2727
# If the path does not exist it will be created.
2828
downloads = ""
29+
# Max size for displayable content in bytes - after that size a download window pops up
30+
page_max_size = 2097152 # 2 MiB
31+
# Max time it takes to load a page in seconds - after that a download window pops up
32+
page_max_time = 10
33+
2934
# Options for page cache - which is only for text/gemini pages
3035
# Increase the cache size to speed up browsing at the expense of memory
3136
[cache]

display/download.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ import (
2424
// For choosing between download and the portal - copy of YesNo basically
2525
var dlChoiceModal = cview.NewModal().
2626
SetTextColor(tcell.ColorWhite).
27-
SetText("That file could not be displayed. What would you like to do?").
2827
AddButtons([]string{"Download", "Open in portal", "Cancel"})
2928

3029
// Channel to indicate what choice they made using the button text
@@ -72,7 +71,7 @@ func dlInit() {
7271

7372
// dlChoice displays the download choice modal and acts on the user's choice.
7473
// It should run in a goroutine.
75-
func dlChoice(u string, resp *gemini.Response) {
74+
func dlChoice(text, u string, resp *gemini.Response) {
7675
defer resp.Body.Close()
7776

7877
parsed, err := url.Parse(u)
@@ -81,6 +80,7 @@ func dlChoice(u string, resp *gemini.Response) {
8180
return
8281
}
8382

83+
dlChoiceModal.SetText(text)
8484
tabPages.ShowPage("dlChoice")
8585
tabPages.SendToFront("dlChoice")
8686
App.SetFocus(dlChoiceModal)

display/private.go

+18-2
Original file line numberDiff line numberDiff line change
@@ -309,11 +309,27 @@ func handleURL(t *tab, u string) (string, bool) {
309309
return ret("", false)
310310
}
311311

312-
page.Width = termW
312+
// Make new request for downloading purposes
313+
res, clientErr := client.Fetch(u)
314+
if clientErr != nil && clientErr != client.ErrTofu {
315+
Error("URL Fetch Error", err.Error())
316+
return ret("", false)
317+
}
318+
319+
if err == renderer.ErrTooLarge {
320+
go dlChoice("That page is too large. What would you like to do?", u, res)
321+
return ret("", false)
322+
}
323+
if err == renderer.ErrTimedOut {
324+
go dlChoice("Loading that page timed out. What would you like to do?", u, res)
325+
return ret("", false)
326+
}
313327
if err != nil {
314328
Error("Page Error", "Issuing creating page: "+err.Error())
315329
return ret("", false)
316330
}
331+
332+
page.Width = termW
317333
go cache.Add(page)
318334
setPage(t, page)
319335
return ret(u, true)
@@ -359,7 +375,7 @@ func handleURL(t *tab, u string) (string, bool) {
359375
return ret("", false)
360376
}
361377
// Status code 20, but not a document that can be displayed
362-
go dlChoice(u, res)
378+
go dlChoice("That file could not be displayed. What would you like to do?", u, res)
363379
return ret("", false)
364380
}
365381

renderer/page.go

+26-4
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,22 @@
11
package renderer
22

33
import (
4+
"bytes"
45
"errors"
5-
"io/ioutil"
6+
"io"
67
"mime"
78
"strings"
9+
"time"
810

911
"github.com/makeworld-the-better-one/amfora/structs"
1012
"github.com/makeworld-the-better-one/go-gemini"
13+
"github.com/spf13/viper"
1114
"golang.org/x/text/encoding/ianaindex"
1215
)
1316

17+
var ErrTooLarge = errors.New("page content would be too large")
18+
var ErrTimedOut = errors.New("page download timed out")
19+
1420
// isUTF8 returns true for charsets that are compatible with UTF-8 and don't need to be decoded.
1521
func isUTF8(charset string) bool {
1622
utfCharsets := []string{"", "utf-8", "us-ascii"}
@@ -53,11 +59,27 @@ func MakePage(url string, res *gemini.Response, width, leftMargin int) (*structs
5359
return nil, errors.New("not valid content for a Page")
5460
}
5561

56-
rawText, err := ioutil.ReadAll(res.Body) // TODO: Don't use all memory on large pages
57-
if err != nil {
62+
buf := new(bytes.Buffer)
63+
go func() {
64+
time.Sleep(time.Duration(viper.GetInt("a-general.page_max_time")) * time.Second)
65+
res.Body.Close()
66+
}()
67+
68+
_, err := io.CopyN(buf, res.Body, viper.GetInt64("a-general.page_max_size")) // 2 MiB max
69+
res.Body.Close()
70+
rawText := buf.Bytes()
71+
if err == nil {
72+
// Content was larger than 2 MiB
73+
return nil, ErrTooLarge
74+
} else if err != io.EOF {
75+
if strings.HasSuffix(err.Error(), "use of closed network connection") {
76+
// Timed out
77+
return nil, ErrTimedOut
78+
}
79+
// Some other error
5880
return nil, err
5981
}
60-
res.Body.Close()
82+
// Otherwise, the error is EOF, which is what we want.
6183

6284
mediatype, params, _ := mime.ParseMediaType(res.Meta)
6385

0 commit comments

Comments
 (0)