mastoGem/server.go

255 lines
6.3 KiB
Go
Raw Normal View History

2021-03-03 10:02:35 +01:00
/*
MastoGem, A Mastodon proxy for Gemini
Copyright (C) 2021 Romain de Laage
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
2021-02-28 14:41:40 +01:00
package main
import (
"crypto/tls"
2021-02-28 16:30:59 +01:00
"net/url"
2021-02-28 14:41:40 +01:00
"log"
"net"
2021-02-28 16:30:59 +01:00
"bufio"
"strconv"
2021-02-28 14:41:40 +01:00
"fmt"
"strings"
)
func listen(address, certFile, keyFile string) net.Listener {
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
if err != nil {
log.Fatalln("loadkeys: %s", err)
}
config := &tls.Config{
ClientAuth: tls.RequestClientCert,
Certificates: []tls.Certificate{cert},
MinVersion: tls.VersionTLS12,
InsecureSkipVerify: true,
}
listener, err := tls.Listen("tcp", address, config)
if err != nil {
log.Fatalln("failed to listen on 0.0.0.0:1965: %s", err)
}
return listener
}
2021-02-28 18:41:48 +01:00
func serve(listener net.Listener, baseURL, title, home_message string) {
2021-02-28 14:41:40 +01:00
for {
conn, err := listener.Accept()
if err != nil {
log.Println(err)
}
2021-02-28 18:41:48 +01:00
go handleConn(conn.(*tls.Conn), baseURL, title, home_message)
2021-02-28 14:41:40 +01:00
}
}
2021-02-28 16:30:59 +01:00
func getRawURL(conn *tls.Conn) (string, error) {
scanner := bufio.NewScanner(conn)
if ok := scanner.Scan(); !ok {
return "", scanner.Err()
}
rawURL := scanner.Text()
if strings.Contains(rawURL, "://") {
return rawURL, nil
}
return fmt.Sprintf("gemini://%s", rawURL), nil
}
func getPath(conn *tls.Conn) (string, error) {
rawURL, err := getRawURL(conn)
if err != nil {
return "", err
}
parsedURL, err := url.Parse(rawURL)
if err != nil {
return "", err
}
return parsedURL.Path, nil
}
2021-02-28 18:41:48 +01:00
func handleConn(conn *tls.Conn, baseURL, title, home_message string) {
2021-02-28 14:41:40 +01:00
defer conn.Close()
2021-02-28 16:30:59 +01:00
path, err := getPath(conn)
if err != nil {
log.Println("get url: %s", err)
_, err = fmt.Fprintf(conn, "59 Can't parse request\r\n")
if err != nil {
log.Println("send error: %s", err)
return
}
return
}
2021-03-01 18:20:11 +01:00
// home
2021-03-02 14:10:58 +01:00
if path == "" || path == "/" {
2021-02-28 18:41:48 +01:00
_, err = fmt.Fprintf(conn, "20 text/gemini\r\n# " + title + "\n\n" + home_message)
if err != nil {
log.Println("send error: %s", err)
return
}
return
}
2021-03-01 18:20:11 +01:00
// profile
2021-03-02 14:10:58 +01:00
if strings.HasPrefix(path, "/profile/") {
2021-03-01 18:20:11 +01:00
// skip prefix
2021-03-02 14:16:58 +01:00
path = path[9:]
2021-03-01 18:20:11 +01:00
_, err = strconv.ParseUint(path, 10, 64)
2021-02-28 16:30:59 +01:00
if err != nil {
2021-03-01 18:20:11 +01:00
log.Println("invalid request: %s", err)
_, err = fmt.Fprintf(conn, "59 Can't parse request\r\n")
if err != nil {
log.Println("send error: %s", err)
return
}
2021-02-28 16:30:59 +01:00
return
}
2021-03-01 18:20:11 +01:00
log.Println("Received request for account " + path)
2021-02-28 17:55:27 +01:00
2021-03-01 18:20:11 +01:00
printProfile(conn, baseURL, path)
2021-03-02 14:10:58 +01:00
} /* thread */ else if strings.HasPrefix(path, "/thread/") {
2021-03-01 18:20:11 +01:00
// skip prefix
2021-03-02 14:16:58 +01:00
path = path[8:]
2021-03-01 18:20:11 +01:00
_, err = strconv.ParseUint(path, 10, 64)
if err != nil {
log.Println("invalid request: %s", err)
_, err = fmt.Fprintf(conn, "59 Can't parse request\r\n")
if err != nil {
log.Println("send error: %s", err)
return
}
return
}
log.Println("Received request for thread " + path)
printThread(conn, baseURL, path)
} else {
_, err = fmt.Fprintf(conn, "59 Invalid request\r\n")
if err != nil {
log.Println("send: %s", err)
return
}
}
}
func printProfile(conn *tls.Conn, baseURL, profileID string) {
account, err := getAccount(baseURL, profileID)
blogs := getBlog(baseURL, profileID)
2021-02-28 14:41:40 +01:00
if err != nil || blogs == nil {
_, err = fmt.Fprintf(conn, "40 Remote mastodon instance failed\r\n")
if err != nil {
log.Println("handleConn: %s", err)
return
}
return
}
_, err = fmt.Fprintf(conn, "20 text/gemini\r\n# Toots for " + account.Name + " account\n")
2021-02-28 14:41:40 +01:00
if err != nil {
log.Println("handleConn: %s", err)
return
}
for _, blog := range blogs {
date := "\n```\n* Posted at " + blog.Date + " *\n```\n"
2021-02-28 14:41:40 +01:00
2021-03-01 18:20:11 +01:00
text := removeHTMLTags(blog.Content) + "\n"
2021-03-01 19:00:22 +01:00
_, err = fmt.Fprintf(conn, date + text + "=> /thread/" + blog.Id + " View the thread\n")
2021-02-28 14:41:40 +01:00
if err != nil {
2021-03-01 18:20:11 +01:00
log.Println("read blogs: %s", err)
2021-02-28 14:41:40 +01:00
return
}
2021-03-01 18:20:11 +01:00
}
_, err = fmt.Fprintf(conn, "=> " + account.Url + " Go to " + account.Name + " account")
if err != nil {
log.Println("add link: %s", err)
return
}
}
func printThread(conn *tls.Conn, baseURL, tootID string) {
originalToot, err := getToot(baseURL, tootID)
if err != nil {
_, err = fmt.Fprintf(conn, "40 Remote mastodon instance failed\r\n")
2021-02-28 14:41:40 +01:00
if err != nil {
2021-03-01 18:20:11 +01:00
log.Println("handleConn: %s", err)
2021-02-28 14:41:40 +01:00
return
}
2021-03-01 18:20:11 +01:00
return
}
2021-02-28 14:41:40 +01:00
2021-03-01 18:20:11 +01:00
thread, err := getThread(baseURL, tootID)
if err != nil {
_, err = fmt.Fprintf(conn, "40 Remote mastodon instance failed\r\n")
2021-02-28 14:41:40 +01:00
if err != nil {
2021-03-01 18:20:11 +01:00
log.Println("handleConn: %s", err)
2021-02-28 14:41:40 +01:00
return
}
2021-03-01 18:20:11 +01:00
return
2021-02-28 14:41:40 +01:00
}
2021-03-01 18:20:11 +01:00
// Print header
_, err = fmt.Fprintf(conn, "20 text/gemini\r\n# Ancestors\n")
if err != nil {
2021-03-01 18:20:11 +01:00
log.Println("handleConn: %s", err)
return
}
// Print each anscestor
for _, toot := range thread.Ancestors {
2021-03-01 19:00:22 +01:00
_, err = fmt.Fprintf(conn, "\n```\n* Posted on " + toot.Date + " by " + toot.Author.Name + " *\n```\n" + removeHTMLTags(toot.Content) + "\n=> /profile/" + toot.Author.Id + " More toots from " + toot.Author.Name + "\n")
2021-03-01 18:20:11 +01:00
if err != nil {
log.Println("handleConn: %s", err)
return
}
}
// Print original toot
2021-03-01 19:00:22 +01:00
_, err = fmt.Fprintf(conn, "\n# Toot\n\n```\n* Posted on " + originalToot.Date + " by "+ originalToot.Author.Name +" *\n```\n" + removeHTMLTags(originalToot.Content) + "\n=> /profile/" + originalToot.Author.Id + " More toots from " + originalToot.Author.Name + "\n")
2021-03-01 18:20:11 +01:00
if err != nil {
log.Println("handleConn: %s", err)
return
}
2021-03-01 18:20:11 +01:00
// Print each descendant
_, err = fmt.Fprintf(conn, "\n# Descendants\n")
if err != nil {
log.Println("handleConn: %s", err)
return
}
for _, toot := range thread.Descendants {
2021-03-01 19:00:22 +01:00
_, err = fmt.Fprintf(conn, "\n```\n* Posted on " + toot.Date + " by " + toot.Author.Name + " *\n```\n" + removeHTMLTags(toot.Content) + "\n=> /profile/" + toot.Author.Id + " More toots from " + toot.Author.Name + "\n")
2021-03-01 18:20:11 +01:00
if err != nil {
log.Println("handleConn: %s", err)
return
}
}
}