You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
172 lines
3.8 KiB
172 lines
3.8 KiB
package main |
|
|
|
import ( |
|
"bufio" |
|
"fmt" |
|
"io/ioutil" |
|
"mime" |
|
"net" |
|
"net/url" |
|
"os" |
|
"path/filepath" |
|
"strings" |
|
) |
|
|
|
const ( |
|
VERSION float32 = 0.1 |
|
REQ_ERR string = "5 Invalid request\r\n" |
|
SRV_ERR string = "4 Internal server error\r\n" |
|
) |
|
|
|
var conf *config |
|
|
|
func serveFile(path string, conn net.Conn) { |
|
ext := filepath.Ext(path) |
|
var mimeType string |
|
if ext == ".gmi" || ext == ".gemini" { |
|
mimeType = "text/gemini" |
|
} else { |
|
mimeType = mime.TypeByExtension(ext) |
|
} |
|
|
|
if mimeType == "" { |
|
mimeType = "application/octet-stream" |
|
} |
|
contents, err := ioutil.ReadFile(path) |
|
if err != nil { |
|
fmt.Printf("5 - %s\n", string(path)) |
|
conn.Write([]byte(REQ_ERR)) |
|
} |
|
conn.Write([]byte(fmt.Sprintf("2 %s\r\n", mimeType))) |
|
conn.Write(contents) |
|
fmt.Printf("2 - %s\n", string(path)) |
|
} |
|
|
|
func getSystemPath(p string, full string) (string, error) { |
|
hasTrailingSlash := strings.HasSuffix(p, "/") |
|
var sysPath string |
|
if strings.HasPrefix(p, conf.userPrefix) { |
|
segments := strings.SplitN(p, "/", -1) |
|
un := segments[2] |
|
prefix := filepath.Join(conf.userRoot, un, conf.userFolder) |
|
if len(segments) > 3 { |
|
suffix := strings.Join(segments[3:], "/") |
|
sysPath = filepath.Join(prefix, suffix) |
|
} else { |
|
sysPath = prefix |
|
} |
|
} else { |
|
sysPath = filepath.Join(conf.documentRoot, p) |
|
} |
|
fInfo, err := os.Stat(sysPath) |
|
if err != nil { |
|
return "", fmt.Errorf(REQ_ERR) |
|
} |
|
|
|
// Fail if file does not exist or perms aren't right |
|
if os.IsNotExist(err) || os.IsPermission(err) { |
|
return "", fmt.Errorf(REQ_ERR) |
|
} else if err != nil { |
|
return "", fmt.Errorf(SRV_ERR) |
|
} else if uint64(fInfo.Mode().Perm())&0444 != 0444 { |
|
return "", fmt.Errorf(REQ_ERR) |
|
} else if fInfo.IsDir() { |
|
if !hasTrailingSlash { |
|
return "", fmt.Errorf("3 %s/\r\n", full) |
|
} |
|
} |
|
|
|
return sysPath, nil |
|
} |
|
|
|
func handleRequest(conn net.Conn) { |
|
defer conn.Close() |
|
|
|
reader := bufio.NewReaderSize(conn, 1024) |
|
request, overflow, err := reader.ReadLine() |
|
if overflow { |
|
fmt.Printf("5 - %s\n", string(request)) |
|
conn.Write([]byte(REQ_ERR)) |
|
return |
|
} else if err != nil { |
|
fmt.Printf("4 - %s", string(request)) |
|
conn.Write([]byte(SRV_ERR)) |
|
fmt.Println(err) |
|
return |
|
} |
|
|
|
uri, err := url.Parse(string(request)) |
|
if err != nil { |
|
fmt.Printf("5 - %s\n", string(request)) |
|
conn.Write([]byte(REQ_ERR)) |
|
return |
|
} |
|
if uri.Scheme == "" { |
|
uri.Scheme = "mercury" |
|
} |
|
|
|
if uri.Scheme != "mercury" { |
|
fmt.Printf("5 - %s\n", string(request)) |
|
conn.Write([]byte(REQ_ERR)) |
|
return |
|
} |
|
|
|
if reqHn := strings.Split(uri.Host, ":")[0]; reqHn != conf.host { |
|
fmt.Printf("5 - %s\n", string(request)) |
|
conn.Write([]byte(REQ_ERR)) |
|
return |
|
} |
|
|
|
path, err := getSystemPath(uri.Path, uri.String()) |
|
if err != nil { |
|
fmt.Println(err.Error()) |
|
conn.Write([]byte(err.Error())) |
|
return |
|
} |
|
fInfo, err := os.Stat(path) |
|
if err != nil { |
|
fmt.Printf("5 - %s\n", string(request)) |
|
conn.Write([]byte(REQ_ERR)) |
|
return |
|
} |
|
if fInfo.IsDir() { |
|
indexPath := filepath.Join(path, conf.indexFile) |
|
indInfo, err := os.Stat(indexPath) |
|
if err == nil && uint64(indInfo.Mode().Perm())&0444 == 0444 { |
|
serveFile(indexPath, conn) |
|
} else { |
|
fmt.Printf("5 - %s\n", string(request)) |
|
conn.Write([]byte(REQ_ERR)) |
|
} |
|
return |
|
} |
|
|
|
serveFile(path, conn) |
|
return |
|
} |
|
|
|
func main() { |
|
conf = makeConfig() |
|
fmt.Printf("𐌕𐌖𐌓𐌌𐌑 - Version %.1f\n\n", VERSION) |
|
if conf.host == "" { |
|
fmt.Fprintln(os.Stderr, ">> No host is configured for this server\n>> Please recompile with a host name in config.go") |
|
os.Exit(1) |
|
} |
|
|
|
listener, err := net.Listen("tcp", fmt.Sprintf(":%d", conf.port)) |
|
if err != nil { |
|
fmt.Fprintf(os.Stderr, "Error listening on port %d:\n%s\n", conf.port, err.Error()) |
|
os.Exit(1) |
|
} |
|
|
|
fmt.Printf("Listening on port %d\n", conf.port) |
|
|
|
for { |
|
conn, err := listener.Accept() |
|
if err != nil { |
|
fmt.Fprintf(os.Stderr, "Error listening on port %d:\n%s\n", conf.port, err.Error()) |
|
continue |
|
} |
|
go handleRequest(conn) |
|
} |
|
}
|
|
|