in AjaxMinDll/JavaScript/jsscanner.cs [2277:2606]
private void ScanString(char delimiter)
{
int start = ++m_currentPosition;
m_decodedString = null;
m_literalIssues = false;
StringBuilder result = null;
try
{
char ch;
while ((ch = GetChar(m_currentPosition++)) != delimiter)
{
if (ch != '\\')
{
// this is the common non escape case
if (IsLineTerminator(ch, 0))
{
// TODO: we want to flag this string as unterminated *and having issues*,
// and then somehow output a line-break in the output to duplicate the
// source. However, we will need to figure out how to NOT combine the statement
// with the next statement. For instance:
// var x = "unterminated
// var y = 42;
// should NOT get combined to: var x="unterminated,y=42;
// (same for moving it inside for-statements, combining expression statements, etc.)
//m_literalIssues = true;
HandleError(JSError.UnterminatedString);
// back up to the start of the line terminator
--m_currentPosition;
if (GetChar(m_currentPosition - 1) == '\r')
{
--m_currentPosition;
}
break;
}
if ('\0' == ch)
{
// whether it's a null literal character within the string or an
// actual end of file, this string literal has issues....
m_literalIssues = true;
if (IsEndOfFile)
{
m_currentPosition--;
HandleError(JSError.UnterminatedString);
break;
}
}
if (AllowEmbeddedAspNetBlocks
&& ch == '<'
&& GetChar(m_currentPosition) == '%')
{
// start of an ASP.NET block INSIDE a string literal.
// just skip the entire ASP.NET block -- move forward until
// we find the closing %> delimiter, then we'll continue on
// with the next character.
SkipAspNetReplacement();
// asp.net blocks insides strings can cause issues
m_literalIssues = true;
}
else if (0xd800 <= ch && ch <= 0xdbff)
{
// high-surrogate! Make sure the next character is a low surrogate
// or we'll throw an error.
ch = GetChar(m_currentPosition);
if (0xdc00 <= ch && ch <= 0xdfff)
{
// we're good. Advance past the pair.
++m_currentPosition;
}
else if (ch == '\\' && GetChar(m_currentPosition + 1) == 'u')
{
// we have a unicode escape. Start working on that escaped value.
if (null == result)
{
result = StringBuilderPool.Acquire();
}
// start points to the first position that has not been written to the StringBuilder.
// The first time we get in here that position is the beginning of the string, after that
// is the character immediately following the escape sequence
if (m_currentPosition - start > 0)
{
// append all the non escape chars to the string builder
result.Append(m_strSourceCode, start, m_currentPosition - start);
}
int lowSurrogate;
if (ScanHexSequence(m_currentPosition += 2, 'u', out lowSurrogate))
{
// valid escape, so make sure the unescaped value is added to the result regardless.
result.Append((char)lowSurrogate);
start = m_currentPosition;
// now make sure it's in low-surrogate range
if (lowSurrogate < 0xdc00 || 0xdfff < lowSurrogate)
{
// not a low-surrogate
m_literalIssues = true;
HandleError(JSError.HighSurrogate);
}
}
else
{
// not a valid unicode escape sequence, so no -- we are not
// followed by a low-surrogate
m_literalIssues = true;
HandleError(JSError.HighSurrogate);
}
}
else
{
// not followed by a low-surrogate
m_literalIssues = true;
HandleError(JSError.HighSurrogate);
}
}
else if (0xdc00 <= ch && ch <= 0xdfff)
{
// low-surrogate by itself! This is an error, but keep going
m_literalIssues = true;
HandleError(JSError.LowSurrogate);
}
}
else
{
// ESCAPE CASE
var esc = 0;
// got an escape of some sort. Have to use the StringBuilder
if (null == result)
{
result = StringBuilderPool.Acquire();
}
// start points to the first position that has not been written to the StringBuilder.
// The first time we get in here that position is the beginning of the string, after that
// is the character immediately following the escape sequence
if (m_currentPosition - start - 1 > 0)
{
// append all the non escape chars to the string builder
result.Append(m_strSourceCode, start, m_currentPosition - start - 1);
}
// state variable to be reset
bool seqOfThree = false;
ch = GetChar(m_currentPosition++);
switch (ch)
{
// line terminator crap
case '\r':
if ('\n' == GetChar(m_currentPosition))
{
m_currentPosition++;
}
goto case '\n';
case '\n':
case '\u2028':
case '\u2029':
m_currentLine++;
m_startLinePosition = m_currentPosition;
break;
// classic single char escape sequences
case 'b':
result.Append((char)8);
break;
case 't':
result.Append((char)9);
break;
case 'n':
result.Append((char)10);
break;
case 'v':
// \v inside strings can cause issues
m_literalIssues = true;
result.Append((char)11);
break;
case 'f':
result.Append((char)12);
break;
case 'r':
result.Append((char)13);
break;
case '"':
result.Append('"');
break;
case '\'':
result.Append('\'');
break;
case '\\':
result.Append('\\');
break;
// hexadecimal escape sequence /xHH, \uHHHH, or \u{H+}
case 'u':
case 'x':
string unescaped;
if (ScanHexEscape(ch, out unescaped))
{
// successfully escaped the character sequence
result.Append(unescaped);
}
else
{
// wasn't valid -- keep the original and flag this as having issues
result.Append(m_strSourceCode.Substring(m_currentPosition - 2, 2));
m_literalIssues = true;
HandleError(JSError.BadHexEscapeSequence);
}
break;
case '0':
case '1':
case '2':
case '3':
seqOfThree = true;
esc = (ch - '0') << 6;
goto case '4';
case '4':
case '5':
case '6':
case '7':
// octal literals inside strings can cause issues
m_literalIssues = true;
// esc is reset at the beginning of the loop and it is used to check that we did not go through the cases 1, 2 or 3
if (!seqOfThree)
{
esc = (ch - '0') << 3;
}
ch = GetChar(m_currentPosition++);
if ('0' <= ch && ch <= '7')
{
if (seqOfThree)
{
esc |= (ch - '0') << 3;
ch = GetChar(m_currentPosition++);
if ('0' <= ch && ch <= '7')
{
esc |= ch - '0';
result.Append((char)esc);
}
else
{
result.Append((char)(esc >> 3));
// do not skip over this char we have to read it back
--m_currentPosition;
}
}
else
{
esc |= ch - '0';
result.Append((char)esc);
}
}
else
{
if (seqOfThree)
{
result.Append((char)(esc >> 6));
}
else
{
result.Append((char)(esc >> 3));
}
// do not skip over this char we have to read it back
--m_currentPosition;
}
HandleError(JSError.OctalLiteralsDeprecated);
break;
default:
// not an octal number, ignore the escape '/' and simply append the current char
result.Append(ch);
break;
}
start = m_currentPosition;
}
}
// update the unescaped string
if (null != result)
{
if (m_currentPosition - start - 1 > 0)
{
// append all the non escape chars to the string builder
result.Append(m_strSourceCode, start, m_currentPosition - start - 1);
}
m_decodedString = result.ToString();
}
else if (m_currentPosition == m_currentToken.StartPosition + 1)
{
// empty unterminated string!
m_decodedString = string.Empty;
}
else
{
// might be an unterminated string, so make sure that last character is the terminator
int numDelimiters = (GetChar(m_currentPosition - 1) == delimiter ? 2 : 1);
m_decodedString = m_strSourceCode.Substring(m_currentToken.StartPosition + 1, m_currentPosition - m_currentToken.StartPosition - numDelimiters);
}
}
finally
{
result.Release();
}
}