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 // add perms for this user to access the repo
621 err = h.e.AddRepo(did, ThisServer, relativeRepoPath)
622 if err != nil {
623 l.Error("adding repo permissions", "error", err.Error())
624 writeError(w, err.Error(), http.StatusInternalServerError)
625 return
626 }
627
628 w.WriteHeader(http.StatusNoContent)
629}
630
631func (h *Handle) RemoveRepo(w http.ResponseWriter, r *http.Request) {
632 l := h.l.With("handler", "RemoveRepo")
633
634 data := struct {
635 Did string `json:"did"`
636 Name string `json:"name"`
637 }{}
638
639 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
640 writeError(w, "invalid request body", http.StatusBadRequest)
641 return
642 }
643
644 did := data.Did
645 name := data.Name
646
647 if did == "" || name == "" {
648 l.Error("invalid request body, empty did or name")
649 w.WriteHeader(http.StatusBadRequest)
650 return
651 }
652
653 relativeRepoPath := filepath.Join(did, name)
654 repoPath, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, relativeRepoPath)
655 err := os.RemoveAll(repoPath)
656 if err != nil {
657 l.Error("removing repo", "error", err.Error())
658 writeError(w, err.Error(), http.StatusInternalServerError)
659 return
660 }
661
662 w.WriteHeader(http.StatusNoContent)
663
664}
665func (h *Handle) Merge(w http.ResponseWriter, r *http.Request) {
666 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
667
668 data := types.MergeRequest{}
669
670 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
671 writeError(w, err.Error(), http.StatusBadRequest)
672 h.l.Error("git: failed to unmarshal json patch", "handler", "Merge", "error", err)
673 return
674 }
675
676 mo := &git.MergeOptions{
677 AuthorName: data.AuthorName,
678 AuthorEmail: data.AuthorEmail,
679 CommitBody: data.CommitBody,
680 CommitMessage: data.CommitMessage,
681 }
682
683 patch := data.Patch
684 branch := data.Branch
685 gr, err := git.Open(path, branch)
686 if err != nil {
687 notFound(w)
688 return
689 }
690 if err := gr.MergeWithOptions([]byte(patch), branch, mo); err != nil {
691 var mergeErr *git.ErrMerge
692 if errors.As(err, &mergeErr) {
693 conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts))
694 for i, conflict := range mergeErr.Conflicts {
695 conflicts[i] = types.ConflictInfo{
696 Filename: conflict.Filename,
697 Reason: conflict.Reason,
698 }
699 }
700 response := types.MergeCheckResponse{
701 IsConflicted: true,
702 Conflicts: conflicts,
703 Message: mergeErr.Message,
704 }
705 writeConflict(w, response)
706 h.l.Error("git: merge conflict", "handler", "Merge", "error", mergeErr)
707 } else {
708 writeError(w, err.Error(), http.StatusBadRequest)
709 h.l.Error("git: failed to merge", "handler", "Merge", "error", err.Error())
710 }
711 return
712 }
713
714 w.WriteHeader(http.StatusOK)
715}
716
717func (h *Handle) MergeCheck(w http.ResponseWriter, r *http.Request) {
718 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
719
720 var data struct {
721 Patch string `json:"patch"`
722 Branch string `json:"branch"`
723 }
724
725 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
726 writeError(w, err.Error(), http.StatusBadRequest)
727 h.l.Error("git: failed to unmarshal json patch", "handler", "MergeCheck", "error", err)
728 return
729 }
730
731 patch := data.Patch
732 branch := data.Branch
733 gr, err := git.Open(path, branch)
734 if err != nil {
735 notFound(w)
736 return
737 }
738
739 err = gr.MergeCheck([]byte(patch), branch)
740 if err == nil {
741 response := types.MergeCheckResponse{
742 IsConflicted: false,
743 }
744 writeJSON(w, response)
745 return
746 }
747
748 var mergeErr *git.ErrMerge
749 if errors.As(err, &mergeErr) {
750 conflicts := make([]types.ConflictInfo, len(mergeErr.Conflicts))
751 for i, conflict := range mergeErr.Conflicts {
752 conflicts[i] = types.ConflictInfo{
753 Filename: conflict.Filename,
754 Reason: conflict.Reason,
755 }
756 }
757 response := types.MergeCheckResponse{
758 IsConflicted: true,
759 Conflicts: conflicts,
760 Message: mergeErr.Message,
761 }
762 writeConflict(w, response)
763 h.l.Error("git: merge conflict", "handler", "MergeCheck", "error", mergeErr.Error())
764 return
765 }
766 writeError(w, err.Error(), http.StatusInternalServerError)
767 h.l.Error("git: failed to check merge", "handler", "MergeCheck", "error", err.Error())
768}
769
770func (h *Handle) Compare(w http.ResponseWriter, r *http.Request) {
771 rev1 := chi.URLParam(r, "rev1")
772 rev1, _ = url.PathUnescape(rev1)
773
774 rev2 := chi.URLParam(r, "rev2")
775 rev2, _ = url.PathUnescape(rev2)
776
777 l := h.l.With("handler", "Compare", "r1", rev1, "r2", rev2)
778
779 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
780 gr, err := git.PlainOpen(path)
781 if err != nil {
782 notFound(w)
783 return
784 }
785
786 commit1, err := gr.ResolveRevision(rev1)
787 if err != nil {
788 l.Error("error resolving revision 1", "msg", err.Error())
789 writeError(w, fmt.Sprintf("error resolving revision %s", rev1), http.StatusBadRequest)
790 return
791 }
792
793 commit2, err := gr.ResolveRevision(rev2)
794 if err != nil {
795 l.Error("error resolving revision 2", "msg", err.Error())
796 writeError(w, fmt.Sprintf("error resolving revision %s", rev2), http.StatusBadRequest)
797 return
798 }
799
800 mergeBase, err := gr.MergeBase(commit1, commit2)
801 if err != nil {
802 l.Error("failed to find merge-base", "msg", err.Error())
803 writeError(w, "failed to calculate diff", http.StatusBadRequest)
804 return
805 }
806
807 difftree, err := gr.DiffTree(mergeBase, commit2)
808 if err != nil {
809 l.Error("error comparing revisions", "msg", err.Error())
810 writeError(w, "error comparing revisions", http.StatusBadRequest)
811 return
812 }
813
814 writeJSON(w, types.RepoDiffTreeResponse{difftree})
815 return
816}
817
818func (h *Handle) NewHiddenRef(w http.ResponseWriter, r *http.Request) {
819 l := h.l.With("handler", "NewHiddenRef")
820
821 forkRef := chi.URLParam(r, "forkRef")
822 remoteRef := chi.URLParam(r, "remoteRef")
823 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
824 gr, err := git.PlainOpen(path)
825 if err != nil {
826 notFound(w)
827 return
828 }
829
830 err = gr.TrackHiddenRemoteRef(forkRef, remoteRef)
831 if err != nil {
832 l.Error("error tracking hidden remote ref", "msg", err.Error())
833 writeError(w, "error tracking hidden remote ref", http.StatusBadRequest)
834 return
835 }
836
837 w.WriteHeader(http.StatusNoContent)
838 return
839}
840
841func (h *Handle) AddMember(w http.ResponseWriter, r *http.Request) {
842 l := h.l.With("handler", "AddMember")
843
844 data := struct {
845 Did string `json:"did"`
846 }{}
847
848 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
849 writeError(w, "invalid request body", http.StatusBadRequest)
850 return
851 }
852
853 did := data.Did
854
855 if err := h.db.AddDid(did); err != nil {
856 l.Error("adding did", "error", err.Error())
857 writeError(w, err.Error(), http.StatusInternalServerError)
858 return
859 }
860 h.jc.AddDid(did)
861
862 if err := h.e.AddMember(ThisServer, did); err != nil {
863 l.Error("adding member", "error", err.Error())
864 writeError(w, err.Error(), http.StatusInternalServerError)
865 return
866 }
867
868 if err := h.fetchAndAddKeys(r.Context(), did); err != nil {
869 l.Error("fetching and adding keys", "error", err.Error())
870 writeError(w, err.Error(), http.StatusInternalServerError)
871 return
872 }
873
874 w.WriteHeader(http.StatusNoContent)
875}
876
877func (h *Handle) AddRepoCollaborator(w http.ResponseWriter, r *http.Request) {
878 l := h.l.With("handler", "AddRepoCollaborator")
879
880 data := struct {
881 Did string `json:"did"`
882 }{}
883
884 ownerDid := chi.URLParam(r, "did")
885 repo := chi.URLParam(r, "name")
886
887 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
888 writeError(w, "invalid request body", http.StatusBadRequest)
889 return
890 }
891
892 if err := h.db.AddDid(data.Did); err != nil {
893 l.Error("adding did", "error", err.Error())
894 writeError(w, err.Error(), http.StatusInternalServerError)
895 return
896 }
897 h.jc.AddDid(data.Did)
898
899 repoName, _ := securejoin.SecureJoin(ownerDid, repo)
900 if err := h.e.AddCollaborator(data.Did, ThisServer, repoName); err != nil {
901 l.Error("adding repo collaborator", "error", err.Error())
902 writeError(w, err.Error(), http.StatusInternalServerError)
903 return
904 }
905
906 if err := h.fetchAndAddKeys(r.Context(), data.Did); err != nil {
907 l.Error("fetching and adding keys", "error", err.Error())
908 writeError(w, err.Error(), http.StatusInternalServerError)
909 return
910 }
911
912 w.WriteHeader(http.StatusNoContent)
913}
914
915func (h *Handle) DefaultBranch(w http.ResponseWriter, r *http.Request) {
916 l := h.l.With("handler", "DefaultBranch")
917 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
918
919 gr, err := git.Open(path, "")
920 if err != nil {
921 notFound(w)
922 return
923 }
924
925 branch, err := gr.FindMainBranch()
926 if err != nil {
927 writeError(w, err.Error(), http.StatusInternalServerError)
928 l.Error("getting default branch", "error", err.Error())
929 return
930 }
931
932 writeJSON(w, types.RepoDefaultBranchResponse{
933 Branch: branch,
934 })
935}
936
937func (h *Handle) SetDefaultBranch(w http.ResponseWriter, r *http.Request) {
938 l := h.l.With("handler", "SetDefaultBranch")
939 path, _ := securejoin.SecureJoin(h.c.Repo.ScanPath, didPath(r))
940
941 data := struct {
942 Branch string `json:"branch"`
943 }{}
944
945 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
946 writeError(w, err.Error(), http.StatusBadRequest)
947 return
948 }
949
950 gr, err := git.Open(path, "")
951 if err != nil {
952 notFound(w)
953 return
954 }
955
956 err = gr.SetDefaultBranch(data.Branch)
957 if err != nil {
958 writeError(w, err.Error(), http.StatusInternalServerError)
959 l.Error("setting default branch", "error", err.Error())
960 return
961 }
962
963 w.WriteHeader(http.StatusNoContent)
964}
965
966func (h *Handle) Init(w http.ResponseWriter, r *http.Request) {
967 l := h.l.With("handler", "Init")
968
969 if h.knotInitialized {
970 writeError(w, "knot already initialized", http.StatusConflict)
971 return
972 }
973
974 data := struct {
975 Did string `json:"did"`
976 }{}
977
978 if err := json.NewDecoder(r.Body).Decode(&data); err != nil {
979 l.Error("failed to decode request body", "error", err.Error())
980 writeError(w, "invalid request body", http.StatusBadRequest)
981 return
982 }
983
984 if data.Did == "" {
985 l.Error("empty DID in request", "did", data.Did)
986 writeError(w, "did is empty", http.StatusBadRequest)
987 return
988 }
989
990 if err := h.db.AddDid(data.Did); err != nil {
991 l.Error("failed to add DID", "error", err.Error())
992 writeError(w, err.Error(), http.StatusInternalServerError)
993 return
994 }
995 h.jc.AddDid(data.Did)
996
997 if err := h.e.AddOwner(ThisServer, data.Did); err != nil {
998 l.Error("adding owner", "error", err.Error())
999 writeError(w, err.Error(), http.StatusInternalServerError)
1000 return
1001 }
1002
1003 if err := h.fetchAndAddKeys(r.Context(), data.Did); err != nil {
1004 l.Error("fetching and adding keys", "error", err.Error())
1005 writeError(w, err.Error(), http.StatusInternalServerError)
1006 return
1007 }
1008
1009 close(h.init)
1010
1011 mac := hmac.New(sha256.New, []byte(h.c.Server.Secret))
1012 mac.Write([]byte("ok"))
1013 w.Header().Add("X-Signature", hex.EncodeToString(mac.Sum(nil)))
1014
1015 w.WriteHeader(http.StatusNoContent)
1016}
1017
1018func (h *Handle) Health(w http.ResponseWriter, r *http.Request) {
1019 w.Write([]byte("ok"))
1020}