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
2021-02-28 17:51:38 +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 {
2021-02-28 17:51:38 +01:00
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-02-28 17:51:38 +01:00
2021-03-01 18:20:11 +01:00
// Print header
_ , err = fmt . Fprintf ( conn , "20 text/gemini\r\n# Ancestors\n" )
2021-02-28 17:51:38 +01:00
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 )
2021-02-28 17:51:38 +01:00
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
}
}
}