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