Skip to content

Commit 7b1f4a0

Browse files
committed
reworked the "Additional command line arguments" line parsing ...
... to use the quote escaping mechanism that is native on that system
1 parent 1d28ed2 commit 7b1f4a0

File tree

2 files changed

+103
-36
lines changed

2 files changed

+103
-36
lines changed

Sources/Utils/MiscUtils.cpp

Lines changed: 102 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -37,55 +37,121 @@ QList< Argument > splitCommandLineArguments( const QString & argsStr )
3737
QList< Argument > args;
3838

3939
QString currentArg;
40+
currentArg.reserve( 32 );
4041

41-
bool escaped = false;
42-
bool inQuotes = false;
43-
for (qsizetype currentPos = 0; currentPos < argsStr.size(); ++currentPos)
42+
auto flushCurrentArg = [ &currentArg, &args ]( bool wasQuoted )
4443
{
45-
QChar currentChar = argsStr[ currentPos ];
44+
args.append( Argument{ std::move(currentArg), wasQuoted } );
45+
currentArg.resize( 0 ); // clear the content without freeing the allocated buffer
46+
};
4647

47-
if (escaped)
48+
if constexpr (IS_WINDOWS) // parse according to the Windows cmd escaping rules
49+
{
50+
bool insideQuotes = false;
51+
bool wasClosingQuotesChar = false;
52+
53+
for (qsizetype currentPos = 0; currentPos < argsStr.size(); ++currentPos)
4854
{
49-
escaped = false;
50-
currentArg += currentChar;
51-
// We should handle all the special characters like '\n', '\t', '\b', but screw it, it's not needed.
55+
QChar currentChar = argsStr[ currentPos ];
56+
57+
if (insideQuotes)
58+
{
59+
if (currentChar == '"') {
60+
insideQuotes = false;
61+
wasClosingQuotesChar = true;
62+
} else {
63+
currentArg += currentChar;
64+
}
65+
}
66+
else // not inside quotes
67+
{
68+
if (currentChar == '"') {
69+
insideQuotes = true;
70+
if (wasClosingQuotesChar) {
71+
// 2 consequent quote characters produce 1 quote character inside the quoted string
72+
currentArg += '"';
73+
}
74+
} else if (currentChar == ' ') {
75+
if (!currentArg.isEmpty() || wasClosingQuotesChar) {
76+
flushCurrentArg( wasClosingQuotesChar );
77+
}
78+
} else {
79+
currentArg += currentChar;
80+
}
81+
wasClosingQuotesChar = false;
82+
}
5283
}
53-
else if (inQuotes) // and not escaped
84+
85+
// We reached the end of the command line without encountering the final terminating space, flush the last word.
86+
if (!currentArg.isEmpty())
5487
{
55-
if (currentChar == '\\') {
56-
escaped = true;
57-
} else if (currentChar == '"') {
58-
inQuotes = false;
59-
args.append( Argument{ std::move(currentArg), true } );
60-
currentArg.clear();
61-
} else {
62-
currentArg += currentChar;
63-
}
88+
// accept also unterminated quoted argument
89+
flushCurrentArg( insideQuotes || wasClosingQuotesChar );
6490
}
65-
else // not escaped and not in quotes
91+
}
92+
else // parse according to the bash escaping rules
93+
{
94+
bool insideSingleQuotes = false;
95+
bool insideDoubleQuotes = false;
96+
bool wasClosingQuotesChar = false;
97+
bool wasEscapeChar = false;
98+
99+
for (qsizetype currentPos = 0; currentPos < argsStr.size(); ++currentPos)
66100
{
67-
if (currentChar == '\\') {
68-
escaped = true;
69-
} else if (currentChar == '"') {
70-
inQuotes = true;
71-
if (!currentArg.isEmpty()) {
72-
args.append( Argument{ std::move(currentArg), false } );
73-
currentArg.clear();
74-
}
75-
} else if (currentChar == ' ') {
76-
if (!currentArg.isEmpty()) {
77-
args.append( Argument{ std::move(currentArg), false } );
78-
currentArg.clear();
101+
QChar currentChar = argsStr[ currentPos ];
102+
103+
if (insideSingleQuotes)
104+
{
105+
if (currentChar == '\'') {
106+
insideSingleQuotes = false;
107+
wasClosingQuotesChar = true;
108+
} else {
109+
currentArg += currentChar;
79110
}
80-
} else {
111+
}
112+
else if (wasEscapeChar)
113+
{
114+
// 2 consequent escape characters produce 1 escape character inside the quoted string and don't escape any further
115+
wasEscapeChar = false;
81116
currentArg += currentChar;
117+
// We should handle all the special characters like '\n', '\t', '\b', but screw it, it's not needed.
118+
}
119+
else if (insideDoubleQuotes) // and wasn't escape char
120+
{
121+
if (currentChar == '\\') {
122+
wasEscapeChar = true;
123+
} else if (currentChar == '"') {
124+
insideDoubleQuotes = false;
125+
wasClosingQuotesChar = true;
126+
} else {
127+
currentArg += currentChar;
128+
}
129+
}
130+
else // wasn't escape char and not in quotes
131+
{
132+
if (currentChar == '\\') {
133+
wasEscapeChar = true;
134+
} else if (currentChar == '\'') {
135+
insideSingleQuotes = true;
136+
} else if (currentChar == '"') {
137+
insideDoubleQuotes = true;
138+
} else if (currentChar == ' ') {
139+
if (!currentArg.isEmpty() || wasClosingQuotesChar) {
140+
flushCurrentArg( wasClosingQuotesChar );
141+
}
142+
} else {
143+
currentArg += currentChar;
144+
}
145+
wasClosingQuotesChar = false;
82146
}
83147
}
84-
}
85148

86-
if (!currentArg.isEmpty())
87-
{
88-
args.append( Argument{ std::move(currentArg), (inQuotes && argsStr.back() != '"') } );
149+
// We reached the end of the command line without encountering the final terminating space, flush the last word.
150+
if (!currentArg.isEmpty())
151+
{
152+
// accept also unterminated quoted argument
153+
flushCurrentArg( insideSingleQuotes || insideDoubleQuotes || wasClosingQuotesChar );
154+
}
89155
}
90156

91157
return args;

changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
- fixed all dmflags2 being mistakenly put into dmflags3
44
- fixed broken layout with Qt versions older than 6.8
55
- fixed compatibility with the KEX-based Doom re-release (the file paths will be absolute)
6+
- fixed the special character escaping in the "Additional arguments" lines
67
- fixed tooltips for the Custom data sub-directories and some gameplay flags
78
- only use -pistolstart and +sv_cheats in the command if the engine supports it (to prevent errors when starting)
89
- allowed entering absolute paths into the "Custom data sub-directories" fields

0 commit comments

Comments
 (0)