aboutsummaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/config/config.go60
-rw-r--r--pkg/config/config_test.go38
-rw-r--r--pkg/ext/auth.go22
-rw-r--r--pkg/ext/compression.go31
-rw-r--r--pkg/ext/log.go4
-rw-r--r--pkg/ext/request.go14
-rw-r--r--pkg/ext/router.go24
-rw-r--r--pkg/git/git.go101
-rw-r--r--pkg/handler/about/handler.go3
-rw-r--r--pkg/handler/auth/login.go4
-rw-r--r--pkg/handler/git/handler.go69
-rw-r--r--pkg/handler/router.go18
-rw-r--r--pkg/handler/static/handler.go63
-rw-r--r--pkg/service/git.go29
14 files changed, 426 insertions, 54 deletions
diff --git a/pkg/config/config.go b/pkg/config/config.go
index 9dbf449..ff969ec 100644
--- a/pkg/config/config.go
+++ b/pkg/config/config.go
@@ -39,6 +39,11 @@ type (
Public bool
}
+ SyntaxHighlight struct {
+ Dark string
+ Light string
+ }
+
// configuration represents file configuration.
// fields needs to be exported to cmp to work
configuration struct {
@@ -50,7 +55,8 @@ type (
Repositories []*GitRepositoryConfiguration
RootReadme string
Scans []*scan
- SyntaxHighlight string
+ SyntaxHighlight SyntaxHighlight
+ Hostname string
}
// This is a per repository configuration.
@@ -74,7 +80,8 @@ type (
removeSuffix bool
repositories []*GitRepositoryConfiguration
rootReadme string
- syntaxHighlight string
+ syntaxHighlight SyntaxHighlight
+ hostname string
}
)
@@ -95,6 +102,7 @@ func LoadConfigurationRepository(configPath string) (*ConfigurationRepository, e
passphrase: []byte(config.Passphrase),
repositories: config.Repositories,
rootReadme: config.RootReadme,
+ hostname: config.Hostname,
syntaxHighlight: config.SyntaxHighlight,
removeSuffix: config.RemoveSuffix,
orderBy: parseOrderBy(config.OrderBy),
@@ -117,12 +125,20 @@ func (c *ConfigurationRepository) GetRootReadme() string {
return c.rootReadme
}
+func (c *ConfigurationRepository) GetHostname() string {
+ return c.hostname
+}
+
func (c *ConfigurationRepository) GetOrderBy() OrderBy {
return c.orderBy
}
func (c *ConfigurationRepository) GetSyntaxHighlight() string {
- return c.syntaxHighlight
+ return c.syntaxHighlight.Light
+}
+
+func (c *ConfigurationRepository) GetSyntaxHighlightDark() string {
+ return c.syntaxHighlight.Dark
}
func (c *ConfigurationRepository) GetListenAddr() string {
@@ -219,6 +235,11 @@ func parse(r io.Reader) (*configuration, error) {
return nil, err
}
+ err = setHostname(block, &config.Hostname)
+ if err != nil {
+ return nil, err
+ }
+
err = setListenAddr(block, &config.ListenAddr)
if err != nil {
return nil, err
@@ -311,11 +332,16 @@ func defaultConfiguration() *configuration {
return &configuration{
Scans: defaultScans(),
RootReadme: "",
+ Hostname: defaultHostname(),
ListenAddr: defaultAddr(),
Repositories: make([]*GitRepositoryConfiguration, 0),
}
}
+func defaultHostname() string {
+ return "https://localhost:8080"
+}
+
func defaultScans() []*scan {
return []*scan{}
}
@@ -339,6 +365,11 @@ func setRootReadme(block scfg.Block, readme *string) error {
return setString(scanDir, readme)
}
+func setHostname(block scfg.Block, hostname *string) error {
+ scanDir := block.Get("hostname")
+ return setString(scanDir, hostname)
+}
+
func setPassphrase(block scfg.Block, listenAddr *string) error {
scanDir := block.Get("passphrase")
return setString(scanDir, listenAddr)
@@ -349,9 +380,26 @@ func setAESKey(block scfg.Block, listenAddr *string) error {
return setString(scanDir, listenAddr)
}
-func setSyntaxHighlight(block scfg.Block, listenAddr *string) error {
- scanDir := block.Get("syntax-highlight")
- return setString(scanDir, listenAddr)
+func setSyntaxHighlight(block scfg.Block, sh *SyntaxHighlight) error {
+ shDir := block.Get("syntax-highlight")
+ if shDir == nil {
+ return nil
+ }
+
+ themes := shDir.Params
+ if len(themes) > 2 || len(themes) == 0 {
+ return errors.New("syntax-highlight must contains at most two params and at least one, light then dark theme name")
+ }
+
+ sh.Light = themes[0]
+ if len(themes) > 1 {
+ sh.Dark = themes[1]
+ } else {
+ // if dark is not set use light
+ sh.Dark = sh.Light
+ }
+
+ return nil
}
func setOrderby(block scfg.Block, orderBy *string) error {
diff --git a/pkg/config/config_test.go b/pkg/config/config_test.go
index 949238e..50744b5 100644
--- a/pkg/config/config_test.go
+++ b/pkg/config/config_test.go
@@ -25,6 +25,7 @@ func TestFileParsing(t *testing.T) {
},
},
ListenAddr: defaultAddr(),
+ Hostname: defaultHostname(),
Repositories: []*GitRepositoryConfiguration{},
},
},
@@ -42,15 +43,32 @@ scan "/srv/git" {
},
},
ListenAddr: defaultAddr(),
+ Hostname: defaultHostname(),
Repositories: []*GitRepositoryConfiguration{},
},
},
{
+ name: "themes",
+ config: `
+syntax-highlight light dark`,
+ expectedConfig: &configuration{
+ Scans: defaultScans(),
+ ListenAddr: defaultAddr(),
+ Hostname: defaultHostname(),
+ Repositories: []*GitRepositoryConfiguration{},
+ SyntaxHighlight: SyntaxHighlight{
+ Light: "light",
+ Dark: "dark",
+ },
+ },
+ },
+ {
name: "minimal repository",
config: `repository /srv/git/cerrado.git`,
expectedConfig: &configuration{
Scans: defaultScans(),
ListenAddr: defaultAddr(),
+ Hostname: defaultHostname(),
Repositories: []*GitRepositoryConfiguration{
{
Name: "cerrado.git",
@@ -74,6 +92,7 @@ repository /srv/git/cerrado.git {
expectedConfig: &configuration{
Scans: defaultScans(),
ListenAddr: defaultAddr(),
+ Hostname: defaultHostname(),
Repositories: []*GitRepositoryConfiguration{
{
Name: "cerrado",
@@ -91,6 +110,7 @@ repository /srv/git/cerrado.git {
expectedConfig: &configuration{
Scans: defaultScans(),
ListenAddr: defaultAddr(),
+ Hostname: defaultHostname(),
Repositories: []*GitRepositoryConfiguration{},
},
},
@@ -99,6 +119,7 @@ repository /srv/git/cerrado.git {
config: `listen-addr unix://var/run/cerrado/cerrado.sock`,
expectedConfig: &configuration{
Scans: defaultScans(),
+ Hostname: defaultHostname(),
ListenAddr: "unix://var/run/cerrado/cerrado.sock",
Repositories: []*GitRepositoryConfiguration{},
},
@@ -112,6 +133,7 @@ aes-key 8XHptZxSWCGs1m7QzztX5zNQ7D9NiQevVX0DaUTNMbDpRwFzoJiB0U7K6O/kqIt01jJVgzBU
syntax-highlight monokailight
order-by lastcommit-desc
remove-suffix true
+hostname https://domain.tld
scan "/srv/git" {
public true
@@ -132,12 +154,16 @@ repository /srv/git/cerrado.git {
Path: "/srv/git",
},
},
- ListenAddr: "unix://var/run/cerrado/cerrado.sock",
- Passphrase: "$2a$14$VnB/ZcB1DUDkMnosRA6Y7.dj8h5eroslDxTeXlLwfQX/x86mh6WAq",
- AESKey: "8XHptZxSWCGs1m7QzztX5zNQ7D9NiQevVX0DaUTNMbDpRwFzoJiB0U7K6O/kqIt01jJVgzBUfiR8ES46ZLLb4w==",
- SyntaxHighlight: "monokailight",
- OrderBy: "lastcommit-desc",
- RemoveSuffix: true,
+ ListenAddr: "unix://var/run/cerrado/cerrado.sock",
+ Passphrase: "$2a$14$VnB/ZcB1DUDkMnosRA6Y7.dj8h5eroslDxTeXlLwfQX/x86mh6WAq",
+ AESKey: "8XHptZxSWCGs1m7QzztX5zNQ7D9NiQevVX0DaUTNMbDpRwFzoJiB0U7K6O/kqIt01jJVgzBUfiR8ES46ZLLb4w==",
+ SyntaxHighlight: SyntaxHighlight{
+ Light: "monokailight",
+ Dark: "monokailight",
+ },
+ OrderBy: "lastcommit-desc",
+ RemoveSuffix: true,
+ Hostname: "https://domain.tld",
Repositories: []*GitRepositoryConfiguration{
{
Name: "linux.git",
diff --git a/pkg/ext/auth.go b/pkg/ext/auth.go
index 5c3070e..ef126ec 100644
--- a/pkg/ext/auth.go
+++ b/pkg/ext/auth.go
@@ -14,19 +14,20 @@ type authService interface {
ValidateToken(token []byte) (bool, error)
}
-func DisableAuthentication(next http.HandlerFunc) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
+func DisableAuthentication(next HandlerFunc) HandlerFunc {
+ return func(w http.ResponseWriter, r *Request) {
ctx := r.Context()
ctx = context.WithValue(ctx, "disableAuthentication", true)
- next(w, r.WithContext(ctx))
+ r.Request = r.WithContext(ctx)
+ next(w, r)
}
}
func VerifyRespository(
config *serverconfig.ConfigurationRepository,
-) func(next http.HandlerFunc) http.HandlerFunc {
- return func(next http.HandlerFunc) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
+) func(next HandlerFunc) HandlerFunc {
+ return func(next HandlerFunc) HandlerFunc {
+ return func(w http.ResponseWriter, r *Request) {
name := r.PathValue("name")
if name != "" {
repo := config.GetByName(name)
@@ -41,9 +42,9 @@ func VerifyRespository(
}
}
-func Authenticate(auth authService) func(next http.HandlerFunc) http.HandlerFunc {
- return func(next http.HandlerFunc) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
+func Authenticate(auth authService) func(next HandlerFunc) HandlerFunc {
+ return func(next HandlerFunc) HandlerFunc {
+ return func(w http.ResponseWriter, r *Request) {
cookie, err := r.Cookie("auth")
if err != nil {
if !errors.Is(err, http.ErrNoCookie) {
@@ -70,9 +71,10 @@ func Authenticate(auth authService) func(next http.HandlerFunc) http.HandlerFunc
ctx := r.Context()
ctx = context.WithValue(ctx, "logged", valid)
+ r.Request = r.WithContext(ctx)
slog.Info("Validated token", "valid?", valid)
- next(w, r.WithContext(ctx))
+ next(w, r)
}
}
}
diff --git a/pkg/ext/compression.go b/pkg/ext/compression.go
index 6c7a219..d3a3df1 100644
--- a/pkg/ext/compression.go
+++ b/pkg/ext/compression.go
@@ -15,18 +15,37 @@ import (
"github.com/klauspost/compress/zstd"
)
-var (
- errInvalidParam = errors.New("Invalid weighted param")
-)
+var errInvalidParam = errors.New("Invalid weighted param")
type CompressionResponseWriter struct {
innerWriter http.ResponseWriter
compressWriter io.Writer
}
-func Compress(next http.HandlerFunc) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
+func Compress(next HandlerFunc) HandlerFunc {
+ return func(w http.ResponseWriter, r *Request) {
+ // TODO: hand this better
+ if strings.HasSuffix(r.URL.Path, ".tar.gz") {
+ next(w, r)
+ return
+ }
+
+ if accept, ok := r.Header["Accept-Encoding"]; ok {
+ if compress, algo := GetCompressionWriter(u.FirstOrZero(accept), w); algo != "" {
+ defer compress.Close()
+ w.Header().Add("Content-Encoding", algo)
+ w = &CompressionResponseWriter{
+ innerWriter: w,
+ compressWriter: compress,
+ }
+ }
+ }
+ next(w, r)
+ }
+}
+func Decompress(next http.HandlerFunc) http.HandlerFunc {
+ return func(w http.ResponseWriter, r *http.Request) {
// TODO: hand this better
if strings.HasSuffix(r.URL.Path, ".tar.gz") {
next(w, r)
@@ -61,12 +80,12 @@ func GetCompressionWriter(header string, inner io.Writer) (io.WriteCloser, strin
default:
return nil, ""
}
-
}
func (c *CompressionResponseWriter) Header() http.Header {
return c.innerWriter.Header()
}
+
func (c *CompressionResponseWriter) Write(b []byte) (int, error) {
return c.compressWriter.Write(b)
}
diff --git a/pkg/ext/log.go b/pkg/ext/log.go
index 8e68134..e0ad89f 100644
--- a/pkg/ext/log.go
+++ b/pkg/ext/log.go
@@ -39,8 +39,8 @@ func wrap(w http.ResponseWriter) *statusWraper {
}
}
-func Log(next http.HandlerFunc) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
+func Log(next HandlerFunc) HandlerFunc {
+ return func(w http.ResponseWriter, r *Request) {
t := time.Now()
s := wrap(w)
next(s, r)
diff --git a/pkg/ext/request.go b/pkg/ext/request.go
new file mode 100644
index 0000000..d1593b2
--- /dev/null
+++ b/pkg/ext/request.go
@@ -0,0 +1,14 @@
+package ext
+
+import (
+ "io"
+ "net/http"
+)
+
+type Request struct {
+ *http.Request
+}
+
+func (r *Request) ReadBody() io.ReadCloser {
+ return r.Body
+}
diff --git a/pkg/ext/router.go b/pkg/ext/router.go
index ce4c126..bbbffa1 100644
--- a/pkg/ext/router.go
+++ b/pkg/ext/router.go
@@ -16,8 +16,9 @@ type (
middlewares []Middleware
router *http.ServeMux
}
- Middleware func(next http.HandlerFunc) http.HandlerFunc
- ErrorRequestHandler func(w http.ResponseWriter, r *http.Request) error
+ HandlerFunc func(http.ResponseWriter, *Request)
+ Middleware func(next HandlerFunc) HandlerFunc
+ ErrorRequestHandler func(w http.ResponseWriter, r *Request) error
)
func NewRouter() *Router {
@@ -34,15 +35,15 @@ func (r *Router) AddMiddleware(middleware Middleware) {
r.middlewares = append(r.middlewares, middleware)
}
-func wrapError(next ErrorRequestHandler) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
+func wrapError(next ErrorRequestHandler) HandlerFunc {
+ return func(w http.ResponseWriter, r *Request) {
if err := next(w, r); err != nil {
if errors.Is(err, service.ErrRepositoryNotFound) ||
errors.Is(err, plumbing.ErrReferenceNotFound) {
NotFound(w, r)
} else {
slog.Error("Internal Server Error", "error", err)
- InternalServerError(r, w, err)
+ InternalServerError(w, r, err)
}
}
}
@@ -54,7 +55,7 @@ func (r *Router) run(next ErrorRequestHandler) http.HandlerFunc {
for _, r := range r.middlewares {
req = r(req)
}
- req(w, re)
+ req(w, &Request{Request: re})
}
}
@@ -62,19 +63,26 @@ func (r *Router) HandleFunc(path string, handler ErrorRequestHandler) {
r.router.HandleFunc(path, r.run(handler))
}
-func NotFound(w http.ResponseWriter, r *http.Request) {
+func NotFound(w http.ResponseWriter, r *Request) {
w.WriteHeader(http.StatusNotFound)
templates.WritePageTemplate(w, &templates.ErrorPage{
Message: "Not Found",
}, r.Context())
}
+func BadRequest(w http.ResponseWriter, r *Request, msg string) {
+ w.WriteHeader(http.StatusBadRequest)
+ templates.WritePageTemplate(w, &templates.ErrorPage{
+ Message: msg,
+ }, r.Context())
+}
+
func Redirect(w http.ResponseWriter, location string) {
w.Header().Add("location", location)
w.WriteHeader(http.StatusTemporaryRedirect)
}
-func InternalServerError(r *http.Request, w http.ResponseWriter, err error) {
+func InternalServerError(w http.ResponseWriter, r *Request, err error) {
w.WriteHeader(http.StatusInternalServerError)
templates.WritePageTemplate(w, &templates.ErrorPage{
Message: fmt.Sprintf("Internal Server Error:\n%s", err.Error()),
diff --git a/pkg/git/git.go b/pkg/git/git.go
index 64c721a..83f3f93 100644
--- a/pkg/git/git.go
+++ b/pkg/git/git.go
@@ -3,10 +3,13 @@ package git
import (
"archive/tar"
"bytes"
+ "context"
"errors"
"fmt"
"io"
"io/fs"
+ "log/slog"
+ "os/exec"
"path"
"sort"
"time"
@@ -432,6 +435,68 @@ func (g *GitRepository) FileContent(path string) ([]byte, error) {
return buf.Bytes(), nil
}
+func (g *GitRepository) WriteInfoRefs(ctx context.Context, w io.Writer) error {
+ cmd := exec.CommandContext(
+ ctx,
+ "git-upload-pack",
+ "--stateless-rpc",
+ "--advertise-refs",
+ ".",
+ )
+
+ cmd.Dir = g.path
+ cmd.Env = []string{
+ // TODO: get this from header.
+ "GIT_PROTOCOL=version=2",
+ }
+
+ var errBuff bytes.Buffer
+ cmd.Stderr = &errBuff
+ cmd.Stdout = w
+
+ err := packLine(w, "# service=git-upload-pack\n")
+ if err != nil {
+ return err
+ }
+
+ err = packFlush(w)
+ if err != nil {
+ return err
+ }
+
+ err = cmd.Run()
+ if err != nil {
+ slog.Error("Error upload pack refs", "message", errBuff.String())
+ return err
+ }
+ return nil
+}
+
+func (g *GitRepository) WriteUploadPack(ctx context.Context, r io.Reader, w io.Writer) error {
+ cmd := exec.CommandContext(
+ ctx,
+ "git-upload-pack",
+ "--stateless-rpc",
+ ".",
+ )
+ cmd.Dir = g.Path()
+ cmd.Env = []string{
+ // TODO: get this from header.
+ "GIT_PROTOCOL=version=2",
+ }
+ var errBuff bytes.Buffer
+ cmd.Stderr = &errBuff
+ cmd.Stdout = w
+ cmd.Stdin = r
+
+ if err := cmd.Run(); err != nil {
+ slog.ErrorContext(ctx, "Git upload pack failed", "error", err, "message", errBuff.String())
+ return err
+ }
+
+ return nil
+}
+
func (g *GitRepository) WriteTar(w io.Writer, prefix string) error {
tw := tar.NewWriter(w)
defer tw.Close()
@@ -613,3 +678,39 @@ func (self *tagList) Less(i, j int) bool {
return dateI.After(dateJ)
}
+
+func packLine(w io.Writer, s string) error {
+ _, err := fmt.Fprintf(w, "%04x%s", len(s)+4, s)
+ return err
+}
+
+func packFlush(w io.Writer) error {
+ _, err := fmt.Fprint(w, "0000")
+ return err
+}
+
+type debugReader struct {
+ r io.Reader
+}
+
+func (d *debugReader) Read(p []byte) (n int, err error) {
+ r, err := d.r.Read(p)
+ if err != nil {
+ if errors.Is(io.EOF, err) {
+ fmt.Printf("READ: EOF\n")
+ }
+ return r, err
+ }
+
+ fmt.Printf("READ: %s\n", p[:r])
+ return r, nil
+}
+
+type debugWriter struct {
+ w io.Writer
+}
+
+func (d *debugWriter) Write(p []byte) (n int, err error) {
+ fmt.Printf("WRITE: %s\n", p)
+ return d.w.Write(p)
+}
diff --git a/pkg/handler/about/handler.go b/pkg/handler/about/handler.go
index ee084cd..b3a1593 100644
--- a/pkg/handler/about/handler.go
+++ b/pkg/handler/about/handler.go
@@ -9,6 +9,7 @@ import (
"github.com/gomarkdown/markdown/html"
"github.com/gomarkdown/markdown/parser"
+ "git.gabrielgio.me/cerrado/pkg/ext"
"git.gabrielgio.me/cerrado/templates"
)
@@ -26,7 +27,7 @@ func NewAboutHandler(configRepo configurationRepository) *AboutHandler {
return &AboutHandler{configRepo.GetRootReadme()}
}
-func (g *AboutHandler) About(w http.ResponseWriter, r *http.Request) error {
+func (g *AboutHandler) About(w http.ResponseWriter, r *ext.Request) error {
f, err := os.Open(g.readmePath)
if err != nil {
return err
diff --git a/pkg/handler/auth/login.go b/pkg/handler/auth/login.go
index 89fd87b..9cc13cc 100644
--- a/pkg/handler/auth/login.go
+++ b/pkg/handler/auth/login.go
@@ -26,7 +26,7 @@ func NewLoginHandler(auth authService) *LoginHandler {
}
}
-func (g *LoginHandler) Logout(w http.ResponseWriter, r *http.Request) error {
+func (g *LoginHandler) Logout(w http.ResponseWriter, r *ext.Request) error {
cookie := &http.Cookie{
Name: "auth",
Value: "",
@@ -44,7 +44,7 @@ func (g *LoginHandler) Logout(w http.ResponseWriter, r *http.Request) error {
return nil
}
-func (g *LoginHandler) Login(w http.ResponseWriter, r *http.Request) error {
+func (g *LoginHandler) Login(w http.ResponseWriter, r *ext.Request) error {
referer := r.URL.Query().Get("referer")
// if query value is empty tries to get from header
diff --git a/pkg/handler/git/handler.go b/pkg/handler/git/handler.go
index a9be54c..d046d19 100644
--- a/pkg/handler/git/handler.go
+++ b/pkg/handler/git/handler.go
@@ -2,6 +2,7 @@ package git
import (
"bytes"
+ "compress/gzip"
"errors"
"fmt"
"io"
@@ -37,6 +38,7 @@ type (
GetRootReadme() string
GetSyntaxHighlight() string
GetOrderBy() config.OrderBy
+ GetHostname() string
}
)
@@ -47,7 +49,7 @@ func NewGitHandler(gitService *service.GitService, confRepo configurationReposit
}
}
-func (g *GitHandler) List(w http.ResponseWriter, r *http.Request) error {
+func (g *GitHandler) List(w http.ResponseWriter, r *ext.Request) error {
// this is the only handler that needs to handle authentication itself.
// everything else relay on name path parameter
logged := ext.IsLoggedIn(r.Context())
@@ -89,7 +91,7 @@ func (g *GitHandler) List(w http.ResponseWriter, r *http.Request) error {
return nil
}
-func (g *GitHandler) Archive(w http.ResponseWriter, r *http.Request) error {
+func (g *GitHandler) Archive(w http.ResponseWriter, r *ext.Request) error {
ext.SetGZip(w)
name := r.PathValue("name")
file := r.PathValue("file")
@@ -115,7 +117,51 @@ func (g *GitHandler) Archive(w http.ResponseWriter, r *http.Request) error {
return nil
}
-func (g *GitHandler) Summary(w http.ResponseWriter, r *http.Request) error {
+func (g *GitHandler) Multiplex(w http.ResponseWriter, r *ext.Request) error {
+ path := r.PathValue("rest")
+ name := r.PathValue("name")
+
+ if r.URL.RawQuery == "service=git-receive-pack" {
+ ext.BadRequest(w, r, "no pushing allowed")
+ return nil
+ }
+
+ if path == "info/refs" && r.URL.RawQuery == "service=git-upload-pack" && r.Method == "GET" {
+ w.Header().Set("content-type", "application/x-git-upload-pack-advertisement")
+
+ err := g.gitService.WriteInfoRefs(r.Context(), name, w)
+ if err != nil {
+ slog.Error("Error WriteInfoRefs", "error", err)
+ }
+ } else if path == "git-upload-pack" && r.Method == "POST" {
+ w.Header().Set("content-type", "application/x-git-upload-pack-result")
+ w.Header().Set("Connection", "Keep-Alive")
+ w.Header().Set("Transfer-Encoding", "chunked")
+ w.WriteHeader(http.StatusOK)
+
+ reader := r.Body
+
+ if r.Header.Get("Content-Encoding") == "gzip" {
+ var err error
+ reader, err = gzip.NewReader(r.Body)
+ if err != nil {
+ return err
+ }
+ defer reader.Close()
+ }
+
+ err := g.gitService.WriteUploadPack(r.Context(), name, reader, w)
+ if err != nil {
+ slog.Error("Error WriteUploadPack", "error", err)
+ }
+ } else if r.Method == "GET" {
+ return g.Summary(w, r)
+ }
+
+ return nil
+}
+
+func (g *GitHandler) Summary(w http.ResponseWriter, r *ext.Request) error {
ext.SetHTML(w)
name := r.PathValue("name")
ref, err := g.gitService.GetHead(name)
@@ -149,13 +195,14 @@ func (g *GitHandler) Summary(w http.ResponseWriter, r *http.Request) error {
Tags: tags,
Branches: branches,
Commits: commits,
+ Hostname: g.config.GetHostname(),
},
}
templates.WritePageTemplate(w, gitList, r.Context())
return nil
}
-func (g *GitHandler) About(w http.ResponseWriter, r *http.Request) error {
+func (g *GitHandler) About(w http.ResponseWriter, r *ext.Request) error {
ext.SetHTML(w)
name := r.PathValue("name")
ref, err := g.gitService.GetHead(name)
@@ -199,7 +246,7 @@ func (g *GitHandler) About(w http.ResponseWriter, r *http.Request) error {
return nil
}
-func (g *GitHandler) Refs(w http.ResponseWriter, r *http.Request) error {
+func (g *GitHandler) Refs(w http.ResponseWriter, r *ext.Request) error {
ext.SetHTML(w)
name := r.PathValue("name")
@@ -230,7 +277,7 @@ func (g *GitHandler) Refs(w http.ResponseWriter, r *http.Request) error {
return nil
}
-func (g *GitHandler) Tree(w http.ResponseWriter, r *http.Request) error {
+func (g *GitHandler) Tree(w http.ResponseWriter, r *ext.Request) error {
ext.SetHTML(w)
name := r.PathValue("name")
ref := r.PathValue("ref")
@@ -259,7 +306,7 @@ func (g *GitHandler) Tree(w http.ResponseWriter, r *http.Request) error {
return nil
}
-func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) error {
+func (g *GitHandler) Blob(w http.ResponseWriter, r *ext.Request) error {
ext.SetHTML(w)
name := r.PathValue("name")
ref := r.PathValue("ref")
@@ -302,6 +349,7 @@ func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) error {
formatter := html.New(
html.WithLineNumbers(true),
html.WithLinkableLineNumbers(true, "L"),
+ html.WithClasses(true),
)
iterator, err := lexer.Tokenise(nil, string(file))
@@ -327,7 +375,7 @@ func (g *GitHandler) Blob(w http.ResponseWriter, r *http.Request) error {
return nil
}
-func (g *GitHandler) Log(w http.ResponseWriter, r *http.Request) error {
+func (g *GitHandler) Log(w http.ResponseWriter, r *ext.Request) error {
ext.SetHTML(w)
name := r.PathValue("name")
ref := r.PathValue("ref")
@@ -350,7 +398,7 @@ func (g *GitHandler) Log(w http.ResponseWriter, r *http.Request) error {
return nil
}
-func (g *GitHandler) Ref(w http.ResponseWriter, r *http.Request) error {
+func (g *GitHandler) Ref(w http.ResponseWriter, r *ext.Request) error {
ext.SetHTML(w)
name := r.PathValue("name")
ref := r.PathValue("ref")
@@ -372,7 +420,7 @@ func (g *GitHandler) Ref(w http.ResponseWriter, r *http.Request) error {
return nil
}
-func (g *GitHandler) Commit(w http.ResponseWriter, r *http.Request) error {
+func (g *GitHandler) Commit(w http.ResponseWriter, r *ext.Request) error {
ext.SetHTML(w)
name := r.PathValue("name")
ref := r.PathValue("ref")
@@ -393,6 +441,7 @@ func (g *GitHandler) Commit(w http.ResponseWriter, r *http.Request) error {
formatter := html.New(
html.WithLineNumbers(true),
html.WithLinkableLineNumbers(true, "L"),
+ html.WithClasses(true),
)
iterator, err := lexer.Tokenise(nil, diff)
diff --git a/pkg/handler/router.go b/pkg/handler/router.go
index e461922..1fbc4e3 100644
--- a/pkg/handler/router.go
+++ b/pkg/handler/router.go
@@ -1,6 +1,7 @@
package handler
import (
+ "fmt"
"net/http"
serverconfig "git.gabrielgio.me/cerrado/pkg/config"
@@ -10,6 +11,7 @@ import (
"git.gabrielgio.me/cerrado/pkg/handler/git"
"git.gabrielgio.me/cerrado/pkg/handler/static"
"git.gabrielgio.me/cerrado/pkg/service"
+ "git.gabrielgio.me/cerrado/templates"
)
// Mount handler gets the requires service and repository to build the handlers
@@ -31,6 +33,14 @@ func MountHandler(
return nil, err
}
+ cssStaticHandler, err := static.ServeStaticCSSHandler(
+ configRepo.GetSyntaxHighlight(),
+ configRepo.GetSyntaxHighlightDark(),
+ )
+ if err != nil {
+ return nil, err
+ }
+
mux := ext.NewRouter()
mux.AddMiddleware(ext.Compress)
mux.AddMiddleware(ext.Log)
@@ -45,8 +55,14 @@ func MountHandler(
}
mux.HandleFunc("/static/{file}", staticHandler)
+ // add slug and session so css file can be cached forever.
+ // Slug follow commit id, which is update every new version
+ // Session is update every time server restarts, this allows the css to be
+ // cached forever but refresh if the admin updates the server configuration.
+ mux.HandleFunc(fmt.Sprintf("/static/theme.%s%s.css", templates.Session, templates.Slug), cssStaticHandler)
mux.HandleFunc("/{name}/about/{$}", gitHandler.About)
- mux.HandleFunc("/{name}/", gitHandler.Summary)
+ mux.HandleFunc("/{name}", gitHandler.Multiplex)
+ mux.HandleFunc("/{name}/{rest...}", gitHandler.Multiplex)
mux.HandleFunc("/{name}/refs/{$}", gitHandler.Refs)
mux.HandleFunc("/{name}/tree/{ref}/{rest...}", gitHandler.Tree)
mux.HandleFunc("/{name}/blob/{ref}/{rest...}", gitHandler.Blob)
diff --git a/pkg/handler/static/handler.go b/pkg/handler/static/handler.go
index 361f690..6cc884e 100644
--- a/pkg/handler/static/handler.go
+++ b/pkg/handler/static/handler.go
@@ -1,6 +1,9 @@
package static
import (
+ "bytes"
+ "fmt"
+ "io"
"io/fs"
"mime"
"net/http"
@@ -8,6 +11,9 @@ import (
"git.gabrielgio.me/cerrado/pkg/ext"
"git.gabrielgio.me/cerrado/static"
+ "github.com/alecthomas/chroma/v2"
+ "github.com/alecthomas/chroma/v2/formatters/html"
+ "github.com/alecthomas/chroma/v2/styles"
)
func ServeStaticHandler() (ext.ErrorRequestHandler, error) {
@@ -16,7 +22,7 @@ func ServeStaticHandler() (ext.ErrorRequestHandler, error) {
return nil, err
}
- return func(w http.ResponseWriter, r *http.Request) error {
+ return func(w http.ResponseWriter, r *ext.Request) error {
var (
f = r.PathValue("file")
e = filepath.Ext(f)
@@ -24,7 +30,60 @@ func ServeStaticHandler() (ext.ErrorRequestHandler, error) {
)
ext.SetMIME(w, m)
w.Header().Add("Cache-Control", "max-age=31536000")
- http.ServeFileFS(w, r, staticFs, f)
+ http.ServeFileFS(w, r.Request, staticFs, f)
return nil
}, nil
}
+
+func ServeStaticCSSHandler(lightTheme, darkTheme string) (ext.ErrorRequestHandler, error) {
+ var (
+ lightStyle = styles.Get(lightTheme)
+ darkStyle = styles.Get(darkTheme)
+ formatter = html.New(
+ html.WithCSSComments(false),
+ )
+ )
+
+ return func(w http.ResponseWriter, r *ext.Request) error {
+ ext.SetMIME(w, "text/css")
+ w.Header().Add("Cache-Control", "max-age=31536000")
+
+ // use buffer so this function can fail before writing to http.ResponseWriter
+ var buffer bytes.Buffer
+
+ var style *chroma.Style
+ style = darkStyle
+ buffer.Write([]byte("[data-bs-theme=\"dark\"] {\n"))
+ err := formatter.WriteCSS(&ws{&buffer}, style)
+ if err != nil {
+ return err
+ }
+ buffer.Write([]byte("}\n"))
+
+ style = lightStyle
+ buffer.Write([]byte("[data-bs-theme=\"light\"] {\n"))
+ err = formatter.WriteCSS(&ws{&buffer}, style)
+ if err != nil {
+ return err
+ }
+ buffer.Write([]byte("}"))
+
+ _, err = io.Copy(w, &buffer)
+ if err != nil {
+ return err
+ }
+
+ return nil
+ }, nil
+}
+
+type ws struct {
+ inner io.Writer
+}
+
+// This is very cursed, and rely on the fact that it writes every css rule at time.
+// it adds & to the begging so it can be nested by the ServeStaticCSSHandler.
+// This will allow the follow bootstrap data-bs-theme.
+func (w *ws) Write(p []byte) (n int, err error) {
+ return fmt.Fprintf(w.inner, "& %s", string(p))
+}
diff --git a/pkg/service/git.go b/pkg/service/git.go
index 5410d7a..6aa5cd6 100644
--- a/pkg/service/git.go
+++ b/pkg/service/git.go
@@ -2,6 +2,7 @@ package service
import (
"compress/gzip"
+ "context"
"errors"
"io"
"log/slog"
@@ -299,3 +300,31 @@ func (g *GitService) GetHead(name string) (*plumbing.Reference, error) {
return repo.Head()
}
+
+func (g *GitService) WriteInfoRefs(ctx context.Context, name string, w io.Writer) error {
+ r := g.configRepo.GetByName(name)
+ if r == nil {
+ return ErrRepositoryNotFound
+ }
+
+ repo, err := git.OpenRepository(r.Path)
+ if err != nil {
+ return err
+ }
+
+ return repo.WriteInfoRefs(ctx, w)
+}
+
+func (g *GitService) WriteUploadPack(ctx context.Context, name string, re io.Reader, w io.Writer) error {
+ r := g.configRepo.GetByName(name)
+ if r == nil {
+ return ErrRepositoryNotFound
+ }
+
+ repo, err := git.OpenRepository(r.Path)
+ if err != nil {
+ return err
+ }
+
+ return repo.WriteUploadPack(ctx, re, w)
+}