Commit 04aa42be authored by Zack Weinberg's avatar Zack Weinberg
Browse files

Bug 229827: escape unprintable characters in CSS parser diagnostics. r=dbaron

--HG--
extra : rebase_source : 9298095c2cd7669b5ca1a07cd5378a852c4a7f36
parent 0f08bc57
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
#include "nsIScriptError.h"
#include "nsIServiceManager.h"
#include "nsIStringBundle.h"
#include "nsStyleUtil.h"
#include "nsThreadUtils.h"

#ifdef CSS_REPORT_PARSE_ERRORS
@@ -252,7 +253,10 @@ ErrorReporter::ReportUnexpected(const char *aMessage,
{
  if (!ShouldReportErrors()) return;

  const PRUnichar *params[1] = { aParam.get() };
  nsAutoString qparam;
  nsStyleUtil::AppendEscapedCSSIdent(aParam, qparam);
  const PRUnichar *params[1] = { qparam.get() };

  nsAutoString str;
  sStringBundle->FormatStringFromName(NS_ConvertASCIItoUTF16(aMessage).get(),
                                      params, ArrayLength(params),
+46 −27
Original line number Diff line number Diff line
@@ -9,6 +9,7 @@
#include <math.h> // must be first due to symbol conflicts

#include "nsCSSScanner.h"
#include "nsStyleUtil.h"
#include "mozilla/css/ErrorReporter.h"
#include "mozilla/Likely.h"
#include "mozilla/Util.h"
@@ -144,64 +145,85 @@ void
nsCSSToken::AppendToString(nsString& aBuffer) const
{
  switch (mType) {
    case eCSSToken_AtKeyword:
      aBuffer.Append(PRUnichar('@')); // fall through intentional
    case eCSSToken_Ident:
    case eCSSToken_WhiteSpace:
      nsStyleUtil::AppendEscapedCSSIdent(mIdent, aBuffer);
      break;

    case eCSSToken_AtKeyword:
      aBuffer.Append('@');
      nsStyleUtil::AppendEscapedCSSIdent(mIdent, aBuffer);
      break;

    case eCSSToken_ID:
    case eCSSToken_Ref:
      aBuffer.Append('#');
      nsStyleUtil::AppendEscapedCSSIdent(mIdent, aBuffer);
      break;

    case eCSSToken_Function:
    case eCSSToken_HTMLComment:
    case eCSSToken_URange:
      aBuffer.Append(mIdent);
      if (mType == eCSSToken_Function)
        aBuffer.Append(PRUnichar('('));
      nsStyleUtil::AppendEscapedCSSIdent(mIdent, aBuffer);
      aBuffer.Append('(');
      break;

    case eCSSToken_URL:
    case eCSSToken_Bad_URL:
      aBuffer.AppendLiteral("url(");
      if (mSymbol != PRUnichar(0)) {
        aBuffer.Append(mSymbol);
      }
        nsStyleUtil::AppendEscapedCSSString(mIdent, aBuffer, mSymbol);
      } else {
        aBuffer.Append(mIdent);
      if (mSymbol != PRUnichar(0)) {
        aBuffer.Append(mSymbol);
      }
      if (mType == eCSSToken_URL) {
        aBuffer.Append(PRUnichar(')'));
      }
      break;

    case eCSSToken_Number:
      if (mIntegerValid) {
        aBuffer.AppendInt(mInteger, 10);
      }
      else {
      } else {
        aBuffer.AppendFloat(mNumber);
      }
      break;

    case eCSSToken_Percentage:
      NS_ASSERTION(!mIntegerValid, "How did a percentage token get this set?");
      aBuffer.AppendFloat(mNumber * 100.0f);
      aBuffer.Append(PRUnichar('%'));
      break;

    case eCSSToken_Dimension:
      if (mIntegerValid) {
        aBuffer.AppendInt(mInteger, 10);
      }
      else {
      } else {
        aBuffer.AppendFloat(mNumber);
      }
      aBuffer.Append(mIdent);
      nsStyleUtil::AppendEscapedCSSIdent(mIdent, aBuffer);
      break;

    case eCSSToken_Bad_String:
      nsStyleUtil::AppendEscapedCSSString(mIdent, aBuffer, mSymbol);
      // remove the trailing quote character
      aBuffer.Truncate(aBuffer.Length() - 1);
      break;

    case eCSSToken_String:
      aBuffer.Append(mSymbol);
      aBuffer.Append(mIdent); // fall through intentional
      nsStyleUtil::AppendEscapedCSSString(mIdent, aBuffer, mSymbol);
      break;

    case eCSSToken_Symbol:
      aBuffer.Append(mSymbol);
      break;
    case eCSSToken_ID:
    case eCSSToken_Ref:
      aBuffer.Append(PRUnichar('#'));

    case eCSSToken_WhiteSpace:
      aBuffer.Append(' ');
      break;

    case eCSSToken_HTMLComment:
    case eCSSToken_URange:
      aBuffer.Append(mIdent);
      break;

    case eCSSToken_Includes:
      aBuffer.AppendLiteral("~=");
      break;
@@ -217,10 +239,7 @@ nsCSSToken::AppendToString(nsString& aBuffer) const
    case eCSSToken_Containsmatch:
      aBuffer.AppendLiteral("*=");
      break;
    case eCSSToken_Bad_String:
      aBuffer.Append(mSymbol);
      aBuffer.Append(mIdent);
      break;

    default:
      NS_ERROR("invalid token type");
      break;
+2 −1
Original line number Diff line number Diff line
@@ -372,8 +372,9 @@ static nscoord CalcLengthWith(const nsCSSValue& aValue,
  }
  switch (aValue.GetUnit()) {
    case eCSSUnit_EM: {
      // CSS2.1 specifies that this unit scales to the computed font
      // size, not the em-width in the font metrics, despite the name.
      return ScaleCoord(aValue, float(aFontSize));
      // XXX scale against font metrics height instead?
    }
    case eCSSUnit_XHeight: {
      nsRefPtr<nsFontMetrics> fm =
+59 −63
Original line number Diff line number Diff line
@@ -55,45 +55,37 @@ bool nsStyleUtil::DashMatchCompare(const nsAString& aAttributeValue,
  return result;
}

void nsStyleUtil::AppendEscapedCSSString(const nsString& aString,
                                         nsAString& aReturn)
void nsStyleUtil::AppendEscapedCSSString(const nsAString& aString,
                                         nsAString& aReturn,
                                         PRUnichar quoteChar)
{
  aReturn.Append(PRUnichar('"'));

  const nsString::char_type* in = aString.get();
  const nsString::char_type* const end = in + aString.Length();
  for (; in != end; in++)
  {
    if (*in < 0x20)
    {
     // Escape all characters below 0x20 numerically.
   
     /*
      This is the buffer into which snprintf should write. As the hex. value is,
      for numbers below 0x20, max. 2 characters long, we don't need more than 5
      characters ("\XX "+NUL).
     */
     PRUnichar buf[5];
     nsTextFormatter::snprintf(buf, ArrayLength(buf), NS_LITERAL_STRING("\\%hX ").get(), *in);
     aReturn.Append(buf);
   
    } else switch (*in) {
      // Special characters which should be escaped: Quotes and backslash
      case '\\':
      case '\"':
      case '\'':
  NS_PRECONDITION(quoteChar == '\'' || quoteChar == '"',
                  "CSS strings must be quoted with ' or \"");
  aReturn.Append(quoteChar);

  const PRUnichar* in = aString.BeginReading();
  const PRUnichar* const end = aString.EndReading();
  for (; in != end; in++) {
    if (*in < 0x20 || (*in >= 0x7F && *in < 0xA0)) {
      // Escape U+0000 through U+001F and U+007F through U+009F numerically.
      aReturn.AppendPrintf("\\%hX ", *in);
    } else {
      if (*in == '"' || *in == '\'' || *in == '\\') {
        // Escape backslash and quote characters symbolically.
        // It's not technically necessary to escape the quote
        // character that isn't being used to delimit the string,
        // but we do it anyway because that makes testing simpler.
        aReturn.Append(PRUnichar('\\'));
      // And now, after the eventual escaping character, the actual one.
      default:
       aReturn.Append(PRUnichar(*in));
      }
      aReturn.Append(*in);
    }
  }

  aReturn.Append(PRUnichar('"'));
  aReturn.Append(quoteChar);
}

/* static */ void
nsStyleUtil::AppendEscapedCSSIdent(const nsString& aIdent, nsAString& aReturn)
nsStyleUtil::AppendEscapedCSSIdent(const nsAString& aIdent, nsAString& aReturn)
{
  // The relevant parts of the CSS grammar are:
  //   ident    [-]?{nmstart}{nmchar}*
@@ -104,44 +96,48 @@ nsStyleUtil::AppendEscapedCSSIdent(const nsString& aIdent, nsAString& aReturn)
  //   unicode  \\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?
  // from http://www.w3.org/TR/CSS21/syndata.html#tokenization

  const nsString::char_type* in = aIdent.get();
  const nsString::char_type* const end = in + aIdent.Length();
  const PRUnichar* in = aIdent.BeginReading();
  const PRUnichar* const end = aIdent.EndReading();

  // Deal with the leading dash separately so we don't need to
  // unnecessarily escape digits.
  if (in != end && *in == '-') {
  if (in == end)
    return;

  // A leading dash does not need to be escaped as long as it is not the
  // *only* character in the identifier.
  if (in + 1 != end && *in == '-') {
    aReturn.Append(PRUnichar('-'));
    ++in;
  }

  bool first = true;
  for (; in != end; ++in, first = false)
  {
    if (*in < 0x20 || (first && '0' <= *in && *in <= '9'))
    {
      // Escape all characters below 0x20, and digits at the start
      // (including after a dash), numerically.  If we didn't escape
      // digits numerically, they'd get interpreted as a numeric escape
      // for the wrong character.

      /*
       This is the buffer into which snprintf should write. As the hex.
       value is, for numbers below 0x7F, max. 2 characters long, we
       don't need more than 5 characters ("\XX "+NUL).
      */
      PRUnichar buf[5];
      nsTextFormatter::snprintf(buf, ArrayLength(buf),
                                NS_LITERAL_STRING("\\%hX ").get(), *in);
      aReturn.Append(buf);
  // Escape a digit at the start (including after a dash),
  // numerically.  If we didn't escape it numerically, it would get
  // interpreted as a numeric escape for the wrong character.
  // A second dash immediately after a leading dash must also be
  // escaped, but this may be done symbolically.
  if (in != end && (*in == '-' ||
                    ('0' <= *in && *in <= '9'))) {
    if (*in == '-') {
      aReturn.Append(PRUnichar('\\'));
      aReturn.Append(PRUnichar('-'));
    } else {
      aReturn.AppendPrintf("\\%hX ", *in);
    }
    ++in;
  }

  for (; in != end; ++in) {
    PRUnichar ch = *in;
      if (!((ch == PRUnichar('_')) ||
            (PRUnichar('A') <= ch && ch <= PRUnichar('Z')) ||
            (PRUnichar('a') <= ch && ch <= PRUnichar('z')) ||
            PRUnichar(0x80) <= ch ||
            (!first && ch == PRUnichar('-')) ||
            (PRUnichar('0') <= ch && ch <= PRUnichar('9')))) {
        // Character needs to be escaped
    if (ch < 0x20 || (0x7F <= ch && ch < 0xA0)) {
      // Escape U+0000 through U+001F and U+007F through U+009F numerically.
      aReturn.AppendPrintf("\\%hX ", *in);
    } else {
      // Escape ASCII non-identifier printables as a backslash plus
      // the character.
      if (ch < 0x7F &&
          ch != '_' && ch != '-' &&
          (ch < '0' || '9' < ch) &&
          (ch < 'A' || 'Z' < ch) &&
          (ch < 'a' || 'z' < ch)) {
        aReturn.Append(PRUnichar('\\'));
      }
      aReturn.Append(ch);
+8 −5
Original line number Diff line number Diff line
@@ -26,13 +26,16 @@ public:
                                const nsAString& aSelectorValue,
                                const nsStringComparator& aComparator);

  // Append a quoted (with "") and escaped version of aString to aResult.
  static void AppendEscapedCSSString(const nsString& aString,
                                     nsAString& aResult);
  // Append a quoted (with 'quoteChar') and escaped version of aString
  // to aResult.  'quoteChar' must be ' or ".
  static void AppendEscapedCSSString(const nsAString& aString,
                                     nsAString& aResult,
                                     PRUnichar quoteChar = '"');

  // Append the identifier given by |aIdent| to |aResult|, with
  // appropriate escaping so that it can be reparsed to the same
  // identifier.
  static void AppendEscapedCSSIdent(const nsString& aIdent,
  static void AppendEscapedCSSIdent(const nsAString& aIdent,
                                    nsAString& aResult);

  // Append a bitmask-valued property's value(s) (space-separated) to aResult.
Loading