From 4bf44b16562335b3d09b6df0150521bb5b5f776f Mon Sep 17 00:00:00 2001 From: Feuerfuchs Date: Mon, 18 May 2020 12:12:43 +0200 Subject: WIP: Refactoring --- internal/port/gemini.go | 205 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 internal/port/gemini.go (limited to 'internal/port/gemini.go') diff --git a/internal/port/gemini.go b/internal/port/gemini.go new file mode 100644 index 0000000..f9b0b97 --- /dev/null +++ b/internal/port/gemini.go @@ -0,0 +1,205 @@ +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) + } + } +} -- cgit v1.2.3-70-g09d2