2023-08-21 00:27:54 +01:00
|
|
|
package serve
|
|
|
|
|
|
|
|
import (
|
2024-08-16 16:48:50 +01:00
|
|
|
"context"
|
2024-08-10 13:28:30 +01:00
|
|
|
"github.com/1f349/bluebell/conf"
|
2024-08-16 16:48:50 +01:00
|
|
|
"github.com/1f349/bluebell/database"
|
2023-08-21 00:27:54 +01:00
|
|
|
"github.com/julienschmidt/httprouter"
|
|
|
|
"github.com/spf13/afero"
|
|
|
|
"io"
|
2024-08-16 16:48:50 +01:00
|
|
|
"net"
|
2023-08-21 00:27:54 +01:00
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
indexBranches = []string{
|
|
|
|
"main",
|
|
|
|
"master",
|
|
|
|
}
|
|
|
|
indexFiles = []func(p string) string{
|
|
|
|
func(p string) string { return path.Join(p, "index.html") },
|
|
|
|
func(p string) string { return p + ".html" },
|
|
|
|
func(p string) string { return p },
|
|
|
|
}
|
|
|
|
)
|
|
|
|
|
2024-08-16 16:48:50 +01:00
|
|
|
type sitesQueries interface {
|
|
|
|
GetSiteByDomain(ctx context.Context, domain string) (database.Site, error)
|
|
|
|
}
|
|
|
|
|
|
|
|
func New(storage afero.Fs, db sitesQueries, domain string) *Handler {
|
|
|
|
return &Handler{storage, db, domain}
|
2023-08-21 00:27:54 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
type Handler struct {
|
|
|
|
storageFs afero.Fs
|
2024-08-16 16:48:50 +01:00
|
|
|
db sitesQueries
|
|
|
|
domain string
|
2023-08-21 00:27:54 +01:00
|
|
|
}
|
|
|
|
|
2024-08-16 16:48:50 +01:00
|
|
|
func (h *Handler) Handle(rw http.ResponseWriter, req *http.Request, _ httprouter.Params) {
|
|
|
|
host, _, err := net.SplitHostPort(req.Host)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(rw, "Bad Gateway", http.StatusBadGateway)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
site, ok := strings.CutSuffix(host, "."+h.domain)
|
2023-08-21 00:27:54 +01:00
|
|
|
if !ok {
|
|
|
|
http.Error(rw, "Bad Gateway", http.StatusBadGateway)
|
|
|
|
return
|
|
|
|
}
|
2024-08-16 16:48:50 +01:00
|
|
|
site = conf.SlugFromDomain(site)
|
|
|
|
branch := req.URL.User.Username()
|
2023-08-21 00:27:54 +01:00
|
|
|
if branch == "" {
|
|
|
|
for _, i := range indexBranches {
|
2024-08-16 16:48:50 +01:00
|
|
|
if h.tryServePath(rw, site, i, req.URL.Path) {
|
2023-08-21 00:27:54 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
}
|
2024-08-16 16:48:50 +01:00
|
|
|
} else if h.tryServePath(rw, site, branch, req.URL.Path) {
|
2023-08-21 00:27:54 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
http.Error(rw, "404 Not Found", http.StatusNotFound)
|
|
|
|
}
|
|
|
|
|
2024-08-16 16:48:50 +01:00
|
|
|
func (h *Handler) tryServePath(rw http.ResponseWriter, site, branch, p string) bool {
|
2023-08-21 00:27:54 +01:00
|
|
|
for _, i := range indexFiles {
|
2024-08-16 16:48:50 +01:00
|
|
|
if h.tryServeFile(rw, site, branch, i(p)) {
|
2023-08-21 00:27:54 +01:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2024-08-16 16:48:50 +01:00
|
|
|
func (h *Handler) tryServeFile(rw http.ResponseWriter, site, branch, p string) bool {
|
|
|
|
// prevent path traversal
|
|
|
|
if strings.Contains(site, "..") || strings.Contains(branch, "..") || strings.Contains(p, "..") {
|
|
|
|
http.Error(rw, "400 Bad Request", http.StatusBadRequest)
|
|
|
|
return true
|
2023-08-21 00:27:54 +01:00
|
|
|
}
|
|
|
|
open, err := h.storageFs.Open(filepath.Join(site, branch, p))
|
|
|
|
switch {
|
|
|
|
case err == nil:
|
|
|
|
rw.WriteHeader(http.StatusOK)
|
|
|
|
_, _ = io.Copy(rw, open)
|
|
|
|
case os.IsNotExist(err):
|
|
|
|
// check next path
|
|
|
|
return false
|
|
|
|
default:
|
|
|
|
http.Error(rw, "500 Internal Server Error", http.StatusInternalServerError)
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|