/* * Copyright (C) 2022 Brian Evans * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ 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[a-zA-Z]+):\/\/)?(?P[\w\-\.\d]+)(?::(?P\d+)?)?(?:/(?P[01345679gIhisp])?)?(?P.*)?$`) 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 }