// Contains the building blocks of a gopher client: history, url, and view. // History handles the browsing session and view represents individual // text based resources, the url represents a parsed url. package gopher import ( "errors" "fmt" "io/ioutil" "net" "strings" "time" ) //------------------------------------------------\\ // + + + V A R I A B L E S + + + \\ //--------------------------------------------------\\ // types is a map of gophertypes to a string representing their // type, to be used when displaying gophermaps var types = map[string]string{ "0": "TXT", "1": "MAP", "3": "ERR", "4": "BIN", "5": "DOS", "6": "UUE", "7": "FTS", "8": "TEL", "9": "BIN", "g": "GIF", "G": "GEM", "h": "HTM", "I": "IMG", "p": "PNG", "s": "SND", "S": "SSH", "T": "TEL", } var Timeout time.Duration = time.Duration(15) * time.Second //------------------------------------------------\\ // + + + F U N C T I O N S + + + \\ //--------------------------------------------------\\ // Retrieve makes a request to a Url and resturns // the response as []byte/error. This function is // available to use directly, but in most implementations // using the "Visit" receiver of the History struct will // be better. func Retrieve(host, port, resource string) ([]byte, error) { nullRes := make([]byte, 0) if host == "" || port == "" { return nullRes, errors.New("Incomplete request url") } addr := host + ":" + port conn, err := net.DialTimeout("tcp", addr, Timeout) if err != nil { return nullRes, err } send := resource + "\n" _, err = conn.Write([]byte(send)) if err != nil { return nullRes, err } result, err := ioutil.ReadAll(conn) if err != nil { return nullRes, err } return result, nil } // Visit handles the making of the request, parsing of maps, and returning // the correct information to the client func Visit(gophertype, host, port, resource string) (string, []string, error) { resp, err := Retrieve(host, port, resource) if err != nil { return "", []string{}, err } text := string(resp) links := []string{} if IsDownloadOnly(gophertype) { return text, []string{}, nil } if gophertype == "1" { text, links = parseMap(text) } return text, links, nil } func getType(t string) string { if val, ok := types[t]; ok { return val } return "???" } func isWebLink(resource string) (string, bool) { split := strings.SplitN(resource, ":", 2) if first := strings.ToUpper(split[0]); first == "URL" && len(split) > 1 { return split[1], true } return "", false } func parseMap(text string) (string, []string) { splitContent := strings.Split(text, "\n") links := make([]string, 0, 10) for i, e := range splitContent { e = strings.Trim(e, "\r\n") if e == "." { splitContent[i] = "" continue } line := strings.Split(e, "\t") var title string if len(line[0]) > 1 { title = line[0][1:] } else if len(line[0]) == 1 { title = "" } else { title = "" line[0] = "i" } if len(line) < 4 || strings.HasPrefix(line[0], "i") { splitContent[i] = " " + string(title) } else { link := buildLink(line[2], line[3], string(line[0][0]), line[1]) links = append(links, link) linkNum := fmt.Sprintf("[%d]",len(links)) linktext := fmt.Sprintf("%s %5s %s", getType(string(line[0][0])), linkNum, title) splitContent[i] = linktext } } return strings.Join(splitContent, "\n"), links } // Returns false for all text formats (including html // even though it may link out. Things like telnet // should never make it into the retrieve call for // this module, having been handled in the client // based on their protocol. func IsDownloadOnly(gophertype string) bool { switch gophertype { case "0", "1", "3", "7", "h": return false default: return true } } func buildLink(host, port, gtype, resource string) string { switch gtype { case "8", "T": return fmt.Sprintf("telnet://%s:%s", host, port) case "G": return fmt.Sprintf("gemini://%s:%s%s", host, port, resource) case "h": u, tf := isWebLink(resource) if tf { if strings.Index(u, "://") > 0 { return u } else { return fmt.Sprintf("http://%s", u) } } return fmt.Sprintf("gopher://%s:%s/h%s", host, port, resource) default: return fmt.Sprintf("gopher://%s:%s/%s%s", host, port, gtype, resource) } }