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 "net/http" 13 "path/filepath" 14 "strconv" 15 "strings" 16 17 "github.com/gliderlabs/ssh" 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/russross/blackfriday/v2" 22 "github.com/sotangled/tangled/knotserver/db" 23 "github.com/sotangled/tangled/knotserver/git" 24 "github.com/sotangled/tangled/types" 25) 26 27func (h *Handle) Index(w http.ResponseWriter, r *http.Request) { 28 w.Write([]byte("This is a knot, part of the wider Tangle network: https://tangled.sh")) 29} 30 31func (h *Handle) RepoIndex(w http.ResponseWriter, r *http.Request) { 32 path := filepath.Join(h.c.Repo.ScanPath, didPath(r)) 33 l := h.l.With("path", path, "handler", "RepoIndex") 34 35 gr, err := git.Open(path, "") 36 if err != nil { 37 if errors.Is(err, plumbing.ErrReferenceNotFound) { 38 resp := types.RepoIndexResponse{ 39 IsEmpty: true, 40 } 41 writeJSON(w, resp) 42 return 43 } else { 44 l.Error("opening repo", "error", err.Error()) 45 notFound(w) 46 return 47 } 48 } 49 commits, err := gr.Commits() 50 if err != nil { 51 writeError(w, err.Error(), http.StatusInternalServerError) 52 l.Error("fetching commits", "error", err.Error()) 53 return 54 } 55 56 var readmeContent template.HTML 57 for _, readme := range h.c.Repo.Readme { 58 ext := filepath.Ext(readme) 59 content, _ := gr.FileContent(readme) 60 if len(content) > 0 { 61 switch ext { 62 case ".md", ".mkd", ".markdown": 63 unsafe := blackfriday.Run( 64 []byte(content), 65 blackfriday.WithExtensions(blackfriday.CommonExtensions), 66 ) 67 html := sanitize(unsafe) 68 readmeContent = template.HTML(html) 69 default: 70 safe := sanitize([]byte(content)) 71 readmeContent = template.HTML( 72 fmt.Sprintf(`<pre>%s</pre>`, safe), 73 ) 74 } 75 break 76 } 77 } 78 79 if readmeContent == "" { 80 l.Warn("no readme found") 81 } 82 83 mainBranch, err := gr.FindMainBranch(h.c.Repo.MainBranch) 84 if err != nil { 85 writeError(w, err.Error(), http.StatusInternalServerError) 86 l.Error("finding main branch", "error", err.Error()) 87 return 88 } 89 90 if len(commits) >= 3 { 91 commits = commits[:3] 92 } 93 resp := types.RepoIndexResponse{ 94 IsEmpty: false, 95 Ref: mainBranch, 96 Commits: commits, 97 Description: getDescription(path), 98 Readme: readmeContent, 99 } 100 101 writeJSON(w, resp) 102 return 103} 104 105func (h *Handle) RepoTree(w http.ResponseWriter, r *http.Request) { 106 treePath := chi.URLParam(r, "*") 107 ref := chi.URLParam(r, "ref") 108 109 l := h.l.With("handler", "RepoTree", "ref", ref, "treePath", treePath) 110 111 path := filepath.Join(h.c.Repo.ScanPath, didPath(r)) 112 gr, err := git.Open(path, ref) 113 if err != nil { 114 notFound(w) 115 return 116 } 117 118 files, err := gr.FileTree(treePath) 119 if err != nil { 120 writeError(w, err.Error(), http.StatusInternalServerError) 121 l.Error("file tree", "error", err.Error()) 122 return 123 } 124 125 data := make(map[string]any) 126 data["ref"] = ref 127 data["parent"] = treePath 128 data["desc"] = getDescription(path) 129 data["dotdot"] = filepath.Dir(treePath) 130 131 h.listFiles(files, data, w) 132 return 133} 134 135func (h *Handle) FileContent(w http.ResponseWriter, r *http.Request) { 136 var raw bool 137 if rawParam, err := strconv.ParseBool(r.URL.Query().Get("raw")); err == nil { 138 raw = rawParam 139 } 140 141 treePath := chi.URLParam(r, "*") 142 ref := chi.URLParam(r, "ref") 143 144 l := h.l.With("handler", "FileContent", "ref", ref, "treePath", treePath) 145 146 path := filepath.Join(h.c.Repo.ScanPath, didPath(r)) 147 gr, err := git.Open(path, ref) 148 if err != nil { 149 notFound(w) 150 return 151 } 152 153 contents, err := gr.FileContent(treePath) 154 if err != nil { 155 writeError(w, err.Error(), http.StatusInternalServerError) 156 return 157 } 158 data := make(map[string]any) 159 data["ref"] = ref 160 data["desc"] = getDescription(path) 161 data["path"] = treePath 162 163 safe := sanitize([]byte(contents)) 164 165 if raw { 166 h.showRaw(string(safe), w) 167 } else { 168 h.showFile(string(safe), data, w, l) 169 } 170} 171 172func (h *Handle) Archive(w http.ResponseWriter, r *http.Request) { 173 name := chi.URLParam(r, "name") 174 file := chi.URLParam(r, "file") 175 176 l := h.l.With("handler", "Archive", "name", name, "file", file) 177 178 // TODO: extend this to add more files compression (e.g.: xz) 179 if !strings.HasSuffix(file, ".tar.gz") { 180 notFound(w) 181 return 182 } 183 184 ref := strings.TrimSuffix(file, ".tar.gz") 185 186 // This allows the browser to use a proper name for the file when 187 // downloading 188 filename := fmt.Sprintf("%s-%s.tar.gz", name, ref) 189 setContentDisposition(w, filename) 190 setGZipMIME(w) 191 192 path := filepath.Join(h.c.Repo.ScanPath, didPath(r)) 193 gr, err := git.Open(path, ref) 194 if err != nil { 195 notFound(w) 196 return 197 } 198 199 gw := gzip.NewWriter(w) 200 defer gw.Close() 201 202 prefix := fmt.Sprintf("%s-%s", name, ref) 203 err = gr.WriteTar(gw, prefix) 204 if err != nil { 205 // once we start writing to the body we can't report error anymore 206 // so we are only left with printing the error. 207 l.Error("writing tar file", "error", err.Error()) 208 return 209 } 210 211 err = gw.Flush() 212 if err != nil { 213 // once we start writing to the body we can't report error anymore 214 // so we are only left with printing the error. 215 l.Error("flushing?", "error", err.Error()) 216 return 217 } 218} 219 220func (h *Handle) Log(w http.ResponseWriter, r *http.Request) { 221 ref := chi.URLParam(r, "ref") 222 path := filepath.Join(h.c.Repo.ScanPath, didPath(r)) 223 224 l := h.l.With("handler", "Log", "ref", ref, "path", path) 225 226 gr, err := git.Open(path, ref) 227 if err != nil { 228 notFound(w) 229 return 230 } 231 232 commits, err := gr.Commits() 233 if err != nil { 234 writeError(w, err.Error(), http.StatusInternalServerError) 235 l.Error("fetching commits", "error", err.Error()) 236 return 237 } 238 239 // Get page parameters 240 page := 1 241 pageSize := 30 242 243 if pageParam := r.URL.Query().Get("page"); pageParam != "" { 244 if p, err := strconv.Atoi(pageParam); err == nil && p > 0 { 245 page = p 246 } 247 } 248 249 if pageSizeParam := r.URL.Query().Get("per_page"); pageSizeParam != "" { 250 if ps, err := strconv.Atoi(pageSizeParam); err == nil && ps > 0 { 251 pageSize = ps 252 } 253 } 254 255 // Calculate pagination 256 start := (page - 1) * pageSize 257 end := start + pageSize 258 total := len(commits) 259 260 if start >= total { 261 commits = []*object.Commit{} 262 } else { 263 if end > total { 264 end = total 265 } 266 commits = commits[start:end] 267 } 268 269 data := make(map[string]interface{}) 270 data["commits"] = commits 271 data["ref"] = ref 272 data["desc"] = getDescription(path) 273 data["log"] = true 274 data["total"] = total 275 data["page"] = page 276 data["per_page"] = pageSize 277 278 writeJSON(w, data) 279 return 280} 281 282func (h *Handle) Diff(w http.ResponseWriter, r *http.Request) { 283 ref := chi.URLParam(r, "ref") 284 285 l := h.l.With("handler", "Diff", "ref", ref) 286 287 path := filepath.Join(h.c.Repo.ScanPath, didPath(r)) 288 gr, err := git.Open(path, ref) 289 if err != nil { 290 notFound(w) 291 return 292 } 293 294 diff, err := gr.Diff() 295 if err != nil { 296 writeError(w, err.Error(), http.StatusInternalServerError) 297 l.Error("getting diff", "error", err.Error()) 298 return 299 } 300 301 data := make(map[string]interface{}) 302 303 data["commit"] = diff.Commit 304 data["stat"] = diff.Stat 305 data["diff"] = diff.Diff 306 data["ref"] = ref 307 data["desc"] = getDescription(path) 308 309 writeJSON(w, data) 310 return 311} 312 313func (h *Handle) Refs(w http.ResponseWriter, r *http.Request) { 314 path := filepath.Join(h.c.Repo.ScanPath, didPath(r)) 315 l := h.l.With("handler", "Refs") 316 317 gr, err := git.Open(path, "") 318 if err != nil { 319 notFound(w) 320 return 321 } 322 323 tags, err := gr.Tags() 324 if err != nil { 325 // Non-fatal, we *should* have at least one branch to show. 326 l.Error("getting tags", "error", err.Error()) 327 } 328 329 branches, err := gr.Branches() 330 if err != nil { 331 l.Error("getting branches", "error", err.Error()) 332 writeError(w, err.Error(), http.StatusInternalServerError) 333 return 334 } 335 336 data := make(map[string]interface{}) 337 338 data["branches"] = branches 339 data["tags"] = tags 340 data["desc"] = getDescription(path) 341 342 writeJSON(w, data) 343 return 344} 345 346func (h *Handle) Keys(w http.ResponseWriter, r *http.Request) { 347 l := h.l.With("handler", "Keys") 348 349 switch r.Method { 350 case http.MethodGet: 351 keys, err := h.db.GetAllPublicKeys() 352 if err != nil { 353 writeError(w, err.Error(), http.StatusInternalServerError) 354 l.Error("getting public keys", "error", err.Error()) 355 return 356 } 357 358 data := make([]map[string]interface{}, 0) 359 for _, key := range keys { 360 j := key.JSON() 361 data = append(data, j) 362 } 363 writeJSON(w, data) 364 return 365 366 case http.MethodPut: 367 pk := db.PublicKey{} 368 if err := json.NewDecoder(r.Body).Decode(&pk); err != nil { 369 writeError(w, "invalid request body", http.StatusBadRequest) 370 return 371 } 372 373 _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pk.Key)) 374 if err != nil { 375 writeError(w, "invalid pubkey", http.StatusBadRequest) 376 } 377 378 if err := h.db.AddPublicKey(pk); err != nil { 379 writeError(w, err.Error(), http.StatusInternalServerError) 380 l.Error("adding public key", "error", err.Error()) 381 return 382 } 383 384 w.WriteHeader(http.StatusNoContent) 385 return 386 } 387} 388 389func (h *Handle) NewRepo(w http.ResponseWriter, r *http.Request) { 390 l := h.l.With("handler", "NewRepo") 391 392 data := struct { 393 Did string `json:"did"` 394 Name string `json:"name"` 395 }{} 396 397 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 398 writeError(w, "invalid request body", http.StatusBadRequest) 399 return 400 } 401 402 did := data.Did 403 name := data.Name 404 405 relativeRepoPath := filepath.Join(did, name) 406 repoPath := filepath.Join(h.c.Repo.ScanPath, relativeRepoPath) 407 err := git.InitBare(repoPath) 408 if err != nil { 409 l.Error("initializing bare repo", "error", err.Error()) 410 writeError(w, err.Error(), http.StatusInternalServerError) 411 return 412 } 413 414 // add perms for this user to access the repo 415 err = h.e.AddRepo(did, ThisServer, relativeRepoPath) 416 if err != nil { 417 l.Error("adding repo permissions", "error", err.Error()) 418 writeError(w, err.Error(), http.StatusInternalServerError) 419 return 420 } 421 422 w.WriteHeader(http.StatusNoContent) 423} 424 425func (h *Handle) AddMember(w http.ResponseWriter, r *http.Request) { 426 l := h.l.With("handler", "AddMember") 427 428 data := struct { 429 Did string `json:"did"` 430 }{} 431 432 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 433 writeError(w, "invalid request body", http.StatusBadRequest) 434 return 435 } 436 437 did := data.Did 438 439 if err := h.db.AddDid(did); err != nil { 440 l.Error("adding did", "error", err.Error()) 441 writeError(w, err.Error(), http.StatusInternalServerError) 442 return 443 } 444 445 h.jc.UpdateDids([]string{did}) 446 if err := h.e.AddMember(ThisServer, did); err != nil { 447 l.Error("adding member", "error", err.Error()) 448 writeError(w, err.Error(), http.StatusInternalServerError) 449 return 450 } 451 452 if err := h.fetchAndAddKeys(r.Context(), did); err != nil { 453 l.Error("fetching and adding keys", "error", err.Error()) 454 writeError(w, err.Error(), http.StatusInternalServerError) 455 return 456 } 457 458 w.WriteHeader(http.StatusNoContent) 459} 460 461func (h *Handle) AddRepoCollaborator(w http.ResponseWriter, r *http.Request) { 462 l := h.l.With("handler", "AddRepoCollaborator") 463 464 data := struct { 465 Did string `json:"did"` 466 }{} 467 468 ownerDid := chi.URLParam(r, "did") 469 repo := chi.URLParam(r, "name") 470 471 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 472 writeError(w, "invalid request body", http.StatusBadRequest) 473 return 474 } 475 476 if err := h.db.AddDid(data.Did); err != nil { 477 l.Error("adding did", "error", err.Error()) 478 writeError(w, err.Error(), http.StatusInternalServerError) 479 return 480 } 481 h.jc.UpdateDids([]string{data.Did}) 482 483 repoName := filepath.Join(ownerDid, repo) 484 if err := h.e.AddRepo(data.Did, ThisServer, repoName); err != nil { 485 l.Error("adding repo collaborator", "error", err.Error()) 486 writeError(w, err.Error(), http.StatusInternalServerError) 487 return 488 } 489 490 if err := h.fetchAndAddKeys(r.Context(), data.Did); err != nil { 491 l.Error("fetching and adding keys", "error", err.Error()) 492 writeError(w, err.Error(), http.StatusInternalServerError) 493 return 494 } 495 496 w.WriteHeader(http.StatusOK) 497} 498 499func (h *Handle) Init(w http.ResponseWriter, r *http.Request) { 500 l := h.l.With("handler", "Init") 501 502 if h.knotInitialized { 503 writeError(w, "knot already initialized", http.StatusConflict) 504 return 505 } 506 507 data := struct { 508 Did string `json:"did"` 509 }{} 510 511 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 512 l.Error("failed to decode request body", "error", err.Error()) 513 writeError(w, "invalid request body", http.StatusBadRequest) 514 return 515 } 516 517 if data.Did == "" { 518 l.Error("empty DID in request", "did", data.Did) 519 writeError(w, "did is empty", http.StatusBadRequest) 520 return 521 } 522 523 if err := h.db.AddDid(data.Did); err != nil { 524 l.Error("failed to add DID", "error", err.Error()) 525 writeError(w, err.Error(), http.StatusInternalServerError) 526 return 527 } 528 529 h.jc.UpdateDids([]string{data.Did}) 530 if err := h.e.AddOwner(ThisServer, data.Did); err != nil { 531 l.Error("adding owner", "error", err.Error()) 532 writeError(w, err.Error(), http.StatusInternalServerError) 533 return 534 } 535 536 if err := h.fetchAndAddKeys(r.Context(), data.Did); err != nil { 537 l.Error("fetching and adding keys", "error", err.Error()) 538 writeError(w, err.Error(), http.StatusInternalServerError) 539 return 540 } 541 542 close(h.init) 543 544 mac := hmac.New(sha256.New, []byte(h.c.Server.Secret)) 545 mac.Write([]byte("ok")) 546 w.Header().Add("X-Signature", hex.EncodeToString(mac.Sum(nil))) 547 548 w.WriteHeader(http.StatusNoContent) 549} 550 551func (h *Handle) Health(w http.ResponseWriter, r *http.Request) { 552 w.Write([]byte("ok")) 553}