package main import ( "crypto/tls" "fmt" "io/ioutil" "net" "net/url" "strconv" "strings" "time" ) var TlsTimeout time.Duration = time.Duration(5) * time.Second var redirectCount int = 0 func RetrieveGemini(u *url.URL) (string, error) { conf := &tls.Config{ MinVersion: tls.VersionTLS12, InsecureSkipVerify: true, } conn, err := tls.DialWithDialer(&net.Dialer{Timeout: time.Duration(settings.TimeOutSeconds) * time.Second}, "tcp", u.Host, conf) if err != nil { return "", fmt.Errorf("TLS Dial Error: %s. URL: %+v", err.Error(), u) } defer conn.Close() _, err = conn.Write([]byte(u.String() + "\r\n")) if err != nil { return "", err } result, err := ioutil.ReadAll(conn) if err != nil { return "", err } return string(result), nil } func splitHeaderBody(resp string) (string, string) { r := strings.SplitN(resp, "\r\n", 2) var header, body string if len(r) == 2 { header = r[0] body = r[1] } else if len(r) == 1 { header = r[0] } return strings.TrimSpace(header), strings.TrimSpace(body) } func splitStatusMeta(header string) (int, string) { h := strings.SplitN(header, " ", 2) if len(h) != 2 { h = strings.SplitN(header, "\t", 2) } if len(h) != 2 || len(h[0]) < 1 { return -1, "Invalid response from server; mangled header" } code, err := strconv.Atoi(h[0]) if err != nil { code = 99 } return code, h[1] } func HandleRelativeURL(relLink string, current *url.URL) string { rel, err := url.Parse(relLink) if err != nil { return relLink } return current.ResolveReference(rel).String() } func ParseGeminiBody(body string, u *url.URL) string { // Check mime type. It wont always be text/gemini // Base64 encode images and return an img tag with data src var out strings.Builder splitContent := strings.Split(body, "\n") inPreBlock := false inList := false for i, ln := range splitContent { splitContent[i] = strings.Trim(ln, "\r\n") isPreBlockDeclaration := strings.HasPrefix(ln, "```") isLinkLine := strings.HasPrefix(ln, "=>") && len([]rune(ln)) > 3 isListItem := strings.HasPrefix(ln, "* ") if inList && !isListItem { out.WriteString("\n") inList = false } if inPreBlock { if isPreBlockDeclaration { inPreBlock = false out.WriteString("\n") } else { out.WriteString(ln) out.WriteRune('\n') } } else { ln = strings.TrimSpace(ln) if isPreBlockDeclaration { inPreBlock = !inPreBlock alt := "" if len(ln) > 3 { alt = strings.TrimSpace(ln[3:]) } if alt == "" { out.WriteString("
")
				} else {
					out.WriteString("
")
				}
			} else if isLinkLine {
				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, u)
				}
				out.WriteString(fmt.Sprintf("%s
\n", link, decorator)) } else if isListItem { if !inList { inList = true out.WriteString("