From 980f236d84c8a6b23a7204578c0e6527bcfed7e3 Mon Sep 17 00:00:00 2001 From: asdf Date: Sun, 21 Jun 2020 14:24:12 +1000 Subject: [PATCH 01/12] Corrects wrapped lines to ensure they fit the required terminal width --- page.go | 1 + 1 file changed, 1 insertion(+) diff --git a/page.go b/page.go index 6973a7c..ad44ab2 100644 --- a/page.go +++ b/page.go @@ -130,6 +130,7 @@ func (p *Page) WrapContent(width int, color bool) { counter += len(spacer) } content.WriteRune(ch) + counter++ } } } From 57b3afd4d79a31dfa1e14fceb0f48fba2de65782 Mon Sep 17 00:00:00 2001 From: asdf Date: Sun, 21 Jun 2020 14:58:51 +1000 Subject: [PATCH 02/12] Add supporting unit tests for WrapContent --- page_test.go | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 page_test.go diff --git a/page_test.go b/page_test.go new file mode 100644 index 0000000..ef41a80 --- /dev/null +++ b/page_test.go @@ -0,0 +1,74 @@ +package main + +import ( + "reflect" + "testing" +) + +func Test_WrapContent_Wrapped_Line_Length(t *testing.T) { + type fields struct { + WrappedContent []string + RawContent string + Links []string + Location Url + ScrollPosition int + FoundLinkLines []int + SearchTerm string + SearchIndex int + FileType string + WrapWidth int + Color bool + } + type args struct { + width int + color bool + } + + // create a Url for use by the MakePage function + url, _ := MakeUrl("gemini://rawtext.club") + + tests := []struct { + name string + input string + expects []string + args args + }{ + { + "Short line that doesn't wrap", + "0123456789\n", + []string{ + "0123456789", + "", + }, + args{ + 10, + false, + }, + }, + { + "Long line wrapped to 10 columns", + "0123456789 123456789 123456789 123456789 123456789\n", + []string{ + "0123456789", + " 123456789", + " 123456789", + " 123456789", + " 123456789", + "", + }, + args{ + 10, + false, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + p := MakePage(url, tt.input, []string{""}) + p.WrapContent(tt.args.width-1, tt.args.color) + if !reflect.DeepEqual(p.WrappedContent, tt.expects) { + t.Errorf("Test failed - %s\nexpects %s\nactual %s", tt.name, tt.expects, p.WrappedContent) + } + }) + } +} From d2c8af2a0809b35334a5d1a0a91af4b4669a9b32 Mon Sep 17 00:00:00 2001 From: asdf Date: Tue, 23 Jun 2020 14:56:10 +1000 Subject: [PATCH 03/12] Page content now renders to the entire terminal width --- client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client.go b/client.go index 25d7723..c84757a 100644 --- a/client.go +++ b/client.go @@ -122,8 +122,8 @@ func (c *client) Draw() { } else { for i := 0; i < c.Height-3; i++ { if i < len(pageContent) { - screen.WriteString(pageContent[i]) screen.WriteString("\033[0K") + screen.WriteString(pageContent[i]) screen.WriteString("\n") } else { screen.WriteString("\033[0K") From 80bdbb642dbf2ed38634f3c2e6f8022d514e8f1b Mon Sep 17 00:00:00 2001 From: sloum Date: Mon, 29 Jun 2020 21:45:24 -0700 Subject: [PATCH 04/12] Adds timeout to tls for gemini and increases gopher timeout --- gemini/gemini.go | 4 +++- gopher/gopher.go | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/gemini/gemini.go b/gemini/gemini.go index 44c8545..ae5af8e 100644 --- a/gemini/gemini.go +++ b/gemini/gemini.go @@ -6,6 +6,7 @@ import ( "crypto/tls" "fmt" "io/ioutil" + "net" "net/url" "strconv" "strings" @@ -26,6 +27,7 @@ type TofuDigest struct { } var BlockBehavior = "block" +var TlsTimeout = time.Duration(15) * time.Second //------------------------------------------------\\ // + + + R E C E I V E R S + + + \\ @@ -189,7 +191,7 @@ func Retrieve(host, port, resource string, td *TofuDigest) (string, error) { return &td.ClientCert, nil } - conn, err := tls.Dial("tcp", addr, conf) + conn, err := tls.DialWithDialer(&net.Dialer{Timeout: TlsTimeout}, "tcp", addr, conf) if err != nil { return "", fmt.Errorf("TLS Dial Error: %s", err.Error()) } diff --git a/gopher/gopher.go b/gopher/gopher.go index 20e76e9..68721d3 100644 --- a/gopher/gopher.go +++ b/gopher/gopher.go @@ -49,7 +49,7 @@ var types = map[string]string{ // be better. func Retrieve(host, port, resource string) ([]byte, error) { nullRes := make([]byte, 0) - timeOut := time.Duration(5) * time.Second + timeOut := time.Duration(15) * time.Second if host == "" || port == "" { return nullRes, errors.New("Incomplete request url") From 185c62f27200a85bd8a7bbb63be1a36217c181e4 Mon Sep 17 00:00:00 2001 From: sloum Date: Mon, 29 Jun 2020 22:19:14 -0700 Subject: [PATCH 05/12] Adds timeout config option --- bombadillo.1 | 4 ++++ client.go | 15 +++++++++++++++ defaults.go | 3 ++- gemini/gemini.go | 4 ++-- gopher/gopher.go | 5 +++-- main.go | 10 ++++++++++ 6 files changed, 36 insertions(+), 5 deletions(-) diff --git a/bombadillo.1 b/bombadillo.1 index c8b19a0..ea650b2 100644 --- a/bombadillo.1 +++ b/bombadillo.1 @@ -257,6 +257,10 @@ theme Can toggle between visual modes. Valid values are \fInormal\fP, \fIcolor\fP, and \fIinverse\fP. When set to inverse, the normal mode colors are inverted. Both normal and inverse modes filter out terminal escape sequences. When set to color, Bombadillo will render terminal escape sequences representing colors when it finds them in documents. .TP .B +timeout +The number of seconds after which connections to gopher or gemini servers should time out if the server has not responded. +.TP +.B tlscertificate A path to a tls certificate file on a user's local filesystem. Defaults to NULL. Both \fItlscertificate\fP and \fItlskey\fP must be set for client certificates to work in gemini. .TP diff --git a/client.go b/client.go index 25d7723..f14565f 100644 --- a/client.go +++ b/client.go @@ -455,6 +455,8 @@ func (c *client) doCommandAs(action string, values []string) { c.Certs.LoadCertificate(c.Options["tlscertificate"], c.Options["tlskey"]) } else if values[0] == "geminiblocks" { gemini.BlockBehavior = c.Options[values[0]] + } else if values[0] == "timeout" { + updateTimeouts(c.Options[values[0]]) } else if values[0] == "configlocation" { c.SetMessage("Cannot set READ ONLY setting 'configlocation'", true) c.DrawMessage() @@ -1195,3 +1197,16 @@ func findAvailableFileName(fpath, fname string) (string, error) { return savePath, nil } + +func updateTimeouts(timeoutString string) error { + sec, err := strconv.Atoi(timeoutString) + if err != nil { + return err + } + timeout := time.Duration(sec) * time.Second + + gopher.Timeout = timeout + gemini.TlsTimeout = timeout + + return nil +} diff --git a/defaults.go b/defaults.go index fc47a64..b0f28a8 100644 --- a/defaults.go +++ b/defaults.go @@ -54,9 +54,10 @@ var defaultOptions = map[string]string{ "showimages": "true", "telnetcommand": "telnet", "theme": "normal", // "normal", "inverted", "color" + "timeout": "15", // connection timeout for gopher/gemini in seconds "tlscertificate": "", "tlskey": "", - "webmode": "none", // "none", "gui", "lynx", "w3m", "elinks" + "webmode": "none", // "none", "gui", "lynx", "w3m", "elinks" } // homePath will return the path to your home directory as a string diff --git a/gemini/gemini.go b/gemini/gemini.go index ae5af8e..a3625f2 100644 --- a/gemini/gemini.go +++ b/gemini/gemini.go @@ -26,8 +26,8 @@ type TofuDigest struct { ClientCert tls.Certificate } -var BlockBehavior = "block" -var TlsTimeout = time.Duration(15) * time.Second +var BlockBehavior string = "block" +var TlsTimeout time.Duration = time.Duration(15) * time.Second //------------------------------------------------\\ // + + + R E C E I V E R S + + + \\ diff --git a/gopher/gopher.go b/gopher/gopher.go index 68721d3..fbe579f 100644 --- a/gopher/gopher.go +++ b/gopher/gopher.go @@ -38,6 +38,8 @@ var types = map[string]string{ "T": "TEL", } +var Timeout time.Duration = time.Duration(15) * time.Second + //------------------------------------------------\\ // + + + F U N C T I O N S + + + \\ //--------------------------------------------------\\ @@ -49,7 +51,6 @@ var types = map[string]string{ // be better. func Retrieve(host, port, resource string) ([]byte, error) { nullRes := make([]byte, 0) - timeOut := time.Duration(15) * time.Second if host == "" || port == "" { return nullRes, errors.New("Incomplete request url") @@ -57,7 +58,7 @@ func Retrieve(host, port, resource string) ([]byte, error) { addr := host + ":" + port - conn, err := net.DialTimeout("tcp", addr, timeOut) + conn, err := net.DialTimeout("tcp", addr, Timeout) if err != nil { return nullRes, err } diff --git a/main.go b/main.go index 28fcf36..a9b5d83 100644 --- a/main.go +++ b/main.go @@ -82,6 +82,14 @@ func validateOpt(opt, val string) bool { } return false } + + if opt == "timeout" { + _, err := strconv.Atoi(val) + if err != nil { + return false + } + } + return true } @@ -126,6 +134,8 @@ func loadConfig() { bombadillo.Options[lowerkey] = v.Value if lowerkey == "geminiblocks" { gemini.BlockBehavior = v.Value + } else if lowerkey == "timeout" { + updateTimeouts(v.Value) } } else { bombadillo.Options[lowerkey] = defaultOptions[lowerkey] From 81457d84067b704dee37d3da21f06674956b696c Mon Sep 17 00:00:00 2001 From: sloum Date: Mon, 29 Jun 2020 22:21:43 -0700 Subject: [PATCH 06/12] increments version for release --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 2bf1c1c..f90b1af 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -2.3.1 +2.3.2 From 4ac6c8c2e5a8857f75e45f8eafad0d55628eda30 Mon Sep 17 00:00:00 2001 From: sloum Date: Thu, 2 Jul 2020 10:08:36 -0700 Subject: [PATCH 07/12] Removes unneeded file and simplifies gui mode opening --- http/open_browser_linux.go | 30 ------------------------------ http/open_browser_other.go | 29 ++++++++++++++++++++++++----- 2 files changed, 24 insertions(+), 35 deletions(-) delete mode 100644 http/open_browser_linux.go diff --git a/http/open_browser_linux.go b/http/open_browser_linux.go deleted file mode 100644 index 3b7dfee..0000000 --- a/http/open_browser_linux.go +++ /dev/null @@ -1,30 +0,0 @@ -// +build linux - -package http - -import ( - "fmt" - "os" - "os/exec" -) - -// OpenInBrowser checks for the presence of a display server -// and environment variables indicating a gui is present. If found -// then xdg-open is called on a url to open said url in the default -// gui web browser for the system -func OpenInBrowser(url string) (string, error) { - disp := os.Getenv("DISPLAY") - wayland := os.Getenv("WAYLAND_DISPLAY") - _, err := exec.LookPath("Xorg") - if disp == "" && wayland == "" && err != nil { - return "", fmt.Errorf("No gui is available, check 'webmode' setting") - } - - // Use start rather than run or output in order - // to release the process and not block - err = exec.Command("xdg-open", url).Start() - if err != nil { - return "", err - } - return "Opened in system default web browser", nil -} diff --git a/http/open_browser_other.go b/http/open_browser_other.go index 1388c6b..2f74d22 100644 --- a/http/open_browser_other.go +++ b/http/open_browser_other.go @@ -1,11 +1,30 @@ -// +build !linux -// +build !darwin -// +build !windows +// +build !darwin,!windows package http -import "fmt" +import ( + "fmt" + "os" + "os/exec" +) +// OpenInBrowser checks for the presence of a display server +// and environment variables indicating a gui is present. If found +// then xdg-open is called on a url to open said url in the default +// gui web browser for the system func OpenInBrowser(url string) (string, error) { - return "", fmt.Errorf("Unsupported os for 'webmode' 'gui' setting") + disp := os.Getenv("DISPLAY") + wayland := os.Getenv("WAYLAND_DISPLAY") + _, err := exec.LookPath("Xorg") + if disp == "" && wayland == "" && err != nil { + return "", fmt.Errorf("No gui is available, check 'webmode' setting") + } + + // Use start rather than run or output in order + // to release the process and not block + err = exec.Command("xdg-open", url).Start() + if err != nil { + return "", err + } + return "Opened in system default web browser", nil } From e3a02926387c05a60835bacbbdbbc8730dbda606 Mon Sep 17 00:00:00 2001 From: sloum Date: Thu, 2 Jul 2020 10:19:53 -0700 Subject: [PATCH 08/12] Removes unneeded build tags from darwin and windows files --- http/open_browser_darwin.go | 2 +- http/open_browser_windows.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/http/open_browser_darwin.go b/http/open_browser_darwin.go index dd7da7a..d57a290 100644 --- a/http/open_browser_darwin.go +++ b/http/open_browser_darwin.go @@ -1,4 +1,4 @@ -// +build darwin +// This will build for osx without a build tag based on the filename package http diff --git a/http/open_browser_windows.go b/http/open_browser_windows.go index 0ddf6c7..496d00b 100644 --- a/http/open_browser_windows.go +++ b/http/open_browser_windows.go @@ -1,5 +1,5 @@ -// +build windows - +// This will only build for windows based on the filename +// no build tag required package http import "os/exec" From 21e87706aab34a2853d8db13007db9c2a8cc96a0 Mon Sep 17 00:00:00 2001 From: sloum Date: Thu, 2 Jul 2020 11:51:07 -0700 Subject: [PATCH 09/12] Removes gemini client certificate support from Bombadillo --- bombadillo.1 | 10 +--------- client.go | 4 +--- gemini/gemini.go | 16 +--------------- main.go | 5 +---- 4 files changed, 4 insertions(+), 31 deletions(-) diff --git a/bombadillo.1 b/bombadillo.1 index c8b19a0..8de8859 100644 --- a/bombadillo.1 +++ b/bombadillo.1 @@ -33,7 +33,7 @@ Gopher is the default protocol for \fBbombadillo\fP. Any textual item types will .TP .B gemini -Gemini is supported, but as a new protocol with an incomplete specification, features may change over time. At present Bombadillo supports TLS with a trust on first use certificate pinning system (similar to SSH). Client certificates are also supported as a configurable option. Gemini maps and other text types are rendered in the browser and non-text types will be downloaded. +Gemini is supported, but as a new protocol with an incomplete specification, features may change over time. At present Bombadillo supports TLS with a trust on first use certificate pinning system (similar to SSH). Gemini maps and other text types are rendered in the browser and non-text types will be downloaded. .TP .B finger @@ -257,14 +257,6 @@ theme Can toggle between visual modes. Valid values are \fInormal\fP, \fIcolor\fP, and \fIinverse\fP. When set to inverse, the normal mode colors are inverted. Both normal and inverse modes filter out terminal escape sequences. When set to color, Bombadillo will render terminal escape sequences representing colors when it finds them in documents. .TP .B -tlscertificate -A path to a tls certificate file on a user's local filesystem. Defaults to NULL. Both \fItlscertificate\fP and \fItlskey\fP must be set for client certificates to work in gemini. -.TP -.B -tlskey -A path to a tls key that pairs with the tlscertificate setting, on a user's local filesystem. Defaults to NULL. Both \fItlskey\fP and \fItlscertificate\fP must be set for client certificates to work in gemini. -.TP -.B webmode Controls behavior when following web links. The following values are valid: \fInone\fP will disable following web links, \fIgui\fP will have the browser attempt to open web links in a user's default graphical web browser; \fIlynx\fP, \fIw3m\fP, and \fIelinks\fP will have the browser attempt to use the selected terminal web browser to handle the rendering of web pages and will display the pages directly in Bombadillo. diff --git a/client.go b/client.go index 25d7723..53f50fa 100644 --- a/client.go +++ b/client.go @@ -451,9 +451,7 @@ func (c *client) doCommandAs(action string, values []string) { return } c.Options[values[0]] = lowerCaseOpt(values[0], val) - if values[0] == "tlskey" || values[0] == "tlscertificate" { - c.Certs.LoadCertificate(c.Options["tlscertificate"], c.Options["tlskey"]) - } else if values[0] == "geminiblocks" { + if values[0] == "geminiblocks" { gemini.BlockBehavior = c.Options[values[0]] } else if values[0] == "configlocation" { c.SetMessage("Cannot set READ ONLY setting 'configlocation'", true) diff --git a/gemini/gemini.go b/gemini/gemini.go index 44c8545..40e07d7 100644 --- a/gemini/gemini.go +++ b/gemini/gemini.go @@ -22,7 +22,6 @@ type Capsule struct { type TofuDigest struct { certs map[string]string - ClientCert tls.Certificate } var BlockBehavior = "block" @@ -31,15 +30,6 @@ var BlockBehavior = "block" // + + + R E C E I V E R S + + + \\ //--------------------------------------------------\\ -func (t *TofuDigest) LoadCertificate(cert, key string) { - certificate, err := tls.LoadX509KeyPair(cert, key) - if err != nil { - t.ClientCert = tls.Certificate{} - return - } - t.ClientCert = certificate -} - func (t *TofuDigest) Purge(host string) error { host = strings.ToLower(host) if host == "*" { @@ -185,10 +175,6 @@ func Retrieve(host, port, resource string, td *TofuDigest) (string, error) { InsecureSkipVerify: true, } - conf.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { - return &td.ClientCert, nil - } - conn, err := tls.Dial("tcp", addr, conf) if err != nil { return "", fmt.Errorf("TLS Dial Error: %s", err.Error()) @@ -444,5 +430,5 @@ func MakeCapsule() Capsule { } func MakeTofuDigest() TofuDigest { - return TofuDigest{make(map[string]string), tls.Certificate{}} + return TofuDigest{make(map[string]string)} } diff --git a/main.go b/main.go index 28fcf36..9a35d84 100644 --- a/main.go +++ b/main.go @@ -143,8 +143,8 @@ func loadConfig() { if len(vals) < 2 { continue } - ts, err := strconv.ParseInt(vals[1], 10, 64) now := time.Now() + ts, err := strconv.ParseInt(vals[1], 10, 64) if err != nil || now.Unix() > ts { continue } @@ -158,9 +158,6 @@ func loadConfig() { func initClient() { bombadillo = MakeClient(" ((( Bombadillo ))) ") loadConfig() - if bombadillo.Options["tlscertificate"] != "" && bombadillo.Options["tlskey"] != "" { - bombadillo.Certs.LoadCertificate(bombadillo.Options["tlscertificate"], bombadillo.Options["tlskey"]) - } } // In the event of specific signals, ensure the display is shown correctly. From d7a65e66799be84049daf1f2c4082de55277fcbc Mon Sep 17 00:00:00 2001 From: sloum Date: Thu, 2 Jul 2020 14:47:38 -0700 Subject: [PATCH 10/12] Allows for relative redirects --- client.go | 7 +++++++ gemini/gemini.go | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/client.go b/client.go index c84757a..dafb5af 100644 --- a/client.go +++ b/client.go @@ -999,6 +999,13 @@ func (c *client) handleGemini(u Url) { if strings.Replace(lowerRedirect, lowerOriginal, "", 1) == "/" { c.Visit(capsule.Content) } else { + if !strings.Contains(capsule.Content, "://") { + lnk, lnkErr := gemini.HandleRelativeUrl(capsule.Content, u.Full) + if lnkErr == nil { + capsule.Content = lnk + } + } + c.SetMessage(fmt.Sprintf("Follow redirect (y/n): %s?", capsule.Content), false) c.DrawMessage() ch := cui.Getch() diff --git a/gemini/gemini.go b/gemini/gemini.go index 44c8545..b0a87a8 100644 --- a/gemini/gemini.go +++ b/gemini/gemini.go @@ -399,7 +399,7 @@ func parseGemini(b, currentUrl string) (string, []string) { } if strings.Index(link, "://") < 0 { - link, _ = handleRelativeUrl(link, currentUrl) + link, _ = HandleRelativeUrl(link, currentUrl) } links = append(links, link) @@ -418,7 +418,7 @@ func parseGemini(b, currentUrl string) (string, []string) { } // handleRelativeUrl provides link completion -func handleRelativeUrl(relLink, current string) (string, error) { +func HandleRelativeUrl(relLink, current string) (string, error) { base, err := url.Parse(current) if err != nil { return relLink, err From df4f09a6bb6d592589ed0d3f9c1440c124f94daa Mon Sep 17 00:00:00 2001 From: sloum Date: Fri, 3 Jul 2020 14:56:12 -0700 Subject: [PATCH 11/12] Adds default mime per gemini spec --- gemini/gemini.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/gemini/gemini.go b/gemini/gemini.go index 44c8545..d5cdffb 100644 --- a/gemini/gemini.go +++ b/gemini/gemini.go @@ -331,6 +331,9 @@ func Visit(host, port, resource string, td *TofuDigest) (Capsule, error) { case 2: mimeAndCharset := strings.Split(header[1], ";") meta = mimeAndCharset[0] + if meta == "" { + meta = "text/gemini" + } minMajMime := strings.Split(meta, "/") if len(minMajMime) < 2 { return capsule, fmt.Errorf("Improperly formatted mimetype received from server") From 328d16a191f85efdd06f441eb69f1813dc066b09 Mon Sep 17 00:00:00 2001 From: sloum Date: Fri, 3 Jul 2020 15:04:16 -0700 Subject: [PATCH 12/12] Removes additional cert references and improves error messaging --- defaults.go | 2 -- gemini/gemini.go | 4 ++-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/defaults.go b/defaults.go index fc47a64..7308f1c 100644 --- a/defaults.go +++ b/defaults.go @@ -54,8 +54,6 @@ var defaultOptions = map[string]string{ "showimages": "true", "telnetcommand": "telnet", "theme": "normal", // "normal", "inverted", "color" - "tlscertificate": "", - "tlskey": "", "webmode": "none", // "none", "gui", "lynx", "w3m", "elinks" } diff --git a/gemini/gemini.go b/gemini/gemini.go index 40e07d7..467f616 100644 --- a/gemini/gemini.go +++ b/gemini/gemini.go @@ -268,7 +268,7 @@ func Fetch(host, port, resource string, td *TofuDigest) ([]byte, error) { case 5: return make([]byte, 0), fmt.Errorf("[5] Permanent Failure.") case 6: - return make([]byte, 0), fmt.Errorf("[6] Client Certificate Required") + return make([]byte, 0), fmt.Errorf("[6] Client Certificate Required (Unsupported)") default: return make([]byte, 0), fmt.Errorf("Invalid response status from server") } @@ -345,7 +345,7 @@ func Visit(host, port, resource string, td *TofuDigest) (Capsule, error) { case 5: return capsule, fmt.Errorf("[5] Permanent Failure. %s", header[1]) case 6: - return capsule, fmt.Errorf("[6] Client Certificate Required") + return capsule, fmt.Errorf("[6] Client Certificate Required (Unsupported)") default: return capsule, fmt.Errorf("Invalid response status from server") }