forked from sloum/bombadillo
Compare commits
2 Commits
Author | SHA1 | Date |
---|---|---|
sloum | 7b7505990f | |
sloum | 36eb7565e0 |
52
client.go
52
client.go
|
@ -18,6 +18,7 @@ import (
|
|||
"tildegit.org/sloum/bombadillo/gopher"
|
||||
"tildegit.org/sloum/bombadillo/http"
|
||||
"tildegit.org/sloum/bombadillo/local"
|
||||
"tildegit.org/sloum/bombadillo/mercury"
|
||||
"tildegit.org/sloum/bombadillo/telnet"
|
||||
"tildegit.org/sloum/bombadillo/termios"
|
||||
)
|
||||
|
@ -703,7 +704,7 @@ func (c *client) search(query, uri, question string) {
|
|||
rootUrl = u.Full
|
||||
}
|
||||
c.Visit(fmt.Sprintf("%s\t%s", rootUrl, entry))
|
||||
case "gemini":
|
||||
case "gemini", "mercury":
|
||||
if ind := strings.Index(u.Full, "?"); ind >= 0 {
|
||||
rootUrl = u.Full[:ind]
|
||||
} else {
|
||||
|
@ -913,6 +914,8 @@ func (c *client) Visit(url string) {
|
|||
c.handleGopher(u)
|
||||
case "gemini":
|
||||
c.handleGemini(u)
|
||||
case "mercury":
|
||||
c.handleMercury(u)
|
||||
case "telnet":
|
||||
c.handleTelnet(u)
|
||||
case "http", "https":
|
||||
|
@ -1012,6 +1015,53 @@ func (c *client) handleGemini(u Url) {
|
|||
}
|
||||
}
|
||||
|
||||
func (c *client) handleMercury(u Url) {
|
||||
capsule, err := mercury.Visit(u.Host, u.Port, u.Resource)
|
||||
if err != nil {
|
||||
c.SetMessage(err.Error(), true)
|
||||
c.DrawMessage()
|
||||
return
|
||||
}
|
||||
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
|
||||
pg.WrapContent(c.Width-1, (c.Options["theme"] == "color"))
|
||||
c.PageState.Add(pg)
|
||||
c.SetPercentRead()
|
||||
c.ClearMessage()
|
||||
c.SetHeaderUrl()
|
||||
c.Draw()
|
||||
} else {
|
||||
c.SetMessage("The file is non-text: writing to disk...", false)
|
||||
c.DrawMessage()
|
||||
c.saveFileFromData(capsule.Content, filepath.Base(u.Resource))
|
||||
}
|
||||
case 3:
|
||||
// Redirect
|
||||
lowerRedirect := strings.ToLower(capsule.Content)
|
||||
lowerOriginal := strings.ToLower(u.Full)
|
||||
if strings.Replace(lowerRedirect, lowerOriginal, "", 1) == "/" {
|
||||
c.Visit(capsule.Content)
|
||||
} else {
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *client) handleTelnet(u Url) {
|
||||
c.SetMessage("Attempting to start telnet session", false)
|
||||
c.DrawMessage()
|
||||
|
|
|
@ -61,6 +61,7 @@ func Retrieve(host, port, resource string) ([]byte, error) {
|
|||
if err != nil {
|
||||
return nullRes, err
|
||||
}
|
||||
defer conn.Close()
|
||||
|
||||
send := resource + "\n"
|
||||
|
||||
|
|
|
@ -0,0 +1,221 @@
|
|||
package mercury
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
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) (string, error) {
|
||||
timeOut := time.Duration(5) * time.Second
|
||||
|
||||
if host == "" || port == "" {
|
||||
return "", fmt.Errorf("Incomplete request url")
|
||||
}
|
||||
|
||||
addr := host + ":" + port
|
||||
|
||||
conn, err := net.DialTimeout("tcp", addr, timeOut)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
defer conn.Close()
|
||||
|
||||
send := "mercury://" + addr + "/" + resource + "\r\n"
|
||||
|
||||
_, err = conn.Write([]byte(send))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
result, err := ioutil.ReadAll(conn)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(result), nil
|
||||
}
|
||||
|
||||
func Fetch(host, port, resource string) ([]byte, error) {
|
||||
rawResp, err := Retrieve(host, port, resource)
|
||||
if err != nil {
|
||||
return make([]byte, 0), err
|
||||
}
|
||||
|
||||
resp := strings.SplitN(rawResp, "\r\n", 2)
|
||||
if len(resp) != 2 {
|
||||
if err != nil {
|
||||
return make([]byte, 0), 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 make([]byte, 0), fmt.Errorf("Invalid response format from server")
|
||||
}
|
||||
}
|
||||
|
||||
// Get status code single digit form
|
||||
status, err := strconv.Atoi(string(header[0][0]))
|
||||
if err != nil {
|
||||
return make([]byte, 0), fmt.Errorf("Invalid status response from server")
|
||||
}
|
||||
|
||||
if status != 2 {
|
||||
switch status {
|
||||
case 1:
|
||||
return make([]byte, 0), fmt.Errorf("[1] Queries cannot be saved.")
|
||||
case 3:
|
||||
return make([]byte, 0), fmt.Errorf("[3] Redirects cannot be saved.")
|
||||
case 4:
|
||||
return make([]byte, 0), fmt.Errorf("[4] Temporary Failure.")
|
||||
case 5:
|
||||
return make([]byte, 0), fmt.Errorf("[5] Permanent Failure.")
|
||||
default:
|
||||
return make([]byte, 0), fmt.Errorf("Invalid response status from server")
|
||||
}
|
||||
}
|
||||
|
||||
return []byte(resp[1]), nil
|
||||
}
|
||||
|
||||
func Visit(host, port, resource string) (Capsule, error) {
|
||||
capsule := MakeCapsule()
|
||||
rawResp, err := Retrieve(host, port, resource)
|
||||
if err != nil {
|
||||
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 {
|
||||
return capsule, fmt.Errorf("%d: %s", len(header), header[0]) // TODO return this to a regular error message
|
||||
}
|
||||
|
||||
body := resp[1]
|
||||
|
||||
// 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:
|
||||
capsule.Content = header[1]
|
||||
return capsule, nil
|
||||
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" {
|
||||
if len(resource) > 0 && resource[0] != '/' {
|
||||
resource = fmt.Sprintf("/%s", resource)
|
||||
} else if resource == "" {
|
||||
resource = "/"
|
||||
}
|
||||
currentUrl := fmt.Sprintf("mercury://%s:%s%s", host, port, resource)
|
||||
capsule.Content, capsule.Links = parseMercury(body, currentUrl)
|
||||
} 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])
|
||||
default:
|
||||
return capsule, fmt.Errorf("Invalid response status from server")
|
||||
}
|
||||
}
|
||||
|
||||
func parseMercury(b, currentUrl string) (string, []string) {
|
||||
splitContent := strings.Split(b, "\n")
|
||||
links := make([]string, 0, 10)
|
||||
|
||||
outputIndex := 0
|
||||
for i, ln := range splitContent {
|
||||
splitContent[i] = strings.Trim(ln, "\r\n")
|
||||
if strings.HasPrefix(ln, "=>") && len(ln) > 2 {
|
||||
var link, decorator string
|
||||
subLn := strings.Trim(ln[2:], "\r\n\t \a")
|
||||
splitPoint := strings.IndexAny(subLn, " \t")
|
||||
|
||||
if splitPoint < 0 || len([]rune(subLn))-1 <= splitPoint {
|
||||
link = subLn
|
||||
decorator = subLn
|
||||
} else {
|
||||
link = strings.Trim(subLn[:splitPoint], "\t\n\r \a")
|
||||
decorator = strings.Trim(subLn[splitPoint:], "\t\n\r \a")
|
||||
}
|
||||
|
||||
if strings.Index(link, "://") < 0 {
|
||||
link, _ = handleRelativeUrl(link, currentUrl)
|
||||
} else if strings.HasPrefix(link, "//") {
|
||||
link = fmt.Sprintf("mercury:%s", link)
|
||||
}
|
||||
|
||||
links = append(links, link)
|
||||
linknum := fmt.Sprintf("[%d]", len(links))
|
||||
splitContent[outputIndex] = fmt.Sprintf("%-5s %s", linknum, decorator)
|
||||
outputIndex++
|
||||
} else {
|
||||
splitContent[outputIndex] = ln
|
||||
outputIndex++
|
||||
}
|
||||
}
|
||||
return strings.Join(splitContent[:outputIndex], "\n"), links
|
||||
}
|
||||
|
||||
// handleRelativeUrl provides link completion
|
||||
func handleRelativeUrl(relLink, current string) (string, error) {
|
||||
base, err := url.Parse(current)
|
||||
if err != nil {
|
||||
return relLink, err
|
||||
}
|
||||
rel, err := url.Parse(relLink)
|
||||
if err != nil {
|
||||
return relLink, err
|
||||
}
|
||||
return base.ResolveReference(rel).String(), nil
|
||||
}
|
||||
|
||||
func MakeCapsule() Capsule {
|
||||
return Capsule{"", "", 0, "", make([]string, 0, 5)}
|
||||
}
|
2
url.go
2
url.go
|
@ -115,6 +115,8 @@ func MakeUrl(u string) (Url, error) {
|
|||
out.Port = "443"
|
||||
} else if out.Scheme == "gemini" && out.Port == "" {
|
||||
out.Port = "1965"
|
||||
} else if out.Scheme == "mercury" && out.Port == "" {
|
||||
out.Port = "1961"
|
||||
} else if out.Scheme == "telnet" && out.Port == "" {
|
||||
out.Port = "23"
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue