http.go

 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}