forked from tangled.org/core
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 "log" 12 "net/http" 13 "net/url" 14 "os" 15 "path/filepath" 16 "strconv" 17 "strings" 18 19 securejoin "github.com/cyphar/filepath-securejoin" 20 "github.com/gliderlabs/ssh" 21 "github.com/go-chi/chi/v5" 22 gogit "github.com/go-git/go-git/v5" 23 "github.com/go-git/go-git/v5/plumbing" 24 "github.com/go-git/go-git/v5/plumbing/object" 25 "tangled.sh/tangled.sh/core/knotserver/db" 26 "tangled.sh/tangled.sh/core/knotserver/git" 27 "tangled.sh/tangled.sh/core/types" 28) 29 30func (h *Handle) Index(w http.ResponseWriter, r *http.Request) { 31 w.Write([]byte("This is a knot server. More info at https://tangled.sh")) 32} 33 34func (h *Handle) Capabilities(w http.ResponseWriter, r *http.Request) { 35 w.Header().Set("Content-Type", "application/json") 36 37 capabilities := map[string]any{ 38 "pull_requests": map[string]any{ 39 "patch_submissions": true, 40 "branch_submissions": true, 41 "fork_submissions": true, 42 }, 43 } 44 45 jsonData, err := json.Marshal(capabilities) 46 if err != nil { 47 http.Error(w, "Failed to serialize JSON", http.StatusInternalServerError) 48 return 49 } 50 51 w.Write(jsonData) 52} 53 54func (h *Handle) RepoIndex(w http.ResponseWriter, r *http.Request) { 55 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 56 l := h.l.With("path", path, "handler", "RepoIndex") 57 ref := chi.URLParam(r, "ref") 58 ref, _ = url.PathUnescape(ref) 59 60 gr, err := git.Open(path, ref) 61 if err != nil { 62 log.Println(err) 63 if errors.Is(err, plumbing.ErrReferenceNotFound) { 64 resp := types.RepoIndexResponse{ 65 IsEmpty: true, 66 } 67 writeJSON(w, resp) 68 return 69 } else { 70 l.Error("opening repo", "error", err.Error()) 71 notFound(w) 72 return 73 } 74 } 75 76 commits, err := gr.Commits() 77 total := len(commits) 78 if err != nil { 79 writeError(w, err.Error(), http.StatusInternalServerError) 80 l.Error("fetching commits", "error", err.Error()) 81 return 82 } 83 if len(commits) > 10 { 84 commits = commits[:10] 85 } 86 87 branches, err := gr.Branches() 88 if err != nil { 89 l.Error("getting branches", "error", err.Error()) 90 writeError(w, err.Error(), http.StatusInternalServerError) 91 return 92 } 93 94 bs := []types.Branch{} 95 for _, branch := range branches { 96 b := types.Branch{} 97 b.Hash = branch.Hash().String() 98 b.Name = branch.Name().Short() 99 bs = append(bs, b) 100 } 101 102 tags, err := gr.Tags() 103 if err != nil { 104 // Non-fatal, we *should* have at least one branch to show. 105 l.Warn("getting tags", "error", err.Error()) 106 } 107 108 rtags := []*types.TagReference{} 109 for _, tag := range tags { 110 tr := types.TagReference{ 111 Tag: tag.TagObject(), 112 } 113 114 tr.Reference = types.Reference{ 115 Name: tag.Name(), 116 Hash: tag.Hash().String(), 117 } 118 119 if tag.Message() != "" { 120 tr.Message = tag.Message() 121 } 122 123 rtags = append(rtags, &tr) 124 } 125 126 var readmeContent string 127 var readmeFile string 128 for _, readme := range h.c.Repo.Readme { 129 content, _ := gr.FileContent(readme) 130 if len(content) > 0 { 131 readmeContent = string(content) 132 readmeFile = readme 133 } 134 } 135 136 files, err := gr.FileTree("") 137 if err != nil { 138 writeError(w, err.Error(), http.StatusInternalServerError) 139 l.Error("file tree", "error", err.Error()) 140 return 141 } 142 143 if ref == "" { 144 mainBranch, err := gr.FindMainBranch() 145 if err != nil { 146 writeError(w, err.Error(), http.StatusInternalServerError) 147 l.Error("finding main branch", "error", err.Error()) 148 return 149 } 150 ref = mainBranch 151 } 152 153 resp := types.RepoIndexResponse{ 154 IsEmpty: false, 155 Ref: ref, 156 Commits: commits, 157 Description: getDescription(path), 158 Readme: readmeContent, 159 ReadmeFileName: readmeFile, 160 Files: files, 161 Branches: bs, 162 Tags: rtags, 163 TotalCommits: total, 164 } 165 166 writeJSON(w, resp) 167 return 168} 169 170func (h *Handle) RepoTree(w http.ResponseWriter, r *http.Request) { 171 treePath := chi.URLParam(r, "*") 172 ref := chi.URLParam(r, "ref") 173 ref, _ = url.PathUnescape(ref) 174 175 l := h.l.With("handler", "RepoTree", "ref", ref, "treePath", treePath) 176 177 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 178 gr, err := git.Open(path, ref) 179 if err != nil { 180 notFound(w) 181 return 182 } 183 184 files, err := gr.FileTree(treePath) 185 if err != nil { 186 writeError(w, err.Error(), http.StatusInternalServerError) 187 l.Error("file tree", "error", err.Error()) 188 return 189 } 190 191 resp := types.RepoTreeResponse{ 192 Ref: ref, 193 Parent: treePath, 194 Description: getDescription(path), 195 DotDot: filepath.Dir(treePath), 196 Files: files, 197 } 198 199 writeJSON(w, resp) 200 return 201} 202 203func (h *Handle) Blob(w http.ResponseWriter, r *http.Request) { 204 treePath := chi.URLParam(r, "*") 205 ref := chi.URLParam(r, "ref") 206 ref, _ = url.PathUnescape(ref) 207 208 l := h.l.With("handler", "FileContent", "ref", ref, "treePath", treePath) 209 210 path, _ := securejoin.SecureJoin(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 var isBinaryFile bool = false 218 contents, err := gr.FileContent(treePath) 219 if errors.Is(err, git.ErrBinaryFile) { 220 isBinaryFile = true 221 } else if errors.Is(err, object.ErrFileNotFound) { 222 notFound(w) 223 return 224 } else if err != nil { 225 writeError(w, err.Error(), http.StatusInternalServerError) 226 return 227 } 228 229 bytes := []byte(contents) 230 // safe := string(sanitize(bytes)) 231 sizeHint := len(bytes) 232 233 resp := types.RepoBlobResponse{ 234 Ref: ref, 235 Contents: string(bytes), 236 Path: treePath, 237 IsBinary: isBinaryFile, 238 SizeHint: uint64(sizeHint), 239 } 240 241 h.showFile(resp, w, l) 242} 243 244func (h *Handle) Archive(w http.ResponseWriter, r *http.Request) { 245 name := chi.URLParam(r, "name") 246 file := chi.URLParam(r, "file") 247 248 l := h.l.With("handler", "Archive", "name", name, "file", file) 249 250 // TODO: extend this to add more files compression (e.g.: xz) 251 if !strings.HasSuffix(file, ".tar.gz") { 252 notFound(w) 253 return 254 } 255 256 ref := strings.TrimSuffix(file, ".tar.gz") 257 258 // This allows the browser to use a proper name for the file when 259 // downloading 260 filename := fmt.Sprintf("%s-%s.tar.gz", name, ref) 261 setContentDisposition(w, filename) 262 setGZipMIME(w) 263 264 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 265 gr, err := git.Open(path, ref) 266 if err != nil { 267 notFound(w) 268 return 269 } 270 271 gw := gzip.NewWriter(w) 272 defer gw.Close() 273 274 prefix := fmt.Sprintf("%s-%s", name, ref) 275 err = gr.WriteTar(gw, prefix) 276 if err != nil { 277 // once we start writing to the body we can't report error anymore 278 // so we are only left with printing the error. 279 l.Error("writing tar file", "error", err.Error()) 280 return 281 } 282 283 err = gw.Flush() 284 if err != nil { 285 // once we start writing to the body we can't report error anymore 286 // so we are only left with printing the error. 287 l.Error("flushing?", "error", err.Error()) 288 return 289 } 290} 291 292func (h *Handle) Log(w http.ResponseWriter, r *http.Request) { 293 ref := chi.URLParam(r, "ref") 294 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 295 296 l := h.l.With("handler", "Log", "ref", ref, "path", path) 297 298 gr, err := git.Open(path, ref) 299 if err != nil { 300 notFound(w) 301 return 302 } 303 304 commits, err := gr.Commits() 305 if err != nil { 306 writeError(w, err.Error(), http.StatusInternalServerError) 307 l.Error("fetching commits", "error", err.Error()) 308 return 309 } 310 311 // Get page parameters 312 page := 1 313 pageSize := 30 314 315 if pageParam := r.URL.Query().Get("page"); pageParam != "" { 316 if p, err := strconv.Atoi(pageParam); err == nil && p > 0 { 317 page = p 318 } 319 } 320 321 if pageSizeParam := r.URL.Query().Get("per_page"); pageSizeParam != "" { 322 if ps, err := strconv.Atoi(pageSizeParam); err == nil && ps > 0 { 323 pageSize = ps 324 } 325 } 326 327 // Calculate pagination 328 start := (page - 1) * pageSize 329 end := start + pageSize 330 total := len(commits) 331 332 if start >= total { 333 commits = []*object.Commit{} 334 } else { 335 if end > total { 336 end = total 337 } 338 commits = commits[start:end] 339 } 340 341 resp := types.RepoLogResponse{ 342 Commits: commits, 343 Ref: ref, 344 Description: getDescription(path), 345 Log: true, 346 Total: total, 347 Page: page, 348 PerPage: pageSize, 349 } 350 351 writeJSON(w, resp) 352 return 353} 354 355func (h *Handle) Diff(w http.ResponseWriter, r *http.Request) { 356 ref := chi.URLParam(r, "ref") 357 ref, _ = url.PathUnescape(ref) 358 359 l := h.l.With("handler", "Diff", "ref", ref) 360 361 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 362 gr, err := git.Open(path, ref) 363 if err != nil { 364 notFound(w) 365 return 366 } 367 368 diff, err := gr.Diff() 369 if err != nil { 370 writeError(w, err.Error(), http.StatusInternalServerError) 371 l.Error("getting diff", "error", err.Error()) 372 return 373 } 374 375 resp := types.RepoCommitResponse{ 376 Ref: ref, 377 Diff: diff, 378 } 379 380 writeJSON(w, resp) 381 return 382} 383 384func (h *Handle) Tags(w http.ResponseWriter, r *http.Request) { 385 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 386 l := h.l.With("handler", "Refs") 387 388 gr, err := git.Open(path, "") 389 if err != nil { 390 notFound(w) 391 return 392 } 393 394 tags, err := gr.Tags() 395 if err != nil { 396 // Non-fatal, we *should* have at least one branch to show. 397 l.Warn("getting tags", "error", err.Error()) 398 } 399 400 rtags := []*types.TagReference{} 401 for _, tag := range tags { 402 tr := types.TagReference{ 403 Tag: tag.TagObject(), 404 } 405 406 tr.Reference = types.Reference{ 407 Name: tag.Name(), 408 Hash: tag.Hash().String(), 409 } 410 411 if tag.Message() != "" { 412 tr.Message = tag.Message() 413 } 414 415 rtags = append(rtags, &tr) 416 } 417 418 resp := types.RepoTagsResponse{ 419 Tags: rtags, 420 } 421 422 writeJSON(w, resp) 423 return 424} 425 426func (h *Handle) Branches(w http.ResponseWriter, r *http.Request) { 427 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 428 l := h.l.With("handler", "Branches") 429 430 gr, err := git.Open(path, "") 431 if err != nil { 432 notFound(w) 433 return 434 } 435 436 branches, err := gr.Branches() 437 if err != nil { 438 l.Error("getting branches", "error", err.Error()) 439 writeError(w, err.Error(), http.StatusInternalServerError) 440 return 441 } 442 443 bs := []types.Branch{} 444 for _, branch := range branches { 445 b := types.Branch{} 446 b.Hash = branch.Hash().String() 447 b.Name = branch.Name().Short() 448 bs = append(bs, b) 449 } 450 451 resp := types.RepoBranchesResponse{ 452 Branches: bs, 453 } 454 455 writeJSON(w, resp) 456 return 457} 458 459func (h *Handle) Branch(w http.ResponseWriter, r *http.Request) { 460 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 461 branchName := chi.URLParam(r, "branch") 462 l := h.l.With("handler", "Branch") 463 464 gr, err := git.PlainOpen(path) 465 if err != nil { 466 notFound(w) 467 return 468 } 469 470 ref, err := gr.Branch(branchName) 471 if err != nil { 472 l.Error("getting branches", "error", err.Error()) 473 writeError(w, err.Error(), http.StatusInternalServerError) 474 return 475 } 476 477 resp := types.RepoBranchResponse{ 478 Branch: types.Branch{ 479 Reference: types.Reference{ 480 Name: ref.Name().Short(), 481 Hash: ref.Hash().String(), 482 }, 483 }, 484 } 485 486 writeJSON(w, resp) 487 return 488} 489 490func (h *Handle) Keys(w http.ResponseWriter, r *http.Request) { 491 l := h.l.With("handler", "Keys") 492 493 switch r.Method { 494 case http.MethodGet: 495 keys, err := h.db.GetAllPublicKeys() 496 if err != nil { 497 writeError(w, err.Error(), http.StatusInternalServerError) 498 l.Error("getting public keys", "error", err.Error()) 499 return 500 } 501 502 data := make([]map[string]any, 0) 503 for _, key := range keys { 504 j := key.JSON() 505 data = append(data, j) 506 } 507 writeJSON(w, data) 508 return 509 510 case http.MethodPut: 511 pk := db.PublicKey{} 512 if err := json.NewDecoder(r.Body).Decode(&pk); err != nil { 513 writeError(w, "invalid request body", http.StatusBadRequest) 514 return 515 } 516 517 _, _, _, _, err := ssh.ParseAuthorizedKey([]byte(pk.Key)) 518 if err != nil { 519 writeError(w, "invalid pubkey", http.StatusBadRequest) 520 } 521 522 if err := h.db.AddPublicKey(pk); err != nil { 523 writeError(w, err.Error(), http.StatusInternalServerError) 524 l.Error("adding public key", "error", err.Error()) 525 return 526 } 527 528 w.WriteHeader(http.StatusNoContent) 529 return 530 } 531} 532 533func (h *Handle) NewRepo(w http.ResponseWriter, r *http.Request) { 534 l := h.l.With("handler", "NewRepo") 535 536 data := struct { 537 Did string `json:"did"` 538 Name string `json:"name"` 539 DefaultBranch string `json:"default_branch,omitempty"` 540 }{} 541 542 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 543 writeError(w, "invalid request body", http.StatusBadRequest) 544 return 545 } 546 547 if data.DefaultBranch == "" { 548 data.DefaultBranch = h.c.Repo.MainBranch 549 } 550 551 did := data.Did 552 name := data.Name 553 defaultBranch := data.DefaultBranch 554 555 relativeRepoPath := filepath.Join(did, name) 556 repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath) 557 err := git.InitBare(repoPath, defaultBranch) 558 if err != nil { 559 l.Error("initializing bare repo", "error", err.Error()) 560 if errors.Is(err, gogit.ErrRepositoryAlreadyExists) { 561 writeError(w, "That repo already exists!", http.StatusConflict) 562 return 563 } else { 564 writeError(w, err.Error(), http.StatusInternalServerError) 565 return 566 } 567 } 568 569 // add perms for this user to access the repo 570 err = h.e.AddRepo(did, ThisServer, relativeRepoPath) 571 if err != nil { 572 l.Error("adding repo permissions", "error", err.Error()) 573 writeError(w, err.Error(), http.StatusInternalServerError) 574 return 575 } 576 577 w.WriteHeader(http.StatusNoContent) 578} 579 580func (h *Handle) RepoFork(w http.ResponseWriter, r *http.Request) { 581 l := h.l.With("handler", "RepoFork") 582 583 data := struct { 584 Did string `json:"did"` 585 Source string `json:"source"` 586 Name string `json:"name,omitempty"` 587 }{} 588 589 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 590 writeError(w, "invalid request body", http.StatusBadRequest) 591 return 592 } 593 594 did := data.Did 595 source := data.Source 596 597 if did == "" || source == "" { 598 l.Error("invalid request body, empty did or name") 599 w.WriteHeader(http.StatusBadRequest) 600 return 601 } 602 603 var name string 604 if data.Name != "" { 605 name = data.Name 606 } else { 607 name = filepath.Base(source) 608 } 609 610 relativeRepoPath := filepath.Join(did, name) 611 repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath) 612 613 err := git.Fork(repoPath, source) 614 if err != nil { 615 l.Error("forking repo", "error", err.Error()) 616 writeError(w, err.Error(), http.StatusInternalServerError) 617 return 618 } 619 620 w.WriteHeader(http.StatusNoContent) 621} 622 623func (h *Handle) RemoveRepo(w http.ResponseWriter, r *http.Request) { 624 l := h.l.With("handler", "RemoveRepo") 625 626 data := struct { 627 Did string `json:"did"` 628 Name string `json:"name"` 629 }{} 630 631 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 632 writeError(w, "invalid request body", http.StatusBadRequest) 633 return 634 } 635 636 did := data.Did 637 name := data.Name 638 639 if did == "" || name == "" { 640 l.Error("invalid request body, empty did or name") 641 w.WriteHeader(http.StatusBadRequest) 642 return 643 } 644 645 relativeRepoPath := filepath.Join(did, name) 646 repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath) 647 err := os.RemoveAll(repoPath) 648 if err != nil { 649 l.Error("removing repo", "error", err.Error()) 650 writeError(w, err.Error(), http.StatusInternalServerError) 651 return 652 } 653 654 w.WriteHeader(http.StatusNoContent) 655 656} 657func (h *Handle) Merge(w http.ResponseWriter, r *http.Request) { 658 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 659 660 data := types.MergeRequest{} 661 662 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 663 writeError(w, err.Error(), http.StatusBadRequest) 664 h.l.Error("git: failed to unmarshal json patch", "handler", "Merge", "error", err) 665 return 666 } 667 668 mo := &git.MergeOptions{ 669 AuthorName: data.AuthorName, 670 AuthorEmail: data.AuthorEmail, 671 CommitBody: data.CommitBody, 672 CommitMessage: data.CommitMessage, 673 } 674 675 patch := data.Patch 676 branch := data.Branch 677 gr, err := git.Open(path, branch) 678 if err != nil { 679 notFound(w) 680 return 681 } 682 if err := gr.MergeWithOptions([]byte(patch), branch, mo); err != nil { 683 var mergeErr *git.ErrMerge 684 if errors.As(err, &mergeErr) { 685 conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts)) 686 for i, conflict := range mergeErr.Conflicts { 687 conflicts[i] = types.ConflictInfo{ 688 Filename: conflict.Filename, 689 Reason: conflict.Reason, 690 } 691 } 692 response := types.MergeCheckResponse{ 693 IsConflicted: true, 694 Conflicts: conflicts, 695 Message: mergeErr.Message, 696 } 697 writeConflict(w, response) 698 h.l.Error("git: merge conflict", "handler", "Merge", "error", mergeErr) 699 } else { 700 writeError(w, err.Error(), http.StatusBadRequest) 701 h.l.Error("git: failed to merge", "handler", "Merge", "error", err.Error()) 702 } 703 return 704 } 705 706 w.WriteHeader(http.StatusOK) 707} 708 709func (h *Handle) MergeCheck(w http.ResponseWriter, r *http.Request) { 710 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 711 712 var data struct { 713 Patch string `json:"patch"` 714 Branch string `json:"branch"` 715 } 716 717 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 718 writeError(w, err.Error(), http.StatusBadRequest) 719 h.l.Error("git: failed to unmarshal json patch", "handler", "MergeCheck", "error", err) 720 return 721 } 722 723 patch := data.Patch 724 branch := data.Branch 725 gr, err := git.Open(path, branch) 726 if err != nil { 727 notFound(w) 728 return 729 } 730 731 err = gr.MergeCheck([]byte(patch), branch) 732 if err == nil { 733 response := types.MergeCheckResponse{ 734 IsConflicted: false, 735 } 736 writeJSON(w, response) 737 return 738 } 739 740 var mergeErr *git.ErrMerge 741 if errors.As(err, &mergeErr) { 742 conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts)) 743 for i, conflict := range mergeErr.Conflicts { 744 conflicts[i] = types.ConflictInfo{ 745 Filename: conflict.Filename, 746 Reason: conflict.Reason, 747 } 748 } 749 response := types.MergeCheckResponse{ 750 IsConflicted: true, 751 Conflicts: conflicts, 752 Message: mergeErr.Message, 753 } 754 writeConflict(w, response) 755 h.l.Error("git: merge conflict", "handler", "MergeCheck", "error", mergeErr.Error()) 756 return 757 } 758 writeError(w, err.Error(), http.StatusInternalServerError) 759 h.l.Error("git: failed to check merge", "handler", "MergeCheck", "error", err.Error()) 760} 761 762func (h *Handle) Compare(w http.ResponseWriter, r *http.Request) { 763 rev1 := chi.URLParam(r, "rev1") 764 rev1, _ = url.PathUnescape(rev1) 765 766 rev2 := chi.URLParam(r, "rev2") 767 rev2, _ = url.PathUnescape(rev2) 768 769 l := h.l.With("handler", "Compare", "r1", rev1, "r2", rev2) 770 771 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 772 gr, err := git.PlainOpen(path) 773 if err != nil { 774 notFound(w) 775 return 776 } 777 778 commit1, err := gr.ResolveRevision(rev1) 779 if err != nil { 780 l.Error("error resolving revision 1", "msg", err.Error()) 781 writeError(w, fmt.Sprintf("error resolving revision %s", rev1), http.StatusBadRequest) 782 return 783 } 784 785 commit2, err := gr.ResolveRevision(rev2) 786 if err != nil { 787 l.Error("error resolving revision 2", "msg", err.Error()) 788 writeError(w, fmt.Sprintf("error resolving revision %s", rev2), http.StatusBadRequest) 789 return 790 } 791 792 mergeBase, err := gr.MergeBase(commit1, commit2) 793 if err != nil { 794 l.Error("failed to find merge-base", "msg", err.Error()) 795 writeError(w, "failed to calculate diff", http.StatusBadRequest) 796 return 797 } 798 799 difftree, err := gr.DiffTree(mergeBase, commit2) 800 if err != nil { 801 l.Error("error comparing revisions", "msg", err.Error()) 802 writeError(w, "error comparing revisions", http.StatusBadRequest) 803 return 804 } 805 806 writeJSON(w, types.RepoDiffTreeResponse{difftree}) 807 return 808} 809 810func (h *Handle) NewHiddenRef(w http.ResponseWriter, r *http.Request) { 811 l := h.l.With("handler", "NewHiddenRef") 812 813 forkRef := chi.URLParam(r, "forkRef") 814 remoteRef := chi.URLParam(r, "remoteRef") 815 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 816 gr, err := git.PlainOpen(path) 817 if err != nil { 818 notFound(w) 819 return 820 } 821 822 err = gr.TrackHiddenRemoteRef(forkRef, remoteRef) 823 if err != nil { 824 l.Error("error tracking hidden remote ref", "msg", err.Error()) 825 writeError(w, "error tracking hidden remote ref", http.StatusBadRequest) 826 return 827 } 828 829 w.WriteHeader(http.StatusNoContent) 830 return 831} 832 833func (h *Handle) AddMember(w http.ResponseWriter, r *http.Request) { 834 l := h.l.With("handler", "AddMember") 835 836 data := struct { 837 Did string `json:"did"` 838 }{} 839 840 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 841 writeError(w, "invalid request body", http.StatusBadRequest) 842 return 843 } 844 845 did := data.Did 846 847 if err := h.db.AddDid(did); err != nil { 848 l.Error("adding did", "error", err.Error()) 849 writeError(w, err.Error(), http.StatusInternalServerError) 850 return 851 } 852 h.jc.AddDid(did) 853 854 if err := h.e.AddMember(ThisServer, did); err != nil { 855 l.Error("adding member", "error", err.Error()) 856 writeError(w, err.Error(), http.StatusInternalServerError) 857 return 858 } 859 860 if err := h.fetchAndAddKeys(r.Context(), did); err != nil { 861 l.Error("fetching and adding keys", "error", err.Error()) 862 writeError(w, err.Error(), http.StatusInternalServerError) 863 return 864 } 865 866 w.WriteHeader(http.StatusNoContent) 867} 868 869func (h *Handle) AddRepoCollaborator(w http.ResponseWriter, r *http.Request) { 870 l := h.l.With("handler", "AddRepoCollaborator") 871 872 data := struct { 873 Did string `json:"did"` 874 }{} 875 876 ownerDid := chi.URLParam(r, "did") 877 repo := chi.URLParam(r, "name") 878 879 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 880 writeError(w, "invalid request body", http.StatusBadRequest) 881 return 882 } 883 884 if err := h.db.AddDid(data.Did); err != nil { 885 l.Error("adding did", "error", err.Error()) 886 writeError(w, err.Error(), http.StatusInternalServerError) 887 return 888 } 889 h.jc.AddDid(data.Did) 890 891 repoName, _ := securejoin.SecureJoin(ownerDid, repo) 892 if err := h.e.AddCollaborator(data.Did, ThisServer, repoName); err != nil { 893 l.Error("adding repo collaborator", "error", err.Error()) 894 writeError(w, err.Error(), http.StatusInternalServerError) 895 return 896 } 897 898 if err := h.fetchAndAddKeys(r.Context(), data.Did); err != nil { 899 l.Error("fetching and adding keys", "error", err.Error()) 900 writeError(w, err.Error(), http.StatusInternalServerError) 901 return 902 } 903 904 w.WriteHeader(http.StatusNoContent) 905} 906 907func (h *Handle) DefaultBranch(w http.ResponseWriter, r *http.Request) { 908 l := h.l.With("handler", "DefaultBranch") 909 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 910 911 gr, err := git.Open(path, "") 912 if err != nil { 913 notFound(w) 914 return 915 } 916 917 branch, err := gr.FindMainBranch() 918 if err != nil { 919 writeError(w, err.Error(), http.StatusInternalServerError) 920 l.Error("getting default branch", "error", err.Error()) 921 return 922 } 923 924 writeJSON(w, types.RepoDefaultBranchResponse{ 925 Branch: branch, 926 }) 927} 928 929func (h *Handle) SetDefaultBranch(w http.ResponseWriter, r *http.Request) { 930 l := h.l.With("handler", "SetDefaultBranch") 931 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r)) 932 933 data := struct { 934 Branch string `json:"branch"` 935 }{} 936 937 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 938 writeError(w, err.Error(), http.StatusBadRequest) 939 return 940 } 941 942 gr, err := git.Open(path, "") 943 if err != nil { 944 notFound(w) 945 return 946 } 947 948 err = gr.SetDefaultBranch(data.Branch) 949 if err != nil { 950 writeError(w, err.Error(), http.StatusInternalServerError) 951 l.Error("setting default branch", "error", err.Error()) 952 return 953 } 954 955 w.WriteHeader(http.StatusNoContent) 956} 957 958func (h *Handle) Init(w http.ResponseWriter, r *http.Request) { 959 l := h.l.With("handler", "Init") 960 961 if h.knotInitialized { 962 writeError(w, "knot already initialized", http.StatusConflict) 963 return 964 } 965 966 data := struct { 967 Did string `json:"did"` 968 }{} 969 970 if err := json.NewDecoder(r.Body).Decode(&data); err != nil { 971 l.Error("failed to decode request body", "error", err.Error()) 972 writeError(w, "invalid request body", http.StatusBadRequest) 973 return 974 } 975 976 if data.Did == "" { 977 l.Error("empty DID in request", "did", data.Did) 978 writeError(w, "did is empty", http.StatusBadRequest) 979 return 980 } 981 982 if err := h.db.AddDid(data.Did); err != nil { 983 l.Error("failed to add DID", "error", err.Error()) 984 writeError(w, err.Error(), http.StatusInternalServerError) 985 return 986 } 987 h.jc.AddDid(data.Did) 988 989 if err := h.e.AddOwner(ThisServer, data.Did); err != nil { 990 l.Error("adding owner", "error", err.Error()) 991 writeError(w, err.Error(), http.StatusInternalServerError) 992 return 993 } 994 995 if err := h.fetchAndAddKeys(r.Context(), data.Did); err != nil { 996 l.Error("fetching and adding keys", "error", err.Error()) 997 writeError(w, err.Error(), http.StatusInternalServerError) 998 return 999 } 1000 1001 close(h.init) 1002 1003 mac := hmac.New(sha256.New, []byte(h.c.Server.Secret)) 1004 mac.Write([]byte("ok")) 1005 w.Header().Add("X-Signature", hex.EncodeToString(mac.Sum(nil))) 1006 1007 w.WriteHeader(http.StatusNoContent) 1008} 1009 1010func (h *Handle) Health(w http.ResponseWriter, r *http.Request) { 1011 w.Write([]byte("ok")) 1012}