forked from tslocum/twins
8 changed files with 361 additions and 100 deletions
@ -0,0 +1,212 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"crypto/tls" |
||||
"fmt" |
||||
"io/ioutil" |
||||
"net/http" |
||||
"net/url" |
||||
"os" |
||||
"path" |
||||
"strings" |
||||
|
||||
"gitlab.com/tslocum/gmitohtml/pkg/gmitohtml" |
||||
) |
||||
|
||||
var cssBytes = []byte(gmitohtml.StyleCSS) |
||||
|
||||
func serveHTTPS(w http.ResponseWriter, r *http.Request) (int, int64, string) { |
||||
if r.URL.Path == "" { |
||||
// Redirect to /
|
||||
u, err := url.Parse(r.URL.String()) |
||||
if err != nil { |
||||
status := http.StatusInternalServerError |
||||
http.Error(w, "Failed to parse URL", status) |
||||
return status, -1, "" |
||||
} |
||||
u.Path += "/" |
||||
|
||||
status := http.StatusTemporaryRedirect |
||||
http.Redirect(w, r, u.String(), status) |
||||
return status, -1, "" |
||||
} else if r.URL.Path == "/assets/style.css" { |
||||
status := http.StatusOK |
||||
w.Header().Set("Content-Type", cssType) |
||||
w.WriteHeader(status) |
||||
|
||||
w.Write(cssBytes) |
||||
return status, int64(len(cssBytes)), "" |
||||
} |
||||
|
||||
pathBytes := []byte(r.URL.Path) |
||||
strippedPath := r.URL.Path |
||||
if strippedPath[0] == '/' { |
||||
strippedPath = strippedPath[1:] |
||||
} |
||||
|
||||
if host, ok := config.Hosts[r.URL.Hostname()]; ok { |
||||
for _, serve := range host.Paths { |
||||
matchedRegexp := serve.r != nil && serve.r.Match(pathBytes) |
||||
matchedPrefix := serve.r == nil && strings.HasPrefix(r.URL.Path, serve.Path) |
||||
if !matchedRegexp && !matchedPrefix { |
||||
continue |
||||
} |
||||
|
||||
requireInput := serve.Input != "" || serve.SensitiveInput != "" |
||||
if r.URL.RawQuery == "" && requireInput { |
||||
if serve.SensitiveInput != "" { |
||||
// TODO
|
||||
} |
||||
status := http.StatusInternalServerError |
||||
http.Error(w, "Gemini to HTML conversion is not supported for this page", status) |
||||
return status, -1, serve.Log |
||||
} |
||||
|
||||
if matchedRegexp || matchedPrefix { |
||||
if serve.Root == "" || serve.FastCGI != "" { |
||||
status := http.StatusInternalServerError |
||||
http.Error(w, "Gemini to HTML conversion is not supported for this page", status) |
||||
return status, -1, serve.Log |
||||
} |
||||
|
||||
var filePath string |
||||
if serve.Root != "" { |
||||
root := serve.Root |
||||
if root[len(root)-1] != '/' { |
||||
root += "/" |
||||
} |
||||
|
||||
requestSplit := strings.Split(r.URL.Path, "/") |
||||
|
||||
if !serve.SymLinks { |
||||
for i := 1; i < len(requestSplit); i++ { |
||||
info, err := os.Lstat(path.Join(root, strings.Join(requestSplit[1:i+1], "/"))) |
||||
if err != nil || info.Mode()&os.ModeSymlink == os.ModeSymlink { |
||||
http.NotFound(w, r) |
||||
return http.StatusNotFound, -1, serve.Log |
||||
} |
||||
} |
||||
} |
||||
|
||||
filePath = path.Join(root, strings.Join(requestSplit[1:], "/")) |
||||
} |
||||
|
||||
fi, err := os.Stat(filePath) |
||||
if err != nil { |
||||
http.NotFound(w, r) |
||||
return http.StatusNotFound, -1, serve.Log |
||||
} |
||||
|
||||
mode := fi.Mode() |
||||
hasTrailingSlash := len(r.URL.Path) > 0 && r.URL.Path[len(r.URL.Path)-1] == '/' |
||||
if mode.IsDir() { |
||||
if !hasTrailingSlash { |
||||
u, err := url.Parse(r.URL.String()) |
||||
if err != nil { |
||||
status := http.StatusInternalServerError |
||||
http.Error(w, "Failed to parse URL", status) |
||||
return status, -1, serve.Log |
||||
} |
||||
u.Path += "/" |
||||
|
||||
status := http.StatusTemporaryRedirect |
||||
http.Redirect(w, r, u.String(), status) |
||||
return status, -1, serve.Log |
||||
} |
||||
|
||||
_, err := os.Stat(path.Join(filePath, "index.gmi")) |
||||
if err != nil { |
||||
_, err := os.Stat(path.Join(filePath, "index.gemini")) |
||||
if err != nil { |
||||
if serve.List { |
||||
status := http.StatusInternalServerError |
||||
http.Error(w, "HTTPS dir lost not yet implemented", status) |
||||
return status, -1, serve.Log |
||||
} |
||||
|
||||
http.NotFound(w, r) |
||||
return http.StatusNotFound, -1, serve.Log |
||||
} |
||||
filePath = path.Join(filePath, "index.gemini") |
||||
} else { |
||||
filePath = path.Join(filePath, "index.gmi") |
||||
} |
||||
} else if hasTrailingSlash && len(r.URL.Path) > 1 { |
||||
u, err := url.Parse(r.URL.String()) |
||||
if err != nil { |
||||
status := http.StatusInternalServerError |
||||
http.Error(w, "Failed to parse URL", status) |
||||
return status, -1, serve.Log |
||||
} |
||||
u.Path = u.Path[:len(u.Path)-1] |
||||
|
||||
status := http.StatusTemporaryRedirect |
||||
http.Redirect(w, r, u.String(), status) |
||||
return status, -1, serve.Log |
||||
} |
||||
|
||||
data, err := ioutil.ReadFile(filePath) |
||||
if err != nil { |
||||
status := http.StatusInternalServerError |
||||
http.Error(w, err.Error(), status) |
||||
return status, -1, serve.Log |
||||
} |
||||
|
||||
result := gmitohtml.Convert([]byte(data), r.URL.String()) |
||||
|
||||
status := http.StatusOK |
||||
w.Header().Set("Content-Type", htmlType) |
||||
w.WriteHeader(status) |
||||
|
||||
w.Write(result) |
||||
return status, int64(len(result)), serve.Log |
||||
} |
||||
} |
||||
} |
||||
|
||||
http.NotFound(w, r) |
||||
return http.StatusNotFound, -1, "" |
||||
} |
||||
|
||||
type responseWriter struct { |
||||
statusCode int |
||||
header http.Header |
||||
conn *tls.Conn |
||||
wroteHeader bool |
||||
} |
||||
|
||||
func newResponseWriter(conn *tls.Conn) *responseWriter { |
||||
return &responseWriter{ |
||||
header: http.Header{}, |
||||
conn: conn, |
||||
} |
||||
} |
||||
|
||||
func (w *responseWriter) Header() http.Header { |
||||
return w.header |
||||
} |
||||
|
||||
func (w *responseWriter) Write(b []byte) (int, error) { |
||||
if !w.wroteHeader { |
||||
w.wroteHeader = true |
||||
w.WriteHeader(http.StatusOK) |
||||
} |
||||
return w.conn.Write(b) |
||||
} |
||||
|
||||
func (w *responseWriter) WriteHeader(statusCode int) { |
||||
if w.wroteHeader { |
||||
return |
||||
} |
||||
|
||||
w.statusCode = statusCode |
||||
|
||||
statusText := http.StatusText(statusCode) |
||||
if statusText == "" { |
||||
statusText = "Unknown" |
||||
} |
||||
|
||||
w.conn.Write([]byte(fmt.Sprintf("HTTP/1.1 %d %s\r\n", statusCode, statusText))) |
||||
w.header.Write(w.conn) |
||||
w.conn.Write([]byte("\r\n")) |
||||
} |
Loading…
Reference in new issue