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/cui"
|
||||
// "tildegit.org/sloum/bombadillo/gemini"
|
||||
"tildegit.org/sloum/bombadillo/gemini"
|
||||
"tildegit.org/sloum/bombadillo/gopher"
|
||||
"tildegit.org/sloum/bombadillo/http"
|
||||
"tildegit.org/sloum/bombadillo/telnet"
|
||||
|
@ -715,9 +715,35 @@ func (c *client) Visit(url string) {
|
|||
c.SetHeaderUrl()
|
||||
c.Draw()
|
||||
case "gemini":
|
||||
// TODO send over to gemini request
|
||||
c.SetMessage("Bombadillo has not mastered Gemini yet, check back soon", false)
|
||||
c.DrawMessage()
|
||||
capsule, err := gemini.Visit(u.Host, u.Port, u.Resource)
|
||||
if err != nil {
|
||||
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":
|
||||
c.SetMessage("Attempting to start telnet session", false)
|
||||
c.DrawMessage()
|
||||
|
|
147
gemini/gemini.go
147
gemini/gemini.go
|
@ -3,81 +3,154 @@ package gemini
|
|||
import (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"net"
|
||||
"io/ioutil"
|
||||
// "strings"
|
||||
"time"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
// "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 + + + \\
|
||||
//--------------------------------------------------\\
|
||||
|
||||
func Retrieve(host, port, resource string) ([]byte, error) {
|
||||
nullRes := make([]byte, 0)
|
||||
timeOut := time.Duration(5) * time.Second
|
||||
|
||||
func Retrieve(host, port, resource string) (string, error) {
|
||||
if host == "" || port == "" {
|
||||
return nullRes, fmt.Errorf("Incomplete request url")
|
||||
return "", fmt.Errorf("Incomplete request url")
|
||||
}
|
||||
|
||||
addr := host + ":" + port
|
||||
|
||||
conf := &tls.Config{
|
||||
MinVersion: tls.VersionTLS12,
|
||||
InsecureSkipVerify: true,
|
||||
}
|
||||
|
||||
conn, err := net.DialTimeout("tcp", addr, timeOut)
|
||||
conn, err := tls.Dial("tcp", addr, conf)
|
||||
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 {
|
||||
return nullRes, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
result, err := ioutil.ReadAll(conn)
|
||||
if err != nil {
|
||||
return nullRes, err
|
||||
return "", err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
return string(result), nil
|
||||
}
|
||||
|
||||
func Visit(host, port, resource string) (string, []string, error) {
|
||||
resp, err := Retrieve(host, port, resource)
|
||||
func Visit(host, port, resource string) (Capsule, error) {
|
||||
capsule := MakeCapsule()
|
||||
rawResp, err := Retrieve(host, port, resource)
|
||||
if err != nil {
|
||||
return "", []string{}, err
|
||||
return capsule, err
|
||||
}
|
||||
|
||||
// TODO break out the header
|
||||
// header := ""
|
||||
mime := ""
|
||||
mimeMaj := mime
|
||||
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
|
||||
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]
|
||||
|
||||
return content, []string{}, nil
|
||||
// Get status code single digit form
|
||||
capsule.Status, err = strconv.Atoi(string(header[0][0]))
|
||||
if err != nil {
|
||||
return capsule, fmt.Errorf("Invalid status response from server")
|
||||
}
|
||||
|
||||
// Parse the meta as needed
|
||||
var meta string
|
||||
|
||||
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)}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue