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