Skip to content

Commit 8220e50

Browse files
authored
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](https://github.com/go-gitea/gitea/assets/12700993/621feb09-0ef3-460e-afa8-da74cd84fa4e)
1 parent e50c3e8 commit 8220e50

13 files changed

+77
-10
lines changed

docs/content/doc/usage/template-repositories.en-us.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ a/b/c/d.json
5151

5252
In any file matched by the above globs, certain variables will be expanded.
5353

54+
Matching filenames and paths can also be expanded, and are conservatively sanitized to support cross-platform filesystems.
55+
5456
All variables must be of the form `$VAR` or `${VAR}`. To escape an expansion, use a double `$$`, such as `$$VAR` or `$${VAR}`
5557

5658
| Variable | Expands To | Transformable |

modules/repository/generate.go

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import (
1111
"os"
1212
"path"
1313
"path/filepath"
14+
"regexp"
1415
"strings"
1516
"time"
1617

@@ -48,7 +49,7 @@ var defaultTransformers = []transformer{
4849
{Name: "TITLE", Transform: util.ToTitleCase},
4950
}
5051

51-
func generateExpansion(src string, templateRepo, generateRepo *repo_model.Repository) string {
52+
func generateExpansion(src string, templateRepo, generateRepo *repo_model.Repository, sanitizeFileName bool) string {
5253
expansions := []expansion{
5354
{Name: "REPO_NAME", Value: generateRepo.Name, Transformers: defaultTransformers},
5455
{Name: "TEMPLATE_NAME", Value: templateRepo.Name, Transformers: defaultTransformers},
@@ -74,6 +75,9 @@ func generateExpansion(src string, templateRepo, generateRepo *repo_model.Reposi
7475

7576
return os.Expand(src, func(key string) string {
7677
if expansion, ok := expansionMap[key]; ok {
78+
if sanitizeFileName {
79+
return fileNameSanitize(expansion)
80+
}
7781
return expansion
7882
}
7983
return key
@@ -191,10 +195,24 @@ func generateRepoCommit(ctx context.Context, repo, templateRepo, generateRepo *r
191195
}
192196

193197
if err := os.WriteFile(path,
194-
[]byte(generateExpansion(string(content), templateRepo, generateRepo)),
198+
[]byte(generateExpansion(string(content), templateRepo, generateRepo, false)),
195199
0o644); err != nil {
196200
return err
197201
}
202+
203+
substPath := filepath.FromSlash(filepath.Join(tmpDirSlash,
204+
generateExpansion(base, templateRepo, generateRepo, true)))
205+
206+
// Create parent subdirectories if needed or continue silently if it exists
207+
if err := os.MkdirAll(filepath.Dir(substPath), 0o755); err != nil {
208+
return err
209+
}
210+
211+
// Substitute filename variables
212+
if err := os.Rename(path, substPath); err != nil {
213+
return err
214+
}
215+
198216
break
199217
}
200218
}
@@ -353,3 +371,13 @@ func GenerateRepository(ctx context.Context, doer, owner *user_model.User, templ
353371

354372
return generateRepo, nil
355373
}
374+
375+
// Sanitize user input to valid OS filenames
376+
//
377+
// Based on https://github.com/sindresorhus/filename-reserved-regex
378+
// Adds ".." to prevent directory traversal
379+
func fileNameSanitize(s string) string {
380+
re := regexp.MustCompile(`(?i)\.\.|[<>:\"/\\|?*\x{0000}-\x{001F}]|^(con|prn|aux|nul|com\d|lpt\d)$`)
381+
382+
return strings.TrimSpace(re.ReplaceAllString(s, "_"))
383+
}

modules/repository/generate_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,14 @@ func TestGiteaTemplate(t *testing.T) {
5454
})
5555
}
5656
}
57+
58+
func TestFileNameSanitize(t *testing.T) {
59+
assert.Equal(t, "test_CON", fileNameSanitize("test_CON"))
60+
assert.Equal(t, "test CON", fileNameSanitize("test CON "))
61+
assert.Equal(t, "__traverse__", fileNameSanitize("../traverse/.."))
62+
assert.Equal(t, "http___localhost_3003_user_test.git", fileNameSanitize("http://localhost:3003/user/test.git"))
63+
assert.Equal(t, "_", fileNameSanitize("CON"))
64+
assert.Equal(t, "_", fileNameSanitize("con"))
65+
assert.Equal(t, "_", fileNameSanitize("\u0000"))
66+
assert.Equal(t, "目标", fileNameSanitize("目标"))
67+
}

tests/gitea-repositories-meta/user27/template1.git/objects/2a/83b349fa234131fc5db6f2a0498d3f4d3d6038

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
x��AJ�0�a�9�\@Ij2��C�w�"��h�i���޷q���~�{_ � ����+c�)M���* rȉSD&��M��*�l�pm*��5fE_�P�8���D�QC�ɕa�o?��+\>���f۸����O��HH9G"x��{w��;��8
2+
i�s�������0��9�/�IH
192 Bytes
Binary file not shown.
53 Bytes
Binary file not shown.
28 Bytes
Binary file not shown.
106 Bytes
Binary file not shown.
90 Bytes
Binary file not shown.
50 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)