package port import ( "bytes" "fmt" "html/template" "io" "log" "mime" "net/http" "net/url" "regexp" "strings" "golang.org/x/net/html/charset" "golang.org/x/text/transform" "git.vulpes.one/Feuerfuchs/port/port/libgemini" "github.com/temoto/robotstxt" ) var ( TermEscapeSGRPattern = regexp.MustCompile("\\[\\d+(;\\d+)*m") ) func resolveURI(uri string, baseURL *url.URL) (resolvedURI string) { if strings.HasPrefix(uri, "//") { resolvedURI = "/gemini/" + strings.TrimPrefix(uri, "//") } else if strings.HasPrefix(uri, "gemini://") { resolvedURI = "/gemini/" + strings.TrimPrefix(uri, "gemini://") } else if strings.HasPrefix(uri, "gopher://") { resolvedURI = "/gopher/" + strings.TrimPrefix(uri, "gopher://") } else { url, err := url.Parse(uri) if err != nil { return "" } adjustedURI := baseURL.ResolveReference(url) path := adjustedURI.Path if !strings.HasPrefix(path, "/") { path = "/" + path } if adjustedURI.Scheme == "gemini" { resolvedURI = "/gemini/" + adjustedURI.Host + path } else if adjustedURI.Scheme == "gopher" { resolvedURI = "/gopher/" + adjustedURI.Host + path } else { resolvedURI = adjustedURI.String() } } return } func GeminiHandler(tpl *template.Template, robotsdata *robotstxt.RobotsData, assetList AssetList, robotsdebug bool) http.HandlerFunc { return func(w http.ResponseWriter, req *http.Request) { agent := req.UserAgent() path := strings.TrimPrefix(req.URL.Path, "/gemini/") if robotsdata != nil && robotsdebug && !robotsdata.TestAgent(path, agent) { log.Printf("UserAgent %s ignored robots.txt", agent) } parts := strings.Split(path, "/") hostport := parts[0] if len(hostport) == 0 { http.Redirect(w, req, "/", http.StatusFound) return } title := hostport var qs string if req.URL.RawQuery != "" { qs = fmt.Sprintf("?%s", url.QueryEscape(req.URL.RawQuery)) } uri, err := url.QueryUnescape(strings.Join(parts[1:], "/")) if err != nil { if e := tpl.Execute(w, TemplateVariables{ Title: title, URI: hostport, Assets: assetList, RawText: fmt.Sprintf("Error: %s", err), Error: true, Protocol: "gemini", }); e != nil { log.Println("Template error: " + e.Error()) log.Println(err.Error()) } return } if uri != "" { title = fmt.Sprintf("%s/%s", hostport, uri) } res, err := libgemini.Get( fmt.Sprintf( "gemini://%s/%s%s", hostport, uri, qs, ), ) if err != nil { if e := tpl.Execute(w, TemplateVariables{ Title: title, URI: fmt.Sprintf("%s/%s", hostport, uri), Assets: assetList, RawText: fmt.Sprintf("Error: %s", err), Error: true, Protocol: "gemini", }); e != nil { log.Println("Template error: " + e.Error()) log.Println(err.Error()) } return } if int(res.Header.Status/10) == 3 { baseURL, err := url.Parse(fmt.Sprintf( "gemini://%s/%s", hostport, uri, )) if err != nil { if e := tpl.Execute(w, TemplateVariables{ Title: title, URI: fmt.Sprintf("%s/%s", hostport, uri), Assets: assetList, RawText: fmt.Sprintf("Error: %s", err), Error: true, Protocol: "gemini", }); e != nil { log.Println("Template error: " + e.Error()) log.Println(err.Error()) } return } http.Redirect(w, req, resolveURI(res.Header.Meta, baseURL), http.StatusFound) return } if int(res.Header.Status/10) != 2 { if err := tpl.Execute(w, TemplateVariables{ Title: title, URI: fmt.Sprintf("%s/%s", hostport, uri), Assets: assetList, RawText: fmt.Sprintf("Error %d: %s", res.Header.Status, res.Header.Meta), Error: true, Protocol: "gemini", }); err != nil { log.Println("Template error: " + err.Error()) } return } if strings.HasPrefix(res.Header.Meta, "text/") { buf := new(bytes.Buffer) _, params, err := mime.ParseMediaType(res.Header.Meta) if err != nil { buf.ReadFrom(res.Body) } else { encoding, _ := charset.Lookup(params["charset"]) readbuf := new(bytes.Buffer) readbuf.ReadFrom(res.Body) writer := transform.NewWriter(buf, encoding.NewDecoder()) writer.Write(readbuf.Bytes()) writer.Close() } var ( rawText string items []Item ) if strings.HasPrefix(res.Header.Meta, libgemini.MIME_GEMINI) { items = parseGeminiDocument(buf, uri, hostport) } else { rawText = buf.String() } if err := tpl.Execute(w, TemplateVariables{ Title: title, URI: fmt.Sprintf("%s/%s", hostport, uri), Assets: assetList, Lines: items, RawText: rawText, Protocol: "gemini", }); err != nil { log.Println("Template error: " + err.Error()) } } else { io.Copy(w, res.Body) } } }