forked from tslocum/twins
4 changed files with 318 additions and 303 deletions
@ -0,0 +1,43 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"bytes" |
||||
"log" |
||||
"net" |
||||
"net/url" |
||||
"os/exec" |
||||
"strings" |
||||
) |
||||
|
||||
func serveCommand(c net.Conn, request *url.URL, command []string) { |
||||
var args []string |
||||
if len(command) > 0 { |
||||
args = command[1:] |
||||
} |
||||
cmd := exec.Command(command[0], args...) |
||||
|
||||
var buf bytes.Buffer |
||||
if request.RawQuery != "" { |
||||
requestQuery, err := url.QueryUnescape(request.RawQuery) |
||||
if err != nil { |
||||
writeStatus(c, statusBadRequest) |
||||
return |
||||
} |
||||
cmd.Stdin = strings.NewReader(requestQuery + "\n") |
||||
} |
||||
cmd.Stdout = &buf |
||||
cmd.Stderr = &buf |
||||
|
||||
err := cmd.Run() |
||||
if err != nil { |
||||
writeStatus(c, statusProxyError) |
||||
return |
||||
} |
||||
|
||||
writeHeader(c, statusSuccess, "text/gemini; charset=utf-8") |
||||
c.Write(buf.Bytes()) |
||||
|
||||
if verbose { |
||||
log.Printf("< %s\n", command) |
||||
} |
||||
} |
@ -0,0 +1,164 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io" |
||||
"net" |
||||
"net/url" |
||||
"os" |
||||
"path" |
||||
"path/filepath" |
||||
"sort" |
||||
"strings" |
||||
|
||||
"github.com/h2non/filetype" |
||||
) |
||||
|
||||
func serveDirList(c net.Conn, request *url.URL, dirPath string) { |
||||
var ( |
||||
files []os.FileInfo |
||||
numDirs int |
||||
numFiles int |
||||
) |
||||
err := filepath.Walk(dirPath, func(path string, info os.FileInfo, err error) error { |
||||
if err != nil { |
||||
return err |
||||
} else if path == dirPath { |
||||
return nil |
||||
} |
||||
files = append(files, info) |
||||
if info.IsDir() || info.Mode()&os.ModeSymlink != 0 { |
||||
numDirs++ |
||||
} else { |
||||
numFiles++ |
||||
} |
||||
if info.IsDir() { |
||||
return filepath.SkipDir |
||||
} |
||||
return nil |
||||
}) |
||||
if err != nil { |
||||
writeStatus(c, statusTemporaryFailure) |
||||
return |
||||
} |
||||
// List directories first
|
||||
sort.Slice(files, func(i, j int) bool { |
||||
iDir := files[i].IsDir() || files[i].Mode()&os.ModeSymlink != 0 |
||||
jDir := files[j].IsDir() || files[j].Mode()&os.ModeSymlink != 0 |
||||
if iDir != jDir { |
||||
return iDir |
||||
} |
||||
return i < j |
||||
}) |
||||
|
||||
writeHeader(c, statusSuccess, "text/gemini; charset=utf-8") |
||||
|
||||
fmt.Fprintf(c, "# %s\r\n", request.Path) |
||||
if numDirs > 0 || numFiles > 0 { |
||||
if numDirs > 0 { |
||||
if numDirs == 1 { |
||||
c.Write([]byte("1 directory")) |
||||
} else { |
||||
fmt.Fprintf(c, "%d directories", numDirs) |
||||
} |
||||
} |
||||
if numFiles > 0 { |
||||
if numDirs > 0 { |
||||
c.Write([]byte(" and ")) |
||||
} |
||||
if numDirs == 1 { |
||||
c.Write([]byte("1 file")) |
||||
} else { |
||||
fmt.Fprintf(c, "%d files", numFiles) |
||||
} |
||||
} |
||||
c.Write([]byte("\r\n\n")) |
||||
} |
||||
|
||||
if request.Path != "/" { |
||||
c.Write([]byte("=> ../ ../\r\n\r\n")) |
||||
} |
||||
|
||||
for _, info := range files { |
||||
fileName := info.Name() |
||||
filePath := url.PathEscape(info.Name()) |
||||
if info.IsDir() || info.Mode()&os.ModeSymlink != 0 { |
||||
fileName += "/" |
||||
filePath += "/" |
||||
} |
||||
|
||||
c.Write([]byte("=> " + fileName + " " + filePath + "\r\n")) |
||||
|
||||
if info.IsDir() || info.Mode()&os.ModeSymlink != 0 { |
||||
c.Write([]byte("\r\n")) |
||||
continue |
||||
} |
||||
|
||||
modified := "Never" |
||||
if !info.ModTime().IsZero() { |
||||
modified = info.ModTime().Format("2006-01-02 3:04 PM") |
||||
} |
||||
c.Write([]byte(modified + " - " + formatFileSize(info.Size()) + "\r\n\r\n")) |
||||
} |
||||
} |
||||
|
||||
func serveFile(c net.Conn, request *url.URL, filePath string, listDir bool) { |
||||
fi, err := os.Stat(filePath) |
||||
if err != nil { |
||||
writeStatus(c, statusNotFound) |
||||
return |
||||
} |
||||
|
||||
if mode := fi.Mode(); mode.IsDir() { |
||||
if len(request.Path) == 0 || request.Path[len(request.Path)-1] != '/' { |
||||
// Add trailing slash
|
||||
writeHeader(c, statusRedirectPermanent, request.String()+"/") |
||||
return |
||||
} |
||||
|
||||
_, err := os.Stat(path.Join(filePath, "index.gmi")) |
||||
if err != nil { |
||||
_, err := os.Stat(path.Join(filePath, "index.gemini")) |
||||
if err != nil { |
||||
if listDir { |
||||
serveDirList(c, request, filePath) |
||||
return |
||||
} |
||||
writeStatus(c, statusNotFound) |
||||
return |
||||
} |
||||
filePath = path.Join(filePath, "index.gemini") |
||||
} else { |
||||
filePath = path.Join(filePath, "index.gmi") |
||||
} |
||||
} |
||||
|
||||
// Open file
|
||||
file, _ := os.Open(filePath) |
||||
defer file.Close() |
||||
|
||||
// Read file header
|
||||
buf := make([]byte, 261) |
||||
n, _ := file.Read(buf) |
||||
|
||||
// Write header
|
||||
var mimeType string |
||||
if strings.HasSuffix(filePath, ".html") && strings.HasSuffix(filePath, ".htm") { |
||||
mimeType = "text/html; charset=utf-8" |
||||
} else if strings.HasSuffix(filePath, ".txt") && strings.HasSuffix(filePath, ".text") { |
||||
mimeType = "text/plain; charset=utf-8" |
||||
} else if !strings.HasSuffix(filePath, ".gmi") && !strings.HasSuffix(filePath, ".gemini") { |
||||
kind, _ := filetype.Match(buf[:n]) |
||||
if kind != filetype.Unknown { |
||||
mimeType = kind.MIME.Value |
||||
} |
||||
} |
||||
if mimeType == "" { |
||||
mimeType = "text/gemini; charset=utf-8" |
||||
} |
||||
writeHeader(c, statusSuccess, mimeType) |
||||
|
||||
// Write body
|
||||
c.Write(buf[:n]) |
||||
io.Copy(c, file) |
||||
} |
@ -0,0 +1,39 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"crypto/tls" |
||||
"io" |
||||
"log" |
||||
"net" |
||||
"net/url" |
||||
"strings" |
||||
) |
||||
|
||||
func serveProxy(c net.Conn, request *url.URL, proxyURL string) { |
||||
original := proxyURL |
||||
|
||||
tlsConfig := &tls.Config{} |
||||
if strings.HasPrefix(proxyURL, "gemini://") { |
||||
proxyURL = proxyURL[9:] |
||||
} else if strings.HasPrefix(proxyURL, "gemini-insecure://") { |
||||
proxyURL = proxyURL[18:] |
||||
tlsConfig.InsecureSkipVerify = true |
||||
} |
||||
proxy, err := tls.Dial("tcp", proxyURL, tlsConfig) |
||||
if err != nil { |
||||
writeStatus(c, statusProxyError) |
||||
return |
||||
} |
||||
defer proxy.Close() |
||||
|
||||
// Forward request
|
||||
proxy.Write([]byte(request.String())) |
||||
proxy.Write([]byte("\r\n")) |
||||
|
||||
// Forward response
|
||||
io.Copy(c, proxy) |
||||
|
||||
if verbose { |
||||
log.Printf("< %s\n", original) |
||||
} |
||||
} |
Loading…
Reference in new issue