diff --git a/main.go b/main.go index 310e0a2..8417416 100644 --- a/main.go +++ b/main.go @@ -6,6 +6,7 @@ import ( "log" "sort" "time" + "bufio" "errors" "context" "strings" @@ -21,6 +22,7 @@ import ( var ( hosts tofu.KnownHosts hostsfile *tofu.HostWriter + predirs map[string]*url.URL ) func xdgDataHome() string { @@ -41,6 +43,75 @@ func init() { if err != nil { log.Fatal(err) } + + err = populatePRedirs() + if err != nil { + log.Fatal(err) + } +} + +func populatePRedirs() error { + file, err := os.OpenFile(filepath.Join(xdgDataHome(), "konbata", "predirs"), os.O_RDONLY|os.O_CREATE, 0600) + if err != nil { + return err + } + defer file.Close() + scanner := bufio.NewScanner(file) + predirs = make(map[string]*url.URL) + for scanner.Scan() { + urls := strings.Split(scanner.Text(), " ") + if len(urls) != 2 { + continue + } + url0, err := url.Parse(urls[0]) + if err != nil { + continue + } + if url0.Scheme != "gemini" { + continue + } + url1, err := url.Parse(urls[1]) + if err != nil { + continue + } + if url1.Scheme != "gemini" { + continue + } + predirs[url0.String()] = url1 + } + return nil +} + +func savePRedirs() error { + file, err := os.OpenFile(filepath.Join(xdgDataHome(), "konbata", "predirs"), os.O_WRONLY|os.O_CREATE, 0600) + if err != nil { + return err + } + defer file.Close() + err = file.Truncate(0) + if err != nil { + return err + } + writer := bufio.NewWriter(file) + for url0, url1 := range predirs { + _, err = writer.WriteString(url0) + if err != nil { + return err + } + _, err = writer.WriteRune(' ') + if err != nil { + return err + } + _, err = writer.WriteString(url1.String()) + if err != nil { + return err + } + _, err = writer.WriteRune('\n') + if err != nil { + return err + } + } + return writer.Flush() } func trustCertificate(hostname string, cert *x509.Certificate) error { @@ -60,11 +131,36 @@ func trustCertificate(hostname string, cert *x509.Certificate) error { } func do(client gemini.Client, ctx context.Context, req *gemini.Request, via []*gemini.Request) (*gemini.Response, *gemini.Request, error) { + if target, exists := predirs[req.URL.String()]; exists { + via = append(via, req) + if len(via) > 5 { + return nil, req, errors.New("too many redirects") + } + + redirect := *req + redirect.URL = target + return do(client, ctx, &redirect, via) + } resp, err := client.Do(ctx, req) if err != nil { return resp, req, err } + if resp.Status == gemini.StatusPermanentRedirect { + target, err := url.Parse(resp.Meta) + if err != nil { + return resp, req, err + } + if target.Scheme != "gemini" && target.Scheme != "" { + return resp, req, errors.New(fmt.Sprintf("tried to redirect to scheme %s", target.Scheme)) + } + target = req.URL.ResolveReference(target) + predirs[req.URL.String()] = target + err = savePRedirs() + if err != nil { + return resp, req, err + } + } if resp.Status.Class() == gemini.StatusRedirect { via = append(via, req) if len(via) > 5 {