Compare commits

...

21 Commits

Author SHA1 Message Date
Sloom Sloum Sluom IV ba38b78ca6 Merge pull request 'Adds option to handle preformatted code blocks in different ways for gemini' (#148) from gemini-alt-text into release-2.3.0 2020-05-22 17:49:58 -04:00
sloum 0257ca92b1 Adds clarification to man page and removes old comment 2020-05-19 14:40:56 -07:00
Sloom Sloum Sluom IV c34226e4c1 Merge pull request 'Reworks how relative URLs are handled for gemini' (#153) from pathing-fixes into release-2.3.0 2020-05-19 10:36:48 -04:00
sloum 64590f53e7 Resolved merge conflict 2020-05-18 20:14:32 -07:00
sloum b9057508d9 Reworks how relative URLs are handled for gemini 2020-05-18 20:10:02 -07:00
sloum 9f1eb632bc Adds geminiblocks to the man page 2020-05-17 22:01:31 -07:00
sloum b23b9b3121 Fixes the workflow for allowing alt text and handling preformatted blocks 2020-05-17 21:36:49 -07:00
sloum 909131cda8 Lets images be full width 2020-05-15 22:25:56 -07:00
sloum 2bb8272cf9 Combines a few features into a release branch 2020-05-15 22:22:32 -07:00
sloum c508498b42 Merge branch 'gemini-cert-expiry' of https://tildegit.org/sloum/Bombadillo into release-2.3.0 2020-05-15 22:21:38 -07:00
sloum 322002ba66 Adds a max width of 100, anything more gets weird to read. Also adds a slight optimization. 2020-05-15 22:19:09 -07:00
sloum a23e0026fa Adds command aliasing to vim keys for forward and back 2020-05-15 17:32:08 -07:00
sloum c58b40def2 Removes goroutines in search that were causing input issues on gemini cgi scripts 2020-05-14 08:28:39 -07:00
sloum 00313442d4 Hopefully an improvement to the initial way of dealing with expired certs 2020-05-09 16:18:38 -07:00
sloum cb151f75aa Handles cert expirations silently 2020-05-09 11:04:06 -07:00
Sloom Sloum Sluom IV 36ae4a228f Merge pull request 'Release setup for 2.2.1' (#141) from release-setup-2.2.1 into develop 2020-04-23 13:44:46 -04:00
sloum 961bdfc92f Minor release with improved term handling and quick link navigation 2020-04-23 10:41:32 -07:00
sloum 26b3223379 Merge branch 'short-links' of https://tildegit.org/sloum/Bombadillo into release-setup-2.2.1 2020-04-23 10:40:26 -07:00
sloum cdfec887fd Solves the issue where line wrapping is not turned back on 2020-04-14 18:26:03 +00:00
sloum bfb6b85844 Updates man page with quick link information 2020-04-12 22:57:50 -07:00
sloum 9af1a4d642 Adds quick-link navigation by number keys 2020-04-12 22:53:08 -07:00
8 changed files with 154 additions and 40 deletions

View File

@ -1 +1 @@
2.2.0
2.3.0

View File

@ -59,7 +59,7 @@ Displaying web content directly in \fBbombadillo\fP requires lynx, w3m or elinks
These commands work as a single keypress anytime \fBbombadillo\fP is not taking in a line based command or when the user is being prompted for action. This is the default command mode of \fBbombadillo\fP.
.TP
.B
b
b, h
Navigate back one place in your document history.
.TP
.B
@ -71,7 +71,7 @@ d
Scroll down an amount corresponding to 75% of your terminal window height in the current document.
.TP
.B
f
f, l
Navigate forward one place in your document history.
.TP
.B
@ -95,6 +95,7 @@ n
Jump to next found text item.
.TP
.B
N
Jump to previous found text item.
.TP
.B
@ -106,6 +107,10 @@ R
Reload the current page (does not destroy forward history).
.TP
.B
1, 2, 3, 4, 5, 6, 7, 8, 9, 0
Quick navigation to the first 10 links on a page. The 0 key will navigate to the link numbered '10', all other numbers navigate to their matching link number.
.TP
.B
u
Scroll up an amount corresponding to 75% of your terminal window height in the current document.
.TP
@ -228,6 +233,10 @@ defaultscheme
The scheme that should be used when no scheme is present in a given URL. \fIgopher\fP, \fIgemini\fP, \fIhttp\fP, and \fIhttps\fP are valid values.
.TP
.B
geminiblocks
Determines how to treat preformatted text blocks in text/gemini documents. \fIblock\fP will show the contents of the block, \fIalt\fP will show any available alt text for the block, \fIboth\fP will show both the content and the alt text, and \fIneither\fP will show neither. Unlike other settings, a change to this value will require a fresh page load to see the change.
.TP
.B
homeurl
The url that \fBbombadillo\fP navigates to when the program loads or when the \fIhome\fP or \fIh\fP LINE COMMAND is issued. This should be a valid url. If a scheme/protocol is not included, gopher will be assumed.
.TP

View File

@ -162,15 +162,21 @@ func (c *client) TakeControlInput() {
input := cui.Getch()
switch input {
case 'j', 'J':
case '1', '2', '3', '4', '5', '6', '7', '8', '9', '0':
if input == '0' {
c.goToLink("10")
} else {
c.goToLink(string(input))
}
case 'j':
// scroll down one line
c.ClearMessage()
c.Scroll(1)
case 'k', 'K':
case 'k':
// scroll up one line
c.ClearMessage()
c.Scroll(-1)
case 'q', 'Q':
case 'q':
// quit bombadillo
cui.Exit(0, "")
case 'g':
@ -191,7 +197,7 @@ func (c *client) TakeControlInput() {
c.ClearMessage()
distance := c.Height - c.Height/4
c.Scroll(-distance)
case 'b':
case 'b', 'h':
// go back
c.ClearMessage()
err := c.PageState.NavigateHistory(-1)
@ -216,7 +222,7 @@ func (c *client) TakeControlInput() {
// open the bookmarks browser
c.BookMarks.ToggleOpen()
c.Draw()
case 'f', 'F':
case 'f', 'l':
// go forward
c.ClearMessage()
err := c.PageState.NavigateHistory(1)
@ -464,6 +470,8 @@ func (c *client) doCommandAs(action string, values []string) {
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" {
gemini.BlockBehavior = c.Options[values[0]]
} else if values[0] == "configlocation" {
c.SetMessage("Cannot set READ ONLY setting 'configlocation'", true)
c.DrawMessage()
@ -705,11 +713,11 @@ func (c *client) search(query, url, question string) {
}
switch u.Scheme {
case "gopher":
go c.Visit(fmt.Sprintf("%s\t%s", u.Full, entry))
c.Visit(fmt.Sprintf("%s\t%s", u.Full, entry))
case "gemini":
// TODO url escape the entry variable
escapedEntry := entry
go c.Visit(fmt.Sprintf("%s?%s", u.Full, escapedEntry))
c.Visit(fmt.Sprintf("%s?%s", u.Full, escapedEntry))
case "http", "https":
c.Visit(u.Full)
default:
@ -971,8 +979,10 @@ func (c *client) handleGemini(u Url) {
go saveConfig()
switch capsule.Status {
case 1:
// Query
c.search("", u.Full, capsule.Content)
case 2:
// Success
if capsule.MimeMaj == "text" || (c.Options["showimages"] == "true" && capsule.MimeMaj == "image") {
pg := MakePage(u, capsule.Content, capsule.Links)
pg.FileType = capsule.MimeMaj
@ -990,14 +1000,21 @@ func (c *client) handleGemini(u Url) {
c.saveFileFromData(capsule.Content, filename)
}
case 3:
c.SetMessage(fmt.Sprintf("Follow redirect (y/n): %s?", capsule.Content), false)
c.DrawMessage()
ch := cui.Getch()
if ch == 'y' || ch == 'Y' {
// Redirect
lowerRedirect := strings.ToLower(capsule.Content)
lowerOriginal := strings.ToLower(u.Full)
if strings.Replace(lowerRedirect, lowerOriginal, "", 1) == "/" {
c.Visit(capsule.Content)
} else {
c.SetMessage("Redirect aborted", false)
c.SetMessage(fmt.Sprintf("Follow redirect (y/n): %s?", capsule.Content), false)
c.DrawMessage()
ch := cui.Getch()
if ch == 'y' || ch == 'Y' {
c.Visit(capsule.Content)
} else {
c.SetMessage("Redirect aborted", false)
c.DrawMessage()
}
}
}
}

View File

@ -56,8 +56,8 @@ func Exit(exitCode int, msg string) {
// InitTerm sets the terminal modes appropriate for Bombadillo
func InitTerm() {
SetCharMode()
Tput("rmam") // turn off line wrapping
Tput("smcup") // use alternate screen
Tput("rmam") // turn off line wrapping
}
// CleanupTerm reverts changs to terminal mode made by InitTerm

View File

@ -47,6 +47,7 @@ var defaultOptions = map[string]string{
"configlocation": xdgConfigPath(),
"defaultscheme": "gopher", // "gopher", "gemini", "http", "https"
"geminiblocks": "block", // "block", "alt", "neither", "both"
"homeurl": "gopher://bombadillo.colorfield.space:70/1/user-guide.map",
"savelocation": homePath(),
"searchengine": "gopher://gopher.floodgap.com:70/7/v2/vs",

View File

@ -24,6 +24,8 @@ type TofuDigest struct {
ClientCert tls.Certificate
}
var BlockBehavior = "block"
//------------------------------------------------\\
// + + + R E C E I V E R S + + + \\
//--------------------------------------------------\\
@ -49,8 +51,8 @@ func (t *TofuDigest) Purge(host string) error {
return fmt.Errorf("Invalid host %q", host)
}
func (t *TofuDigest) Add(host, hash string) {
t.certs[strings.ToLower(host)] = hash
func (t *TofuDigest) Add(host, hash string, time int64) {
t.certs[strings.ToLower(host)] = fmt.Sprintf("%s|%d", hash, time)
}
func (t *TofuDigest) Exists(host string) bool {
@ -67,12 +69,11 @@ func (t *TofuDigest) Find(host string) (string, error) {
return "", fmt.Errorf("Invalid hostname, no key saved")
}
func (t *TofuDigest) Match(host string, cState *tls.ConnectionState) error {
host = strings.ToLower(host)
func (t *TofuDigest) Match(host, localCert string, cState *tls.ConnectionState) error {
now := time.Now()
for _, cert := range cState.PeerCertificates {
if t.certs[host] != hashCert(cert.Raw) {
if localCert != hashCert(cert.Raw) {
continue
}
@ -118,13 +119,40 @@ func (t *TofuDigest) newCert(host string, cState *tls.ConnectionState) error {
continue
}
t.Add(host, hashCert(cert.Raw))
t.Add(host, hashCert(cert.Raw), cert.NotAfter.Unix())
return nil
}
return fmt.Errorf(reasons.String())
}
func (t *TofuDigest) GetCertAndTimestamp(host string) (string, int64, error) {
certTs, err := t.Find(host)
if err != nil {
return "", -1, err
}
certTsSplit := strings.SplitN(certTs, "|", -1)
if len(certTsSplit) < 2 {
_ = t.Purge(host)
return certTsSplit[0], -1, fmt.Errorf("Invalid certstring, no delimiter")
}
ts, err := strconv.ParseInt(certTsSplit[1], 10, 64)
if err != nil {
_ = t.Purge(host)
return certTsSplit[0], -1, err
}
now := time.Now()
if ts < now.Unix() {
// Ignore error return here since an error would indicate
// the host does not exist and we have already checked for
// that and the desired outcome of the action is that the
// host will no longer exist, so we are good either way
_ = t.Purge(host)
return "", -1, fmt.Errorf("Expired cert")
}
return certTsSplit[0], ts, nil
}
func (t *TofuDigest) IniDump() string {
if len(t.certs) < 1 {
return ""
@ -176,9 +204,11 @@ func Retrieve(host, port, resource string, td *TofuDigest) (string, error) {
return "", fmt.Errorf("Insecure, no certificates offered by server")
}
if td.Exists(host) {
localCert, localTs, err := td.GetCertAndTimestamp(host)
if localTs > 0 {
// See if we have a matching cert
err := td.Match(host, &connState)
err := td.Match(host, localCert, &connState)
if err != nil && err.Error() != "EXP" {
// If there is no match and it isnt because of an expiration
// just return the error
@ -339,16 +369,23 @@ func parseGemini(b, rootUrl, currentUrl string) (string, []string) {
splitContent := strings.Split(b, "\n")
links := make([]string, 0, 10)
inPreBlock := false
outputIndex := 0
for i, ln := range splitContent {
splitContent[i] = strings.Trim(ln, "\r\n")
if ln == "```" {
// By continuing we create a variance between i and outputIndex
// the other branches here will write to the outputIndex rather
// than i, thus removing these lines while itterating without
// needing mroe allocations.
continue
} else if len([]rune(ln)) > 3 && ln[:2] == "=>" {
isPreBlockDeclaration := strings.HasPrefix(ln, "```")
if isPreBlockDeclaration && !inPreBlock && (BlockBehavior == "both" || BlockBehavior == "alt") {
inPreBlock = !inPreBlock
alt := strings.TrimSpace(ln)
if len(alt) > 3 {
alt = strings.TrimSpace(alt[3:])
splitContent[outputIndex] = fmt.Sprintf("[ %s ]", alt)
outputIndex++
}
} else if isPreBlockDeclaration {
inPreBlock = !inPreBlock
} else if len([]rune(ln)) > 3 && ln[:2] == "=>" && !inPreBlock {
var link, decorator string
subLn := strings.Trim(ln[2:], "\r\n\t \a")
splitPoint := strings.IndexAny(subLn, " \t")
@ -370,6 +407,9 @@ func parseGemini(b, rootUrl, currentUrl string) (string, []string) {
splitContent[outputIndex] = fmt.Sprintf("%-5s %s", linknum, decorator)
outputIndex++
} else {
if inPreBlock && (BlockBehavior == "alt" || BlockBehavior == "neither") {
continue
}
splitContent[outputIndex] = ln
outputIndex++
}
@ -381,18 +421,38 @@ func handleRelativeUrl(u, root, current string) string {
if len(u) < 1 {
return u
}
currentIsDir := (current[len(current)-1] == '/')
if u[0] == '/' {
return fmt.Sprintf("%s%s", root, u)
} else if strings.HasPrefix(u, "../") {
currentDir := strings.LastIndex(current, "/")
if currentIsDir {
upOne := strings.LastIndex(current[:currentDir], "/")
dirRoot := current[:upOne]
return dirRoot + u[2:]
}
return current[:currentDir] + u[2:]
}
if strings.HasPrefix(u, "./") {
if len(u) == 2 {
return current
}
u = u[2:]
}
if currentIsDir {
indPrevDir := strings.LastIndex(current[:len(current)-1], "/")
if indPrevDir < 9 {
return current + u
}
return current[:indPrevDir+1] + u
}
ind := strings.LastIndex(current, "/")
if ind < 10 {
return fmt.Sprintf("%s/%s", root, u)
}
current = current[:ind+1]
return fmt.Sprintf("%s%s", current, u)
return current + u
}
func hashCert(cert []byte) string {

25
main.go
View File

@ -25,12 +25,14 @@ import (
"os"
"os/signal"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
"tildegit.org/sloum/bombadillo/config"
"tildegit.org/sloum/bombadillo/cui"
_ "tildegit.org/sloum/bombadillo/gemini"
"tildegit.org/sloum/bombadillo/gemini"
)
var version string
@ -66,6 +68,7 @@ func validateOpt(opt, val string) bool {
"theme": []string{"normal", "inverse", "color"},
"defaultscheme": []string{"gopher", "gemini", "http", "https"},
"showimages": []string{"true", "false"},
"geminiblocks": []string{"block", "neither", "alt", "both"},
}
opt = strings.ToLower(opt)
@ -84,7 +87,7 @@ func validateOpt(opt, val string) bool {
func lowerCaseOpt(opt, val string) string {
switch opt {
case "webmode", "theme", "defaultscheme", "showimages":
case "webmode", "theme", "defaultscheme", "showimages", "geminiblocks":
return strings.ToLower(val)
default:
return val
@ -121,6 +124,9 @@ func loadConfig() {
if _, ok := bombadillo.Options[lowerkey]; ok {
if validateOpt(lowerkey, v.Value) {
bombadillo.Options[lowerkey] = v.Value
if lowerkey == "geminiblocks" {
gemini.BlockBehavior = v.Value
}
} else {
bombadillo.Options[lowerkey] = defaultOptions[lowerkey]
}
@ -132,7 +138,20 @@ func loadConfig() {
}
for _, v := range settings.Certs {
bombadillo.Certs.Add(v.Key, v.Value)
// Remove expired certs
vals := strings.SplitN(v.Value, "|", -1)
if len(vals) < 2 {
continue
}
ts, err := strconv.ParseInt(vals[1], 10, 64)
now := time.Now()
if err != nil || now.Unix() > ts {
continue
}
// Satisfied that the cert is not expired
// or malformed: add to the current client
// instance
bombadillo.Certs.Add(v.Key, vals[0], ts)
}
}

10
page.go
View File

@ -70,7 +70,9 @@ func (p *Page) WrapContent(width int, color bool) {
p.RenderImage(width)
return
}
width = min(width, 100)
counter := 0
spacer := " "
var content strings.Builder
var esc strings.Builder
escape := false
@ -124,7 +126,6 @@ func (p *Page) WrapContent(width int, color bool) {
content.WriteRune('\n')
counter = 0
if p.Location.Mime == "1" {
spacer := " "
content.WriteString(spacer)
counter += len(spacer)
}
@ -188,3 +189,10 @@ func MakePage(url Url, content string, links []string) Page {
p := Page{make([]string, 0), content, links, url, 0, make([]int, 0), "", 0, "", 40, false}
return p
}
func min(a, b int) int {
if a < b {
return a
}
return b
}