Added cNameValueParser.
This commit is contained in:
parent
e31343297e
commit
58f5ac84ab
412
source/HTTPServer/NameValueParser.cpp
Normal file
412
source/HTTPServer/NameValueParser.cpp
Normal file
@ -0,0 +1,412 @@
|
|||||||
|
|
||||||
|
// NameValueParser.cpp
|
||||||
|
|
||||||
|
// Implements the cNameValueParser class that parses strings in the "name=value;name2=value2" format into a stringmap
|
||||||
|
|
||||||
|
#include "Globals.h"
|
||||||
|
#include "NameValueParser.h"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// DEBUG: Self-test
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
|
||||||
|
class cNameValueParserTest
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
cNameValueParserTest(void)
|
||||||
|
{
|
||||||
|
const char Data[] = " Name1=Value1;Name2 = Value 2; Name3 =\"Value 3\"; Name4 =\'Value 4\'; Name5=\"Confusing; isn\'t it?\"";
|
||||||
|
|
||||||
|
// Now try parsing char-by-char, to debug transitions across datachunk boundaries:
|
||||||
|
cNameValueParser Parser2;
|
||||||
|
for (int i = 0; i < sizeof(Data) - 1; i++)
|
||||||
|
{
|
||||||
|
Parser2.Parse(Data + i, 1);
|
||||||
|
}
|
||||||
|
Parser2.Finish();
|
||||||
|
|
||||||
|
// Parse as a single chunk of data:
|
||||||
|
cNameValueParser Parser(Data, sizeof(Data) - 1);
|
||||||
|
|
||||||
|
// Use the debugger to inspect the Parser variable
|
||||||
|
|
||||||
|
// Check that the two parsers have the same content:
|
||||||
|
for (cNameValueParser::const_iterator itr = Parser.begin(), end = Parser.end(); itr != end; ++itr)
|
||||||
|
{
|
||||||
|
ASSERT(Parser2[itr->first] == itr->second);
|
||||||
|
} // for itr - Parser[]
|
||||||
|
|
||||||
|
// Try parsing in 2-char chunks:
|
||||||
|
cNameValueParser Parser3;
|
||||||
|
for (int i = 0; i < sizeof(Data) - 2; i += 2)
|
||||||
|
{
|
||||||
|
Parser3.Parse(Data + i, 2);
|
||||||
|
}
|
||||||
|
if ((sizeof(Data) % 2) == 0) // There are even number of chars, including the NUL, so the data has an odd length. Parse one more char
|
||||||
|
{
|
||||||
|
Parser3.Parse(Data + sizeof(Data) - 2, 1);
|
||||||
|
}
|
||||||
|
Parser3.Finish();
|
||||||
|
|
||||||
|
// Check that the third parser has the same content:
|
||||||
|
for (cNameValueParser::const_iterator itr = Parser.begin(), end = Parser.end(); itr != end; ++itr)
|
||||||
|
{
|
||||||
|
ASSERT(Parser3[itr->first] == itr->second);
|
||||||
|
} // for itr - Parser[]
|
||||||
|
|
||||||
|
printf("cNameValueParserTest done");
|
||||||
|
}
|
||||||
|
} g_Test;
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// cNameValueParser:
|
||||||
|
|
||||||
|
cNameValueParser::cNameValueParser(bool a_AllowsKeyOnly) :
|
||||||
|
m_State(psKeySpace),
|
||||||
|
m_AllowsKeyOnly(a_AllowsKeyOnly)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
cNameValueParser::cNameValueParser(const char * a_Data, int a_Size, bool a_AllowsKeyOnly) :
|
||||||
|
m_State(psKeySpace),
|
||||||
|
m_AllowsKeyOnly(a_AllowsKeyOnly)
|
||||||
|
{
|
||||||
|
Parse(a_Data, a_Size);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void cNameValueParser::Parse(const char * a_Data, int a_Size)
|
||||||
|
{
|
||||||
|
ASSERT(m_State != psFinished); // Calling Parse() after Finish() is wrong!
|
||||||
|
|
||||||
|
if ((m_State == psInvalid) || (m_State == psFinished))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int Last = 0;
|
||||||
|
for (int i = 0; i < a_Size;)
|
||||||
|
{
|
||||||
|
switch (m_State)
|
||||||
|
{
|
||||||
|
case psKeySpace:
|
||||||
|
{
|
||||||
|
// Skip whitespace until a non-whitespace is found, then start the key:
|
||||||
|
while ((i < a_Size) && (a_Data[i] <= ' '))
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if ((i < a_Size) && (a_Data[i] > ' '))
|
||||||
|
{
|
||||||
|
m_State = psKey;
|
||||||
|
Last = i;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case psKey:
|
||||||
|
{
|
||||||
|
// Read the key until whitespace or an equal sign:
|
||||||
|
while (i < a_Size)
|
||||||
|
{
|
||||||
|
if (a_Data[i] == '=')
|
||||||
|
{
|
||||||
|
m_CurrentKey.append(a_Data + Last, i - Last);
|
||||||
|
i++;
|
||||||
|
Last = i;
|
||||||
|
m_State = psEqual;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (a_Data[i] <= ' ')
|
||||||
|
{
|
||||||
|
m_CurrentKey.append(a_Data + Last, i - Last);
|
||||||
|
i++;
|
||||||
|
Last = i;
|
||||||
|
m_State = psEqualSpace;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (a_Data[i] == ';')
|
||||||
|
{
|
||||||
|
if (!m_AllowsKeyOnly)
|
||||||
|
{
|
||||||
|
m_State = psInvalid;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_CurrentKey.append(a_Data + Last, i - Last);
|
||||||
|
i++;
|
||||||
|
Last = i;
|
||||||
|
(*this)[m_CurrentKey] = "";
|
||||||
|
m_CurrentKey.clear();
|
||||||
|
m_State = psKeySpace;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if ((a_Data[i] == '\"') || (a_Data[i] == '\''))
|
||||||
|
{
|
||||||
|
m_State = psInvalid;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
} // while (i < a_Size)
|
||||||
|
if (i == a_Size)
|
||||||
|
{
|
||||||
|
// Still the key, ran out of data to parse, store the part of the key parsed so far:
|
||||||
|
m_CurrentKey.append(a_Data + Last, a_Size - Last);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case psEqualSpace:
|
||||||
|
{
|
||||||
|
// The space before the expected equal sign; the current key is already assigned
|
||||||
|
while (i < a_Size)
|
||||||
|
{
|
||||||
|
if (a_Data[i] == '=')
|
||||||
|
{
|
||||||
|
m_State = psEqual;
|
||||||
|
i++;
|
||||||
|
Last = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (a_Data[i] == ';')
|
||||||
|
{
|
||||||
|
// Key-only
|
||||||
|
if (!m_AllowsKeyOnly)
|
||||||
|
{
|
||||||
|
m_State = psInvalid;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
Last = i;
|
||||||
|
(*this)[m_CurrentKey] = "";
|
||||||
|
m_CurrentKey.clear();
|
||||||
|
m_State = psKeySpace;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (a_Data[i] > ' ')
|
||||||
|
{
|
||||||
|
m_State = psInvalid;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
} // while (i < a_Size)
|
||||||
|
break;
|
||||||
|
} // case psEqualSpace
|
||||||
|
|
||||||
|
case psEqual:
|
||||||
|
{
|
||||||
|
// just parsed the equal-sign
|
||||||
|
while (i < a_Size)
|
||||||
|
{
|
||||||
|
if (a_Data[i] == ';')
|
||||||
|
{
|
||||||
|
if (!m_AllowsKeyOnly)
|
||||||
|
{
|
||||||
|
m_State = psInvalid;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
Last = i;
|
||||||
|
(*this)[m_CurrentKey] = "";
|
||||||
|
m_CurrentKey.clear();
|
||||||
|
m_State = psKeySpace;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (a_Data[i] == '\"')
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
Last = i;
|
||||||
|
m_State = psValueInDQuotes;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (a_Data[i] == '\'')
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
Last = i;
|
||||||
|
m_State = psValueInSQuotes;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
m_CurrentValue.push_back(a_Data[i]);
|
||||||
|
i++;
|
||||||
|
Last = i;
|
||||||
|
m_State = psValueRaw;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
} // while (i < a_Size)
|
||||||
|
break;
|
||||||
|
} // case psEqual
|
||||||
|
|
||||||
|
case psValueInDQuotes:
|
||||||
|
{
|
||||||
|
while (i < a_Size)
|
||||||
|
{
|
||||||
|
if (a_Data[i] == '\"')
|
||||||
|
{
|
||||||
|
m_CurrentValue.append(a_Data + Last, i - Last);
|
||||||
|
(*this)[m_CurrentKey] = m_CurrentValue;
|
||||||
|
m_CurrentKey.clear();
|
||||||
|
m_CurrentValue.clear();
|
||||||
|
m_State = psAfterValue;
|
||||||
|
i++;
|
||||||
|
Last = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
} // while (i < a_Size)
|
||||||
|
if (i == a_Size)
|
||||||
|
{
|
||||||
|
m_CurrentValue.append(a_Data + Last, a_Size - Last);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} // case psValueInDQuotes
|
||||||
|
|
||||||
|
case psValueInSQuotes:
|
||||||
|
{
|
||||||
|
while (i < a_Size)
|
||||||
|
{
|
||||||
|
if (a_Data[i] == '\'')
|
||||||
|
{
|
||||||
|
m_CurrentValue.append(a_Data + Last, i - Last);
|
||||||
|
(*this)[m_CurrentKey] = m_CurrentValue;
|
||||||
|
m_CurrentKey.clear();
|
||||||
|
m_CurrentValue.clear();
|
||||||
|
m_State = psAfterValue;
|
||||||
|
i++;
|
||||||
|
Last = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
} // while (i < a_Size)
|
||||||
|
if (i == a_Size)
|
||||||
|
{
|
||||||
|
m_CurrentValue.append(a_Data + Last, a_Size - Last);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} // case psValueInSQuotes
|
||||||
|
|
||||||
|
case psValueRaw:
|
||||||
|
{
|
||||||
|
while (i < a_Size)
|
||||||
|
{
|
||||||
|
if (a_Data[i] == ';')
|
||||||
|
{
|
||||||
|
m_CurrentValue.append(a_Data + Last, i - Last);
|
||||||
|
(*this)[m_CurrentKey] = m_CurrentValue;
|
||||||
|
m_CurrentKey.clear();
|
||||||
|
m_CurrentValue.clear();
|
||||||
|
m_State = psKeySpace;
|
||||||
|
i++;
|
||||||
|
Last = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
if (i == a_Size)
|
||||||
|
{
|
||||||
|
m_CurrentValue.append(a_Data + Last, a_Size - Last);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
} // case psValueRaw
|
||||||
|
|
||||||
|
case psAfterValue:
|
||||||
|
{
|
||||||
|
// Between the closing DQuote or SQuote and the terminating semicolon
|
||||||
|
while (i < a_Size)
|
||||||
|
{
|
||||||
|
if (a_Data[i] == ';')
|
||||||
|
{
|
||||||
|
m_State = psKeySpace;
|
||||||
|
i++;
|
||||||
|
Last = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else if (a_Data[i] < ' ')
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
m_State = psInvalid;
|
||||||
|
return;
|
||||||
|
} // while (i < a_Size)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} // switch (m_State)
|
||||||
|
} // for i - a_Data[]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
bool cNameValueParser::Finish(void)
|
||||||
|
{
|
||||||
|
switch (m_State)
|
||||||
|
{
|
||||||
|
case psInvalid:
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
case psFinished:
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case psKey:
|
||||||
|
case psEqualSpace:
|
||||||
|
case psEqual:
|
||||||
|
{
|
||||||
|
if ((m_AllowsKeyOnly) && !m_CurrentKey.empty())
|
||||||
|
{
|
||||||
|
(*this)[m_CurrentKey] = "";
|
||||||
|
m_State = psFinished;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
m_State = psInvalid;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
case psValueRaw:
|
||||||
|
{
|
||||||
|
(*this)[m_CurrentKey] = m_CurrentValue;
|
||||||
|
m_State = psFinished;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
case psValueInDQuotes:
|
||||||
|
case psValueInSQuotes:
|
||||||
|
{
|
||||||
|
// Missing the terminating quotes, this is an error
|
||||||
|
m_State = psInvalid;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
case psKeySpace:
|
||||||
|
case psAfterValue:
|
||||||
|
{
|
||||||
|
m_State = psFinished;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ASSERT(!"Unhandled parser state!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
70
source/HTTPServer/NameValueParser.h
Normal file
70
source/HTTPServer/NameValueParser.h
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
|
||||||
|
// NameValueParser.h
|
||||||
|
|
||||||
|
// Declares the cNameValueParser class that parses strings in the "name=value;name2=value2" format into a stringmap
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class cNameValueParser :
|
||||||
|
public std::map<AString, AString>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// Creates an empty parser
|
||||||
|
cNameValueParser(bool a_AllowsKeyOnly = true);
|
||||||
|
|
||||||
|
/// Creates an empty parser, then parses the data given
|
||||||
|
cNameValueParser(const char * a_Data, int a_Size, bool a_AllowsKeyOnly = true);
|
||||||
|
|
||||||
|
/// Parses the data given
|
||||||
|
void Parse(const char * a_Data, int a_Size);
|
||||||
|
|
||||||
|
/// Notifies the parser that no more data will be coming. Returns true if the parser state is valid
|
||||||
|
bool Finish(void);
|
||||||
|
|
||||||
|
/// Returns true if the data parsed so far was valid
|
||||||
|
bool IsValid(void) const { return (m_State != psInvalid); }
|
||||||
|
|
||||||
|
/// Returns true if the parser expects no more data
|
||||||
|
bool IsFinished(void) const { return ((m_State == psInvalid) || (m_State == psFinished)); }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
enum eState
|
||||||
|
{
|
||||||
|
psKeySpace, ///< Parsing the space in front of the next key
|
||||||
|
psKey, ///< Currently adding more chars to the key in m_CurrentKey
|
||||||
|
psEqualSpace, ///< Space after m_CurrentKey
|
||||||
|
psEqual, ///< Just parsed the = sign after a name
|
||||||
|
psValueInSQuotes, ///< Just parsed a Single-quote sign after the Equal sign
|
||||||
|
psValueInDQuotes, ///< Just parsed a Double-quote sign after the Equal sign
|
||||||
|
psValueRaw, ///< Just parsed a raw value without a quote
|
||||||
|
psAfterValue, ///< Just finished parsing the value, waiting for semicolon or data end
|
||||||
|
psInvalid, ///< The parser has encountered an invalid input; further parsing is skipped
|
||||||
|
psFinished, ///< The parser has already been instructed to finish and doesn't expect any more data
|
||||||
|
} ;
|
||||||
|
|
||||||
|
/// The current state of the parser
|
||||||
|
eState m_State;
|
||||||
|
|
||||||
|
/// If true, the parser will accept keys without an equal sign and the value
|
||||||
|
bool m_AllowsKeyOnly;
|
||||||
|
|
||||||
|
/// Buffer for the current Key
|
||||||
|
AString m_CurrentKey;
|
||||||
|
|
||||||
|
/// Buffer for the current Value;
|
||||||
|
AString m_CurrentValue;
|
||||||
|
|
||||||
|
|
||||||
|
} ;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user