💾 Archived View for gemini.rmf-dev.com › repo › Vaati › Gemigit › files › accba6af8454e1250e45fcd23b… captured on 2023-09-08 at 16:30:56. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2023-04-19)
-=-=-=-=-=-=-
0 package httpgit
1
2 import (
3 "gemigit/access"
4 "gemigit/config"
5 "gemigit/db"
6 "log"
7 "net/http"
8 "strconv"
9 "strings"
10 "time"
11
12 githttpxfer "github.com/nulab/go-git-http-xfer/githttpxfer"
13 )
14
15 func Listen(path string, address string, port int) {
16 ghx, err := githttpxfer.New(path, "git")
17 if err != nil {
18 log.Fatalln("GitHTTPXfer instance could not be created. ",
19 err.Error())
20 }
21
22 chain := newChain()
23 chain.use(logging)
24 chain.use(basicAuth)
25 handler := chain.build(ghx)
26
27 log.Println("Http server started on port", port)
28 if err := http.ListenAndServe(":"+strconv.Itoa(port), handler);
29 err != nil {
30 log.Fatalln("ListenAndServe: ", err.Error())
31 }
32 }
33
34 type middleware func(http.Handler) http.Handler
35
36 func newChain() *chain {
37 return &chain{[]middleware{}}
38 }
39
40 type chain struct {
41 middlewares []middleware
42 }
43
44 func (c *chain) use(m middleware) {
45 c.middlewares = append(c.middlewares, m)
46 }
47
48 func (c *chain) build(h http.Handler) http.Handler {
49 for i := range c.middlewares {
50 h = c.middlewares[len(c.middlewares)-1-i](h)
51 }
52 return h
53 }
54
55 func logging(next http.Handler) http.Handler {
56 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
57 t1 := time.Now()
58 next.ServeHTTP(w, r)
59 t2 := time.Now()
60 realIP := r.Header.Get("X-Real-IP")
61 log.Println("["+realIP+"]["+r.Method+"]",
62 r.URL.String(), t2.Sub(t1))
63 })
64 }
65
66 func basicAuth(next http.Handler) http.Handler {
67 return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
68 var err error
69 readOnly := false
70 public := false
71 params := strings.Split(r.URL.Path[1:], "/")
72 if len(params) < 2 {
73 renderNotFound(w)
74 return
75 }
76 owner := params[0]
77 repo := params[1]
78 if strings.Contains(r.URL.Path, "git-upload-pack") ||
79 strings.Contains(r.URL.RawQuery, "git-upload-pack") {
80 readOnly = true
81 public, err = db.IsRepoPublic(repo, owner)
82 if err != nil {
83 renderNotFound(w)
84 return
85 }
86 if config.Cfg.Git.Public && public {
87 next.ServeHTTP(w, r)
88 return
89 }
90 }
91
92 username, password, ok := r.BasicAuth()
93 if !ok {
94 renderUnauthorized(w)
95 return
96 }
97 /* The git key in the configuration file is empty by default,
98 so that root# authentication is disabled by default.
99 The root authentication is necessary to run instance in
100 stateless mode. */
101 if config.Cfg.Git.Key != "" && username == "root#" &&
102 password == config.Cfg.Git.Key {
103 next.ServeHTTP(w, r)
104 return
105 }
106 /* check if it is allowed to use password instead of token*/
107 pass, err := db.CanUsePassword(repo, owner, username)
108 if err != nil {
109 log.Println(err.Error())
110 renderUnauthorized(w)
111 return
112 }
113 err = access.Login(username, password, true, pass)
114 if err != nil {
115 log.Println(err.Error())
116 renderUnauthorized(w)
117 return
118 }
119 if readOnly && public {
120 next.ServeHTTP(w, r)
121 return
122 }
123 if readOnly {
124 err = access.HasReadAccess(repo, owner, username)
125 } else {
126 err = access.HasWriteAccess(repo, owner, username)
127 }
128 if err != nil {
129 log.Println(err.Error())
130 renderUnauthorized(w)
131 return
132 }
133 next.ServeHTTP(w, r)
134 })
135 }
136
137 func renderNotFound(w http.ResponseWriter) {
138 w.WriteHeader(http.StatusNotFound)
139 w.Write([]byte(http.StatusText(http.StatusNotFound)))
140 w.Header().Set("Content-Type", "text/plain")
141 }
142
143 func renderUnauthorized(w http.ResponseWriter) {
144 w.Header().Set("WWW-Authenticate", "Basic realm=\"" +
145 "Please enter your username and password.\"")
146 w.WriteHeader(http.StatusUnauthorized)
147 w.Write([]byte(http.StatusText(http.StatusUnauthorized)))
148 w.Header().Set("Content-Type", "text/plain")
149 }
150