+3
-3
appview/pages/templates/repo/tree.html
+3
-3
appview/pages/templates/repo/tree.html
···
25
25
<div class="flex flex-col md:flex-row md:justify-between gap-2">
26
26
<div id="breadcrumbs" class="overflow-x-auto whitespace-nowrap text-gray-400 dark:text-gray-500">
27
27
{{ range .BreadCrumbs }}
28
-
<a href="{{ index . 1}}" class="text-bold text-gray-500 dark:text-gray-400 {{ $linkstyle }}">{{ pathUnescape (index . 0) }}</a> /
28
+
<a href="{{ index . 1 }}" class="text-bold text-gray-500 dark:text-gray-400 {{ $linkstyle }}">{{ pathUnescape (index . 0) }}</a> /
29
29
{{ end }}
30
30
</div>
31
31
<div id="dir-info" class="text-gray-500 dark:text-gray-400 text-xs md:text-sm flex flex-wrap items-center gap-1 md:gap-0">
32
32
{{ $stats := .TreeStats }}
33
33
34
-
<span>at <a href="/{{ $.RepoInfo.FullName }}/tree/{{ $.Ref }}">{{ $.Ref }}</a></span>
34
+
<span>at <a href="/{{ $.RepoInfo.FullName }}/tree/{{ pathEscape $.Ref }}">{{ $.Ref }}</a></span>
35
35
{{ if eq $stats.NumFolders 1 }}
36
36
<span class="select-none px-1 md:px-2 [&:before]:content-['·']"></span>
37
37
<span>{{ $stats.NumFolders }} folder</span>
···
55
55
{{ range .Files }}
56
56
<div class="grid grid-cols-12 gap-4 items-center py-1">
57
57
<div class="col-span-8 md:col-span-4">
58
-
{{ $link := printf "/%s/%s/%s/%s/%s" $.RepoInfo.FullName "tree" (urlquery $.Ref) $.TreePath .Name }}
58
+
{{ $link := printf "/%s/%s/%s/%s/%s" $.RepoInfo.FullName "tree" (pathEscape $.Ref) $.TreePath .Name }}
59
59
{{ $icon := "folder" }}
60
60
{{ $iconStyle := "size-4 fill-current" }}
61
61
+9
-7
appview/repo/index.go
+9
-7
appview/repo/index.go
···
5
5
"fmt"
6
6
"log"
7
7
"net/http"
8
+
"net/url"
8
9
"slices"
9
10
"sort"
10
11
"strings"
···
31
32
32
33
func (rp *Repo) RepoIndex(w http.ResponseWriter, r *http.Request) {
33
34
ref := chi.URLParam(r, "ref")
35
+
ref, _ = url.PathUnescape(ref)
34
36
35
37
f, err := rp.repoResolver.Resolve(r)
36
38
if err != nil {
···
245
247
// first get branches to determine the ref if not specified
246
248
branchesBytes, err := tangled.RepoBranches(ctx, xrpcc, "", 0, repo)
247
249
if err != nil {
248
-
return nil, err
250
+
return nil, fmt.Errorf("failed to call repoBranches: %w", err)
249
251
}
250
252
251
253
var branchesResp types.RepoBranchesResponse
252
254
if err := json.Unmarshal(branchesBytes, &branchesResp); err != nil {
253
-
return nil, err
255
+
return nil, fmt.Errorf("failed to unmarshal branches response: %w", err)
254
256
}
255
257
256
258
// if no ref specified, use default branch or first available
···
292
294
defer wg.Done()
293
295
tagsBytes, err := tangled.RepoTags(ctx, xrpcc, "", 0, repo)
294
296
if err != nil {
295
-
errs = errors.Join(errs, err)
297
+
errs = errors.Join(errs, fmt.Errorf("failed to call repoTags: %w", err))
296
298
return
297
299
}
298
300
299
301
if err := json.Unmarshal(tagsBytes, &tagsResp); err != nil {
300
-
errs = errors.Join(errs, err)
302
+
errs = errors.Join(errs, fmt.Errorf("failed to unmarshal repoTags: %w", err))
301
303
}
302
304
}()
303
305
···
307
309
defer wg.Done()
308
310
resp, err := tangled.RepoTree(ctx, xrpcc, "", ref, repo)
309
311
if err != nil {
310
-
errs = errors.Join(errs, err)
312
+
errs = errors.Join(errs, fmt.Errorf("failed to call repoTree: %w", err))
311
313
return
312
314
}
313
315
treeResp = resp
···
319
321
defer wg.Done()
320
322
logBytes, err := tangled.RepoLog(ctx, xrpcc, "", 50, "", ref, repo)
321
323
if err != nil {
322
-
errs = errors.Join(errs, err)
324
+
errs = errors.Join(errs, fmt.Errorf("failed to call repoLog: %w", err))
323
325
return
324
326
}
325
327
326
328
if err := json.Unmarshal(logBytes, &logResp); err != nil {
327
-
errs = errors.Join(errs, err)
329
+
errs = errors.Join(errs, fmt.Errorf("failed to unmarshal repoLog: %w", err))
328
330
}
329
331
}()
330
332
+97
-118
appview/repo/repo.go
+97
-118
appview/repo/repo.go
···
85
85
}
86
86
87
87
func (rp *Repo) DownloadArchive(w http.ResponseWriter, r *http.Request) {
88
-
refParam := chi.URLParam(r, "ref")
88
+
ref := chi.URLParam(r, "ref")
89
+
ref, _ = url.PathUnescape(ref)
90
+
89
91
f, err := rp.repoResolver.Resolve(r)
90
92
if err != nil {
91
93
log.Println("failed to get repo and knot", err)
···
102
104
}
103
105
104
106
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
105
-
archiveBytes, err := tangled.RepoArchive(r.Context(), xrpcc, "tar.gz", "", refParam, repo)
106
-
if err != nil {
107
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
108
-
log.Println("failed to call XRPC repo.archive", xrpcerr)
109
-
rp.pages.Error503(w)
110
-
return
111
-
}
112
-
rp.pages.Error404(w)
107
+
archiveBytes, err := tangled.RepoArchive(r.Context(), xrpcc, "tar.gz", "", ref, repo)
108
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
109
+
log.Println("failed to call XRPC repo.archive", xrpcerr)
110
+
rp.pages.Error503(w)
113
111
return
114
112
}
115
113
116
-
// Set headers for file download
117
-
filename := fmt.Sprintf("%s-%s.tar.gz", f.Name, refParam)
114
+
// Set headers for file download, just pass along whatever the knot specifies
115
+
safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-")
116
+
filename := fmt.Sprintf("%s-%s.tar.gz", f.Name, safeRefFilename)
118
117
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", filename))
119
118
w.Header().Set("Content-Type", "application/gzip")
120
119
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(archiveBytes)))
···
139
138
}
140
139
141
140
ref := chi.URLParam(r, "ref")
141
+
ref, _ = url.PathUnescape(ref)
142
142
143
143
scheme := "http"
144
144
if !rp.config.Core.Dev {
···
159
159
160
160
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
161
161
xrpcBytes, err := tangled.RepoLog(r.Context(), xrpcc, cursor, limit, "", ref, repo)
162
-
if err != nil {
163
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
164
-
log.Println("failed to call XRPC repo.log", xrpcerr)
165
-
rp.pages.Error503(w)
166
-
return
167
-
}
168
-
rp.pages.Error404(w)
162
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
163
+
log.Println("failed to call XRPC repo.log", xrpcerr)
164
+
rp.pages.Error503(w)
169
165
return
170
166
}
171
167
···
177
173
}
178
174
179
175
tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo)
180
-
if err != nil {
181
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
182
-
log.Println("failed to call XRPC repo.tags", xrpcerr)
183
-
rp.pages.Error503(w)
184
-
return
185
-
}
176
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
177
+
log.Println("failed to call XRPC repo.tags", xrpcerr)
178
+
rp.pages.Error503(w)
179
+
return
186
180
}
187
181
188
182
tagMap := make(map[string][]string)
···
196
190
}
197
191
198
192
branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
199
-
if err != nil {
200
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
201
-
log.Println("failed to call XRPC repo.branches", xrpcerr)
202
-
rp.pages.Error503(w)
203
-
return
204
-
}
193
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
194
+
log.Println("failed to call XRPC repo.branches", xrpcerr)
195
+
rp.pages.Error503(w)
196
+
return
205
197
}
206
198
207
199
if branchBytes != nil {
···
353
345
return
354
346
}
355
347
ref := chi.URLParam(r, "ref")
348
+
ref, _ = url.PathUnescape(ref)
356
349
357
350
var diffOpts types.DiffOpts
358
351
if d := r.URL.Query().Get("diff"); d == "split" {
···
375
368
376
369
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
377
370
xrpcBytes, err := tangled.RepoDiff(r.Context(), xrpcc, ref, repo)
378
-
if err != nil {
379
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
380
-
log.Println("failed to call XRPC repo.diff", xrpcerr)
381
-
rp.pages.Error503(w)
382
-
return
383
-
}
384
-
rp.pages.Error404(w)
371
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
372
+
log.Println("failed to call XRPC repo.diff", xrpcerr)
373
+
rp.pages.Error503(w)
385
374
return
386
375
}
387
376
···
433
422
}
434
423
435
424
ref := chi.URLParam(r, "ref")
436
-
treePath := chi.URLParam(r, "*")
425
+
ref, _ = url.PathUnescape(ref)
437
426
438
427
// if the tree path has a trailing slash, let's strip it
439
428
// so we don't 404
429
+
treePath := chi.URLParam(r, "*")
430
+
treePath, _ = url.PathUnescape(treePath)
440
431
treePath = strings.TrimSuffix(treePath, "/")
441
432
442
433
scheme := "http"
···
450
441
451
442
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
452
443
xrpcResp, err := tangled.RepoTree(r.Context(), xrpcc, treePath, ref, repo)
453
-
if err != nil {
454
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
455
-
log.Println("failed to call XRPC repo.tree", xrpcerr)
456
-
rp.pages.Error503(w)
457
-
return
458
-
}
459
-
rp.pages.Error404(w)
444
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
445
+
log.Println("failed to call XRPC repo.tree", xrpcerr)
446
+
rp.pages.Error503(w)
460
447
return
461
448
}
462
449
···
498
485
499
486
// redirects tree paths trying to access a blob; in this case the result.Files is unpopulated,
500
487
// so we can safely redirect to the "parent" (which is the same file).
501
-
unescapedTreePath, _ := url.PathUnescape(treePath)
502
-
if len(result.Files) == 0 && result.Parent == unescapedTreePath {
503
-
http.Redirect(w, r, fmt.Sprintf("/%s/blob/%s/%s", f.OwnerSlashRepo(), ref, result.Parent), http.StatusFound)
488
+
if len(result.Files) == 0 && result.Parent == treePath {
489
+
redirectTo := fmt.Sprintf("/%s/blob/%s/%s", f.OwnerSlashRepo(), url.PathEscape(ref), result.Parent)
490
+
http.Redirect(w, r, redirectTo, http.StatusFound)
504
491
return
505
492
}
506
493
507
494
user := rp.oauth.GetUser(r)
508
495
509
496
var breadcrumbs [][]string
510
-
breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), ref)})
497
+
breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), url.PathEscape(ref))})
511
498
if treePath != "" {
512
499
for idx, elem := range strings.Split(treePath, "/") {
513
-
breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], elem)})
500
+
breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], url.PathEscape(elem))})
514
501
}
515
502
}
516
503
···
543
530
544
531
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
545
532
xrpcBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo)
546
-
if err != nil {
547
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
548
-
log.Println("failed to call XRPC repo.tags", xrpcerr)
549
-
rp.pages.Error503(w)
550
-
return
551
-
}
552
-
rp.pages.Error404(w)
533
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
534
+
log.Println("failed to call XRPC repo.tags", xrpcerr)
535
+
rp.pages.Error503(w)
553
536
return
554
537
}
555
538
···
616
599
617
600
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
618
601
xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
619
-
if err != nil {
620
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
621
-
log.Println("failed to call XRPC repo.branches", xrpcerr)
622
-
rp.pages.Error503(w)
623
-
return
624
-
}
625
-
rp.pages.Error404(w)
602
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
603
+
log.Println("failed to call XRPC repo.branches", xrpcerr)
604
+
rp.pages.Error503(w)
626
605
return
627
606
}
628
607
···
651
630
}
652
631
653
632
ref := chi.URLParam(r, "ref")
633
+
ref, _ = url.PathUnescape(ref)
634
+
654
635
filePath := chi.URLParam(r, "*")
636
+
filePath, _ = url.PathUnescape(filePath)
655
637
656
638
scheme := "http"
657
639
if !rp.config.Core.Dev {
···
664
646
665
647
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Repo.Name)
666
648
resp, err := tangled.RepoBlob(r.Context(), xrpcc, filePath, false, ref, repo)
667
-
if err != nil {
668
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
669
-
log.Println("failed to call XRPC repo.blob", xrpcerr)
670
-
rp.pages.Error503(w)
671
-
return
672
-
}
673
-
rp.pages.Error404(w)
649
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
650
+
log.Println("failed to call XRPC repo.blob", xrpcerr)
651
+
rp.pages.Error503(w)
674
652
return
675
653
}
676
654
677
655
// Use XRPC response directly instead of converting to internal types
678
656
679
657
var breadcrumbs [][]string
680
-
breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), ref)})
658
+
breadcrumbs = append(breadcrumbs, []string{f.Name, fmt.Sprintf("/%s/tree/%s", f.OwnerSlashRepo(), url.PathEscape(ref))})
681
659
if filePath != "" {
682
660
for idx, elem := range strings.Split(filePath, "/") {
683
-
breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], elem)})
661
+
breadcrumbs = append(breadcrumbs, []string{elem, fmt.Sprintf("%s/%s", breadcrumbs[idx][1], url.PathEscape(elem))})
684
662
}
685
663
}
686
664
···
710
688
711
689
// fetch the raw binary content using sh.tangled.repo.blob xrpc
712
690
repoName := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
713
-
blobURL := fmt.Sprintf("%s://%s/xrpc/sh.tangled.repo.blob?repo=%s&ref=%s&path=%s&raw=true",
714
-
scheme, f.Knot, url.QueryEscape(repoName), url.QueryEscape(ref), url.QueryEscape(filePath))
691
+
692
+
baseURL := &url.URL{
693
+
Scheme: scheme,
694
+
Host: f.Knot,
695
+
Path: "/xrpc/sh.tangled.repo.blob",
696
+
}
697
+
query := baseURL.Query()
698
+
query.Set("repo", repoName)
699
+
query.Set("ref", ref)
700
+
query.Set("path", filePath)
701
+
query.Set("raw", "true")
702
+
baseURL.RawQuery = query.Encode()
703
+
blobURL := baseURL.String()
715
704
716
705
contentSrc = blobURL
717
706
if !rp.config.Core.Dev {
···
766
755
}
767
756
768
757
ref := chi.URLParam(r, "ref")
758
+
ref, _ = url.PathUnescape(ref)
759
+
769
760
filePath := chi.URLParam(r, "*")
761
+
filePath, _ = url.PathUnescape(filePath)
770
762
771
763
scheme := "http"
772
764
if !rp.config.Core.Dev {
···
774
766
}
775
767
776
768
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Repo.Name)
777
-
blobURL := fmt.Sprintf("%s://%s/xrpc/sh.tangled.repo.blob?repo=%s&ref=%s&path=%s&raw=true",
778
-
scheme, f.Knot, url.QueryEscape(repo), url.QueryEscape(ref), url.QueryEscape(filePath))
769
+
baseURL := &url.URL{
770
+
Scheme: scheme,
771
+
Host: f.Knot,
772
+
Path: "/xrpc/sh.tangled.repo.blob",
773
+
}
774
+
query := baseURL.Query()
775
+
query.Set("repo", repo)
776
+
query.Set("ref", ref)
777
+
query.Set("path", filePath)
778
+
query.Set("raw", "true")
779
+
baseURL.RawQuery = query.Encode()
780
+
blobURL := baseURL.String()
779
781
780
782
req, err := http.NewRequest("GET", blobURL, nil)
781
783
if err != nil {
···
1364
1366
1365
1367
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
1366
1368
xrpcBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
1367
-
if err != nil {
1368
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1369
-
log.Println("failed to call XRPC repo.branches", xrpcerr)
1370
-
rp.pages.Error503(w)
1371
-
return
1372
-
}
1369
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1370
+
log.Println("failed to call XRPC repo.branches", xrpcerr)
1373
1371
rp.pages.Error503(w)
1374
1372
return
1375
1373
}
···
1471
1469
1472
1470
func (rp *Repo) SyncRepoFork(w http.ResponseWriter, r *http.Request) {
1473
1471
ref := chi.URLParam(r, "ref")
1472
+
ref, _ = url.PathUnescape(ref)
1474
1473
1475
1474
user := rp.oauth.GetUser(r)
1476
1475
f, err := rp.repoResolver.Resolve(r)
···
1759
1758
1760
1759
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
1761
1760
branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
1762
-
if err != nil {
1763
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1764
-
log.Println("failed to call XRPC repo.branches", xrpcerr)
1765
-
rp.pages.Error503(w)
1766
-
return
1767
-
}
1768
-
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
1761
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1762
+
log.Println("failed to call XRPC repo.branches", xrpcerr)
1763
+
rp.pages.Error503(w)
1769
1764
return
1770
1765
}
1771
1766
···
1800
1795
}
1801
1796
1802
1797
tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo)
1803
-
if err != nil {
1804
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1805
-
log.Println("failed to call XRPC repo.tags", xrpcerr)
1806
-
rp.pages.Error503(w)
1807
-
return
1808
-
}
1809
-
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
1798
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1799
+
log.Println("failed to call XRPC repo.tags", xrpcerr)
1800
+
rp.pages.Error503(w)
1810
1801
return
1811
1802
}
1812
1803
···
1877
1868
repo := fmt.Sprintf("%s/%s", f.OwnerDid(), f.Name)
1878
1869
1879
1870
branchBytes, err := tangled.RepoBranches(r.Context(), xrpcc, "", 0, repo)
1880
-
if err != nil {
1881
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1882
-
log.Println("failed to call XRPC repo.branches", xrpcerr)
1883
-
rp.pages.Error503(w)
1884
-
return
1885
-
}
1886
-
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
1871
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1872
+
log.Println("failed to call XRPC repo.branches", xrpcerr)
1873
+
rp.pages.Error503(w)
1887
1874
return
1888
1875
}
1889
1876
···
1895
1882
}
1896
1883
1897
1884
tagBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 0, repo)
1898
-
if err != nil {
1899
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1900
-
log.Println("failed to call XRPC repo.tags", xrpcerr)
1901
-
rp.pages.Error503(w)
1902
-
return
1903
-
}
1904
-
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
1885
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1886
+
log.Println("failed to call XRPC repo.tags", xrpcerr)
1887
+
rp.pages.Error503(w)
1905
1888
return
1906
1889
}
1907
1890
···
1913
1896
}
1914
1897
1915
1898
compareBytes, err := tangled.RepoCompare(r.Context(), xrpcc, repo, base, head)
1916
-
if err != nil {
1917
-
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1918
-
log.Println("failed to call XRPC repo.compare", xrpcerr)
1919
-
rp.pages.Error503(w)
1920
-
return
1921
-
}
1922
-
rp.pages.Notice(w, "compare-error", "Failed to produce comparison. Try again later.")
1899
+
if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil {
1900
+
log.Println("failed to call XRPC repo.compare", xrpcerr)
1901
+
rp.pages.Error503(w)
1923
1902
return
1924
1903
}
1925
1904
+7
-3
knotserver/xrpc/repo_archive.go
+7
-3
knotserver/xrpc/repo_archive.go
···
13
13
)
14
14
15
15
func (x *Xrpc) RepoArchive(w http.ResponseWriter, r *http.Request) {
16
-
repo, repoPath, unescapedRef, err := x.parseStandardParams(r)
16
+
repo := r.URL.Query().Get("repo")
17
+
repoPath, err := x.parseRepoParam(repo)
17
18
if err != nil {
18
19
writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest)
19
20
return
20
21
}
21
22
23
+
ref := r.URL.Query().Get("ref")
24
+
// ref can be empty (git.Open handles this)
25
+
22
26
format := r.URL.Query().Get("format")
23
27
if format == "" {
24
28
format = "tar.gz" // default
···
34
38
return
35
39
}
36
40
37
-
gr, err := git.Open(repoPath, unescapedRef)
41
+
gr, err := git.Open(repoPath, ref)
38
42
if err != nil {
39
43
writeError(w, xrpcerr.NewXrpcError(
40
44
xrpcerr.WithTag("RefNotFound"),
···
46
50
repoParts := strings.Split(repo, "/")
47
51
repoName := repoParts[len(repoParts)-1]
48
52
49
-
safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(unescapedRef).Short(), "/", "-")
53
+
safeRefFilename := strings.ReplaceAll(plumbing.ReferenceName(ref).Short(), "/", "-")
50
54
51
55
var archivePrefix string
52
56
if prefix != "" {
+5
-1
knotserver/xrpc/repo_blob.go
+5
-1
knotserver/xrpc/repo_blob.go
···
16
16
)
17
17
18
18
func (x *Xrpc) RepoBlob(w http.ResponseWriter, r *http.Request) {
19
-
_, repoPath, ref, err := x.parseStandardParams(r)
19
+
repo := r.URL.Query().Get("repo")
20
+
repoPath, err := x.parseRepoParam(repo)
20
21
if err != nil {
21
22
writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest)
22
23
return
23
24
}
25
+
26
+
ref := r.URL.Query().Get("ref")
27
+
// ref can be empty (git.Open handles this)
24
28
25
29
treePath := r.URL.Query().Get("path")
26
30
if treePath == "" {
+3
-2
knotserver/xrpc/repo_branch.go
+3
-2
knotserver/xrpc/repo_branch.go
···
4
4
"encoding/json"
5
5
"net/http"
6
6
"net/url"
7
+
"time"
7
8
8
9
"tangled.sh/tangled.sh/core/api/tangled"
9
10
"tangled.sh/tangled.sh/core/knotserver/git"
···
70
71
Name: ref.Name().Short(),
71
72
Hash: ref.Hash().String(),
72
73
ShortHash: &[]string{ref.Hash().String()[:7]}[0],
73
-
When: commit.Author.When.Format("2006-01-02T15:04:05.000Z"),
74
+
When: commit.Author.When.Format(time.RFC3339),
74
75
IsDefault: &isDefault,
75
76
}
76
77
···
81
82
response.Author = &tangled.RepoBranch_Signature{
82
83
Name: commit.Author.Name,
83
84
Email: commit.Author.Email,
84
-
When: commit.Author.When.Format("2006-01-02T15:04:05.000Z"),
85
+
When: commit.Author.When.Format(time.RFC3339),
85
86
}
86
87
87
88
w.Header().Set("Content-Type", "application/json")
+1
-4
knotserver/xrpc/repo_branches.go
+1
-4
knotserver/xrpc/repo_branches.go
+4
-8
knotserver/xrpc/repo_compare.go
+4
-8
knotserver/xrpc/repo_compare.go
···
4
4
"encoding/json"
5
5
"fmt"
6
6
"net/http"
7
-
"net/url"
8
7
9
8
"tangled.sh/tangled.sh/core/knotserver/git"
10
9
"tangled.sh/tangled.sh/core/types"
···
19
18
return
20
19
}
21
20
22
-
rev1Param := r.URL.Query().Get("rev1")
23
-
if rev1Param == "" {
21
+
rev1 := r.URL.Query().Get("rev1")
22
+
if rev1 == "" {
24
23
writeError(w, xrpcerr.NewXrpcError(
25
24
xrpcerr.WithTag("InvalidRequest"),
26
25
xrpcerr.WithMessage("missing rev1 parameter"),
···
28
27
return
29
28
}
30
29
31
-
rev2Param := r.URL.Query().Get("rev2")
32
-
if rev2Param == "" {
30
+
rev2 := r.URL.Query().Get("rev2")
31
+
if rev2 == "" {
33
32
writeError(w, xrpcerr.NewXrpcError(
34
33
xrpcerr.WithTag("InvalidRequest"),
35
34
xrpcerr.WithMessage("missing rev2 parameter"),
36
35
), http.StatusBadRequest)
37
36
return
38
37
}
39
-
40
-
rev1, _ := url.PathUnescape(rev1Param)
41
-
rev2, _ := url.PathUnescape(rev2Param)
42
38
43
39
gr, err := git.PlainOpen(repoPath)
44
40
if err != nil {
+2
-11
knotserver/xrpc/repo_diff.go
+2
-11
knotserver/xrpc/repo_diff.go
···
3
3
import (
4
4
"encoding/json"
5
5
"net/http"
6
-
"net/url"
7
6
8
7
"tangled.sh/tangled.sh/core/knotserver/git"
9
8
"tangled.sh/tangled.sh/core/types"
···
18
17
return
19
18
}
20
19
21
-
refParam := r.URL.Query().Get("ref")
22
-
if refParam == "" {
23
-
writeError(w, xrpcerr.NewXrpcError(
24
-
xrpcerr.WithTag("InvalidRequest"),
25
-
xrpcerr.WithMessage("missing ref parameter"),
26
-
), http.StatusBadRequest)
27
-
return
28
-
}
29
-
30
-
ref, _ := url.QueryUnescape(refParam)
20
+
ref := r.URL.Query().Get("ref")
21
+
// ref can be empty (git.Open handles this)
31
22
32
23
gr, err := git.Open(repoPath, ref)
33
24
if err != nil {
+3
-9
knotserver/xrpc/repo_get_default_branch.go
+3
-9
knotserver/xrpc/repo_get_default_branch.go
···
3
3
import (
4
4
"encoding/json"
5
5
"net/http"
6
+
"time"
6
7
7
8
"tangled.sh/tangled.sh/core/api/tangled"
8
9
"tangled.sh/tangled.sh/core/knotserver/git"
···
17
18
return
18
19
}
19
20
20
-
gr, err := git.Open(repoPath, "")
21
-
if err != nil {
22
-
writeError(w, xrpcerr.NewXrpcError(
23
-
xrpcerr.WithTag("RepoNotFound"),
24
-
xrpcerr.WithMessage("repository not found"),
25
-
), http.StatusNotFound)
26
-
return
27
-
}
21
+
gr, err := git.PlainOpen(repoPath)
28
22
29
23
branch, err := gr.FindMainBranch()
30
24
if err != nil {
···
39
33
response := tangled.RepoGetDefaultBranch_Output{
40
34
Name: branch,
41
35
Hash: "",
42
-
When: "1970-01-01T00:00:00.000Z",
36
+
When: time.UnixMicro(0).Format(time.RFC3339),
43
37
}
44
38
45
39
w.Header().Set("Content-Type", "application/json")
+2
-7
knotserver/xrpc/repo_languages.go
+2
-7
knotserver/xrpc/repo_languages.go
···
5
5
"encoding/json"
6
6
"math"
7
7
"net/http"
8
-
"net/url"
9
8
"time"
10
9
11
10
"tangled.sh/tangled.sh/core/api/tangled"
···
14
13
)
15
14
16
15
func (x *Xrpc) RepoLanguages(w http.ResponseWriter, r *http.Request) {
17
-
refParam := r.URL.Query().Get("ref")
18
-
if refParam == "" {
19
-
refParam = "HEAD" // default
20
-
}
21
-
ref, _ := url.PathUnescape(refParam)
22
-
23
16
repo := r.URL.Query().Get("repo")
24
17
repoPath, err := x.parseRepoParam(repo)
25
18
if err != nil {
26
19
writeError(w, err.(xrpcerr.XrpcError), http.StatusBadRequest)
27
20
return
28
21
}
22
+
23
+
ref := r.URL.Query().Get("ref")
29
24
30
25
gr, err := git.Open(repoPath, ref)
31
26
if err != nil {
+1
-18
knotserver/xrpc/repo_log.go
+1
-18
knotserver/xrpc/repo_log.go
···
3
3
import (
4
4
"encoding/json"
5
5
"net/http"
6
-
"net/url"
7
6
"strconv"
8
7
9
8
"tangled.sh/tangled.sh/core/knotserver/git"
···
19
18
return
20
19
}
21
20
22
-
refParam := r.URL.Query().Get("ref")
23
-
if refParam == "" {
24
-
writeError(w, xrpcerr.NewXrpcError(
25
-
xrpcerr.WithTag("InvalidRequest"),
26
-
xrpcerr.WithMessage("missing ref parameter"),
27
-
), http.StatusBadRequest)
28
-
return
29
-
}
21
+
ref := r.URL.Query().Get("ref")
30
22
31
23
path := r.URL.Query().Get("path")
32
24
cursor := r.URL.Query().Get("cursor")
···
36
28
if l, err := strconv.Atoi(limitStr); err == nil && l > 0 && l <= 100 {
37
29
limit = l
38
30
}
39
-
}
40
-
41
-
ref, err := url.QueryUnescape(refParam)
42
-
if err != nil {
43
-
writeError(w, xrpcerr.NewXrpcError(
44
-
xrpcerr.WithTag("InvalidRequest"),
45
-
xrpcerr.WithMessage("invalid ref parameter"),
46
-
), http.StatusBadRequest)
47
-
return
48
31
}
49
32
50
33
gr, err := git.Open(repoPath, ref)
+4
-19
knotserver/xrpc/repo_tree.go
+4
-19
knotserver/xrpc/repo_tree.go
···
3
3
import (
4
4
"encoding/json"
5
5
"net/http"
6
-
"net/url"
7
6
"path/filepath"
7
+
"time"
8
8
9
9
"tangled.sh/tangled.sh/core/api/tangled"
10
10
"tangled.sh/tangled.sh/core/knotserver/git"
···
21
21
return
22
22
}
23
23
24
-
refParam := r.URL.Query().Get("ref")
25
-
if refParam == "" {
26
-
writeError(w, xrpcerr.NewXrpcError(
27
-
xrpcerr.WithTag("InvalidRequest"),
28
-
xrpcerr.WithMessage("missing ref parameter"),
29
-
), http.StatusBadRequest)
30
-
return
31
-
}
24
+
ref := r.URL.Query().Get("ref")
25
+
// ref can be empty (git.Open handles this)
32
26
33
27
path := r.URL.Query().Get("path")
34
28
// path can be empty (defaults to root)
35
29
36
-
ref, err := url.QueryUnescape(refParam)
37
-
if err != nil {
38
-
writeError(w, xrpcerr.NewXrpcError(
39
-
xrpcerr.WithTag("InvalidRequest"),
40
-
xrpcerr.WithMessage("invalid ref parameter"),
41
-
), http.StatusBadRequest)
42
-
return
43
-
}
44
-
45
30
gr, err := git.Open(repoPath, ref)
46
31
if err != nil {
47
32
x.Logger.Error("failed to open git repository", "error", err, "path", repoPath, "ref", ref)
···
77
62
entry.Last_commit = &tangled.RepoTree_LastCommit{
78
63
Hash: file.LastCommit.Hash.String(),
79
64
Message: file.LastCommit.Message,
80
-
When: file.LastCommit.When.Format("2006-01-02T15:04:05.000Z"),
65
+
When: file.LastCommit.When.Format(time.RFC3339),
81
66
}
82
67
}
83
68
+4
-27
knotserver/xrpc/xrpc.go
+4
-27
knotserver/xrpc/xrpc.go
···
4
4
"encoding/json"
5
5
"log/slog"
6
6
"net/http"
7
-
"net/url"
8
7
"strings"
9
8
10
9
securejoin "github.com/cyphar/filepath-securejoin"
···
88
87
}
89
88
90
89
// Parse repo string (did/repoName format)
91
-
parts := strings.Split(repo, "/")
92
-
if len(parts) < 2 {
90
+
parts := strings.SplitN(repo, "/", 2)
91
+
if len(parts) != 2 {
93
92
return "", xrpcerr.NewXrpcError(
94
93
xrpcerr.WithTag("InvalidRequest"),
95
94
xrpcerr.WithMessage("invalid repo format, expected 'did/repoName'"),
96
95
)
97
96
}
98
97
99
-
did := strings.Join(parts[:len(parts)-1], "/")
100
-
repoName := parts[len(parts)-1]
98
+
did := parts[0]
99
+
repoName := parts[1]
101
100
102
101
// Construct repository path using the same logic as didPath
103
102
didRepoPath, err := securejoin.SecureJoin(did, repoName)
···
117
116
}
118
117
119
118
return repoPath, nil
120
-
}
121
-
122
-
// parseStandardParams parses common query parameters used by most handlers
123
-
func (x *Xrpc) parseStandardParams(r *http.Request) (repo, repoPath, ref string, err error) {
124
-
// Parse repo parameter
125
-
repo = r.URL.Query().Get("repo")
126
-
repoPath, err = x.parseRepoParam(repo)
127
-
if err != nil {
128
-
return "", "", "", err
129
-
}
130
-
131
-
// Parse and unescape ref parameter
132
-
refParam := r.URL.Query().Get("ref")
133
-
if refParam == "" {
134
-
return "", "", "", xrpcerr.NewXrpcError(
135
-
xrpcerr.WithTag("InvalidRequest"),
136
-
xrpcerr.WithMessage("missing ref parameter"),
137
-
)
138
-
}
139
-
140
-
ref, _ = url.QueryUnescape(refParam)
141
-
return repo, repoPath, ref, nil
142
119
}
143
120
144
121
func writeError(w http.ResponseWriter, e xrpcerr.XrpcError, status int) {