diff --git a/appview/issues/issues.go b/appview/issues/issues.go index f2cbf8bb..5f502a61 100644 --- a/appview/issues/issues.go +++ b/appview/issues/issues.go @@ -872,7 +872,7 @@ func (rp *Issues) RepoIssues(w http.ResponseWriter, r *http.Request) { searchOpts := models.IssueSearchOptions{ Keywords: tf.Keywords, Phrases: tf.Phrases, - RepoAt: f.RepoAt().String(), + RepoDid: f.RepoDid, IsOpen: isOpen, AuthorDid: authorDid, Labels: labels, @@ -932,7 +932,7 @@ func (rp *Issues) RepoIssues(w http.ResponseWriter, r *http.Request) { } } else { filters := []orm.Filter{ - orm.FilterEq("repo_at", f.RepoAt()), + orm.FilterEq("repo_did", f.RepoDid), } if isOpen != nil { openInt := 0 @@ -1013,7 +1013,7 @@ func (rp *Issues) NewIssue(w http.ResponseWriter, r *http.Request) { mentions, references := rp.mentionsResolver.Resolve(r.Context(), body) issue := &models.Issue{ - RepoAt: f.RepoAt(), + RepoDid: syntax.DID(f.RepoDid), Rkey: tid.TID(), Title: r.FormValue("title"), Body: body, diff --git a/appview/labels/labels.go b/appview/labels/labels.go index 216a68ad..c63c5678 100644 --- a/appview/labels/labels.go +++ b/appview/labels/labels.go @@ -100,7 +100,7 @@ func (l *Labels) PerformLabelOp(w http.ResponseWriter, r *http.Request) { } // find all the labels that this repo subscribes to - repoLabels, err := db.GetRepoLabels(l.db, orm.FilterEq("repo_at", repoAt)) + repoLabels, err := db.GetRepoLabels(l.db, orm.FilterEq("repo_did", repo.RepoDid)) if err != nil { fail("Failed to get labels for this repository.", err) return diff --git a/appview/middleware/middleware.go b/appview/middleware/middleware.go index 63b6f664..602bf764 100644 --- a/appview/middleware/middleware.go +++ b/appview/middleware/middleware.go @@ -2,6 +2,8 @@ package middleware import ( "context" + "database/sql" + "errors" "fmt" "log/slog" "net/http" @@ -219,6 +221,7 @@ func (mw Middleware) ResolveRepo() middlewareFunc { l := mw.logger.With("middleware", "ResolveRepo") repoName := chi.URLParam(req, "repo") repoName = strings.TrimSuffix(repoName, ".git") + rkey := strings.ToLower(repoName) id, ok := req.Context().Value("resolvedId").(identity.Identity) if !ok { @@ -230,10 +233,30 @@ func (mw Middleware) ResolveRepo() middlewareFunc { repo, err := db.GetRepo( mw.db, orm.FilterEq("did", id.DID.String()), - orm.FilterEq("name", repoName), + orm.FilterEq("rkey", rkey), ) if err != nil { - l.Error("failed to resolve repo", "err", err) + if !errors.Is(err, sql.ErrNoRows) { + l.Error("failed to resolve repo", "err", err) + http.Error(w, "internal server error", http.StatusInternalServerError) + return + } + hint, hintErr := db.LookupRepoRename(mw.db, id.DID.String(), rkey) + if hintErr != nil && !errors.Is(hintErr, sql.ErrNoRows) { + l.Error("failed to lookup repo rename hint", "err", hintErr) + } + if hint != nil { + parts := strings.SplitN(strings.TrimPrefix(req.URL.Path, "/"), "/", 3) + target := "/" + parts[0] + "/" + hint.Rkey + if len(parts) == 3 { + target += "/" + parts[2] + } + if req.URL.RawQuery != "" { + target += "?" + req.URL.RawQuery + } + http.Redirect(w, req, target, http.StatusMovedPermanently) + return + } w.WriteHeader(http.StatusNotFound) mw.pages.ErrorKnot404(w) return @@ -266,7 +289,7 @@ func (mw Middleware) ResolvePull() middlewareFunc { return } - pr, err := db.GetPull(mw.db, orm.FilterEq("repo_at", f.RepoAt()), orm.FilterEq("pull_id", prIdInt)) + pr, err := db.GetPull(mw.db, orm.FilterEq("repo_did", f.RepoDid), orm.FilterEq("pull_id", prIdInt)) if err != nil { l.Error("failed to get pull and comments", "err", err) mw.pages.Error404(w) @@ -309,7 +332,7 @@ func (mw Middleware) ResolveIssue(next http.Handler) http.Handler { return } - issue, err := db.GetIssue(mw.db, f.RepoAt(), issueId) + issue, err := db.GetIssue(mw.db, f.RepoDid, issueId) if err != nil { l.Error("failed to get issues", "err", err) mw.pages.Error404(w) @@ -345,7 +368,7 @@ func (mw Middleware) GoImport() middlewareFunc { if r.URL.Query().Get("go-get") == "1" { modulePath := userutil.FlattenDid(fullName) if strings.Contains(modulePath, ":") { - modulePath = userutil.FlattenDid(f.Did) + "/" + f.Name + modulePath = userutil.FlattenDid(f.Did) + "/" + f.Rkey } html := fmt.Sprintf( ` diff --git a/appview/models/repo.go b/appview/models/repo.go index 7ef98dac..b80416ff 100644 --- a/appview/models/repo.go +++ b/appview/models/repo.go @@ -136,14 +136,17 @@ func ValidateRepoName(name string) error { return fmt.Errorf("Repository name must be 100 characters or fewer") } + // check for path traversal attempts if strings.Contains(name, "/") || strings.Contains(name, "\\") { return fmt.Errorf("Repository name contains invalid path characters") } + // check for sequences that could be used for traversal when normalized if strings.HasPrefix(name, ".") || strings.HasSuffix(name, ".") { return fmt.Errorf("Repository name contains invalid path sequence") } + // then continue with character validation for _, char := range name { if !((char >= 'a' && char <= 'z') || (char >= 'A' && char <= 'Z') || @@ -153,6 +156,7 @@ func ValidateRepoName(name string) error { } } + // additional check to prevent multiple sequential dots if strings.Contains(name, "..") { return fmt.Errorf("Repository name cannot contain sequential dots") } @@ -161,6 +165,7 @@ func ValidateRepoName(name string) error { return fmt.Errorf("Repository name %q is reserved", name) } + // if all checks pass return nil } diff --git a/appview/pages/repoinfo/repoinfo.go b/appview/pages/repoinfo/repoinfo.go index f5b505bb..152f0bc0 100644 --- a/appview/pages/repoinfo/repoinfo.go +++ b/appview/pages/repoinfo/repoinfo.go @@ -20,7 +20,7 @@ func (r RepoInfo) owner() string { } func (r RepoInfo) FullName() string { - return path.Join(r.owner(), r.Name) + return path.Join(r.owner(), r.Rkey) } func (r RepoInfo) ownerWithoutAt() string { @@ -32,7 +32,7 @@ func (r RepoInfo) ownerWithoutAt() string { } func (r RepoInfo) FullNameWithoutAt() string { - return path.Join(r.ownerWithoutAt(), r.Name) + return path.Join(r.ownerWithoutAt(), r.Rkey) } func (r RepoInfo) GetTabs() [][]string { @@ -59,6 +59,7 @@ type RepoInfo struct { Rkey string OwnerDid string OwnerHandle string + RepoDid string Description string Website string Topics []string diff --git a/appview/pages/templates/goodfirstissues/index.html b/appview/pages/templates/goodfirstissues/index.html index b4fa8dd1..d0ca6af0 100644 --- a/appview/pages/templates/goodfirstissues/index.html +++ b/appview/pages/templates/goodfirstissues/index.html @@ -46,7 +46,7 @@ {{ i "book-marked" "w-4 h-4 mr-1.5 shrink-0" }} {{ end }} {{ $repoOwner := resolve .Repo.Did }} - {{ $repoOwner }}/{{ .Repo.Name }} + {{ $repoOwner }}/{{ .Repo.Name }} @@ -90,7 +90,7 @@ {{ if gt (len .Issues) 0 }}
1First, generate a new SSH key pair.
2Then add the public key to your account from the keys page in your settings.
-3Configure your remote to git@{{ $knot | stripPort }}:{{ resolve .RepoInfo.OwnerDid }}/{{ .RepoInfo.Name }}
3Configure your remote to git@{{ $knot | stripPort }}:{{ .RepoInfo.RepoDid }}
4Push!
diff --git a/appview/pages/templates/repo/index.html b/appview/pages/templates/repo/index.html index 5c7bc825..473ec70e 100644 --- a/appview/pages/templates/repo/index.html +++ b/appview/pages/templates/repo/index.html @@ -361,3 +361,4 @@ {{ template "repo/fragments/readme" . }} {{- end -}} {{ end }} + diff --git a/appview/pages/templates/repo/pulls/fragments/pullActions.html b/appview/pages/templates/repo/pulls/fragments/pullActions.html index 248694d2..f6d4b697 100644 --- a/appview/pages/templates/repo/pulls/fragments/pullActions.html +++ b/appview/pages/templates/repo/pulls/fragments/pullActions.html @@ -34,7 +34,7 @@ {{ if .BranchDeleteStatus }}
Additional event types (pull requests, issues) will be available in future updates.
@@ -294,13 +298,19 @@Additional event types (pull requests, issues) will be available in future updates.
diff --git a/appview/pages/templates/repo/settings/sites.html b/appview/pages/templates/repo/settings/sites.html index 1ac98026..96d7cb6d 100644 --- a/appview/pages/templates/repo/settings/sites.html +++ b/appview/pages/templates/repo/settings/sites.html @@ -32,7 +32,7 @@ {{ else }}
{{ if .OwnerClaim }}
- {{ .OwnerClaim.Domain }}/{{ $.RepoInfo.Name }}
+ {{ .OwnerClaim.Domain }}/{{ $.RepoInfo.Rkey }}
{{ else }}
- e.g. you.tngl.page/{{ $.RepoInfo.Name }}
+ e.g. you.tngl.page/{{ $.RepoInfo.Rkey }}
{{ end }}