Reworks the protocol handling and makes html downloads work
This commit is contained in:
parent
e5c0bd7443
commit
67d7df6804
402
client.go
402
client.go
|
@ -514,6 +514,8 @@ func (c *client) saveFile(u Url, name string) {
|
||||||
file, err = gopher.Retrieve(u.Host, u.Port, u.Resource)
|
file, err = gopher.Retrieve(u.Host, u.Port, u.Resource)
|
||||||
case "gemini":
|
case "gemini":
|
||||||
file, err = gemini.Fetch(u.Host, u.Port, u.Resource, &c.Certs)
|
file, err = gemini.Fetch(u.Host, u.Port, u.Resource, &c.Certs)
|
||||||
|
case "http", "https":
|
||||||
|
file, err = http.Fetch(u.Full)
|
||||||
default:
|
default:
|
||||||
c.SetMessage(fmt.Sprintf("Saving files over %s is not supported", u.Scheme), true)
|
c.SetMessage(fmt.Sprintf("Saving files over %s is not supported", u.Scheme), true)
|
||||||
c.DrawMessage()
|
c.DrawMessage()
|
||||||
|
@ -737,6 +739,21 @@ func (c *client) Scroll(amount int) {
|
||||||
c.Draw()
|
c.Draw()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *client) ReloadPage() error {
|
||||||
|
if c.PageState.Length < 1 {
|
||||||
|
return fmt.Errorf("There is no page to reload")
|
||||||
|
}
|
||||||
|
url := c.PageState.History[c.PageState.Position].Location.Full
|
||||||
|
err := c.PageState.NavigateHistory(-1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
length := c.PageState.Length
|
||||||
|
c.Visit(url)
|
||||||
|
c.PageState.Length = length
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (c *client) SetPercentRead() {
|
func (c *client) SetPercentRead() {
|
||||||
page := c.PageState.History[c.PageState.Position]
|
page := c.PageState.History[c.PageState.Position]
|
||||||
var percentRead int
|
var percentRead int
|
||||||
|
@ -834,6 +851,8 @@ func (c *client) SetHeaderUrl() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Visit functions as a controller/router to the
|
||||||
|
// appropriate protocol handler
|
||||||
func (c *client) Visit(url string) {
|
func (c *client) Visit(url string) {
|
||||||
c.SetMessage("Loading...", false)
|
c.SetMessage("Loading...", false)
|
||||||
c.DrawMessage()
|
c.DrawMessage()
|
||||||
|
@ -848,137 +867,199 @@ func (c *client) Visit(url string) {
|
||||||
|
|
||||||
switch u.Scheme {
|
switch u.Scheme {
|
||||||
case "gopher":
|
case "gopher":
|
||||||
if u.DownloadOnly {
|
c.handleGopher(u)
|
||||||
nameSplit := strings.Split(u.Resource, "/")
|
case "gemini":
|
||||||
filename := nameSplit[len(nameSplit)-1]
|
c.handleGemini(u)
|
||||||
filename = strings.Trim(filename, " \t\r\n\v\f\a")
|
case "telnet":
|
||||||
if filename == "" {
|
c.handleTelnet(u)
|
||||||
filename = "gopherfile"
|
case "http", "https":
|
||||||
}
|
c.handleWeb(u)
|
||||||
c.saveFile(u, filename)
|
case "local":
|
||||||
} else if u.Mime == "7" {
|
c.handleLocal(u)
|
||||||
c.search("", u.Full, "?")
|
case "finger":
|
||||||
} else {
|
c.handleFinger(u)
|
||||||
content, links, err := gopher.Visit(u.Mime, u.Host, u.Port, u.Resource)
|
default:
|
||||||
if err != nil {
|
c.SetMessage(fmt.Sprintf("%q is not a supported protocol", u.Scheme), true)
|
||||||
c.SetMessage(err.Error(), true)
|
c.DrawMessage()
|
||||||
c.DrawMessage()
|
}
|
||||||
return
|
}
|
||||||
}
|
|
||||||
pg := MakePage(u, content, links)
|
|
||||||
|
// +++ Begin Protocol Handlers +++
|
||||||
|
|
||||||
|
func (c *client) handleGopher(u Url) {
|
||||||
|
if u.DownloadOnly {
|
||||||
|
nameSplit := strings.Split(u.Resource, "/")
|
||||||
|
filename := nameSplit[len(nameSplit)-1]
|
||||||
|
filename = strings.Trim(filename, " \t\r\n\v\f\a")
|
||||||
|
if filename == "" {
|
||||||
|
filename = "gopherfile"
|
||||||
|
}
|
||||||
|
c.saveFile(u, filename)
|
||||||
|
} else if u.Mime == "7" {
|
||||||
|
c.search("", u.Full, "?")
|
||||||
|
} else {
|
||||||
|
content, links, err := gopher.Visit(u.Mime, u.Host, u.Port, u.Resource)
|
||||||
|
if err != nil {
|
||||||
|
c.SetMessage(err.Error(), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pg := MakePage(u, content, links)
|
||||||
|
pg.WrapContent(c.Width - 1)
|
||||||
|
c.PageState.Add(pg)
|
||||||
|
c.SetPercentRead()
|
||||||
|
c.ClearMessage()
|
||||||
|
c.SetHeaderUrl()
|
||||||
|
c.Draw()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) handleGemini(u Url) {
|
||||||
|
capsule, err := gemini.Visit(u.Host, u.Port, u.Resource, &c.Certs)
|
||||||
|
if err != nil {
|
||||||
|
c.SetMessage(err.Error(), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go saveConfig()
|
||||||
|
switch capsule.Status {
|
||||||
|
case 1:
|
||||||
|
c.search("", u.Full, capsule.Content)
|
||||||
|
case 2:
|
||||||
|
if capsule.MimeMaj == "text" {
|
||||||
|
pg := MakePage(u, capsule.Content, capsule.Links)
|
||||||
pg.WrapContent(c.Width - 1)
|
pg.WrapContent(c.Width - 1)
|
||||||
c.PageState.Add(pg)
|
c.PageState.Add(pg)
|
||||||
c.SetPercentRead()
|
c.SetPercentRead()
|
||||||
c.ClearMessage()
|
c.ClearMessage()
|
||||||
c.SetHeaderUrl()
|
c.SetHeaderUrl()
|
||||||
c.Draw()
|
c.Draw()
|
||||||
}
|
|
||||||
case "gemini":
|
|
||||||
capsule, err := gemini.Visit(u.Host, u.Port, u.Resource, &c.Certs)
|
|
||||||
if err != nil {
|
|
||||||
c.SetMessage(err.Error(), true)
|
|
||||||
c.DrawMessage()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
go saveConfig()
|
|
||||||
switch capsule.Status {
|
|
||||||
case 1:
|
|
||||||
c.search("", u.Full, capsule.Content)
|
|
||||||
case 2:
|
|
||||||
if capsule.MimeMaj == "text" {
|
|
||||||
pg := MakePage(u, capsule.Content, capsule.Links)
|
|
||||||
pg.WrapContent(c.Width - 1)
|
|
||||||
c.PageState.Add(pg)
|
|
||||||
c.SetPercentRead()
|
|
||||||
c.ClearMessage()
|
|
||||||
c.SetHeaderUrl()
|
|
||||||
c.Draw()
|
|
||||||
} else {
|
|
||||||
c.SetMessage("The file is non-text: (o)pen or (w)rite to disk", false)
|
|
||||||
c.DrawMessage()
|
|
||||||
var ch rune
|
|
||||||
for {
|
|
||||||
ch = cui.Getch()
|
|
||||||
if ch == 'o' || ch == 'w' {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch ch {
|
|
||||||
case 'o':
|
|
||||||
mime := fmt.Sprintf("%s/%s", capsule.MimeMaj, capsule.MimeMin)
|
|
||||||
var term bool
|
|
||||||
if c.Options["terminalonly"] == "true" {
|
|
||||||
term = true
|
|
||||||
} else {
|
|
||||||
term = false
|
|
||||||
}
|
|
||||||
mcEntry, err := mc.FindMatch(mime, "view", term)
|
|
||||||
if err != nil {
|
|
||||||
c.SetMessage(err.Error(), true)
|
|
||||||
c.DrawMessage()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
file, err := ioutil.TempFile("/tmp/", "bombadillo-*.tmp")
|
|
||||||
if err != nil {
|
|
||||||
c.SetMessage("Unable to create temporary file for opening, aborting file open", true)
|
|
||||||
c.DrawMessage()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// defer os.Remove(file.Name())
|
|
||||||
file.Write([]byte(capsule.Content))
|
|
||||||
com, e := mcEntry.Command(file.Name())
|
|
||||||
if e != nil {
|
|
||||||
c.SetMessage(e.Error(), true)
|
|
||||||
c.DrawMessage()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
com.Stdin = os.Stdin
|
|
||||||
com.Stdout = os.Stdout
|
|
||||||
com.Stderr = os.Stderr
|
|
||||||
if c.Options["terminalonly"] == "true" {
|
|
||||||
cui.Clear("screen")
|
|
||||||
}
|
|
||||||
com.Run()
|
|
||||||
c.SetMessage("File opened by an appropriate program", true)
|
|
||||||
c.DrawMessage()
|
|
||||||
c.Draw()
|
|
||||||
case 'w':
|
|
||||||
nameSplit := strings.Split(u.Resource, "/")
|
|
||||||
filename := nameSplit[len(nameSplit)-1]
|
|
||||||
c.saveFileFromData(capsule.Content, filename)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case 3:
|
|
||||||
c.SetMessage("[3] Redirect. Follow redirect? y or any other key for no", false)
|
|
||||||
c.DrawMessage()
|
|
||||||
ch := cui.Getch()
|
|
||||||
if ch == 'y' || ch == 'Y' {
|
|
||||||
c.Visit(capsule.Content)
|
|
||||||
} else {
|
|
||||||
c.SetMessage("Redirect aborted", false)
|
|
||||||
c.DrawMessage()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case "telnet":
|
|
||||||
c.SetMessage("Attempting to start telnet session", false)
|
|
||||||
c.DrawMessage()
|
|
||||||
msg, err := telnet.StartSession(u.Host, u.Port)
|
|
||||||
if err != nil {
|
|
||||||
c.SetMessage(err.Error(), true)
|
|
||||||
c.DrawMessage()
|
|
||||||
} else {
|
} else {
|
||||||
c.SetMessage(msg, true)
|
c.SetMessage("The file is non-text: (o)pen or (w)rite to disk", false)
|
||||||
|
c.DrawMessage()
|
||||||
|
var ch rune
|
||||||
|
for {
|
||||||
|
ch = cui.Getch()
|
||||||
|
if ch == 'o' || ch == 'w' {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch ch {
|
||||||
|
case 'o':
|
||||||
|
mime := fmt.Sprintf("%s/%s", capsule.MimeMaj, capsule.MimeMin)
|
||||||
|
var term bool
|
||||||
|
if c.Options["terminalonly"] == "true" {
|
||||||
|
term = true
|
||||||
|
} else {
|
||||||
|
term = false
|
||||||
|
}
|
||||||
|
mcEntry, err := mc.FindMatch(mime, "view", term)
|
||||||
|
if err != nil {
|
||||||
|
c.SetMessage(err.Error(), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
file, err := ioutil.TempFile("/tmp/", "bombadillo-*.tmp")
|
||||||
|
if err != nil {
|
||||||
|
c.SetMessage("Unable to create temporary file for opening, aborting file open", true)
|
||||||
|
c.DrawMessage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// defer os.Remove(file.Name())
|
||||||
|
file.Write([]byte(capsule.Content))
|
||||||
|
com, e := mcEntry.Command(file.Name())
|
||||||
|
if e != nil {
|
||||||
|
c.SetMessage(e.Error(), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
com.Stdin = os.Stdin
|
||||||
|
com.Stdout = os.Stdout
|
||||||
|
com.Stderr = os.Stderr
|
||||||
|
if c.Options["terminalonly"] == "true" {
|
||||||
|
cui.Clear("screen")
|
||||||
|
}
|
||||||
|
com.Run()
|
||||||
|
c.SetMessage("File opened by an appropriate program", true)
|
||||||
|
c.DrawMessage()
|
||||||
|
c.Draw()
|
||||||
|
case 'w':
|
||||||
|
nameSplit := strings.Split(u.Resource, "/")
|
||||||
|
filename := nameSplit[len(nameSplit)-1]
|
||||||
|
c.saveFileFromData(capsule.Content, filename)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 3:
|
||||||
|
c.SetMessage("[3] Redirect. Follow redirect? y or any other key for no", false)
|
||||||
|
c.DrawMessage()
|
||||||
|
ch := cui.Getch()
|
||||||
|
if ch == 'y' || ch == 'Y' {
|
||||||
|
c.Visit(capsule.Content)
|
||||||
|
} else {
|
||||||
|
c.SetMessage("Redirect aborted", false)
|
||||||
c.DrawMessage()
|
c.DrawMessage()
|
||||||
}
|
}
|
||||||
c.Draw()
|
}
|
||||||
case "http", "https":
|
}
|
||||||
if strings.ToUpper(c.Options["openhttp"]) != "TRUE" {
|
|
||||||
c.SetMessage("'openhttp' is not set to true, cannot open web link", false)
|
func (c *client) handleTelnet(u Url) {
|
||||||
c.DrawMessage()
|
c.SetMessage("Attempting to start telnet session", false)
|
||||||
return
|
c.DrawMessage()
|
||||||
}
|
msg, err := telnet.StartSession(u.Host, u.Port)
|
||||||
switch strings.ToUpper(c.Options["lynxmode"]) {
|
if err != nil {
|
||||||
case "TRUE":
|
c.SetMessage(err.Error(), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
} else {
|
||||||
|
c.SetMessage(msg, true)
|
||||||
|
c.DrawMessage()
|
||||||
|
}
|
||||||
|
c.Draw()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) handleLocal(u Url) {
|
||||||
|
content, err := local.Open(u.Resource)
|
||||||
|
if err != nil {
|
||||||
|
c.SetMessage(err.Error(), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pg := MakePage(u, content, []string{})
|
||||||
|
pg.WrapContent(c.Width - 1)
|
||||||
|
c.PageState.Add(pg)
|
||||||
|
c.SetPercentRead()
|
||||||
|
c.ClearMessage()
|
||||||
|
c.SetHeaderUrl()
|
||||||
|
c.Draw()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) handleFinger(u Url) {
|
||||||
|
content, err := finger.Finger(u.Host, u.Port, u.Resource)
|
||||||
|
if err != nil {
|
||||||
|
c.SetMessage(err.Error(), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
pg := MakePage(u, content, []string{})
|
||||||
|
pg.WrapContent(c.Width - 1)
|
||||||
|
c.PageState.Add(pg)
|
||||||
|
c.SetPercentRead()
|
||||||
|
c.ClearMessage()
|
||||||
|
c.SetHeaderUrl()
|
||||||
|
c.Draw()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *client) handleWeb(u Url) {
|
||||||
|
// Following http is disabled
|
||||||
|
if strings.ToUpper(c.Options["openhttp"]) != "TRUE" {
|
||||||
|
c.SetMessage("'openhttp' is not set to true, cannot open web link", false)
|
||||||
|
c.DrawMessage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use lynxmode
|
||||||
|
if strings.ToUpper(c.Options["lynxmode"]) == "TRUE" {
|
||||||
|
if http.IsTextFile(u.Full) {
|
||||||
page, err := http.Visit(u.Full, c.Width-1)
|
page, err := http.Visit(u.Full, c.Width-1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.SetMessage(fmt.Sprintf("Lynx error: %s", err.Error()), true)
|
c.SetMessage(fmt.Sprintf("Lynx error: %s", err.Error()), true)
|
||||||
|
@ -992,70 +1073,37 @@ func (c *client) Visit(url string) {
|
||||||
c.ClearMessage()
|
c.ClearMessage()
|
||||||
c.SetHeaderUrl()
|
c.SetHeaderUrl()
|
||||||
c.Draw()
|
c.Draw()
|
||||||
default:
|
} else {
|
||||||
if strings.ToUpper(c.Options["terminalonly"]) == "TRUE" {
|
c.SetMessage("The file is non-text: writing to disk...", false)
|
||||||
c.SetMessage("'terminalonly' is set to true and 'lynxmode' is not enabled, cannot open web link", false)
|
c.DrawMessage()
|
||||||
c.DrawMessage()
|
var fn string
|
||||||
|
if i := strings.LastIndex(u.Full, "/"); i > 0 && i + 1 < len(u.Full) {
|
||||||
|
fn = u.Full[i + 1:]
|
||||||
} else {
|
} else {
|
||||||
c.SetMessage("Attempting to open in web browser", false)
|
fn = "bombadillo.download"
|
||||||
c.DrawMessage()
|
|
||||||
msg, err := http.OpenInBrowser(u.Full)
|
|
||||||
if err != nil {
|
|
||||||
c.SetMessage(err.Error(), true)
|
|
||||||
} else {
|
|
||||||
c.SetMessage(msg, false)
|
|
||||||
}
|
|
||||||
c.DrawMessage()
|
|
||||||
}
|
}
|
||||||
|
c.saveFile(u, fn)
|
||||||
}
|
}
|
||||||
case "local":
|
|
||||||
content, err := local.Open(u.Resource)
|
// Open in default web browser if available
|
||||||
if err != nil {
|
} else {
|
||||||
c.SetMessage(err.Error(), true)
|
if strings.ToUpper(c.Options["terminalonly"]) == "TRUE" {
|
||||||
|
c.SetMessage("'terminalonly' is set to true and 'lynxmode' is not enabled, cannot open web link", false)
|
||||||
c.DrawMessage()
|
c.DrawMessage()
|
||||||
return
|
} else {
|
||||||
}
|
c.SetMessage("Attempting to open in web browser", false)
|
||||||
pg := MakePage(u, content, []string{})
|
c.DrawMessage()
|
||||||
pg.WrapContent(c.Width - 1)
|
msg, err := http.OpenInBrowser(u.Full)
|
||||||
c.PageState.Add(pg)
|
if err != nil {
|
||||||
c.SetPercentRead()
|
c.SetMessage(err.Error(), true)
|
||||||
c.ClearMessage()
|
} else {
|
||||||
c.SetHeaderUrl()
|
c.SetMessage(msg, false)
|
||||||
c.Draw()
|
}
|
||||||
case "finger":
|
|
||||||
content, err := finger.Finger(u.Host, u.Port, u.Resource)
|
|
||||||
if err != nil {
|
|
||||||
c.SetMessage(err.Error(), true)
|
|
||||||
c.DrawMessage()
|
c.DrawMessage()
|
||||||
return
|
|
||||||
}
|
}
|
||||||
pg := MakePage(u, content, []string{})
|
|
||||||
pg.WrapContent(c.Width - 1)
|
|
||||||
c.PageState.Add(pg)
|
|
||||||
c.SetPercentRead()
|
|
||||||
c.ClearMessage()
|
|
||||||
c.SetHeaderUrl()
|
|
||||||
c.Draw()
|
|
||||||
default:
|
|
||||||
c.SetMessage(fmt.Sprintf("%q is not a supported protocol", u.Scheme), true)
|
|
||||||
c.DrawMessage()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *client) ReloadPage() error {
|
|
||||||
if c.PageState.Length < 1 {
|
|
||||||
return fmt.Errorf("There is no page to reload")
|
|
||||||
}
|
|
||||||
url := c.PageState.History[c.PageState.Position].Location.Full
|
|
||||||
err := c.PageState.NavigateHistory(-1)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
length := c.PageState.Length
|
|
||||||
c.Visit(url)
|
|
||||||
c.PageState.Length = length
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------------\\
|
//------------------------------------------------\\
|
||||||
// + + + F U N C T I O N S + + + \\
|
// + + + F U N C T I O N S + + + \\
|
||||||
|
|
|
@ -2,6 +2,8 @@ package http
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
@ -23,6 +25,24 @@ func Visit(url string, width int) (page, error) {
|
||||||
return parseLinks(string(c)), nil
|
return parseLinks(string(c)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Returns false on err or non-text type
|
||||||
|
// Else returns true
|
||||||
|
func IsTextFile(url string) bool {
|
||||||
|
c, err := exec.Command("lynx", "-dump", "-head", url).Output()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
content := string(c)
|
||||||
|
content = strings.ToLower(content)
|
||||||
|
headers := strings.Split(content, "\n")
|
||||||
|
for _, header := range headers {
|
||||||
|
if strings.Contains(header, "content-type:") && strings.Contains(header, "text") {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func parseLinks(c string) page {
|
func parseLinks(c string) page {
|
||||||
var out page
|
var out page
|
||||||
contentUntil := strings.LastIndex(c, "References")
|
contentUntil := strings.LastIndex(c, "References")
|
||||||
|
@ -47,3 +67,19 @@ func parseLinks(c string) page {
|
||||||
return out
|
return out
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Fetch(url string) ([]byte, error) {
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
bodyBytes, err := ioutil.ReadAll(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return []byte{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bodyBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue