Support custom content types

This commit is contained in:
Trevor Slocum 2020-11-17 11:31:22 -08:00
parent 3cb855acfa
commit cac10df2f6
4 changed files with 51 additions and 19 deletions

View file

@ -18,6 +18,11 @@ Address to listen for connections on in the format of `interface:port`.
`:1965` `:1965`
## Types
Content types may be defined by file extension. When a type is not defined for
the requested file extension, content type is detected automatically.
## Hosts ## Hosts
Hosts are defined by their hostname followed by one or more paths to serve. Hosts are defined by their hostname followed by one or more paths to serve.
@ -181,6 +186,10 @@ References:
# Address to listen on # Address to listen on
listen: :1965 listen: :1965
# Custom content types
types:
.json: application/json; charset=UTF-8
# Hosts and paths to serve # Hosts and paths to serve
hosts: hosts:
default: # Default host configuration default: # Default host configuration

View file

@ -68,6 +68,8 @@ type hostConfig struct {
type serverConfig struct { type serverConfig struct {
Listen string Listen string
Types map[string]string
Hosts map[string]*hostConfig Hosts map[string]*hostConfig
DisableSize bool DisableSize bool
@ -108,6 +110,10 @@ func readconfig(configPath string) error {
log.Fatal("listen address must be specified") log.Fatal("listen address must be specified")
} }
if config.Types == nil {
config.Types = make(map[string]string)
}
if config.SaneEOL { if config.SaneEOL {
newLine = "\n" newLine = "\n"
} else { } else {
@ -126,6 +132,20 @@ func readconfig(configPath string) error {
} }
} }
// Default content types
if config.Types[".htm"] == "" {
config.Types[".htm"] = htmlType
}
if config.Types[".html"] == "" {
config.Types[".html"] = htmlType
}
if config.Types[".gmi"] == "" {
config.Types[".gmi"] = geminiType
}
if config.Types[".gemini"] == "" {
config.Types[".gemini"] = geminiType
}
defaultHost := config.Hosts["default"] defaultHost := config.Hosts["default"]
delete(config.Hosts, "default") delete(config.Hosts, "default")

View file

@ -8,7 +8,6 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"sort" "sort"
"strings"
"github.com/h2non/filetype" "github.com/h2non/filetype"
) )
@ -98,33 +97,35 @@ func serveFile(c net.Conn, serve *pathConfig, filePath string) {
file, _ := os.Open(filePath) file, _ := os.Open(filePath)
defer file.Close() defer file.Close()
// Read file header // Read content type
buf := make([]byte, 261) var (
n, _ := file.Read(buf) buf = make([]byte, 261)
n int
// Write response header )
var contentType string contentType := config.Types[filepath.Ext(filePath)]
if strings.HasSuffix(filePath, ".html") && strings.HasSuffix(filePath, ".htm") {
contentType = "text/html; charset=utf-8"
} else if strings.HasSuffix(filePath, ".txt") && strings.HasSuffix(filePath, ".text") {
contentType = "text/plain; charset=utf-8"
} else if !strings.HasSuffix(filePath, ".gmi") && !strings.HasSuffix(filePath, ".gemini") {
kind, _ := filetype.Match(buf[:n])
if kind != filetype.Unknown {
contentType = kind.MIME.Value
}
}
if contentType == "" { if contentType == "" {
contentType = geminiType n, _ = file.Read(buf)
kind, err := filetype.Match(buf[:n])
if err == nil && kind != filetype.Unknown && kind.MIME.Value != "" {
contentType = kind.MIME.Value
} else {
contentType = plainType
} }
}
// Read file size
size := int64(-1) size := int64(-1)
info, err := file.Stat() info, err := file.Stat()
if err == nil { if err == nil {
size = info.Size() size = info.Size()
} }
// Write response header
writeSuccess(c, serve, contentType, size) writeSuccess(c, serve, contentType, size)
// Write body // Write file contents
if n > 0 {
c.Write(buf[:n]) c.Write(buf[:n])
}
io.Copy(c, file) io.Copy(c, file)
} }

View file

@ -24,7 +24,9 @@ const (
urlMaxLength = 1024 urlMaxLength = 1024
plainType = "text/plain; charset=utf-8"
geminiType = "text/gemini; charset=utf-8" geminiType = "text/gemini; charset=utf-8"
htmlType = "text/html; charset=utf-8"
logTimeFormat = "2006-01-02 15:04:05" logTimeFormat = "2006-01-02 15:04:05"
) )