-
-
Notifications
You must be signed in to change notification settings - Fork 499
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
POSIX: Invalid handling of parameter values starting with double quotes ("
), e.g. --order-by='"quoted"
value'
#959
Comments
I took a look some more in this. I still comes down to how each shell is gonna handle the quotes and each shell is wildly different. Cmd.exe, pwsh, and even running through the debugger all vary wildly in the scenarios you posted with no consistency. But, on your shell and only this shell it does include the quotes in the args and theoretically we could include them. Went ahead and created a unit test shows the failure. [Fact]
public void Quote_Tests()
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddCommand<GenericCommand<FooCommandSettings>>("test");
});
// When
var result = app.Run(new[]
{
"test", "\"Rufus\"",
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<FooCommandSettings>().And(foo => foo.Qux.ShouldBe("\"Rufus\""));
} This is a pretty extreme edge case - relying on shells to act consistently with quotes is simply a non-starter as the link in the previous issue talk about. If you or anyone else want to give fixing this bug a shot, consider it up for grabs though, and you are welcome to see if you can't get the parser updated to take this scenario into account. But personally, this is not a behavior I'd ever rely on for end users and I would highly recommend something other than nested quotes in the arguments if you are going to have an app running in multiple shells.. |
Thank you for your quick feedback.
I'm really sorry, if I don't understand the inner workings of shells as much as you do - but I'm willing to learn something here. I'll take a look into this and find out, if I can do anything to support you to fix this behaviour without breaking anything else.
This edge case comes from a real world scenario, otherwise I'm pretty sure, nobody would have found it :-) I'm developing a command line audio tagger named tone tag --meta-description='"The aliens will kill us all" said Mr. Jones...' 'an-audio-book.m4b' But I'll investigate the behaviour of other command line parsers and make a detailed comparison. As I see it, somethere in the Parser or Tokenizer the quotes get removed. I'm on it. I'll get back to you as soon as possible. |
I think I found something to investigate. If you look at the code here and below, you see, that the following chars/strings are treated in a special way and I raised issues for handling exactly these wrong under specific circumstances:
and
and
So I think these are all Problems in the |
So after some further investigation, I think that the Lets say, that you have an array of arguments like this: cli-tester test --order-by " " --order-by "-birthday" --order-by '"a quoted field" containing more text' the tokenizer will:
I think here is some work to do - the tokenizer should not silently ignore valid chars or make invalid assumptions about the context (e.g. assume that every |
@phil-scott-78 I did not find any unit tests for the tokenizer... did I miss them? If not, how can I ensure I don't break something when I try to fix this? Sorry, I just want to prevent putting much work into something that is not useful to anyone. |
@phil-scott-78 @patriksvensson |
I have tested this myself and can confirm that my Windows shell does not perform as you found yourself @sandreas
I have found the following example does modify parameters on my local machine:
When run, it produces the following output, which clearly shows the quoting of the command line arguments are being modified before even being made available to the .Net console app: As this in independent of spectreconsole, I struggle to see how consistent handling of various quoted arguments can be addressed within spectre itself, and as @phil-scott-78 pointed out above, spectre is at the mercy of the executing shell when it comes to the initial handling of arguments. |
I think we have a misunderstanding here. Windows shell does not support single quotes ( # windows
ConsoleApp.exe --order-by """quoted"" value" input.mp3
# linux
ConsoleApp --order-by '"quoted" value'
ConsoleApp --order-by "\"quoted\" value" # variant with escaped double quotes So it's not about the windows shell does things WRONG, it just has different rules for calling your app. Same as # linux
ConsoleApp *
# expands to files in ./, its like calling this for your app
# ConsoleApp input1.mp3 input2.mp3 (e.g. args will be # use the * as string argument that can be treated by the app
ConsoleApp '*' will give you one single arg ( find . -name '*.mp3' Can you confirm the described behaviour? As a result I would not even handle single quotes in ConsoleApp --order-by '"quoted" value' on Windows, he is calling it wrong - not the app is handling it wrong. |
Ok, apologies if I totally misunderstood - I see your comments are about what happens once quotes have made their way into the argument (one way or another). I’ll take another more detailed look….. |
No need to apologize... As long as I get responses to my issue postings, I'm fine with writing whole essays about what I think needs to be improved ;-) Sometimes command line is very tricky because of the many layers involved. There is a OPERATING SYSTEM, aTERMINAL, a SHELL and an APP - all with different options, encodings and possibilities. One of my golden rules in software development is: Don't you ever fiddle around with strings - and boy prevent modifying them without a valid encoding/decoding pair of functions Since |
Ok, I can reproduce what you have reported across several bug reports and the discussion thread (#1025) with the following unit test: [Theory]
[InlineData("\"\"")]
[InlineData("\" \"")]
// Special character handling
[InlineData("-R")]
[InlineData("-Rufus")]
[InlineData("--R")]
[InlineData("--Rufus")]
// Double-quote handling
[InlineData("\"Rufus\"")]
[InlineData("\" Rufus\"")]
[InlineData("\"-R\"")]
[InlineData("\"-Rufus\"")]
[InlineData("\" -Rufus\"")]
// Single-quote handling
[InlineData("'Rufus'")]
[InlineData("' Rufus'")]
[InlineData("'-R'")]
[InlineData("'-Rufus'")]
[InlineData("' -Rufus'")]
public void Quote_Tests(string actualAndExpected)
{
// Given
var app = new CommandAppTester();
app.Configure(config =>
{
config.PropagateExceptions();
config.AddCommand<GenericCommand<FooCommandSettings>>("test");
});
// When
var result = app.Run(new[]
{
"test", actualAndExpected,
});
// Then
result.ExitCode.ShouldBe(0);
result.Settings.ShouldBeOfType<FooCommandSettings>().And(foo => foo.Qux.ShouldBe(actualAndExpected));
} All of the test cases (except for the 5 single quote scenarios) fail, see: I don't mind having a look into what may be required to fix these failing tests, which if successful, would be a massive step forward in addressing many edge case issues regarding command line argument handling. |
Awesome :-)
Great, thank you very much. Maybe we should keep in mind that fixing these unit-tests are not gonna fix the design problem, that EVERY argument is going through the method
This design problem does not only result in unnecessary parsings (Performance, Security), but not regarding the previous type of Token for an argument leads to other bugs:
So maybe it's worth also taking a look at:
I'm very excited that now it is clear, what I meant :-) Thank you for your hard work. |
As part of fixing the ‘flags cannot be set’ issue, I introduced a HadSeparator flag on command tree token which allows the CommandTreeParser some knowledge of context, see here: https://github.com/spectreconsole/spectre.console/blob/dded816d97f5a6cbf9c1831aa00fe46684d1ac5e/src/Spectre.Console.Cli/Internal/Parsing/CommandTreeToken.cs however, this is more of a tactical implementation when compared to your design suggestion above. I don’t know the codebase well enough to refactor extensively yet, however the command tree parser looks easily backed by an interface and dependency injected. For now, I’ll see what I can do tactically to fix the unit tests, so it can be released sooner than later. Then look at perhaps a new implementation of the parser following a statemachine or similar. Which can be injected and validated with existing unit tests, instead of attempting open heart surgery on the currrent codebase. |
You sir, deserve a cookie! Maybe it would be a good idea to implement the class SubCommand {
public string Name;
}
class Flag {
public string Name;
public string[] Aliases; // e.g. for shortName
}
class Option {
public string Name;
public string[] Aliases; // e.g. for shortName
public string? Value; // not a real type because every arg is at first a string - this can be done by the parser
}
class Argument {
public int index;
public string? Value; // not a real type because every arg is at first a string - this can be done by the parser
}
class SubCommand {
public IEnumerable<SubCommand> SubCommands {get; set;}
public IEnumerable<Flag> Flags {get; set;}
public IEnumerable<Option> Options {get; set;}
public IEnumerable<Argument> Arguments {get; set;}
}
var tokenizer = new Tokenizer("=");
var subCommands = new List<SubCommand>();
// configure subcommands with arguments and stuff
var parser = new ArgsParser(subCommands, tokenizer);
var result = parser.Parse(args); Just a thought... |
See this @sandreas: https://gist.github.com/FrankRay78/de3e30dafbe44265cba2372e809326a1 Regarding handling of quotes, hyphens and spaces - do you see any cases I've missed? |
Maybe. I don't see the following (maybe this is intended):
I'm sorry |
I'm starting to suspect that unit testing the POSIX compliant parsing should trim each argument received in the |
Oh, please don't do that! Trying to fix shell behaviour within a library is not the right place and this is exactly what led to these problems. My suggestion stays the same: Do not modify the arguments in any way.
And this is the correct behaviour... Let's say you want to develop a string splitting command line app with a delimiter (unix
Result if you trim each arg: You can't write this app... it just trims away the space... If you really plan to do this, this should not be the default behaviour... maybe this could be a global setting (e.g. |
Ok fair enough. I'm just in the middle of mental gymnastics, trying to think of all the unit test cases to support. And what not to support. My PR isn't far away now so you will be able to try it out with your Tone soon enough.... |
No offense. I'm just trying to put in my two cents preventing possible mistakes from the past.
Sounds awesome, thank you very much for your huge effort. |
I've just created this PR #1036 @sandreas (and will fix any build errors that may appear.....) As mentioned in the PR, the following three PR's when taken jointly represent a significant enhancement to existing command line parsing behaviour: #1036, #1029, #732 If it would be useful, I could merge them all into a local 'preview' branch for you to build and test Tone against? |
Awesome, thank you.
Well, this is nice, but if you've built a little command line app and tested some of the the discussed use cases manually besides the unit tests, I don't think it is worth the effort... Personally I still don't think it is worth to write and maintain a complex Tokenizer for command line argument parsing but hey, I'm so happy that you took care of this, so I won't complain. Thank you so much, this bothered me for some months now. I can't wait to see the next release and integrate it into my tools. The only suggestion for improvement I would like to make: The project maintainers should take issues like this more seriously and respond more quickly, if it is in their time allotment. I was really frustrated about how my issues were treated (or better ignored for months) until you took care of it and I was about to switch to https://github.com/Tyrrrz/CliFx at least for command line parsing. |
We're doing this in our free time. We all have family and work outside of this project. There is no SLA for anything, and everything in this repository is licensed "AS IS". |
@patriksvensson No offense... I got family, too and I know pretty well, what you are talking about. Just tried to help you with this great project and I felt like there is no interest in additional manpower... this is pretty subjective and I bet not everyone feels like that - maybe it was just a series of unfortunate events. I really hope you just take it as a feedback to improve something, not as criticism and hopefully we can work together in the future improving more stuff of spectre.console :-) |
Having never done any real open-source development previously, this has been a real baptism of fire. I'm grateful for both of your help and can understand the different perspectives. Hopefully in the fullness of time, I could perhaps help with PR reviews and testing etc @patriksvensson to shoulder some of the load. |
Thanks a lot @FrankRay78 |
This issue can be closed/marked as complete @patriksvensson, now that PR #1048 has been successfully merged. |
@FrankRay78 Sounds good to me 😉 |
Important: This is a follow-up issue (#891), since it was closed without possibility to give feedback, reopen or answering my follow-up questions. I really think this is clearly a bug of
spectre
and it should be further investigated, because input values are MODIFIED without any developer interaction... It may also be possible, that this leads to a SECURITY FLAW because parameters are mistreated, but I'm no expert.This also may not be reproducible in any OS (e.g. on Windows) AND also not via Debugger or IDE, since command line parameter handling is different. To reproduce the issue, I recommend using Linux and compile the given test program in release mode.
The last answer I got stated this about argument parsing:
I think, this is wrong, because at least something in
spectre
seems toTrim
whitespaces (Details: #891 (comment)) beause the following example does not modify parameters, whilespectre
does.Information
Describe the bug
Parameter values cannot start with double quotes (
"
). They are either replaced or handled completely wrong. Additionally, parameter values are TRIMMED (remove leading spaces), so adding a space to workaround this is not possible.The following example even cuts off the parameter value and treats everything after the space as EXTRA argument, in the following case
value
is handled as positional argument and not part of--order-by
:This may be a security flaw under specific circumstances.
More examples of invalid handling:
The following examples work like expected, if not starting with double quotes or single quotes are used
To Reproduce
Expected behavior
) CAN be a valid part of a parameter value and should not be replaced or parsed out in any way.
I would expect double quotes (
"
) and spaces (The text was updated successfully, but these errors were encountered: