Adds buggy but present gemini support
This commit is contained in:
parent
8a3ddad58e
commit
7e4a32c67a
34
client.go
34
client.go
|
@ -14,7 +14,7 @@ import (
|
||||||
|
|
||||||
"tildegit.org/sloum/bombadillo/cmdparse"
|
"tildegit.org/sloum/bombadillo/cmdparse"
|
||||||
"tildegit.org/sloum/bombadillo/cui"
|
"tildegit.org/sloum/bombadillo/cui"
|
||||||
// "tildegit.org/sloum/bombadillo/gemini"
|
"tildegit.org/sloum/bombadillo/gemini"
|
||||||
"tildegit.org/sloum/bombadillo/gopher"
|
"tildegit.org/sloum/bombadillo/gopher"
|
||||||
"tildegit.org/sloum/bombadillo/http"
|
"tildegit.org/sloum/bombadillo/http"
|
||||||
"tildegit.org/sloum/bombadillo/telnet"
|
"tildegit.org/sloum/bombadillo/telnet"
|
||||||
|
@ -715,9 +715,35 @@ func (c *client) Visit(url string) {
|
||||||
c.SetHeaderUrl()
|
c.SetHeaderUrl()
|
||||||
c.Draw()
|
c.Draw()
|
||||||
case "gemini":
|
case "gemini":
|
||||||
// TODO send over to gemini request
|
capsule, err := gemini.Visit(u.Host, u.Port, u.Resource)
|
||||||
c.SetMessage("Bombadillo has not mastered Gemini yet, check back soon", false)
|
if err != nil {
|
||||||
c.DrawMessage()
|
c.SetMessage(err.Error(), true)
|
||||||
|
c.DrawMessage()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch capsule.Status {
|
||||||
|
case 2:
|
||||||
|
pg := MakePage(u, capsule.Content, capsule.Links)
|
||||||
|
pg.WrapContent(c.Width)
|
||||||
|
c.PageState.Add(pg)
|
||||||
|
c.Scroll(0)
|
||||||
|
c.ClearMessage()
|
||||||
|
c.SetHeaderUrl()
|
||||||
|
c.Draw()
|
||||||
|
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.SetMessage("Bombadillo has not mastered Gemini yet, check back soon", false)
|
||||||
|
// c.DrawMessage()
|
||||||
case "telnet":
|
case "telnet":
|
||||||
c.SetMessage("Attempting to start telnet session", false)
|
c.SetMessage("Attempting to start telnet session", false)
|
||||||
c.DrawMessage()
|
c.DrawMessage()
|
||||||
|
|
147
gemini/gemini.go
147
gemini/gemini.go
|
@ -3,81 +3,154 @@ package gemini
|
||||||
import (
|
import (
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
// "strings"
|
"strconv"
|
||||||
"time"
|
"strings"
|
||||||
|
|
||||||
// "tildegit.org/sloum/mailcap"
|
// "tildegit.org/sloum/mailcap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Capsule struct {
|
||||||
|
MimeMaj string
|
||||||
|
MimeMin string
|
||||||
|
Status int
|
||||||
|
Content string
|
||||||
|
Links []string
|
||||||
|
}
|
||||||
|
|
||||||
//------------------------------------------------\\
|
//------------------------------------------------\\
|
||||||
// + + + F U N C T I O N S + + + \\
|
// + + + F U N C T I O N S + + + \\
|
||||||
//--------------------------------------------------\\
|
//--------------------------------------------------\\
|
||||||
|
|
||||||
func Retrieve(host, port, resource string) ([]byte, error) {
|
func Retrieve(host, port, resource string) (string, error) {
|
||||||
nullRes := make([]byte, 0)
|
|
||||||
timeOut := time.Duration(5) * time.Second
|
|
||||||
|
|
||||||
if host == "" || port == "" {
|
if host == "" || port == "" {
|
||||||
return nullRes, fmt.Errorf("Incomplete request url")
|
return "", fmt.Errorf("Incomplete request url")
|
||||||
}
|
}
|
||||||
|
|
||||||
addr := host + ":" + port
|
addr := host + ":" + port
|
||||||
|
|
||||||
conf := &tls.Config{
|
conf := &tls.Config{
|
||||||
|
MinVersion: tls.VersionTLS12,
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, err := net.DialTimeout("tcp", addr, timeOut)
|
conn, err := tls.Dial("tcp", addr, conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nullRes, err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
secureConn := tls.Client(conn, conf)
|
defer conn.Close()
|
||||||
|
|
||||||
send := resource + "\n"
|
send := "gemini://" + addr + "/" + resource + "\r\n"
|
||||||
|
|
||||||
_, err = secureConn.Write([]byte(send))
|
_, err = conn.Write([]byte(send))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nullRes, err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := ioutil.ReadAll(conn)
|
result, err := ioutil.ReadAll(conn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nullRes, err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
return result, nil
|
return string(result), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func Visit(host, port, resource string) (string, []string, error) {
|
func Visit(host, port, resource string) (Capsule, error) {
|
||||||
resp, err := Retrieve(host, port, resource)
|
capsule := MakeCapsule()
|
||||||
|
rawResp, err := Retrieve(host, port, resource)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", []string{}, err
|
return capsule, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resp := strings.SplitN(rawResp, "\r\n", 2)
|
||||||
|
if len(resp) != 2 {
|
||||||
|
if err != nil {
|
||||||
|
return capsule, fmt.Errorf("Invalid response from server")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
header := strings.SplitN(resp[0], " ", 2)
|
||||||
|
if len([]rune(header[0])) != 2 {
|
||||||
|
header = strings.SplitN(resp[0], "\t", 2)
|
||||||
|
if len([]rune(header[0])) != 2 {
|
||||||
|
return capsule, fmt.Errorf("Invalid response format from server")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
body := resp[1]
|
||||||
|
|
||||||
// TODO break out the header
|
// Get status code single digit form
|
||||||
// header := ""
|
capsule.Status, err = strconv.Atoi(string(header[0][0]))
|
||||||
mime := ""
|
if err != nil {
|
||||||
mimeMaj := mime
|
return capsule, fmt.Errorf("Invalid status response from server")
|
||||||
mimeMin := mime
|
|
||||||
// status := ""
|
|
||||||
content := string(resp)
|
|
||||||
|
|
||||||
if mimeMaj == "text" && mimeMin == "gemini" {
|
|
||||||
// text := string(resp)
|
|
||||||
// links := []string{}
|
|
||||||
|
|
||||||
// TODO parse geminimap from 'content'
|
|
||||||
} else if mimeMaj == "text" {
|
|
||||||
// TODO just return the text
|
|
||||||
} else {
|
|
||||||
// TODO use mailcap to try and open the file
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Parse the meta as needed
|
||||||
|
var meta string
|
||||||
|
|
||||||
return content, []string{}, nil
|
switch capsule.Status {
|
||||||
|
case 1:
|
||||||
|
// handle search
|
||||||
|
return capsule, fmt.Errorf("Gemini input not yet supported")
|
||||||
|
case 2:
|
||||||
|
mimeAndCharset := strings.Split(header[1], ";")
|
||||||
|
meta = mimeAndCharset[0]
|
||||||
|
minMajMime := strings.Split(meta, "/")
|
||||||
|
if len(minMajMime) < 2 {
|
||||||
|
return capsule, fmt.Errorf("Improperly formatted mimetype received from server")
|
||||||
|
}
|
||||||
|
capsule.MimeMaj = minMajMime[0]
|
||||||
|
capsule.MimeMin = minMajMime[1]
|
||||||
|
if capsule.MimeMaj == "text" && capsule.MimeMin == "gemini" {
|
||||||
|
rootUrl := fmt.Sprintf("gemini://%s:%s", host, port)
|
||||||
|
capsule.Content, capsule.Links = parseGemini(body, rootUrl)
|
||||||
|
} else {
|
||||||
|
capsule.Content = body
|
||||||
|
}
|
||||||
|
return capsule, nil
|
||||||
|
case 3:
|
||||||
|
// The client will handle informing the user of a redirect
|
||||||
|
// and then request the new url
|
||||||
|
capsule.Content = header[1]
|
||||||
|
return capsule, nil
|
||||||
|
case 4:
|
||||||
|
return capsule, fmt.Errorf("[4] Temporary Failure. %s", header[1])
|
||||||
|
case 5:
|
||||||
|
return capsule, fmt.Errorf("[5] Permanent Failure. %s", header[1])
|
||||||
|
case 6:
|
||||||
|
return capsule, fmt.Errorf("[6] Client Certificate Required (Not supported by Bombadillo)")
|
||||||
|
default:
|
||||||
|
return capsule, fmt.Errorf("Invalid response status from server")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseGemini(b, rootUrl string) (string, []string) {
|
||||||
|
splitContent := strings.Split(b, "\n")
|
||||||
|
links := make([]string, 0, 10)
|
||||||
|
|
||||||
|
for i, ln := range splitContent {
|
||||||
|
splitContent[i] = strings.Trim(ln, "\r\n")
|
||||||
|
if len([]rune(ln)) > 3 && ln[:2] == "=>" {
|
||||||
|
trimmedSubLn := strings.Trim(ln[2:], "\r\n\t \a")
|
||||||
|
lineSplit := strings.SplitN(trimmedSubLn, " ", 2)
|
||||||
|
if len(lineSplit) != 2 {
|
||||||
|
lineSplit = append(lineSplit, lineSplit[0])
|
||||||
|
}
|
||||||
|
lineSplit[0] = strings.Trim(lineSplit[0], "\t\n\r \a")
|
||||||
|
lineSplit[1] = strings.Trim(lineSplit[1], "\t\n\r \a")
|
||||||
|
if len(lineSplit[0]) > 0 && lineSplit[0][0] == '/' {
|
||||||
|
lineSplit[0] = fmt.Sprintf("%s%s", rootUrl, lineSplit[0])
|
||||||
|
}
|
||||||
|
links = append(links, lineSplit[0])
|
||||||
|
linknum := fmt.Sprintf("[%d]", len(links))
|
||||||
|
splitContent[i] = fmt.Sprintf("%-5s %s", linknum, lineSplit[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(splitContent, "\n"), links
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func MakeCapsule() Capsule {
|
||||||
|
return Capsule{"", "", 0, "", make([]string, 0, 5)}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
2
url.go
2
url.go
|
@ -97,8 +97,8 @@ func MakeUrl(u string) (Url, error) {
|
||||||
out.DownloadOnly = true
|
out.DownloadOnly = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
out.Resource = fmt.Sprintf("%s%s", out.Mime, out.Resource)
|
|
||||||
out.Mime = ""
|
out.Mime = ""
|
||||||
|
out.Resource = fmt.Sprintf("%s%s", out.Mime, out.Resource)
|
||||||
}
|
}
|
||||||
|
|
||||||
if out.Scheme == "http" || out.Scheme == "https" {
|
if out.Scheme == "http" || out.Scheme == "https" {
|
||||||
|
|
Loading…
Reference in New Issue