mirror of
https://git.sr.ht/~adnano/go-gemini
synced 2024-11-26 22:18:24 +01:00
Implement file server
This commit is contained in:
parent
749b765aaa
commit
1288e1bc31
@ -71,7 +71,7 @@ client.TrustCertificate = func(cert *x509.Certificate, knownHosts *gemini.KnownH
|
||||
Advanced clients can prompt the user for what to do when encountering an unknown certificate:
|
||||
|
||||
```go
|
||||
client.TrustCertificate: func(cert *x509.Certificate, knownHosts *gemini.KnownHosts) error {
|
||||
client.TrustCertificate = func(cert *x509.Certificate, knownHosts *gemini.KnownHosts) error {
|
||||
err := knownHosts.Lookup(cert)
|
||||
if err != nil {
|
||||
switch err {
|
||||
|
@ -5,6 +5,7 @@ package main
|
||||
import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
@ -28,6 +29,29 @@ func init() {
|
||||
KnownHosts: knownHosts,
|
||||
}
|
||||
|
||||
client.TrustCertificate = func(cert *x509.Certificate, knownHosts *gemini.KnownHosts) error {
|
||||
err := knownHosts.Lookup(cert)
|
||||
if err != nil {
|
||||
switch err {
|
||||
case gemini.ErrCertificateNotTrusted:
|
||||
// Alert the user that the certificate is not trusted
|
||||
fmt.Println("error: certificate is not trusted!")
|
||||
fmt.Println("This could indicate a Man-in-the-Middle attack.")
|
||||
case gemini.ErrCertificateUnknown:
|
||||
// Prompt the user to trust the certificate
|
||||
if userTrustsCertificateTemporarily() {
|
||||
// Temporarily trust the certificate
|
||||
return nil
|
||||
} else if userTrustsCertificatePermanently() {
|
||||
// Add the certificate to the known hosts file
|
||||
knownHosts.Add(cert)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Configure a client side certificate.
|
||||
// To generate a certificate, run:
|
||||
//
|
||||
@ -81,6 +105,20 @@ func makeRequest(url string) {
|
||||
}
|
||||
}
|
||||
|
||||
func userTrustsCertificateTemporarily() bool {
|
||||
fmt.Println("Do you want to trust the certificate temporarily? (y/n)")
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
scanner.Scan()
|
||||
return scanner.Text() == "y"
|
||||
}
|
||||
|
||||
func userTrustsCertificatePermanently() bool {
|
||||
fmt.Println("How about permanently? (y/n)")
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
scanner.Scan()
|
||||
return scanner.Text() == "y"
|
||||
}
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
log.Fatalf("usage: %s gemini://...", os.Args[0])
|
||||
|
@ -23,11 +23,7 @@ func main() {
|
||||
}
|
||||
|
||||
mux := &gemini.ServeMux{}
|
||||
mux.HandleFunc("/", func(rw *gemini.ResponseWriter, req *gemini.Request) {
|
||||
rw.WriteHeader(gemini.StatusSuccess, "text/gemini")
|
||||
rw.Write([]byte("You requested " + req.URL.String()))
|
||||
log.Printf("Request from %s for %s", req.RemoteAddr.String(), req.URL)
|
||||
})
|
||||
mux.Handle("/", gemini.FileServer(gemini.Dir("/var/www")))
|
||||
|
||||
server := gemini.Server{
|
||||
Handler: mux,
|
||||
|
80
server.go
80
server.go
@ -4,9 +4,12 @@ import (
|
||||
"bufio"
|
||||
"crypto/tls"
|
||||
"errors"
|
||||
"io"
|
||||
"log"
|
||||
"net"
|
||||
"net/url"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -16,6 +19,7 @@ import (
|
||||
// Server errors.
|
||||
var (
|
||||
ErrBodyNotAllowed = errors.New("gemini: response status code does not allow for body")
|
||||
ErrNotAFile = errors.New("gemini: not a file")
|
||||
)
|
||||
|
||||
// Server is a Gemini server.
|
||||
@ -233,23 +237,6 @@ func appendSorted(es []muxEntry, e muxEntry) []muxEntry {
|
||||
// - Entries with a scheme take preference over entries without.
|
||||
// - Entries with a host take preference over entries without.
|
||||
// - Longer paths take preference over shorter paths.
|
||||
//
|
||||
// Long version:
|
||||
// if es[i].scheme != "" {
|
||||
// if e.scheme == "" {
|
||||
// return false
|
||||
// }
|
||||
// return len(es[i].scheme) < len(e.scheme)
|
||||
// }
|
||||
// if es[i].host != "" {
|
||||
// if e.host == "" {
|
||||
// return false
|
||||
// }
|
||||
// return len(es[i].host) < len(e.host)
|
||||
// }
|
||||
// return len(es[i].path) < len(e.path)
|
||||
|
||||
// Condensed version:
|
||||
return (es[i].u.Scheme == "" || (e.u.Scheme != "" && len(es[i].u.Scheme) < len(e.u.Scheme))) &&
|
||||
(es[i].u.Host == "" || (e.u.Host != "" && len(es[i].u.Host) < len(e.u.Host))) &&
|
||||
len(es[i].u.Path) < len(e.u.Path)
|
||||
@ -270,3 +257,62 @@ type HandlerFunc func(*ResponseWriter, *Request)
|
||||
func (f HandlerFunc) Serve(rw *ResponseWriter, req *Request) {
|
||||
f(rw, req)
|
||||
}
|
||||
|
||||
// ServeDir serves files from a directory.
|
||||
type ServeDir struct {
|
||||
path string // path to the directory
|
||||
}
|
||||
|
||||
// FileServer takes a filesystem and returns a handler which uses that filesystem.
|
||||
func FileServer(fsys FS) Handler {
|
||||
return fsHandler{
|
||||
fsys,
|
||||
}
|
||||
}
|
||||
|
||||
type fsHandler struct {
|
||||
FS
|
||||
}
|
||||
|
||||
func (fsys fsHandler) Serve(rw *ResponseWriter, req *Request) {
|
||||
// FIXME: Don't serve paths with .. in them
|
||||
f, err := fsys.Open(req.URL.Path)
|
||||
if err != nil {
|
||||
rw.WriteHeader(StatusNotFound, "Not found")
|
||||
return
|
||||
}
|
||||
// TODO: detect mimetype
|
||||
mime := "text/gemini"
|
||||
rw.WriteHeader(StatusSuccess, mime)
|
||||
// Copy file to response writer
|
||||
io.Copy(rw, f)
|
||||
}
|
||||
|
||||
// TODO: replace with fs.FS when available
|
||||
type FS interface {
|
||||
Open(name string) (File, error)
|
||||
}
|
||||
|
||||
// TODO: replace with fs.File when available
|
||||
type File interface {
|
||||
Stat() (os.FileInfo, error)
|
||||
Read([]byte) (int, error)
|
||||
Close() error
|
||||
}
|
||||
|
||||
// Dir implements FS using the native filesystem restricted to a specific directory.
|
||||
type Dir string
|
||||
|
||||
func (d Dir) Open(name string) (File, error) {
|
||||
path := filepath.Join(string(d), name)
|
||||
f, err := os.OpenFile(path, os.O_RDONLY, 0644)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if stat, err := f.Stat(); err == nil {
|
||||
if !stat.Mode().IsRegular() {
|
||||
return nil, ErrNotAFile
|
||||
}
|
||||
}
|
||||
return f, nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user