m.Get(".rss", feedEnabled, repo.TagsListFeedRSS)
m.Get(".atom", feedEnabled, repo.TagsListFeedAtom)
}, ctxDataSet("EnableFeed", setting.Other.EnableFeed),
- repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag, true))
+ repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag, context.RepoRefByTypeOptions{IgnoreNotExistErr: true}))
m.Post("/tags/delete", repo.DeleteTag, reqSignIn,
repo.MustBeNotEmpty, context.RepoMustNotBeArchived(), reqRepoCodeWriter, context.RepoRef())
}, ignSignIn, context.RepoAssignment, reqRepoCodeReader)
m.Get(".rss", feedEnabled, repo.ReleasesFeedRSS)
m.Get(".atom", feedEnabled, repo.ReleasesFeedAtom)
}, ctxDataSet("EnableFeed", setting.Other.EnableFeed),
- repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag, true))
+ repo.MustBeNotEmpty, context.RepoRefByType(context.RepoRefTag, context.RepoRefByTypeOptions{IgnoreNotExistErr: true}))
m.Get("/releases/attachments/{uuid}", repo.MustBeNotEmpty, repo.GetAttachment)
m.Get("/releases/download/{vTag}/{fileName}", repo.MustBeNotEmpty, repo.RedirectDownload)
m.Group("/releases", func() {
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.SingleDownloadOrLFS)
m.Get("/blob/{sha}", context.RepoRefByType(context.RepoRefBlob), repo.DownloadByIDOrLFS)
// "/*" route is deprecated, and kept for backward compatibility
- m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.SingleDownloadOrLFS)
+ m.Get("/*", context.RepoRefByType(context.RepoRefUnknown), repo.SingleDownloadOrLFS)
}, repo.MustBeNotEmpty)
m.Group("/raw", func() {
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.SingleDownload)
m.Get("/blob/{sha}", context.RepoRefByType(context.RepoRefBlob), repo.DownloadByID)
// "/*" route is deprecated, and kept for backward compatibility
- m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.SingleDownload)
+ m.Get("/*", context.RepoRefByType(context.RepoRefUnknown), repo.SingleDownload)
}, repo.MustBeNotEmpty)
m.Group("/render", func() {
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.RefCommits)
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.RefCommits)
// "/*" route is deprecated, and kept for backward compatibility
- m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.RefCommits)
+ m.Get("/*", context.RepoRefByType(context.RepoRefUnknown), repo.RefCommits)
}, repo.MustBeNotEmpty)
m.Group("/blame", func() {
m.Get("/branch/*", context.RepoRefByType(context.RepoRefBranch), repo.Home)
m.Get("/tag/*", context.RepoRefByType(context.RepoRefTag), repo.Home)
m.Get("/commit/*", context.RepoRefByType(context.RepoRefCommit), repo.Home)
- m.Get("/*", context.RepoRefByType(context.RepoRefLegacy), repo.Home) // "/*" route is deprecated, and kept for backward compatibility
+ m.Get("/*", context.RepoRefByType(context.RepoRefUnknown), repo.Home) // "/*" route is deprecated, and kept for backward compatibility
}, repo.SetEditorconfigIfExists)
m.Get("/forks", context.RepoRef(), repo.Forks)
"code.gitea.io/gitea/modules/cache"
"code.gitea.io/gitea/modules/git"
"code.gitea.io/gitea/modules/gitrepo"
+ "code.gitea.io/gitea/modules/httplib"
code_indexer "code.gitea.io/gitea/modules/indexer/code"
"code.gitea.io/gitea/modules/log"
"code.gitea.io/gitea/modules/optional"
}
// ComposeGoGetImport returns go-get-import meta content.
-func ComposeGoGetImport(owner, repo string) string {
- /// setting.AppUrl is guaranteed to be parse as url
- appURL, _ := url.Parse(setting.AppURL)
-
- return path.Join(appURL.Host, setting.AppSubURL, url.PathEscape(owner), url.PathEscape(repo))
+func ComposeGoGetImport(ctx context.Context, owner, repo string) string {
+ curAppURL, _ := url.Parse(httplib.GuessCurrentAppURL(ctx))
+ return path.Join(curAppURL.Host, setting.AppSubURL, url.PathEscape(owner), url.PathEscape(repo))
}
// EarlyResponseForGoGetMeta responses appropriate go-get meta with status 200
} else {
cloneURL = repo_model.ComposeHTTPSCloneURL(username, reponame)
}
- goImportContent := fmt.Sprintf("%s git %s", ComposeGoGetImport(username, reponame), cloneURL)
+ goImportContent := fmt.Sprintf("%s git %s", ComposeGoGetImport(ctx, username, reponame), cloneURL)
htmlMeta := fmt.Sprintf(`<meta name="go-import" content="%s">`, html.EscapeString(goImportContent))
ctx.PlainText(http.StatusOK, htmlMeta)
}
}
if ctx.FormString("go-get") == "1" {
- ctx.Data["GoGetImport"] = ComposeGoGetImport(owner.Name, repo.Name)
+ ctx.Data["GoGetImport"] = ComposeGoGetImport(ctx, owner.Name, repo.Name)
fullURLPrefix := repo.HTMLURL() + "/src/branch/" + util.PathEscapeSegments(ctx.Repo.BranchName)
ctx.Data["GoDocDirectory"] = fullURLPrefix + "{/dir}"
ctx.Data["GoDocFile"] = fullURLPrefix + "{/dir}/{file}#L{line}"
type RepoRefType int
const (
- // RepoRefLegacy unknown type, make educated guess and redirect.
- // for backward compatibility with previous URL scheme
- RepoRefLegacy RepoRefType = iota
- // RepoRefAny is for usage where educated guess is needed
- // but redirect can not be made
- RepoRefAny
- // RepoRefBranch branch
+ // RepoRefUnknown is for legacy support, makes the code to "guess" the ref type
+ RepoRefUnknown RepoRefType = iota
RepoRefBranch
- // RepoRefTag tag
RepoRefTag
- // RepoRefCommit commit
RepoRefCommit
- // RepoRefBlob blob
RepoRefBlob
)
return RepoRefByType(RepoRefBranch)
}
-// RefTypeIncludesBranches returns true if ref type can be a branch
-func (rt RepoRefType) RefTypeIncludesBranches() bool {
- if rt == RepoRefLegacy || rt == RepoRefAny || rt == RepoRefBranch {
- return true
- }
- return false
-}
-
-// RefTypeIncludesTags returns true if ref type can be a tag
-func (rt RepoRefType) RefTypeIncludesTags() bool {
- if rt == RepoRefLegacy || rt == RepoRefAny || rt == RepoRefTag {
- return true
- }
- return false
-}
-
func getRefNameFromPath(repo *Repository, path string, isExist func(string) bool) string {
refName := ""
parts := strings.Split(path, "/")
return ""
}
+func isStringLikelyCommitID(objFmt git.ObjectFormat, s string, minLength ...int) bool {
+ minLen := util.OptionalArg(minLength, objFmt.FullLength())
+ if len(s) < minLen || len(s) > objFmt.FullLength() {
+ return false
+ }
+ for _, c := range s {
+ isHex := (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f')
+ if !isHex {
+ return false
+ }
+ }
+ return true
+}
+
+func getRefNameLegacy(ctx *Base, repo *Repository, optionalExtraRef ...string) (string, RepoRefType) {
+ extraRef := util.OptionalArg(optionalExtraRef)
+ reqPath := ctx.PathParam("*")
+ reqPath = path.Join(extraRef, reqPath)
+
+ if refName := getRefName(ctx, repo, RepoRefBranch); refName != "" {
+ return refName, RepoRefBranch
+ }
+ if refName := getRefName(ctx, repo, RepoRefTag); refName != "" {
+ return refName, RepoRefTag
+ }
+
+ // For legacy support only full commit sha
+ parts := strings.Split(reqPath, "/")
+ if isStringLikelyCommitID(git.ObjectFormatFromName(repo.Repository.ObjectFormatName), parts[0]) {
+ // FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists
+ repo.TreePath = strings.Join(parts[1:], "/")
+ return parts[0], RepoRefCommit
+ }
+
+ if refName := getRefName(ctx, repo, RepoRefBlob); len(refName) > 0 {
+ return refName, RepoRefBlob
+ }
+ repo.TreePath = reqPath
+ return repo.Repository.DefaultBranch, RepoRefBranch
+}
+
func getRefName(ctx *Base, repo *Repository, pathType RepoRefType) string {
path := ctx.PathParam("*")
switch pathType {
- case RepoRefLegacy, RepoRefAny:
- if refName := getRefName(ctx, repo, RepoRefBranch); len(refName) > 0 {
- return refName
- }
- if refName := getRefName(ctx, repo, RepoRefTag); len(refName) > 0 {
- return refName
- }
- // For legacy and API support only full commit sha
- parts := strings.Split(path, "/")
-
- if len(parts) > 0 && len(parts[0]) == git.ObjectFormatFromName(repo.Repository.ObjectFormatName).FullLength() {
- repo.TreePath = strings.Join(parts[1:], "/")
- return parts[0]
- }
- if refName := getRefName(ctx, repo, RepoRefBlob); len(refName) > 0 {
- return refName
- }
- repo.TreePath = path
- return repo.Repository.DefaultBranch
case RepoRefBranch:
ref := getRefNameFromPath(repo, path, repo.GitRepo.IsBranchExist)
if len(ref) == 0 {
return getRefNameFromPath(repo, path, repo.GitRepo.IsTagExist)
case RepoRefCommit:
parts := strings.Split(path, "/")
-
- if len(parts) > 0 && len(parts[0]) >= 7 && len(parts[0]) <= repo.GetObjectFormat().FullLength() {
+ if isStringLikelyCommitID(repo.GetObjectFormat(), parts[0], 7) {
+ // FIXME: this logic is different from other types. Ideally, it should also try to GetCommit to check if it exists
repo.TreePath = strings.Join(parts[1:], "/")
return parts[0]
}
- if len(parts) > 0 && parts[0] == headRefName {
+ if parts[0] == headRefName {
// HEAD ref points to last default branch commit
commit, err := repo.GitRepo.GetBranchCommit(repo.Repository.DefaultBranch)
if err != nil {
}
return path
default:
- log.Error("Unrecognized path type: %v", path)
+ panic(fmt.Sprintf("Unrecognized path type: %v", pathType))
}
return ""
}
+type RepoRefByTypeOptions struct {
+ IgnoreNotExistErr bool
+}
+
// RepoRefByType handles repository reference name for a specific type
// of repository reference
-func RepoRefByType(refType RepoRefType, ignoreNotExistErr ...bool) func(*Context) context.CancelFunc {
+func RepoRefByType(detectRefType RepoRefType, opts ...RepoRefByTypeOptions) func(*Context) context.CancelFunc {
+ opt := util.OptionalArg(opts)
return func(ctx *Context) (cancel context.CancelFunc) {
+ refType := detectRefType
// Empty repository does not have reference information.
if ctx.Repo.Repository.IsEmpty {
// assume the user is viewing the (non-existent) default branch
}
ctx.Repo.IsViewBranch = true
} else {
- refName = getRefName(ctx.Base, ctx.Repo, refType)
+ guessLegacyPath := refType == RepoRefUnknown
+ if guessLegacyPath {
+ refName, refType = getRefNameLegacy(ctx.Base, ctx.Repo)
+ } else {
+ refName = getRefName(ctx.Base, ctx.Repo, refType)
+ }
ctx.Repo.RefName = refName
isRenamedBranch, has := ctx.Data["IsRenamedBranch"].(bool)
if isRenamedBranch && has {
return cancel
}
- if refType.RefTypeIncludesBranches() && ctx.Repo.GitRepo.IsBranchExist(refName) {
+ if refType == RepoRefBranch && ctx.Repo.GitRepo.IsBranchExist(refName) {
ctx.Repo.IsViewBranch = true
ctx.Repo.BranchName = refName
return cancel
}
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
- } else if refType.RefTypeIncludesTags() && ctx.Repo.GitRepo.IsTagExist(refName) {
+ } else if refType == RepoRefTag && ctx.Repo.GitRepo.IsTagExist(refName) {
ctx.Repo.IsViewTag = true
ctx.Repo.TagName = refName
return cancel
}
ctx.Repo.CommitID = ctx.Repo.Commit.ID.String()
- } else if len(refName) >= 7 && len(refName) <= ctx.Repo.GetObjectFormat().FullLength() {
+ } else if isStringLikelyCommitID(ctx.Repo.GetObjectFormat(), refName, 7) {
ctx.Repo.IsViewCommit = true
ctx.Repo.CommitID = refName
}
// If short commit ID add canonical link header
if len(refName) < ctx.Repo.GetObjectFormat().FullLength() {
- ctx.RespHeader().Set("Link", fmt.Sprintf("<%s>; rel=\"canonical\"",
- util.URLJoin(setting.AppURL, strings.Replace(ctx.Req.URL.RequestURI(), util.PathEscapeSegments(refName), url.PathEscape(ctx.Repo.Commit.ID.String()), 1))))
+ canonicalURL := util.URLJoin(httplib.GuessCurrentAppURL(ctx), strings.Replace(ctx.Req.URL.RequestURI(), util.PathEscapeSegments(refName), url.PathEscape(ctx.Repo.Commit.ID.String()), 1))
+ ctx.RespHeader().Set("Link", fmt.Sprintf(`<%s>; rel="canonical"`, canonicalURL))
}
} else {
- if len(ignoreNotExistErr) > 0 && ignoreNotExistErr[0] {
+ if opt.IgnoreNotExistErr {
return cancel
}
ctx.NotFound("RepoRef invalid repo", fmt.Errorf("branch or tag not exist: %s", refName))
return cancel
}
- if refType == RepoRefLegacy {
+ if guessLegacyPath {
// redirect from old URL scheme to new URL scheme
prefix := strings.TrimPrefix(setting.AppSubURL+strings.ToLower(strings.TrimSuffix(ctx.Req.URL.Path, ctx.PathParam("*"))), strings.ToLower(ctx.Repo.RepoLink))
redirect := path.Join(
func TestRedirectsNoLogin(t *testing.T) {
defer tests.PrepareTestEnv(t)()
- redirects := map[string]string{
- "/user2/repo1/commits/master": "/user2/repo1/commits/branch/master",
- "/user2/repo1/src/master": "/user2/repo1/src/branch/master",
- "/user2/repo1/src/master/file.txt": "/user2/repo1/src/branch/master/file.txt",
- "/user2/repo1/src/master/directory/file.txt": "/user2/repo1/src/branch/master/directory/file.txt",
- "/user/avatar/Ghost/-1": "/assets/img/avatar_default.png",
- "/api/v1/swagger": "/api/swagger",
+ redirects := []struct{ from, to string }{
+ {"/user2/repo1/commits/master", "/user2/repo1/commits/branch/master"},
+ {"/user2/repo1/src/master", "/user2/repo1/src/branch/master"},
+ {"/user2/repo1/src/master/file.txt", "/user2/repo1/src/branch/master/file.txt"},
+ {"/user2/repo1/src/master/directory/file.txt", "/user2/repo1/src/branch/master/directory/file.txt"},
+ {"/user/avatar/Ghost/-1", "/assets/img/avatar_default.png"},
+ {"/api/v1/swagger", "/api/swagger"},
}
- for link, redirectLink := range redirects {
- req := NewRequest(t, "GET", link)
+ for _, c := range redirects {
+ req := NewRequest(t, "GET", c.from)
resp := MakeRequest(t, req, http.StatusSeeOther)
- assert.EqualValues(t, path.Join(setting.AppSubURL, redirectLink), test.RedirectURL(resp))
+ assert.EqualValues(t, path.Join(setting.AppSubURL, c.to), test.RedirectURL(resp))
}
}