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