Files
easy7zip/CPP/Common/CommandLineParser.cpp
sebres 8cfdd747c8 Fixes for "vulnerable command line parsing"
Signed-off-by: Sergey G. Brester <info@sebres.de>
Reviewed-by: Tino Reichardt <milky-7zip@mcmilk.de>
2023-04-14 21:17:50 +02:00

265 lines
5.6 KiB
C++

// CommandLineParser.cpp
#include "StdAfx.h"
#include "CommandLineParser.h"
namespace NCommandLineParser {
static const wchar_t * _SplitCommandLine(const wchar_t* s, UString &dest)
{
unsigned qcount = 0, bcount = 0;
wchar_t c; const wchar_t *f, *b;
dest.Empty();
// skip spaces:
while (isblank(*s)) { s++; };
b = f = s;
while ((c = *s++) != 0)
{
switch (c)
{
case L'\\':
// a backslash - count them up to quote-char or regular char
bcount++;
break;
case L'"':
// check quote char is escaped:
if (!(bcount & 1))
{
// preceded by an even number of '\', this is half that
// number of '\':
dest.AddFrom(f, (unsigned)(s - f - bcount/2 - 1)); f = s;
// count quote chars:
qcount++;
}
else
{
// preceded by an odd number of '\', this is half that
// number of '\' followed by an escaped '"':
dest.AddFrom(f, (unsigned)(s - f - bcount/2 - 2)); f = s;
dest += L'"';
}
bcount = 0;
// now count the number of consecutive quotes (inclusive
// the quote that lead us here):
while (*s == L'"')
{
s++;
if (++qcount == 3)
{
dest += L'"';
qcount = 0;
}
}
f = s;
if (qcount == 2)
qcount = 0;
break;
case L' ':
case L'\t':
// a space (end of arg or regular char):
if (!qcount)
{
// end of argument:
dest.AddFrom(f, (unsigned)(s - f - 1)); f = s;
// skip to the next one:
while (isblank(*s)) { s++; };
bcount = 0;
goto done;
}
// no break - a space as regular char:
default:
// a regular character, reset backslash counter
bcount = 0;
}
}
s--; // back to NTS-zero char
dest.AddFrom(f, (unsigned)(s - f));
done:
// remaining part if argument was found, otherwise NULL:
return (dest.Len() || *b) ? s : NULL;
}
bool SplitCommandLine(const UString& src, UString& dest1, UString& dest2)
{
const wchar_t *s = src.Ptr();
s = _SplitCommandLine(s, dest1);
if (s) {
dest2 = s;
return true;
} else {
dest2.Empty();
return false;
}
}
void SplitCommandLine(const UString &src, UStringVector &parts)
{
const wchar_t *s = src.Ptr();
parts.Clear();
for (;;)
{
UString s1;
s = _SplitCommandLine(s, s1);
if (s)
parts.Add(s1);
if (!s || !*s)
break;
}
}
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;
}
}