-
-
Notifications
You must be signed in to change notification settings - Fork 315
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Should ExecForEach
sanitize or escape arguments differently?
#184
Comments
Thanks for putting so much thought into this, @telemachus—it's most helpful. I think I understand what's going on here now. Let's take the spaces issue first. Something like this: script.Echo("my file").ExecForEach("touch {{.}}").Wait() doesn't work as expected, because we end up with two files: script.Echo("my file").ExecForEach("touch \"{{.}}\"").Wait() That takes care of the spaces issue, but what about files with quotes in their names? script.Echo("my \"quoted\" file").ExecForEach("touch \"{{.}}\"").Wait() This creates a file named script.Echo("my \\\"quoted\\\" file").ExecForEach("touch \"{{.}}\"").Wait() But when the list of filenames comes from some arbitrary source, what can we do? This must be a problem whenever filenames are interpolated into a Go template, mustn't it? Honestly, I'll confess this to you, but keep it to yourself: I don't much care for Go templates. I try not to use them if at all possible. However, if we can figure out the exact right way to rewrite arbitrary data in So, to coin a phrase, PRs are welcome! |
This problem has the same shape as the classic problem of HTML escaping: how do you make sure type SafeString string
func quote(s any) SafeString {
switch v := s.(type) {
case SafeString:
return v
case string:
return SafeString(shell.Quote(v))
default:
panic("bad type!")
}
} Then you use it on the client side like |
You mean this one ? |
It sounds like we have a plan! |
That certainly seems like it would be a good value-add, doesn't it? You've constructed the failing test case, @carlmjohnson has pointed out what the solution should look like, and @gedw99 has identified that the necessary code already exists. Between us, I think we've got this!
Yes, at first I thought we should simply always quote-sanitise input data to the template, but that's not quite good enough, because we don't know that the data contains filenames, for example. Therefore, it needs to be up to the user to call this function in their template if they're interpolating filenames. Adding this to the documentation should also appease the wrath of @matklad:
|
Some gopherology by an ex-crustacean: It feels like there should be a way to configure default escaping rule for Template, because that's what you'll just need for more or less any use-case. And, like, there is html escaping, it should work somehow? And, look, indeed, Templae exports the underlying parse tree: https://pkg.go.dev/text/template#Template type Template struct {
*parse.Tree
// contains filtered or unexported fields
} So I guess that's the way! You could walk the parse tree manually, applying your custom escaping rules. Now, how do I actually use that?
Oh... :-) |
In the current version of Go, if you put a package inside a directory called internal, it becomes unimportable by external packages. The Go team has said the parse tree for templates would have been in internal if internal had existed when it was created, but they don't want to move it now. Cf. golang/go#25357 |
Two things, and please just ignore me if I'm going overboard with the terseness here, but in this use case of "replacing shell" instead of "writing a rather large program" I'm left wondering why Second thing, yes |
@winks, I agree that Go template syntax isn't easy to love. But my thinking is that it's better to use something standard, perfect or not, than to invent my own way of doing this.
Can you elaborate on this point a little bit? I'm not sure I understand what you're referring to. |
After a recent conversation about
script
on Lobste.rs, I'm concerned about howExecForEach
handles arguments (especially filenames?) with tricky characters (e.g., spaces, single quotes, and double quotes).The current example for
ExecForEach
isListFiles("*").ExecForEach("touch {{.}}").Wait()
. I'll take that as a baseline, and start with the following minimal script:Imagine that lives in a folder with the following files:
Here's the result of running the script:
I would expect (hope?) that
script
would sanitize the filenames, so thattouch
ran once with each filename (including the ones with tricky characters).Instead, here are some things that go wrong (I think).
touch
seems to receive the argumentBobby Tables
twice. In a way, this is a good result: a single name with a space! On the other hand, all quotes seem to be stripped away from the two original filenames, one of which has single quotes and one of which has double quotes. If you change the code toscript.ListFiles("*").ExecForEach("ls {{.}}").Stdout()
, you'll seels: Bobby Tables: No such file or directory
(without quotes of any kind) twice in the output. These results suggest that somethingscript
does (the templating, perhaps?) removes quotations from filenames.ExecForEach
seems to split the filenamehello world
into two arguments so that the program runstouch hello
andtouch world
.Normally, in actual shell scripting, you could fix problems like these with proper quoting.
But I can't see any way to do the equivalent using
script
. Ideally,script
itself would handle this for the user. (E.g., to protect users who don't know about such dangers from themselves.)I noodled around with this a bit last night using https://bitbucket.org/creachadair/shell and https://github.com/mvdan/sh, but nothing I did helped. I'd be interested in working on this, but at the moment I can't see a way forward.
The text was updated successfully, but these errors were encountered: