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)
}
}
}