Skip to content

Commit

Permalink
browse: Added configuration and persistent host certificate store.
Browse files Browse the repository at this point in the history
  • Loading branch information
a-h committed Sep 9, 2020
1 parent 680c28b commit 2f87f75
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 25 deletions.
113 changes: 96 additions & 17 deletions browse/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ package main

import (
"bufio"
"bytes"
"crypto/tls"
"fmt"
"io"
"net/url"
"os"
"path"
"strconv"
"strings"
"time"
"unicode"
Expand All @@ -15,19 +18,99 @@ import (
"github.com/a-h/gemini/cert"
"github.com/gdamore/tcell"
"github.com/mattn/go-runewidth"
"github.com/natefinch/atomic"
"github.com/pkg/browser"
)

type Config struct {
Home string
Width int
HostCertificates map[string]string
}

func (c *Config) configFileName() string {
home, _ := os.UserHomeDir()
return path.Join(home, ".min/config.ini")
}

func (c *Config) Save() error {
b := new(bytes.Buffer)
fmt.Fprintf(b, "home=%v\n", c.Home)
fmt.Fprintf(b, "width=%v\n", c.Width)
for host, cert := range c.HostCertificates {
fmt.Fprintf(b, "hostcert/%v=%v\n", host, cert)
}
fn := c.configFileName()
os.MkdirAll(path.Dir(fn), os.ModePerm)
return atomic.WriteFile(fn, b)
}

func NewConfig() (c *Config, err error) {
c = &Config{
Home: "gemini://gus.guru",
Width: 80,
HostCertificates: map[string]string{},
}
lines, err := readLines(c.configFileName())
if err != nil {
return
}
for _, l := range lines {
kv := strings.SplitN(l, "=", 2)
if len(kv) != 2 {
continue
}
k, v := strings.ToLower(strings.TrimSpace(kv[0])), strings.TrimSpace(kv[1])
switch k {
case "home":
c.Home = v
case "width":
w, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return c, err
}
c.Width = int(w)
}
if strings.HasPrefix(k, "hostcert/") {
host := strings.TrimPrefix(k, "hostcert/")
c.HostCertificates[host] = v
}
}
return
}

func readLines(fn string) (lines []string, err error) {
f, err := os.Open(fn)
if err != nil {
if os.IsNotExist(err) {
err = nil
}
return
}
defer f.Close()
scanner := bufio.NewScanner(f)
for scanner.Scan() {
lines = append(lines, scanner.Text())
}
err = scanner.Err()
return
}

func main() {
conf, err := NewConfig()
if err != nil {
fmt.Println("Error loading config:", err)
os.Exit(1)
}

// Create a screen.
tcell.SetEncodingFallback(tcell.EncodingFallbackASCII)
s, err := tcell.NewScreen()
if err != nil {
fmt.Println("Error creating screen:", err)
os.Exit(1)
}
err = s.Init()
if err != nil {
if err = s.Init(); err != nil {
fmt.Println("Error initializing screen:", err)
os.Exit(1)
}
Expand All @@ -43,12 +126,14 @@ func main() {
// Parse the command-line URL, if provided.
urlString := strings.Join(os.Args[1:], "")
if urlString == "" {
//TODO: Load up a home page based on configuration.
urlString = "gemini://localhost"
urlString = conf.Home
}

// Create required top-level variables.
client := gemini.NewClient()
for host, cert := range conf.HostCertificates {
client.AddAlllowedCertificateForHost(host, cert)
}
var redirectCount int

var askForURL, ok bool
Expand All @@ -75,7 +160,6 @@ func main() {
var certificates []string
out:
for {
//TODO: Add cert store etc. to the client.
resp, certificates, _, ok, err = client.RequestURL(u)
if err != nil {
switch NewOptions(s, fmt.Sprintf("Request error: %v", err), "Retry", "Cancel").Focus() {
Expand All @@ -90,7 +174,8 @@ func main() {
//TOFU check required.
switch NewOptions(s, fmt.Sprintf("Accept client certificate?\n %v", certificates[0]), "Accept (Permanent)", "Accept (Temporary)", "Reject").Focus() {
case "Accept (Permanent)":
//TODO: Save this in a persistent store.
conf.HostCertificates[u.Host] = certificates[0]
conf.Save()
client.AddAlllowedCertificateForHost(u.Host, certificates[0])
askForURL = false
continue
Expand Down Expand Up @@ -121,7 +206,7 @@ func main() {
askForURL = false
continue
}
// Check with the user if the redirect is to another domain or protocol.
// Check with the user if the redirect is to another protocol or domain.
redirectTo = u.ResolveReference(redirectTo)
if redirectTo.Scheme != "gemini" {
if open := NewOptions(s, fmt.Sprintf("Follow non-gemini redirect?\n\n %v", redirectTo.String()), "Yes", "No").Focus(); open == "Yes" {
Expand All @@ -132,7 +217,6 @@ func main() {
}
if redirectTo.Host != u.Host {
if open := NewOptions(s, fmt.Sprintf("Follow cross-domain redirect?\n\n %v", redirectTo.String()), "Yes", "No").Focus(); open == "No" {
// Go back.
askForURL = false
continue
}
Expand Down Expand Up @@ -182,7 +266,7 @@ func main() {
continue
}
if strings.HasPrefix(string(resp.Header.Code), "2") {
b, err := NewBrowser(s, u, resp)
b, err := NewBrowser(s, conf.Width, u, resp)
if err != nil {
NewOptions(s, fmt.Sprintf("Error displaying server response:\n\n%v", err), "OK").Focus()
askForURL = true
Expand Down Expand Up @@ -335,8 +419,6 @@ func NewOptions(s tcell.Screen, msg string, opts ...string) *Options {
}
return &Options{
Screen: s,
X: 0,
Y: 0,
Style: tcell.StyleDefault,
Message: msg,
Options: opts,
Expand Down Expand Up @@ -433,7 +515,6 @@ func (lc *LineConverter) process(s string) (l Line, isVisualLine bool) {
return l, false
}
if lc.preFormatted {
// PreformattedTextLine doesn't have a max width.
return PreformattedTextLine{Text: s}, true
}
if strings.HasPrefix(s, "=>") {
Expand Down Expand Up @@ -496,7 +577,6 @@ func (l PreformattedTextLine) Draw(to tcell.Screen, atX, atY int, highlighted bo
c = ' '
w = 1
}
//TODO: Be able to style this via config?
to.SetContent(atX, atY, c, comb, tcell.StyleDefault)
atX += w
}
Expand Down Expand Up @@ -557,17 +637,16 @@ func (l QuoteLine) Draw(to tcell.Screen, atX, atY int, highlighted bool) (x, y i
return NewText(to, l.Text).WithOffset(atX+2, atY).WithMaxWidth(l.MaxWidth).WithStyle(tcell.StyleDefault.Foreground(tcell.ColorLightGrey)).Draw()
}

func NewBrowser(s tcell.Screen, u *url.URL, resp *gemini.Response) (b *Browser, err error) {
func NewBrowser(s tcell.Screen, w int, u *url.URL, resp *gemini.Response) (b *Browser, err error) {
b = &Browser{
Screen: s,
URL: u,
ResponseHeader: resp.Header,
ActiveLineIndex: -1,
}
maxWidth, _ := s.Size()
//TODO: Configure the max width of non-preformatted lines.
if maxWidth > 80 {
maxWidth = 80
if maxWidth > w {
maxWidth = w
}
b.Lines, err = NewLineConverter(resp, maxWidth).Lines()
b.calculateLinkIndices()
Expand Down
16 changes: 13 additions & 3 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,20 @@ func readUntilCrLf(src io.Reader, maxLength int) (output []byte, ok bool, err er
return
}

var validStart map[byte]bool = map[byte]bool{
'1': true,
'2': true,
'3': true,
'4': true,
'5': true,
'6': true,
}

func isValidCode(code Code) bool {
return len(code) == 2 &&
(code[0] >= 49 && code[0] <= 54) &&
(code[1] >= 48 && code[1] <= 57)
if len(code) == 0 {
return false
}
return validStart[code[0]]
}

func isValidMeta(m string) bool {
Expand Down
5 changes: 4 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ go 1.14

require (
github.com/gdamore/tcell v1.3.0
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
github.com/mattn/go-runewidth v0.0.9
github.com/natefinch/atomic v0.0.0-20200526193002-18c0533a5b09
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4
github.com/rivo/tview v0.0.0-20200818120338-53d50e499bf9 // indirect
golang.org/x/sys v0.0.0-20200817155316-9781c653f443 // indirect
golang.org/x/text v0.3.2 // indirect
)
7 changes: 3 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,12 @@ github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/natefinch/atomic v0.0.0-20200526193002-18c0533a5b09 h1:DXR0VtCesBD2ss3toN9OEeXszpQmW9dc3SvUbUfiBC0=
github.com/natefinch/atomic v0.0.0-20200526193002-18c0533a5b09/go.mod h1:1rLVY/DWf3U6vSZgH16S7pymfrhK2lcUlXjgGglw/lY=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 h1:49lOXmGaUpV9Fz3gd7TFZY106KVlPVa5jcYD1gaQf98=
github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA=
github.com/rivo/tview v0.0.0-20200818120338-53d50e499bf9 h1:csnip7QsoiE2Ee0RkELN1YggwejK2EFfcjU6tXOT0Q8=
github.com/rivo/tview v0.0.0-20200818120338-53d50e499bf9/go.mod h1:xV4Aw4WIX8cmhg71U7MUHBdpIQ7zSEXdRruGHLaEAOc=
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200817155316-9781c653f443 h1:X18bCaipMcoJGm27Nv7zr4XYPKGUy92GtqboKC2Hxaw=
golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
Expand Down

0 comments on commit 2f87f75

Please sign in to comment.