this repo has no description
1package knotserver
2
3import (
4 "compress/gzip"
5 "crypto/hmac"
6 "crypto/sha256"
7 "encoding/hex"
8 "encoding/json"
9 "errors"
10 "fmt"
11 "html/template"
12 "log"
13 "net/http"
14 "path/filepath"
15 "strconv"
16 "strings"
17
18 "github.com/go-chi/chi/v5"
19 "github.com/go-git/go-git/v5/plumbing"
20 "github.com/go-git/go-git/v5/plumbing/object"
21 "github.com/icyphox/bild/knotserver/git"
22 "github.com/russross/blackfriday/v2"
23)
24
25func (h *Handle) Index(w http.ResponseWriter, r *http.Request) {
26 w.Write([]byte("This is a knot, part of the wider Tangle network: https://knots.sh"))
27}
28
29func (h *Handle) RepoIndex(w http.ResponseWriter, r *http.Request) {
30 path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
31
32 gr, err := git.Open(path, "")
33 if err != nil {
34 if errors.Is(err, plumbing.ErrReferenceNotFound) {
35 writeMsg(w, "repo empty")
36 return
37 } else {
38 log.Println(err)
39 notFound(w)
40 return
41 }
42 }
43 commits, err := gr.Commits()
44 if err != nil {
45 writeError(w, err.Error(), http.StatusInternalServerError)
46 log.Println(err)
47 return
48 }
49
50 var readmeContent template.HTML
51 for _, readme := range h.c.Repo.Readme {
52 ext := filepath.Ext(readme)
53 content, _ := gr.FileContent(readme)
54 if len(content) > 0 {
55 switch ext {
56 case ".md", ".mkd", ".markdown":
57 unsafe := blackfriday.Run(
58 []byte(content),
59 blackfriday.WithExtensions(blackfriday.CommonExtensions),
60 )
61 html := sanitize(unsafe)
62 readmeContent = template.HTML(html)
63 default:
64 safe := sanitize([]byte(content))
65 readmeContent = template.HTML(
66 fmt.Sprintf(`<pre>%s</pre>`, safe),
67 )
68 }
69 break
70 }
71 }
72
73 if readmeContent == "" {
74 log.Printf("no readme found for %s", path)
75 }
76
77 mainBranch, err := gr.FindMainBranch(h.c.Repo.MainBranch)
78 if err != nil {
79 writeError(w, err.Error(), http.StatusInternalServerError)
80 log.Println(err)
81 return
82 }
83
84 if len(commits) >= 3 {
85 commits = commits[:3]
86 }
87 data := make(map[string]any)
88 data["ref"] = mainBranch
89 data["readme"] = readmeContent
90 data["commits"] = commits
91 data["desc"] = getDescription(path)
92
93 writeJSON(w, data)
94 return
95}
96
97func (h *Handle) RepoTree(w http.ResponseWriter, r *http.Request) {
98 treePath := chi.URLParam(r, "*")
99 ref := chi.URLParam(r, "ref")
100
101 path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
102 gr, err := git.Open(path, ref)
103 if err != nil {
104 notFound(w)
105 return
106 }
107
108 files, err := gr.FileTree(treePath)
109 if err != nil {
110 writeError(w, err.Error(), http.StatusInternalServerError)
111 log.Println(err)
112 return
113 }
114
115 data := make(map[string]any)
116 data["ref"] = ref
117 data["parent"] = treePath
118 data["desc"] = getDescription(path)
119 data["dotdot"] = filepath.Dir(treePath)
120
121 h.listFiles(files, data, w)
122 return
123}
124
125func (h *Handle) FileContent(w http.ResponseWriter, r *http.Request) {
126 var raw bool
127 if rawParam, err := strconv.ParseBool(r.URL.Query().Get("raw")); err == nil {
128 raw = rawParam
129 }
130
131 treePath := chi.URLParam(r, "*")
132 ref := chi.URLParam(r, "ref")
133
134 path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
135 gr, err := git.Open(path, ref)
136 if err != nil {
137 notFound(w)
138 return
139 }
140
141 contents, err := gr.FileContent(treePath)
142 if err != nil {
143 writeError(w, err.Error(), http.StatusInternalServerError)
144 return
145 }
146 data := make(map[string]any)
147 data["ref"] = ref
148 data["desc"] = getDescription(path)
149 data["path"] = treePath
150
151 safe := sanitize([]byte(contents))
152
153 if raw {
154 h.showRaw(string(safe), w)
155 } else {
156 h.showFile(string(safe), data, w)
157 }
158}
159
160func (h *Handle) Archive(w http.ResponseWriter, r *http.Request) {
161 name := chi.URLParam(r, "name")
162 file := chi.URLParam(r, "file")
163
164 // TODO: extend this to add more files compression (e.g.: xz)
165 if !strings.HasSuffix(file, ".tar.gz") {
166 notFound(w)
167 return
168 }
169
170 ref := strings.TrimSuffix(file, ".tar.gz")
171
172 // This allows the browser to use a proper name for the file when
173 // downloading
174 filename := fmt.Sprintf("%s-%s.tar.gz", name, ref)
175 setContentDisposition(w, filename)
176 setGZipMIME(w)
177
178 path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
179 gr, err := git.Open(path, ref)
180 if err != nil {
181 notFound(w)
182 return
183 }
184
185 gw := gzip.NewWriter(w)
186 defer gw.Close()
187
188 prefix := fmt.Sprintf("%s-%s", name, ref)
189 err = gr.WriteTar(gw, prefix)
190 if err != nil {
191 // once we start writing to the body we can't report error anymore
192 // so we are only left with printing the error.
193 log.Println(err)
194 return
195 }
196
197 err = gw.Flush()
198 if err != nil {
199 // once we start writing to the body we can't report error anymore
200 // so we are only left with printing the error.
201 log.Println(err)
202 return
203 }
204}
205
206func (h *Handle) Log(w http.ResponseWriter, r *http.Request) {
207 fmt.Println(r.URL.Path)
208 ref := chi.URLParam(r, "ref")
209
210 path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
211 gr, err := git.Open(path, ref)
212 if err != nil {
213 notFound(w)
214 return
215 }
216
217 commits, err := gr.Commits()
218 if err != nil {
219 writeError(w, err.Error(), http.StatusInternalServerError)
220 log.Println(err)
221 return
222 }
223
224 // Get page parameters
225 page := 1
226 pageSize := 30
227
228 if pageParam := r.URL.Query().Get("page"); pageParam != "" {
229 if p, err := strconv.Atoi(pageParam); err == nil && p > 0 {
230 page = p
231 }
232 }
233
234 if pageSizeParam := r.URL.Query().Get("per_page"); pageSizeParam != "" {
235 if ps, err := strconv.Atoi(pageSizeParam); err == nil && ps > 0 {
236 pageSize = ps
237 }
238 }
239
240 // Calculate pagination
241 start := (page - 1) * pageSize
242 end := start + pageSize
243 total := len(commits)
244
245 if start >= total {
246 commits = []*object.Commit{}
247 } else {
248 if end > total {
249 end = total
250 }
251 commits = commits[start:end]
252 }
253
254 data := make(map[string]interface{})
255 data["commits"] = commits
256 data["ref"] = ref
257 data["desc"] = getDescription(path)
258 data["log"] = true
259 data["total"] = total
260 data["page"] = page
261 data["per_page"] = pageSize
262
263 writeJSON(w, data)
264 return
265}
266
267func (h *Handle) Diff(w http.ResponseWriter, r *http.Request) {
268 ref := chi.URLParam(r, "ref")
269
270 path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
271 gr, err := git.Open(path, ref)
272 if err != nil {
273 notFound(w)
274 return
275 }
276
277 diff, err := gr.Diff()
278 if err != nil {
279 writeError(w, err.Error(), http.StatusInternalServerError)
280 log.Println(err)
281 return
282 }
283
284 data := make(map[string]interface{})
285
286 data["commit"] = diff.Commit
287 data["stat"] = diff.Stat
288 data["diff"] = diff.Diff
289 data["ref"] = ref
290 data["desc"] = getDescription(path)
291
292 writeJSON(w, data)
293 return
294}
295
296func (h *Handle) Refs(w http.ResponseWriter, r *http.Request) {
297 path := filepath.Join(h.c.Repo.ScanPath, didPath(r))
298 gr, err := git.Open(path, "")
299 if err != nil {
300 notFound(w)
301 return
302 }
303
304 tags, err := gr.Tags()
305 if err != nil {
306 // Non-fatal, we *should* have at least one branch to show.
307 log.Println(err)
308 }
309
310 branches, err := gr.Branches()
311 if err != nil {
312 log.Println(err)
313 writeError(w, err.Error(), http.StatusInternalServerError)
314 return
315 }
316
317 data := make(map[string]interface{})
318
319 data["branches"] = branches
320 data["tags"] = tags
321 data["desc"] = getDescription(path)
322
323 writeJSON(w, data)
324 return
325}
326
327// func (h *Handle) Keys(w http.ResponseWriter, r *http.Request) {
328// session, _ := h.s.Get(r, "bild-session")
329// did := session.Values["did"].(string)
330
331// switch r.Method {
332// case http.MethodGet:
333// keys, err := h.db.GetPublicKeys(did)
334// if err != nil {
335// h.WriteOOBNotice(w, "keys", "Failed to list keys. Try again later.")
336// log.Println(err)
337// return
338// }
339
340// data := make(map[string]interface{})
341// data["keys"] = keys
342// if err := h.t.ExecuteTemplate(w, "settings/keys", data); err != nil {
343// log.Println(err)
344// return
345// }
346// case http.MethodPut:
347// key := r.FormValue("key")
348// name := r.FormValue("name")
349// client, _ := h.auth.AuthorizedClient(r)
350
351// _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(key))
352// if err != nil {
353// h.WriteOOBNotice(w, "keys", "Invalid public key. Check your formatting and try again.")
354// log.Printf("parsing public key: %s", err)
355// return
356// }
357
358// if err := h.db.AddPublicKey(did, name, key); err != nil {
359// h.WriteOOBNotice(w, "keys", "Failed to add key.")
360// log.Printf("adding public key: %s", err)
361// return
362// }
363
364// h.WriteOOBNotice(w, "keys", "Key added!")
365// return
366// }
367// }
368
369func (h *Handle) NewRepo(w http.ResponseWriter, r *http.Request) {
370 data := struct {
371 DID string `json:"did"`
372 Name string `json:"name"`
373 }{}
374
375 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
376 writeError(w, "invalid request body", http.StatusBadRequest)
377 return
378 }
379
380 did := data.DID
381 name := data.Name
382
383 repoPath := filepath.Join(h.c.Repo.ScanPath, did, name)
384 err := git.InitBare(repoPath)
385 if err != nil {
386 writeError(w, err.Error(), http.StatusInternalServerError)
387 return
388 }
389
390 w.WriteHeader(http.StatusNoContent)
391}
392
393// func (h *Handle) Timeline(w http.ResponseWriter, r *http.Request) {
394// session, err := h.s.Get(r, "bild-session")
395// user := make(map[string]string)
396// if err != nil || session.IsNew {
397// // user is not logged in
398// } else {
399// user["handle"] = session.Values["handle"].(string)
400// user["did"] = session.Values["did"].(string)
401// }
402
403// if err := h.t.ExecuteTemplate(w, "timeline", user); err != nil {
404// log.Println(err)
405// return
406// }
407// }
408
409func (h *Handle) Health(w http.ResponseWriter, r *http.Request) {
410 log.Println("got health check")
411 mac := hmac.New(sha256.New, []byte(h.c.Secret))
412 mac.Write([]byte("ok"))
413 w.Header().Add("X-Signature", hex.EncodeToString(mac.Sum(nil)))
414
415 w.Write([]byte("ok"))
416}