Compare commits
4 Commits
16658e845d
...
280703edd8
Author | SHA1 | Date |
---|---|---|
blank X | 280703edd8 | |
blank X | 34fffa9d37 | |
blank X | a94dd6fb44 | |
blank X | 3718689e02 |
213
main.go
213
main.go
|
@ -6,8 +6,10 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
"bufio"
|
||||||
"errors"
|
"errors"
|
||||||
"context"
|
"context"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"net/url"
|
"net/url"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
|
@ -21,6 +23,8 @@ import (
|
||||||
var (
|
var (
|
||||||
hosts tofu.KnownHosts
|
hosts tofu.KnownHosts
|
||||||
hostsfile *tofu.HostWriter
|
hostsfile *tofu.HostWriter
|
||||||
|
predirs map[string]*url.URL
|
||||||
|
perrors map[string]PError
|
||||||
)
|
)
|
||||||
|
|
||||||
func xdgDataHome() string {
|
func xdgDataHome() string {
|
||||||
|
@ -41,6 +45,154 @@ func init() {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = populatePRedirs()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = populatePErrors()
|
||||||
|
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 populatePErrors() error {
|
||||||
|
file, err := os.OpenFile(filepath.Join(xdgDataHome(), "konbata", "perrors"), os.O_RDONLY|os.O_CREATE, 0600)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
perrors = make(map[string]PError)
|
||||||
|
unescaper := strings.NewReplacer("\\n", "\n", "\\\\", "\\")
|
||||||
|
for scanner.Scan() {
|
||||||
|
values := strings.SplitN(scanner.Text(), " ", 3)
|
||||||
|
if len(values) != 3 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
url, err := url.Parse(values[0])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if url.Scheme != "gemini" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
code, err := strconv.Atoi(values[1])
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
perrors[url.String()] = PError{
|
||||||
|
code: gemini.Status(code),
|
||||||
|
message: unescaper.Replace(values[2]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func savePErrors() error {
|
||||||
|
file, err := os.OpenFile(filepath.Join(xdgDataHome(), "konbata", "perrors"), 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)
|
||||||
|
escaper := strings.NewReplacer("\\", "\\\\", "\n", "\\n")
|
||||||
|
for url, perror := range perrors {
|
||||||
|
_, err = writer.WriteString(url)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = writer.WriteRune(' ')
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = writer.WriteString(strconv.Itoa(int(perror.code)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = writer.WriteRune(' ')
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = escaper.WriteString(writer, perror.message)
|
||||||
|
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 {
|
func trustCertificate(hostname string, cert *x509.Certificate) error {
|
||||||
|
@ -59,16 +211,50 @@ func trustCertificate(hostname string, cert *x509.Certificate) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func do(req *gemini.Request, via []*gemini.Request) (*gemini.Response, *gemini.Request, error) {
|
func do(client gemini.Client, ctx context.Context, req *gemini.Request, via []*gemini.Request) (*gemini.Response, *gemini.Request, error) {
|
||||||
client := gemini.Client{
|
if target, exists := predirs[req.URL.String()]; exists {
|
||||||
TrustCertificate: trustCertificate,
|
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)
|
||||||
|
}
|
||||||
|
if perror, exists := perrors[req.URL.String()]; exists {
|
||||||
|
return nil, req, errors.New(fmt.Sprintf("%d %s", perror.code, perror.message))
|
||||||
}
|
}
|
||||||
ctx := context.Background()
|
|
||||||
resp, err := client.Do(ctx, req)
|
resp, err := client.Do(ctx, req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return resp, req, err
|
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.StatusPermanentFailure {
|
||||||
|
perrors[req.URL.String()] = PError{
|
||||||
|
code: resp.Status,
|
||||||
|
message: resp.Meta,
|
||||||
|
}
|
||||||
|
err = savePErrors()
|
||||||
|
if err != nil {
|
||||||
|
return resp, req, err
|
||||||
|
}
|
||||||
|
}
|
||||||
if resp.Status.Class() == gemini.StatusRedirect {
|
if resp.Status.Class() == gemini.StatusRedirect {
|
||||||
via = append(via, req)
|
via = append(via, req)
|
||||||
if len(via) > 5 {
|
if len(via) > 5 {
|
||||||
|
@ -85,7 +271,10 @@ func do(req *gemini.Request, via []*gemini.Request) (*gemini.Response, *gemini.R
|
||||||
target = req.URL.ResolveReference(target)
|
target = req.URL.ResolveReference(target)
|
||||||
redirect := *req
|
redirect := *req
|
||||||
redirect.URL = target
|
redirect.URL = target
|
||||||
return do(&redirect, via)
|
return do(client, ctx, &redirect, via)
|
||||||
|
}
|
||||||
|
if resp.Status.Class() != gemini.StatusSuccess {
|
||||||
|
return resp, req, errors.New(fmt.Sprintf("%d %s", resp.Status, resp.Meta))
|
||||||
}
|
}
|
||||||
|
|
||||||
return resp, req, err
|
return resp, req, err
|
||||||
|
@ -106,15 +295,16 @@ func main() {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
resp, req, err := do(req, nil)
|
client := gemini.Client{
|
||||||
|
TrustCertificate: trustCertificate,
|
||||||
|
}
|
||||||
|
ctx := context.Background()
|
||||||
|
resp, req, err := do(client, ctx, req, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
|
|
||||||
if resp.Status.Class() != gemini.StatusSuccess {
|
|
||||||
log.Fatalf("%d %s", resp.Status, resp.Meta)
|
|
||||||
}
|
|
||||||
if resp.Meta != "text/gemini" && !strings.HasPrefix(resp.Meta, "text/gemini;") {
|
if resp.Meta != "text/gemini" && !strings.HasPrefix(resp.Meta, "text/gemini;") {
|
||||||
log.Fatal("mime type is not text/gemini")
|
log.Fatal("mime type is not text/gemini")
|
||||||
}
|
}
|
||||||
|
@ -208,6 +398,11 @@ func (a ByTime) Swap(i, j int) {
|
||||||
a[i], a[j] = a[j], a[i]
|
a[i], a[j] = a[j], a[i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type PError struct {
|
||||||
|
code gemini.Status
|
||||||
|
message string
|
||||||
|
}
|
||||||
|
|
||||||
type AtomWriter struct {
|
type AtomWriter struct {
|
||||||
Title string
|
Title string
|
||||||
Items []FeedItem
|
Items []FeedItem
|
||||||
|
|
Loading…
Reference in New Issue