Bombabillo is a non-web client for the terminal, supporting Gopher, Gemini and much more.
https://bombadillo.colorfield.space
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
192 lines
4.6 KiB
192 lines
4.6 KiB
package main |
|
|
|
import ( |
|
"fmt" |
|
"os/user" |
|
"path" |
|
"path/filepath" |
|
"regexp" |
|
"strings" |
|
) |
|
|
|
//------------------------------------------------\\ |
|
// + + + T Y P E S + + + \\ |
|
//--------------------------------------------------\\ |
|
|
|
// Url is a struct representing the different pieces |
|
// of a url. This custom struct is used rather than the |
|
// built-in url library so-as to support gopher URLs, as |
|
// well as track mime-type and renderability (can the |
|
// response to the url be rendered as text in the client). |
|
type Url struct { |
|
Scheme string |
|
Host string |
|
Port string |
|
Resource string |
|
Full string |
|
Mime string |
|
DownloadOnly bool |
|
} |
|
|
|
//------------------------------------------------\\ |
|
// + + + R E C E I V E R S + + + \\ |
|
//--------------------------------------------------\\ |
|
|
|
// There are currently no receivers for the Url struct |
|
|
|
//------------------------------------------------\\ |
|
// + + + F U N C T I O N S + + + \\ |
|
//--------------------------------------------------\\ |
|
|
|
// MakeUrl is a Url constructor that takes in a string |
|
// representation of a url and returns a Url struct and |
|
// an error (or nil). |
|
func MakeUrl(u string) (Url, error) { |
|
if len(u) < 1 { |
|
return Url{}, fmt.Errorf("Invalid url, unable to parse") |
|
} |
|
|
|
if strings.HasPrefix(u, "finger://") { |
|
return parseFinger(u) |
|
} |
|
|
|
var out Url |
|
if local := strings.HasPrefix(u, "local://"); u[0] == '/' || u[0] == '.' || u[0] == '~' || local { |
|
if local && len(u) > 8 { |
|
u = u[8:] |
|
} |
|
var home string |
|
userinfo, err := user.Current() |
|
if err != nil { |
|
home = "" |
|
} else { |
|
home = userinfo.HomeDir |
|
} |
|
u = strings.Replace(u, "~", home, 1) |
|
res, err := filepath.Abs(u) |
|
if err != nil { |
|
return out, fmt.Errorf("Invalid path, unable to parse") |
|
} |
|
out.Scheme = "local" |
|
out.Host = "" |
|
out.Port = "" |
|
out.Mime = "" |
|
out.Resource = res |
|
out.Full = out.Scheme + "://" + out.Resource |
|
return out, nil |
|
} |
|
|
|
re := regexp.MustCompile(`^((?P<scheme>[a-zA-Z]+):\/\/)?(?P<host>[\w\-\.\d]+)(?::(?P<port>\d+)?)?(?:/(?P<type>[01345679gIhisp])?)?(?P<resource>.*)?$`) |
|
match := re.FindStringSubmatch(u) |
|
|
|
if valid := re.MatchString(u); !valid { |
|
return out, fmt.Errorf("Invalid url, unable to parse") |
|
} |
|
|
|
for i, name := range re.SubexpNames() { |
|
switch name { |
|
case "scheme": |
|
out.Scheme = match[i] |
|
case "host": |
|
out.Host = match[i] |
|
case "port": |
|
out.Port = match[i] |
|
case "type": |
|
out.Mime = match[i] |
|
case "resource": |
|
out.Resource = match[i] |
|
} |
|
} |
|
|
|
if out.Host == "" { |
|
return out, fmt.Errorf("no host") |
|
} |
|
|
|
out.Scheme = strings.ToLower(out.Scheme) |
|
|
|
if out.Scheme == "" { |
|
out.Scheme = bombadillo.Options["defaultscheme"] |
|
} |
|
|
|
if out.Scheme == "gopher" && out.Port == "" { |
|
out.Port = "70" |
|
} else if out.Scheme == "http" && out.Port == "" { |
|
out.Port = "80" |
|
} else if out.Scheme == "https" && out.Port == "" { |
|
out.Port = "443" |
|
} else if out.Scheme == "gemini" && out.Port == "" { |
|
out.Port = "1965" |
|
} else if out.Scheme == "telnet" && out.Port == "" { |
|
out.Port = "23" |
|
} |
|
|
|
if out.Scheme == "gopher" { |
|
if out.Mime == "" { |
|
out.Mime = "1" |
|
} |
|
if out.Resource == "" || out.Resource == "/" { |
|
out.Mime = "1" |
|
} |
|
if out.Mime == "7" && strings.Contains(out.Resource, "\t") { |
|
out.Mime = "1" |
|
} |
|
switch out.Mime { |
|
case "1", "0", "h", "7", "I", "g": |
|
out.DownloadOnly = false |
|
default: |
|
out.DownloadOnly = true |
|
} |
|
} else { |
|
out.Resource = fmt.Sprintf("%s%s", out.Mime, out.Resource) |
|
out.Mime = "" |
|
} |
|
|
|
out.Full = out.Scheme + "://" + out.Host + ":" + out.Port + "/" + out.Mime + out.Resource |
|
|
|
return out, nil |
|
} |
|
|
|
func UpOneDir(u string) string { |
|
url, err := MakeUrl(u) |
|
if len(url.Resource) < 1 || err != nil { |
|
return u |
|
} |
|
if strings.HasSuffix(url.Resource, "/") { |
|
url.Resource = url.Resource[:len(url.Resource)-1] |
|
} |
|
url.Resource, _ = path.Split(url.Resource) |
|
if url.Scheme == "gopher" { |
|
url.Mime = "1" |
|
} |
|
|
|
url.Full = url.Scheme + "://" + url.Host + ":" + url.Port + "/" + url.Mime + url.Resource |
|
|
|
return url.Full |
|
} |
|
|
|
func parseFinger(u string) (Url, error) { |
|
var out Url |
|
out.Scheme = "finger" |
|
if len(u) < 10 { |
|
return out, fmt.Errorf("Invalid finger address") |
|
} |
|
u = u[9:] |
|
userPlusAddress := strings.Split(u, "@") |
|
if len(userPlusAddress) > 1 { |
|
out.Resource = userPlusAddress[0] |
|
u = userPlusAddress[1] |
|
} |
|
hostPort := strings.Split(u, ":") |
|
if len(hostPort) < 2 { |
|
out.Port = "79" |
|
} else { |
|
out.Port = hostPort[1] |
|
} |
|
out.Host = hostPort[0] |
|
resource := "" |
|
if out.Resource != "" { |
|
resource = out.Resource + "@" |
|
} |
|
out.Full = fmt.Sprintf("%s://%s%s:%s", out.Scheme, resource, out.Host, out.Port) |
|
return out, nil |
|
}
|
|
|