repository: validate remote address on mirror address update

Signed-off-by: E99p1ant <i@github.red>
This commit is contained in:
E99p1ant
2026-04-04 14:41:00 +08:00
parent b53d316233
commit a6a16575d8
4 changed files with 52 additions and 8 deletions

View File

@@ -52,12 +52,19 @@ func (f *MigrateRepo) Validate(ctx *macaron.Context, errs binding.Errors) bindin
return validate(errs, ctx.Data, f, ctx.Locale)
}
type ParseRemoteAddrOptions struct {
CloneAddr string
User *database.User
AuthUsername string
AuthPassword string
}
// ParseRemoteAddr checks if given remote address is valid,
// and returns composed URL with needed username and password.
// It also checks if given user has permission when remote address
// is actually a local path.
func (f MigrateRepo) ParseRemoteAddr(user *database.User) (string, error) {
remoteAddr := strings.TrimSpace(f.CloneAddr)
func ParseRemoteAddr(options ParseRemoteAddrOptions) (string, error) {
remoteAddr := strings.TrimSpace(options.CloneAddr)
// Remote address can be HTTP/HTTPS/Git URL or local path.
if strings.HasPrefix(remoteAddr, "http://") ||
@@ -72,15 +79,15 @@ func (f MigrateRepo) ParseRemoteAddr(user *database.User) (string, error) {
return "", database.ErrInvalidCloneAddr{IsBlockedLocalAddress: true}
}
if len(f.AuthUsername)+len(f.AuthPassword) > 0 {
u.User = url.UserPassword(f.AuthUsername, f.AuthPassword)
if len(options.AuthUsername)+len(options.AuthPassword) > 0 {
u.User = url.UserPassword(options.AuthUsername, options.AuthPassword)
}
// To prevent CRLF injection in git protocol, see https://github.com/gogs/gogs/issues/6413
if u.Scheme == "git" && (strings.Contains(remoteAddr, "%0d") || strings.Contains(remoteAddr, "%0a")) {
return "", database.ErrInvalidCloneAddr{IsURLError: true}
}
remoteAddr = u.String()
} else if !user.CanImportLocal() {
} else if !options.User.CanImportLocal() {
return "", database.ErrInvalidCloneAddr{IsPermissionDenied: true}
} else if !osx.IsDir(remoteAddr) {
return "", database.ErrInvalidCloneAddr{IsInvalidPath: true}

View File

@@ -251,7 +251,12 @@ func migrate(c *context.APIContext, f form.MigrateRepo) {
}
}
remoteAddr, err := f.ParseRemoteAddr(c.User)
remoteAddr, err := form.ParseRemoteAddr(form.ParseRemoteAddrOptions{
CloneAddr: f.CloneAddr,
User: c.User,
AuthUsername: f.AuthUsername,
AuthPassword: f.AuthPassword,
})
if err != nil {
if database.IsErrInvalidCloneAddr(err) {
addrErr := err.(database.ErrInvalidCloneAddr)

View File

@@ -170,7 +170,12 @@ func MigratePost(c *context.Context, f form.MigrateRepo) {
return
}
remoteAddr, err := f.ParseRemoteAddr(c.User)
remoteAddr, err := form.ParseRemoteAddr(form.ParseRemoteAddrOptions{
CloneAddr: f.CloneAddr,
User: c.User,
AuthUsername: f.AuthUsername,
AuthPassword: f.AuthPassword,
})
if err != nil {
if database.IsErrInvalidCloneAddr(err) {
c.Data["Err_CloneAddr"] = true

View File

@@ -121,7 +121,34 @@ func SettingsPost(c *context.Context, f form.RepoSetting) {
return
}
}
if err := c.Repo.Mirror.SaveAddress(f.MirrorAddress); err != nil {
remoteAddr, err := form.ParseRemoteAddr(form.ParseRemoteAddrOptions{
CloneAddr: f.MirrorAddress,
User: c.User,
})
if err != nil {
if database.IsErrInvalidCloneAddr(err) {
c.Data["Err_CloneAddr"] = true
addrErr := err.(database.ErrInvalidCloneAddr)
switch {
case addrErr.IsURLError:
c.RenderWithErr(c.Tr("repo.migrate.clone_address")+c.Tr("form.url_error"), http.StatusBadRequest, tmplRepoSettingsOptions, &f)
case addrErr.IsPermissionDenied:
c.RenderWithErr(c.Tr("repo.migrate.permission_denied"), http.StatusForbidden, tmplRepoSettingsOptions, &f)
case addrErr.IsInvalidPath:
c.RenderWithErr(c.Tr("repo.migrate.invalid_local_path"), http.StatusBadRequest, tmplRepoSettingsOptions, &f)
case addrErr.IsBlockedLocalAddress:
c.RenderWithErr(c.Tr("repo.migrate.clone_address_resolved_to_blocked_local_address"), http.StatusForbidden, tmplRepoSettingsOptions, &f)
default:
c.Error(err, "unexpected error")
}
} else {
c.Error(err, "parse remote address")
}
return
}
if err := c.Repo.Mirror.SaveAddress(remoteAddr); err != nil {
c.Error(err, "save address")
return
}