Improve shell quoting
This commit is contained in:
		
							parent
							
								
									fe81d713e1
								
							
						
					
					
						commit
						b594ff78b2
					
				
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								Makefile
									
									
									
									
									
								
							| @ -9,5 +9,7 @@ acid: acid.go | ||||
| 	go build -ldflags "-X 'main.projectVersion=$(version)'" -o $@ | ||||
| acid.1: acid.adoc | ||||
| 	asciidoctor -b manpage -a release-version=$(version) -o $@ acid.adoc | ||||
| test: all | ||||
| 	go test | ||||
| clean: | ||||
| 	rm -f $(outputs) | ||||
|  | ||||
| @ -71,6 +71,9 @@ which has the following fields: | ||||
| *CloneURL*:: | ||||
| 	Gitea link for cloning the repository over HTTP. | ||||
| 
 | ||||
| The special *quote* template function quotes fields for safe usage | ||||
| in *sh*(1) command arguments. | ||||
| 
 | ||||
| Runners | ||||
| ------- | ||||
| Runners receive the following additional environment variables: | ||||
|  | ||||
							
								
								
									
										27
									
								
								acid.go
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								acid.go
									
									
									
									
									
								
							| @ -93,10 +93,30 @@ func parseConfig(path string) error { | ||||
| 	} | ||||
| 
 | ||||
| 	var err error | ||||
| 	gNotifyScript, err = ttemplate.New("notify").Parse(gConfig.Notify) | ||||
| 	gNotifyScript, err = | ||||
| 		ttemplate.New("notify").Funcs(shellFuncs).Parse(gConfig.Notify) | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| var shellFuncs = ttemplate.FuncMap{ | ||||
| 	"quote": func(word string) string { | ||||
| 		// History expansion is annoying, don't let it cut us. | ||||
| 		if strings.IndexRune(word, '!') >= 0 { | ||||
| 			return "'" + strings.ReplaceAll(word, "'", `'"'"'`) + "'" | ||||
| 		} | ||||
| 
 | ||||
| 		const special = "$`\"\\" | ||||
| 		quoted := []rune{'"'} | ||||
| 		for _, r := range word { | ||||
| 			if strings.IndexRune(special, r) >= 0 { | ||||
| 				quoted = append(quoted, '\\') | ||||
| 			} | ||||
| 			quoted = append(quoted, r) | ||||
| 		} | ||||
| 		return string(append(quoted, '"')) | ||||
| 	}, | ||||
| } | ||||
| 
 | ||||
| // --- Utilities --------------------------------------------------------------- | ||||
| 
 | ||||
| func giteaSign(b []byte) string { | ||||
| @ -910,8 +930,9 @@ func executorRunTask(ctx context.Context, task Task) error { | ||||
| 	//  - we might have to clone submodules as well. | ||||
| 	// Otherwise, we could download a source archive from Gitea, | ||||
| 	// and use SFTP to upload it to the runner. | ||||
| 	tmplScript, err := ttemplate.New("script").Parse(rt.Runner.Setup + "\n" + | ||||
| 		rt.ProjectRunner.Setup + "\n" + rt.ProjectRunner.Build) | ||||
| 	tmplScript, err := ttemplate.New("script").Funcs(shellFuncs). | ||||
| 		Parse(rt.Runner.Setup + "\n" + | ||||
| 			rt.ProjectRunner.Setup + "\n" + rt.ProjectRunner.Build) | ||||
| 	if err != nil { | ||||
| 		return fmt.Errorf("script: %w", err) | ||||
| 	} | ||||
|  | ||||
| @ -44,9 +44,9 @@ runners: | ||||
|     setup: | | ||||
|       set -ex | ||||
|       sudo pacman -Syu --noconfirm git | ||||
|       git clone --recursive '{{.CloneURL}}' '{{.Repo}}' | ||||
|       cd '{{.Repo}}' | ||||
|       git -c advice.detachedHead=false checkout '{{.Hash}}' | ||||
|       git clone --recursive {{quote .CloneURL}} {{quote .Repo}} | ||||
|       cd {{quote .Repo}} | ||||
|       git -c advice.detachedHead=false checkout {{quote .Hash}} | ||||
| 
 | ||||
| # Configuration for individual Gitea repositories. | ||||
| projects: | ||||
|  | ||||
							
								
								
									
										32
									
								
								acid_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								acid_test.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,32 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"bytes" | ||||
| 	"testing" | ||||
| 	ttemplate "text/template" | ||||
| ) | ||||
| 
 | ||||
| func TestTemplateQuote(t *testing.T) { | ||||
| 	// Ideally, we should back-parse it using sh syntax. | ||||
| 	// This is an unnecessarily fragile test. | ||||
| 	for _, test := range []struct { | ||||
| 		input, output string | ||||
| 	}{ | ||||
| 		{`!!`, `'!!'`}, | ||||
| 		{``, `""`}, | ||||
| 		{`${var}`, `"\${var}"`}, | ||||
| 		{"`cat`", "\"\\`cat\\`\""}, | ||||
| 		{`"魚\"`, `"\"魚\\\""`}, | ||||
| 	} { | ||||
| 		var b bytes.Buffer | ||||
| 		err := ttemplate.Must(ttemplate.New("test"). | ||||
| 			Funcs(shellFuncs).Parse("{{quote .}}")).Execute(&b, test.input) | ||||
| 		if err != nil { | ||||
| 			t.Errorf("template execution error: %s\n", err) | ||||
| 		} | ||||
| 		if b.String() != test.output { | ||||
| 			t.Errorf("%q should be quoted os %q, not %q\n", | ||||
| 				test.input, test.output, b.String()) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user