Implement file server
This commit is contained in:
parent
6458420454
commit
92a1dbbc0c
|
@ -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:
|
Advanced clients can prompt the user for what to do when encountering an unknown certificate:
|
||||||
|
|
||||||
```go
|
```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)
|
err := knownHosts.Lookup(cert)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
switch err {
|
switch err {
|
||||||
|
|
|
@ -5,6 +5,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
@ -28,6 +29,29 @@ func init() {
|
||||||
KnownHosts: knownHosts,
|
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.
|
// Configure a client side certificate.
|
||||||
// To generate a certificate, run:
|
// 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() {
|
func main() {
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
log.Fatalf("usage: %s gemini://...", os.Args[0])
|
log.Fatalf("usage: %s gemini://...", os.Args[0])
|
||||||
|
|
|
@ -23,11 +23,7 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
mux := &gemini.ServeMux{}
|
mux := &gemini.ServeMux{}
|
||||||
mux.HandleFunc("/", func(rw *gemini.ResponseWriter, req *gemini.Request) {
|
mux.Handle("/", gemini.FileServer(gemini.Dir("/var/www")))
|
||||||
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)
|
|
||||||
})
|
|
||||||
|
|
||||||
server := gemini.Server{
|
server := gemini.Server{
|
||||||
Handler: mux,
|
Handler: mux,
|
||||||
|
|
80
server.go
80
server.go
|
@ -4,9 +4,12 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"errors"
|
"errors"
|
||||||
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"net"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
@ -16,6 +19,7 @@ import (
|
||||||
// Server errors.
|
// Server errors.
|
||||||
var (
|
var (
|
||||||
ErrBodyNotAllowed = errors.New("gemini: response status code does not allow for body")
|
ErrBodyNotAllowed = errors.New("gemini: response status code does not allow for body")
|
||||||
|
ErrNotAFile = errors.New("gemini: not a file")
|
||||||
)
|
)
|
||||||
|
|
||||||
// Server is a Gemini server.
|
// 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 scheme take preference over entries without.
|
||||||
// - Entries with a host take preference over entries without.
|
// - Entries with a host take preference over entries without.
|
||||||
// - Longer paths take preference over shorter paths.
|
// - 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))) &&
|
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))) &&
|
(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)
|
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) {
|
func (f HandlerFunc) Serve(rw *ResponseWriter, req *Request) {
|
||||||
f(rw, req)
|
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