mirror of
https://code.rocketnine.space/tslocum/twins.git
synced 2024-11-27 09:28:14 +01:00
Support directory listing via HTTPS
This commit is contained in:
parent
8d6cb6527e
commit
faab032d45
4 changed files with 91 additions and 72 deletions
|
@ -219,17 +219,17 @@ hosts:
|
||||||
key: /srv/gemini.rocks/data/cert.key
|
key: /srv/gemini.rocks/data/cert.key
|
||||||
paths:
|
paths:
|
||||||
-
|
-
|
||||||
path: ^/sites/.*\.php$
|
path: ^/.*\.php$
|
||||||
root: /home/geminirocks/data
|
root: /home/geminirocks/public_html
|
||||||
fastcgi: unix:///var/run/php.sock
|
fastcgi: unix:///var/run/php.sock
|
||||||
-
|
-
|
||||||
path: /sites
|
path: /files
|
||||||
root: /home/geminirocks/data
|
root: /home/geminirocks/files
|
||||||
cache: 604800 # Cache for 1 week
|
cache: 604800 # Cache for 1 week
|
||||||
list: true # Enable directory listing
|
list: true # Enable directory listing
|
||||||
-
|
-
|
||||||
path: ^/(help|info)$
|
path: ^/(help|info)$
|
||||||
root: /home/geminirocks/data/help
|
root: /home/geminirocks/docs/help
|
||||||
-
|
-
|
||||||
path: ^/proxy-example$
|
path: ^/proxy-example$
|
||||||
proxy: gemini://localhost:1966
|
proxy: gemini://localhost:1966
|
||||||
|
@ -237,6 +237,13 @@ hosts:
|
||||||
path: ^/cmd-example$
|
path: ^/cmd-example$
|
||||||
command: uname -a
|
command: uname -a
|
||||||
cache: 0 # Do not cache
|
cache: 0 # Do not cache
|
||||||
|
-
|
||||||
|
path: /
|
||||||
|
root: /home/geminirocks/public_html
|
||||||
|
twins.rocketnine.space:
|
||||||
|
cert: /srv/twins.rocketnine.space/data/cert.crt
|
||||||
|
key: /srv/twins.rocketnine.space/data/cert.key
|
||||||
|
paths:
|
||||||
-
|
-
|
||||||
path: /redir-path-example
|
path: /redir-path-example
|
||||||
redirect: /other-resource
|
redirect: /other-resource
|
||||||
|
@ -245,15 +252,5 @@ hosts:
|
||||||
redirect: gemini://gemini.circumlunar.space/
|
redirect: gemini://gemini.circumlunar.space/
|
||||||
-
|
-
|
||||||
path: /
|
path: /
|
||||||
root: /home/geminirocks/data/home
|
root: /home/twins/public_html
|
||||||
twins.rocketnine.space:
|
|
||||||
cert: /srv/twins.rocketnine.space/data/cert.crt
|
|
||||||
key: /srv/twins.rocketnine.space/data/cert.key
|
|
||||||
paths:
|
|
||||||
-
|
|
||||||
path: /sites
|
|
||||||
root: /home/twins/data/sites
|
|
||||||
-
|
|
||||||
path: /
|
|
||||||
root: /home/twins/data/home
|
|
||||||
```
|
```
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
|
@ -12,8 +13,9 @@ import (
|
||||||
"github.com/h2non/filetype"
|
"github.com/h2non/filetype"
|
||||||
)
|
)
|
||||||
|
|
||||||
func serveDirList(c net.Conn, serve *pathConfig, request *url.URL, dirPath string) int {
|
func buildDirList(request *url.URL, dirPath string) ([]byte, error) {
|
||||||
var (
|
var (
|
||||||
|
b = &bytes.Buffer{}
|
||||||
files []os.FileInfo
|
files []os.FileInfo
|
||||||
numDirs int
|
numDirs int
|
||||||
numFiles int
|
numFiles int
|
||||||
|
@ -36,7 +38,7 @@ func serveDirList(c net.Conn, serve *pathConfig, request *url.URL, dirPath strin
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return writeStatus(c, statusTemporaryFailure)
|
return nil, err
|
||||||
}
|
}
|
||||||
// List directories first
|
// List directories first
|
||||||
sort.Slice(files, func(i, j int) bool {
|
sort.Slice(files, func(i, j int) bool {
|
||||||
|
@ -48,24 +50,22 @@ func serveDirList(c net.Conn, serve *pathConfig, request *url.URL, dirPath strin
|
||||||
return i < j
|
return i < j
|
||||||
})
|
})
|
||||||
|
|
||||||
writeSuccess(c, serve, geminiType, -1)
|
fmt.Fprintf(b, "# %s%s", request.Path, newLine)
|
||||||
|
|
||||||
fmt.Fprintf(c, "# %s%s", request.Path, newLine)
|
|
||||||
if numDirs == 1 {
|
if numDirs == 1 {
|
||||||
c.Write([]byte("1 directory"))
|
b.Write([]byte("1 directory"))
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(c, "%d directories", numDirs)
|
fmt.Fprintf(b, "%d directories", numDirs)
|
||||||
}
|
}
|
||||||
c.Write([]byte(", "))
|
b.Write([]byte(", "))
|
||||||
if numDirs == 1 {
|
if numDirs == 1 {
|
||||||
c.Write([]byte("1 file"))
|
b.Write([]byte("1 file"))
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintf(c, "%d files", numFiles)
|
fmt.Fprintf(b, "%d files", numFiles)
|
||||||
}
|
}
|
||||||
c.Write([]byte(newLine + newLine))
|
b.Write([]byte(newLine + newLine))
|
||||||
|
|
||||||
if request.Path != "/" {
|
if request.Path != "/" {
|
||||||
c.Write([]byte("=> ../ ../" + newLine + newLine))
|
b.Write([]byte("=> ../ ../" + newLine + newLine))
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, info := range files {
|
for _, info := range files {
|
||||||
|
@ -76,10 +76,10 @@ func serveDirList(c net.Conn, serve *pathConfig, request *url.URL, dirPath strin
|
||||||
filePath += "/"
|
filePath += "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
c.Write([]byte("=> " + fileName + " " + filePath + newLine))
|
b.Write([]byte("=> " + fileName + " " + filePath + newLine))
|
||||||
|
|
||||||
if info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
|
if info.IsDir() || info.Mode()&os.ModeSymlink != 0 {
|
||||||
c.Write([]byte(newLine))
|
b.Write([]byte(newLine))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,8 +87,20 @@ func serveDirList(c net.Conn, serve *pathConfig, request *url.URL, dirPath strin
|
||||||
if !info.ModTime().IsZero() {
|
if !info.ModTime().IsZero() {
|
||||||
modified = info.ModTime().Format("2006-01-02 3:04 PM")
|
modified = info.ModTime().Format("2006-01-02 3:04 PM")
|
||||||
}
|
}
|
||||||
c.Write([]byte(modified + " - " + formatFileSize(info.Size()) + newLine + newLine))
|
b.Write([]byte(modified + " - " + formatFileSize(info.Size()) + newLine + newLine))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return b.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func serveDirList(c net.Conn, serve *pathConfig, request *url.URL, dirPath string) int {
|
||||||
|
dirList, err := buildDirList(request, dirPath)
|
||||||
|
if err != nil {
|
||||||
|
return writeStatus(c, statusTemporaryFailure)
|
||||||
|
}
|
||||||
|
|
||||||
|
writeSuccess(c, serve, geminiType, -1)
|
||||||
|
c.Write(dirList)
|
||||||
return statusSuccess
|
return statusSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"gitlab.com/tslocum/gmitohtml/pkg/gmitohtml"
|
"gitlab.com/tslocum/gmitohtml/pkg/gmitohtml"
|
||||||
|
@ -114,22 +115,34 @@ func serveHTTPS(w http.ResponseWriter, r *http.Request) (int, int64, string) {
|
||||||
return status, -1, serve.Log
|
return status, -1, serve.Log
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := os.Stat(path.Join(filePath, "index.gmi"))
|
var found bool
|
||||||
if err != nil {
|
for _, indexFile := range indexFiles {
|
||||||
_, err := os.Stat(path.Join(filePath, "index.gemini"))
|
_, err := os.Stat(path.Join(filePath, indexFile))
|
||||||
if err != nil {
|
if err == nil || os.IsExist(err) {
|
||||||
if serve.List {
|
filePath = path.Join(filePath, indexFile)
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
if serve.List {
|
||||||
|
dirList, err := buildDirList(r.URL, filePath)
|
||||||
|
if err != nil {
|
||||||
status := http.StatusInternalServerError
|
status := http.StatusInternalServerError
|
||||||
http.Error(w, "HTTPS dir lost not yet implemented", status)
|
http.Error(w, "Failed to build directory listing", status)
|
||||||
return status, -1, serve.Log
|
return status, -1, serve.Log
|
||||||
}
|
}
|
||||||
|
result := gmitohtml.Convert([]byte(dirList), r.URL.String())
|
||||||
|
|
||||||
http.NotFound(w, r)
|
status := http.StatusOK
|
||||||
return http.StatusNotFound, -1, serve.Log
|
w.Header().Set("Content-Type", htmlType)
|
||||||
|
w.WriteHeader(status)
|
||||||
|
|
||||||
|
w.Write(result)
|
||||||
|
return status, int64(len(result)), serve.Log
|
||||||
}
|
}
|
||||||
filePath = path.Join(filePath, "index.gemini")
|
http.NotFound(w, r)
|
||||||
} else {
|
return http.StatusNotFound, -1, serve.Log
|
||||||
filePath = path.Join(filePath, "index.gmi")
|
|
||||||
}
|
}
|
||||||
} else if hasTrailingSlash && len(r.URL.Path) > 1 {
|
} else if hasTrailingSlash && len(r.URL.Path) > 1 {
|
||||||
u, err := url.Parse(r.URL.String())
|
u, err := url.Parse(r.URL.String())
|
||||||
|
@ -152,10 +165,20 @@ func serveHTTPS(w http.ResponseWriter, r *http.Request) (int, int64, string) {
|
||||||
return status, -1, serve.Log
|
return status, -1, serve.Log
|
||||||
}
|
}
|
||||||
|
|
||||||
result := gmitohtml.Convert([]byte(data), r.URL.String())
|
var result []byte
|
||||||
|
contentType := htmlType
|
||||||
|
fileExt := strings.ToLower(filepath.Ext(filePath))
|
||||||
|
if fileExt == ".gmi" || fileExt == ".gemini" {
|
||||||
|
result = gmitohtml.Convert([]byte(data), r.URL.String())
|
||||||
|
} else if fileExt == ".htm" || fileExt == ".html" {
|
||||||
|
result = data
|
||||||
|
} else {
|
||||||
|
result = data
|
||||||
|
contentType = plainType
|
||||||
|
}
|
||||||
|
|
||||||
status := http.StatusOK
|
status := http.StatusOK
|
||||||
w.Header().Set("Content-Type", htmlType)
|
w.Header().Set("Content-Type", contentType)
|
||||||
w.WriteHeader(status)
|
w.WriteHeader(status)
|
||||||
|
|
||||||
w.Write(result)
|
w.Write(result)
|
||||||
|
|
43
server.go
43
server.go
|
@ -2,7 +2,6 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
|
||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"crypto/x509"
|
"crypto/x509"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -61,6 +60,8 @@ var newLine = "\r\n"
|
||||||
|
|
||||||
var logLock sync.Mutex
|
var logLock sync.Mutex
|
||||||
|
|
||||||
|
var indexFiles = []string{"index.gmi", "index.gemini"}
|
||||||
|
|
||||||
func writeHeader(c net.Conn, code int, meta string) int {
|
func writeHeader(c net.Conn, code int, meta string) int {
|
||||||
fmt.Fprintf(c, "%d %s%s", code, meta, newLine)
|
fmt.Fprintf(c, "%d %s%s", code, meta, newLine)
|
||||||
return code
|
return code
|
||||||
|
@ -110,22 +111,6 @@ func writeSuccess(c net.Conn, serve *pathConfig, contentType string, size int64)
|
||||||
return statusSuccess
|
return statusSuccess
|
||||||
}
|
}
|
||||||
|
|
||||||
func scanCRLF(data []byte, atEOF bool) (advance int, token []byte, err error) {
|
|
||||||
if atEOF && len(data) == 0 {
|
|
||||||
return 0, nil, nil
|
|
||||||
}
|
|
||||||
if i := bytes.IndexByte(data, '\r'); i >= 0 {
|
|
||||||
// We have a full newline-terminated line.
|
|
||||||
return i + 1, data[0:i], nil
|
|
||||||
}
|
|
||||||
// If we're at EOF, we have a final, non-terminated line. Return it.
|
|
||||||
if atEOF {
|
|
||||||
return len(data), data, nil
|
|
||||||
}
|
|
||||||
// Request more data.
|
|
||||||
return 0, nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func replaceWithUserInput(command []string, request *url.URL) []string {
|
func replaceWithUserInput(command []string, request *url.URL) []string {
|
||||||
newCommand := make([]string, len(command))
|
newCommand := make([]string, len(command))
|
||||||
copy(newCommand, command)
|
copy(newCommand, command)
|
||||||
|
@ -229,18 +214,20 @@ func servePath(c *tls.Conn, request *url.URL, serve *pathConfig) (int, int64) {
|
||||||
return writeHeader(c, statusRedirectPermanent, request.String()+"/"), -1
|
return writeHeader(c, statusRedirectPermanent, request.String()+"/"), -1
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := os.Stat(path.Join(filePath, "index.gmi"))
|
var found bool
|
||||||
if err != nil {
|
for _, indexFile := range indexFiles {
|
||||||
_, err := os.Stat(path.Join(filePath, "index.gemini"))
|
_, err := os.Stat(path.Join(filePath, indexFile))
|
||||||
if err != nil {
|
if err == nil || os.IsExist(err) {
|
||||||
if serve.List {
|
filePath = path.Join(filePath, indexFile)
|
||||||
return serveDirList(c, serve, request, filePath), -1
|
found = true
|
||||||
}
|
break
|
||||||
return writeStatus(c, statusNotFound), -1
|
|
||||||
}
|
}
|
||||||
filePath = path.Join(filePath, "index.gemini")
|
}
|
||||||
} else {
|
if !found {
|
||||||
filePath = path.Join(filePath, "index.gmi")
|
if serve.List {
|
||||||
|
return serveDirList(c, serve, request, filePath), -1
|
||||||
|
}
|
||||||
|
return writeStatus(c, statusNotFound), -1
|
||||||
}
|
}
|
||||||
} else if hasTrailingSlash && len(request.Path) > 1 {
|
} else if hasTrailingSlash && len(request.Path) > 1 {
|
||||||
r := request.String()
|
r := request.String()
|
||||||
|
|
Loading…
Reference in a new issue