1package server
2
3import (
4 "context"
5 "fmt"
6 "net/http"
7 "os"
8 "path/filepath"
9 "strings"
10
11 "github.com/charmbracelet/soft-serve/config"
12 appCfg "github.com/charmbracelet/soft-serve/internal/config"
13 "github.com/charmbracelet/wish/git"
14 "goji.io"
15 "goji.io/pat"
16 "goji.io/pattern"
17)
18
19type HTTPServer struct {
20 server *http.Server
21 gitHandler http.Handler
22 cfg *config.Config
23 ac *appCfg.Config
24}
25
26func NewHTTPServer(cfg *config.Config, ac *appCfg.Config) *HTTPServer {
27 h := goji.NewMux()
28 s := &HTTPServer{
29 cfg: cfg,
30 ac: ac,
31 gitHandler: http.FileServer(http.Dir(cfg.RepoPath)),
32 server: &http.Server{
33 Addr: fmt.Sprintf(":%d", cfg.HTTPPort),
34 Handler: h,
35 TLSConfig: cfg.TLSConfig,
36 },
37 }
38 h.HandleFunc(pat.Get("/:repo"), s.handleGit)
39 h.HandleFunc(pat.Get("/:repo/*"), s.handleGit)
40 return s
41}
42
43func (s *HTTPServer) Start() error {
44 if s.cfg.TLSConfig != nil {
45 return s.server.ListenAndServeTLS("", "")
46 }
47 return s.server.ListenAndServe()
48}
49
50func (s *HTTPServer) Shutdown(ctx context.Context) error {
51 return s.server.Shutdown(ctx)
52}
53
54func (s *HTTPServer) handleGit(w http.ResponseWriter, r *http.Request) {
55 ua := r.Header.Get("User-Agent")
56 repo := pat.Param(r, "repo")
57 access := s.ac.AuthRepo(repo, nil)
58 path := pattern.Path(r.Context())
59 stat, err := os.Stat(filepath.Join(s.cfg.RepoPath, repo, path))
60 // Restrict access to files
61 if err != nil || stat.IsDir() {
62 http.NotFound(w, r)
63 return
64 }
65 if !strings.HasPrefix(strings.ToLower(ua), "git") {
66 http.Error(w, fmt.Sprintf("%d Bad Request", http.StatusBadRequest), http.StatusBadRequest)
67 return
68 }
69 if access < git.ReadOnlyAccess || !s.ac.AllowKeyless {
70 http.Error(w, fmt.Sprintf("%d Unauthorized", http.StatusUnauthorized), http.StatusUnauthorized)
71 return
72 }
73 w.Header().Set("Content-Type", "text/plain; charset=utf-8")
74 w.Header().Set("X-Content-Type-Options", "nosniff")
75 r.URL.Path = fmt.Sprintf("/%s/%s", repo, path)
76 s.gitHandler.ServeHTTP(w, r)
77}