Reworks the protocol handling and makes html downloads work

This commit is contained in:
sloumdrone 2019-10-25 20:06:13 -07:00
parent e5c0bd7443
commit 67d7df6804
2 changed files with 261 additions and 177 deletions

402
client.go
View File

@ -514,6 +514,8 @@ func (c *client) saveFile(u Url, name string) {
file, err = gopher.Retrieve(u.Host, u.Port, u.Resource)
case "gemini":
file, err = gemini.Fetch(u.Host, u.Port, u.Resource, &c.Certs)
case "http", "https":
file, err = http.Fetch(u.Full)
default:
c.SetMessage(fmt.Sprintf("Saving files over %s is not supported", u.Scheme), true)
c.DrawMessage()
@ -737,6 +739,21 @@ func (c *client) Scroll(amount int) {
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() {
page := c.PageState.History[c.PageState.Position]
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) {
c.SetMessage("Loading...", false)
c.DrawMessage()
@ -848,137 +867,199 @@ func (c *client) Visit(url string) {
switch u.Scheme {
case "gopher":
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)
c.handleGopher(u)
case "gemini":
c.handleGemini(u)
case "telnet":
c.handleTelnet(u)
case "http", "https":
c.handleWeb(u)
case "local":
c.handleLocal(u)
case "finger":
c.handleFinger(u)
default:
c.SetMessage(fmt.Sprintf("%q is not a supported protocol", u.Scheme), true)
c.DrawMessage()
}
}
// +++ 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)
c.PageState.Add(pg)
c.SetPercentRead()
c.ClearMessage()
c.SetHeaderUrl()
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 {
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.Draw()
case "http", "https":
if strings.ToUpper(c.Options["openhttp"]) != "TRUE" {
c.SetMessage("'openhttp' is not set to true, cannot open web link", false)
c.DrawMessage()
return
}
switch strings.ToUpper(c.Options["lynxmode"]) {
case "TRUE":
}
}
func (c *client) handleTelnet(u Url) {
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 {
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)
if err != nil {
c.SetMessage(fmt.Sprintf("Lynx error: %s", err.Error()), true)
@ -992,70 +1073,37 @@ func (c *client) Visit(url string) {
c.ClearMessage()
c.SetHeaderUrl()
c.Draw()
default:
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()
} else {
c.SetMessage("The file is non-text: writing to disk...", false)
c.DrawMessage()
var fn string
if i := strings.LastIndex(u.Full, "/"); i > 0 && i + 1 < len(u.Full) {
fn = u.Full[i + 1:]
} else {
c.SetMessage("Attempting to open in web browser", false)
c.DrawMessage()
msg, err := http.OpenInBrowser(u.Full)
if err != nil {
c.SetMessage(err.Error(), true)
} else {
c.SetMessage(msg, false)
}
c.DrawMessage()
fn = "bombadillo.download"
}
c.saveFile(u, fn)
}
case "local":
content, err := local.Open(u.Resource)
if err != nil {
c.SetMessage(err.Error(), true)
// Open in default web browser if available
} else {
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()
return
}
pg := MakePage(u, content, []string{})
pg.WrapContent(c.Width - 1)
c.PageState.Add(pg)
c.SetPercentRead()
c.ClearMessage()
c.SetHeaderUrl()
c.Draw()
case "finger":
content, err := finger.Finger(u.Host, u.Port, u.Resource)
if err != nil {
c.SetMessage(err.Error(), true)
} else {
c.SetMessage("Attempting to open in web browser", false)
c.DrawMessage()
msg, err := http.OpenInBrowser(u.Full)
if err != nil {
c.SetMessage(err.Error(), true)
} else {
c.SetMessage(msg, false)
}
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 + + + \\

View File

@ -2,6 +2,8 @@ package http
import (
"fmt"
"io/ioutil"
"net/http"
"os/exec"
"strings"
)
@ -23,6 +25,24 @@ func Visit(url string, width int) (page, error) {
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 {
var out page
contentUntil := strings.LastIndex(c, "References")
@ -47,3 +67,19 @@ func parseLinks(c string) page {
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
}