Substitute variables in path names of template repos too (#25294)

### Summary

Extend the template variable substitution to replace file paths. This
can be helpful for setting up log files & directories that should match
the repository name.

### PR Changes

 - Move files matching glob pattern when setting up repos from template
- For security, added ~escaping~ sanitization for cross-platform support
and to prevent directory traversal (thanks @silverwind for the
reference)
 - Added unit testing for escaping function 
- Fixed the integration tests for repo template generation by passing
the repo_template_id
- Updated the integration testfiles to add some variable substitution &
assert the outputs

I had to fix the existing repo template integration test and extend it
to add a check for variable substitutions.

Example:

![image](621feb09-0ef3-460e-afa8-da74cd84fa4e)
This commit is contained in:
Kyle D 2023-06-20 17:14:47 -04:00 committed by GitHub
parent e50c3e8431
commit 8220e50b56
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
13 changed files with 77 additions and 10 deletions

View file

@ -11,6 +11,7 @@ import (
"os"
"path"
"path/filepath"
"regexp"
"strings"
"time"
@ -48,7 +49,7 @@ var defaultTransformers = []transformer{
{Name: "TITLE", Transform: util.ToTitleCase},
}
func generateExpansion(src string, templateRepo, generateRepo *repo_model.Repository) string {
func generateExpansion(src string, templateRepo, generateRepo *repo_model.Repository, sanitizeFileName bool) string {
expansions := []expansion{
{Name: "REPO_NAME", Value: generateRepo.Name, Transformers: defaultTransformers},
{Name: "TEMPLATE_NAME", Value: templateRepo.Name, Transformers: defaultTransformers},
@ -74,6 +75,9 @@ func generateExpansion(src string, templateRepo, generateRepo *repo_model.Reposi
return os.Expand(src, func(key string) string {
if expansion, ok := expansionMap[key]; ok {
if sanitizeFileName {
return fileNameSanitize(expansion)
}
return expansion
}
return key
@ -191,10 +195,24 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r
}
if err := os.WriteFile(path,
[]byte(generateExpansion(string(content), templateRepo, generateRepo)),
[]byte(generateExpansion(string(content), templateRepo, generateRepo, false)),
0o644); err != nil {
return err
}
substPath := filepath.FromSlash(filepath.Join(tmpDirSlash,
generateExpansion(base, templateRepo, generateRepo, true)))
// Create parent subdirectories if needed or continue silently if it exists
if err := os.MkdirAll(filepath.Dir(substPath), 0o755); err != nil {
return err
}
// Substitute filename variables
if err := os.Rename(path, substPath); err != nil {
return err
}
break
}
}
@ -353,3 +371,13 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ
return generateRepo, nil
}
// Sanitize user input to valid OS filenames
//
// Based on https://github.com/sindresorhus/filename-reserved-regex
// Adds ".." to prevent directory traversal
func fileNameSanitize(s string) string {
re := regexp.MustCompile(`(?i)\.\.|[<>:\"/\\|?*\x{0000}-\x{001F}]|^(con|prn|aux|nul|com\d|lpt\d)$`)
return strings.TrimSpace(re.ReplaceAllString(s, "_"))
}

View file

@ -54,3 +54,14 @@ func TestGiteaTemplate(t *testing.T) {
})
}
}
func TestFileNameSanitize(t *testing.T) {
assert.Equal(t, "test_CON", fileNameSanitize("test_CON"))
assert.Equal(t, "test CON", fileNameSanitize("test CON "))
assert.Equal(t, "__traverse__", fileNameSanitize("../traverse/.."))
assert.Equal(t, "http___localhost_3003_user_test.git", fileNameSanitize("http://localhost:3003/user/test.git"))
assert.Equal(t, "_", fileNameSanitize("CON"))
assert.Equal(t, "_", fileNameSanitize("con"))
assert.Equal(t, "_", fileNameSanitize("\u0000"))
assert.Equal(t, "目标", fileNameSanitize("目标"))
}