Files
easy7zip/CPP/Common/CommandLineParser.cpp
sebres cc1192c7d9 fixes vulnerable command line parsing
- considers valid windows escape sequences, like backslashes
  followed by quote-char as well as quote triples)
- SplitCommandLine works now very similar to CommandLineToArgvW
  (but doesn't require dependency to shell32)

Signed-off-by: Sergey G. Brester <serg.brester@sebres.de>
Reviewed-by: Tino Reichardt <milky-zfs@mcmilk.de>
2023-03-22 17:32:20 +01:00

249 lines
5.2 KiB
C++

// CommandLineParser.cpp
#include "StdAfx.h"
#include "CommandLineParser.h"
namespace NCommandLineParser {
bool SplitCommandLine(const UString &src, UString &dest1, UString &dest2)
{
unsigned qcount = 0, bcount = 0;
wchar_t c; const wchar_t *s = src.Ptr();
dest1.Empty();
while ((c = *s++) != 0)
{
switch (c)
{
case L'\\':
// a backslash - firstly add it as a regular char,
// we'll delete escaped later (if followed by quote):
dest1 += c;
bcount++;
break;
case L'"':
// check quote char is escaped:
if (!(bcount & 1))
{
// preceded by an even number of '\', this is half that
// number of '\' (delete bcount/2 chars):
dest1.DeleteFrom(dest1.Len() - bcount/2);
// count quote chars:
qcount++;
}
else
{
// preceded by an odd number of '\', this is half that
// number of '\' followed by an escaped '"':
dest1.DeleteFrom(dest1.Len() - bcount/2 - 1);
dest1 += L'"';
}
bcount = 0;
// now count the number of consecutive quotes (inclusive
// the quote that lead us here):
while (*s == L'"')
{
s++;
if (++qcount == 3)
{
dest1 += L'"';
qcount = 0;
}
}
if (qcount == 2)
qcount = 0;
break;
case L' ':
case L'\t':
// a space (end of arg or regular char):
if (!qcount)
{
// end of argument:
bcount = 0;
// skip to the next one:
while (isblank(*s)) { s++; };
goto done;
}
// no break - a space as regular char:
default:
// a regular character
dest1 += c;
bcount = 0;
}
}
s--; // back to NTS-zero char
done:
dest2 = s;
return (dest1.Len() || src[0]);
}
void SplitCommandLine(const UString &s, UStringVector &parts)
{
UString sTemp (s);
sTemp.Trim();
parts.Clear();
for (;;)
{
UString s1, s2;
if (SplitCommandLine(sTemp, s1, s2))
parts.Add(s1);
if (s2.IsEmpty())
break;
sTemp = s2;
}
}
static const char * const kStopSwitchParsing = "--";
static bool inline IsItSwitchChar(wchar_t c)
{
return (c == '-');
}
CParser::CParser():
_switches(NULL),
StopSwitchIndex(-1)
{
}
CParser::~CParser()
{
delete []_switches;
}
// if (s) contains switch then function updates switch structures
// out: true, if (s) is a switch
bool CParser::ParseString(const UString &s, const CSwitchForm *switchForms, unsigned numSwitches)
{
if (s.IsEmpty() || !IsItSwitchChar(s[0]))
return false;
unsigned pos = 1;
unsigned switchIndex = 0;
int maxLen = -1;
for (unsigned i = 0; i < numSwitches; i++)
{
const char * const key = switchForms[i].Key;
unsigned switchLen = MyStringLen(key);
if ((int)switchLen <= maxLen || pos + switchLen > s.Len())
continue;
if (IsString1PrefixedByString2_NoCase_Ascii((const wchar_t *)s + pos, key))
{
switchIndex = i;
maxLen = (int)switchLen;
}
}
if (maxLen < 0)
{
ErrorMessage = "Unknown switch:";
return false;
}
pos += (unsigned)maxLen;
CSwitchResult &sw = _switches[switchIndex];
const CSwitchForm &form = switchForms[switchIndex];
if (!form.Multi && sw.ThereIs)
{
ErrorMessage = "Multiple instances for switch:";
return false;
}
sw.ThereIs = true;
const unsigned rem = s.Len() - pos;
if (rem < form.MinLen)
{
ErrorMessage = "Too short switch:";
return false;
}
sw.WithMinus = false;
sw.PostCharIndex = -1;
switch (form.Type)
{
case NSwitchType::kMinus:
if (rem == 1)
{
sw.WithMinus = (s[pos] == '-');
if (sw.WithMinus)
return true;
ErrorMessage = "Incorrect switch postfix:";
return false;
}
break;
case NSwitchType::kChar:
if (rem == 1)
{
wchar_t c = s[pos];
if (c <= 0x7F)
{
sw.PostCharIndex = FindCharPosInString(form.PostCharSet, (char)c);
if (sw.PostCharIndex >= 0)
return true;
}
ErrorMessage = "Incorrect switch postfix:";
return false;
}
break;
case NSwitchType::kString:
{
sw.PostStrings.Add(s.Ptr(pos));
return true;
}
}
if (pos != s.Len())
{
ErrorMessage = "Too long switch:";
return false;
}
return true;
}
bool CParser::ParseStrings(const CSwitchForm *switchForms, unsigned numSwitches, const UStringVector &commandStrings)
{
StopSwitchIndex = -1;
ErrorMessage.Empty();
ErrorLine.Empty();
NonSwitchStrings.Clear();
delete []_switches;
_switches = NULL;
_switches = new CSwitchResult[numSwitches];
FOR_VECTOR (i, commandStrings)
{
const UString &s = commandStrings[i];
if (StopSwitchIndex < 0)
{
if (s.IsEqualTo(kStopSwitchParsing))
{
StopSwitchIndex = (int)NonSwitchStrings.Size();
continue;
}
if (!s.IsEmpty() && IsItSwitchChar(s[0]))
{
if (ParseString(s, switchForms, numSwitches))
continue;
ErrorLine = s;
return false;
}
}
NonSwitchStrings.Add(s);
}
return true;
}
}