package gemini import ( "bufio" "crypto/tls" "net" "net/url" "strings" "sync" ) const ( StatusSuccess = 20 StatusTemporaryFail = 40 StatusPermanentFail = 50 StatusNotFound = 51 StatusBadRequest = 59 ) type Handler interface { ServeGemini(ResponseWriter, *Request) } type HandlerFunc func(ResponseWriter, *Request) func (f HandlerFunc) ServeGemini(w ResponseWriter, r *Request) { f(w, r) } type ServeMux struct { mu sync.RWMutex handlers map[string]Handler } func NewServeMux() *ServeMux { return &ServeMux{ handlers: make(map[string]Handler), } } func (m *ServeMux) Handle(pattern string, h Handler) { m.mu.Lock() m.handlers[pattern] = h m.mu.Unlock() } func (m *ServeMux) HandleFunc(pattern string, f func(ResponseWriter, *Request)) { m.Handle(pattern, HandlerFunc(f)) } func (m *ServeMux) ServeGemini(w ResponseWriter, r *Request) { m.mu.RLock() defer m.mu.RUnlock() path := r.URL.Path for pattern, handler := range m.handlers { if strings.HasPrefix(path, pattern) { handler.ServeGemini(w, r) return } } w.WriteHeader(StatusNotFound, "not found") } var DefaultServeMux = NewServeMux() func Handle(pattern string, h Handler) { DefaultServeMux.Handle(pattern, h) } func HandleFunc(pattern string, f func(ResponseWriter, *Request)) { DefaultServeMux.HandleFunc(pattern, f) } type Request struct { URL *url.URL Conn net.Conn } type ResponseWriter interface { Write([]byte) (int, error) WriteHeader(status int, meta string) } type response struct { conn net.Conn wroteHeader bool } func (r *response) WriteHeader(status int, meta string) { if r.wroteHeader { return } r.conn.Write([]byte( strings.TrimSpace( strings.Join([]string{ strings.TrimSpace( strings.Join([]string{ string(rune(status/10 + '0')), string(rune(status%10 + '0')), }, ""), ), meta, }, " "), ) + "\r\n")) r.wroteHeader = true } func (r *response) Write(b []byte) (int, error) { if !r.wroteHeader { r.WriteHeader(StatusSuccess, "text/gemini") } return r.conn.Write(b) } type Server struct { Addr string Handler Handler TLSConfig *tls.Config } func (s *Server) ListenAndServeTLS(certFile, keyFile string) error { handler := s.Handler if handler == nil { handler = DefaultServeMux } cert, err := tls.LoadX509KeyPair(certFile, keyFile) if err != nil { return err } cfg := s.TLSConfig if cfg == nil { cfg = &tls.Config{} } cfg.Certificates = []tls.Certificate{cert} ln, err := tls.Listen("tcp", s.Addr, cfg) if err != nil { return err } for { conn, err := ln.Accept() if err != nil { continue } go s.serve(conn, handler) } } func (s *Server) serve(conn net.Conn, handler Handler) { defer conn.Close() reader := bufio.NewReader(conn) line, err := reader.ReadString('\n') if err != nil { return } if len(line) > 1026 { conn.Write([]byte("59 request too long\r\n")) return } line = strings.TrimSpace(line) u, err := url.Parse(line) if err != nil { conn.Write([]byte("59 bad request\r\n")) return } req := &Request{ URL: u, Conn: conn, } rw := &response{ conn: conn, } handler.ServeGemini(rw, req) } func ListenAndServeTLS(addr, certFile, keyFile string, h Handler) error { s := &Server{ Addr: addr, Handler: h, } return s.ListenAndServeTLS(certFile, keyFile) }