Commit message: fix parsing double % in query parameters
CVE: CVE-2013-7298
CWE: CWE-399
Vulnerability description: query_params.cpp in cxxtools before 2.2.1 allows remote attackers to cause a denial of service (infinite recursion and crash) via an HTTP query that contains %% (double percent) characters.

Project: cxxtools
File: query_params.cpp

std::string QueryParams::param(const std::string& name, size_type n, const std::string& def) const
{
  for (size_type nn = 0; nn < _values.size(); ++nn)
  {
    if (_values[nn].name == name)
    {
      if (n == 0)
        return _values[nn].value;
      --n;
    }
  }

  return def;
}

Project: cxxtools
File: query_params-test.cpp

        void testParseUrl()
        {
            cxxtools::QueryParams q;
            q.parse_url("p2=value2&value3&p1=value1");
            CXXTOOLS_UNIT_ASSERT(q.has("p1"));
            CXXTOOLS_UNIT_ASSERT(q.has("p2"));
            CXXTOOLS_UNIT_ASSERT(!q.has("p3"));
            CXXTOOLS_UNIT_ASSERT_EQUALS(q["p1"], "value1");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q["p2"], "value2");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q[0], "value3");
        }

Project: cxxtools
File: query_params.cpp

bool QueryParams::has(const std::string& name) const
{
  for (size_type nn = 0; nn < _values.size(); ++nn)
    if (_values[nn].name == name)
      return true;

  return false;
}

Project: cxxtools
File: query_params.cpp

  void appendUrl(std::string& url, const std::string& str)
  {
    for (std::string::const_iterator i = str.begin();
         i != str.end(); ++i)
      appendUrl(url, *i);
  }

Project: cxxtools
File: query_params-test.cpp

        void testCopy()
        {
            cxxtools::QueryParams q;
            q.add("p1", "value1");
            q.add("p2", "value2");
            q.add("value3");
            cxxtools::QueryParams q2 = q;
            CXXTOOLS_UNIT_ASSERT(q2.has("p1"));
            CXXTOOLS_UNIT_ASSERT(q2.has("p2"));
            CXXTOOLS_UNIT_ASSERT(!q2.has("p3"));
            CXXTOOLS_UNIT_ASSERT_EQUALS(q2["p1"], "value1");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q2["p2"], "value2");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q2[0], "value3");
        }

Project: cxxtools
File: query_params.cpp

const std::string& QueryParams::param(const std::string& name, size_type n) const
{
  for (size_type nn = 0; nn < _values.size(); ++nn)
  {
    if (_values[nn].name == name)
    {
      if (n == 0)
        return _values[nn].value;
      --n;
    }
  }

  static std::string emptyValue;
  return emptyValue;
}

Project: cxxtools
File: query_params.cpp

void QueryParams::parse_url(std::istream& url_stream)
{
  UrlParser p(*this);

  char ch;
  while (url_stream.get(ch))
    p.parse(ch);

  p.finish();
}

Project: cxxtools
File: query_params.cpp

void QueryParams::parse_url(const char* url)
{
  UrlParser p(*this);

  while (*url)
  {
    p.parse(*url);
    ++url;
  }

  p.finish();
}

Project: cxxtools
File: query_params-test.cpp

        void testCount()
        {
            cxxtools::QueryParams q;
            q.add("p1", "value1");
            q.add("p2", "value2");
            q.add("p2", "value3");
            q.add("value4");
            q.add("value5");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q.paramcount(), 2);
            CXXTOOLS_UNIT_ASSERT_EQUALS(q.paramcount("p1"), 1);
            CXXTOOLS_UNIT_ASSERT_EQUALS(q.paramcount("p2"), 2);
        }

Project: cxxtools
File: query_params.cpp

void QueryParams::parse_url(const std::string& url)
{
  UrlParser p(*this);

  for (std::string::const_iterator it = url.begin(); it != url.end(); ++it)
    p.parse(*it);

  p.finish();
}

Project: cxxtools
File: query_params-test.cpp

        void testQueryParams()
        {
            cxxtools::QueryParams q;
            q.add("p1", "value1");
            q.add("p2", "value2");
            q.add("value3");
            CXXTOOLS_UNIT_ASSERT(q.has("p1"));
            CXXTOOLS_UNIT_ASSERT(q.has("p2"));
            CXXTOOLS_UNIT_ASSERT(!q.has("p3"));
            CXXTOOLS_UNIT_ASSERT_EQUALS(q["p1"], "value1");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q["p2"], "value2");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q[0], "value3");
        }

Project: cxxtools
File: query_params.cpp

      explicit UrlParser(QueryParams& q)
        : _q(q),
          _state(state_0),
          _cnt(0),
          _v(0)
      { }

Project: cxxtools
File: query_params-test.cpp

        void testIterator()
        {
            cxxtools::QueryParams q;
            q.add("p1", "value1");
            q.add("p2", "value2");
            q.add("p2", "value3");
            q.add("value4");
            q.add("value5");

            cxxtools::QueryParams::const_iterator it;

            it = q.begin();
            CXXTOOLS_UNIT_ASSERT(it != q.end());
            CXXTOOLS_UNIT_ASSERT_EQUALS(*it, "value4");
            ++it;
            CXXTOOLS_UNIT_ASSERT(it != q.end());
            CXXTOOLS_UNIT_ASSERT_EQUALS(*it, "value5");
            ++it;
            CXXTOOLS_UNIT_ASSERT(it == q.end());

            it = q.begin("p1");
            CXXTOOLS_UNIT_ASSERT(it != q.end());
            CXXTOOLS_UNIT_ASSERT_EQUALS(*it, "value1");
            ++it;
            CXXTOOLS_UNIT_ASSERT(it == q.end());

            it = q.begin("p2");
            CXXTOOLS_UNIT_ASSERT(it != q.end());
            CXXTOOLS_UNIT_ASSERT_EQUALS(*it, "value2");
            ++it;
            CXXTOOLS_UNIT_ASSERT(it != q.end());
            CXXTOOLS_UNIT_ASSERT_EQUALS(*it, "value3");
            ++it;
            CXXTOOLS_UNIT_ASSERT(it == q.end());
        }

Project: cxxtools
File: query_params.cpp

  void UrlParser::finish()
  {
    switch(_state)
    {
      case state_0:
        break;

      case state_key:
        if (!_key.empty())
        {
          _q.add(_key);
          _key.clear();
        }
        break;

      case state_value:
        _q.add(_key, _value);
        _key.clear();
        _value.clear();
        break;

      case state_keyesc:
      case state_valueesc:
        if (_cnt == 0)
        {
          if (_state == state_keyesc)
          {
            _key += '%';
            _q.add(_key);
          }
          else
          {
            _value += '%';
            _q.add(_key, _value);
          }
        }
        else
        {
          if (_state == state_keyesc)
          {
            _key += static_cast<char>(_v);
            _q.add(_key);
          }
          else
          {
            _value += static_cast<char>(_v);
            _q.add(_key, _value);
          }
        }

        _value.clear();
        _key.clear();
        _cnt = 0;
        _v = 0;
        break;
    }

  }

Project: cxxtools
File: query_params-test.cpp

        QueryParamsTest()
            : cxxtools::unit::TestSuite("queryparams")
        {
            registerMethod("testQueryParams", *this, &QueryParamsTest::testQueryParams);
            registerMethod("testCopy", *this, &QueryParamsTest::testCopy);
            registerMethod("testParseUrl", *this, &QueryParamsTest::testParseUrl);
            registerMethod("testParseUrlSpecialChar", *this, &QueryParamsTest::testParseUrlSpecialChar);
            registerMethod("testParseDoublePercent", *this, &QueryParamsTest::testParseDoublePercent);
            registerMethod("testCount", *this, &QueryParamsTest::testCount);
            registerMethod("testCombine", *this, &QueryParamsTest::testCombine);
            registerMethod("testIterator", *this, &QueryParamsTest::testIterator);
            registerMethod("testGetUrl", *this, &QueryParamsTest::testGetUrl);
        }

Project: cxxtools
File: query_params-test.cpp

        void testCombine()
        {
            cxxtools::QueryParams q;
            cxxtools::QueryParams q2;
            q.add("p1", "value1");
            q2.add("p2", "value2");
            q2.add("value3");
            q.add(q2);
        }

Project: cxxtools
File: query_params-test.cpp

        void testParseUrlSpecialChar()
        {
            cxxtools::QueryParams q;
            q.parse_url("p1=value+with%20spaces&m%a4kitalo=tommi+");
            CXXTOOLS_UNIT_ASSERT(q.has("p1"));
            CXXTOOLS_UNIT_ASSERT(q.has("m\xa4kitalo"));
            CXXTOOLS_UNIT_ASSERT_EQUALS(q["p1"], "value with spaces");
            CXXTOOLS_UNIT_ASSERT_EQUALS(q["m\xa4kitalo"], "tommi ");
        }

Project: cxxtools
File: query_params.cpp

std::string QueryParams::getUrl() const
{
  std::string url;
  for (size_type nn = 0; nn < _values.size(); ++nn)
  {
    if (nn > 0)
      url += '&';

    if (!_values[nn].name.empty())
    {
      appendUrl(url, _values[nn].name);
      url += '=';
    }

    appendUrl(url, _values[nn].value);
  }

  return url;
}

Project: cxxtools
File: query_params-test.cpp

        void testParseDoublePercent()
        {
            cxxtools::QueryParams q;
            q.parse_url("%%=%%%");
            CXXTOOLS_UNIT_ASSERT(q.has("%%"));
            CXXTOOLS_UNIT_ASSERT_EQUALS(q["%%"], "%%%");
        }

Project: cxxtools
File: query_params-test.cpp

        void testGetUrl()
        {
            cxxtools::QueryParams q;
            q.add("first name", "Tommi");
            q.add("last name", "M\xa4kitalo");
            q.add("some value");

            std::string url = q.getUrl();
            CXXTOOLS_UNIT_ASSERT_EQUALS(url, "first+name=Tommi&last+name=M%A4kitalo&some+value");
        }

Project: cxxtools
File: query_params.cpp

  void appendUrl(std::string& url, char ch)
  {
    static const char hex[] = "0123456789ABCDEF";
    if (ch > 32 && ch < 127 && ch != '%' && ch != '+' && ch != '=' && ch != '&')
      url += ch;
    else if (ch == ' ')
      url += '+';
    else
    {
      url += '%';
      char hi = (ch >> 4) & 0x0f;
      char lo = ch & 0x0f;
      url += hex[static_cast<int>(hi)];
      url += hex[static_cast<int>(lo)];
    }
  }

Project: cxxtools
File: query_params.cpp

QueryParams::size_type QueryParams::paramcount(const std::string& name) const
{
  size_type count = 0;

  for (size_type nn = 0; nn < _values.size(); ++nn)
    if (_values[nn].name == name)
      ++count;

  return count;
}

Project: cxxtools
File: query_params.cpp

  void UrlParser::parse(char ch)
  {
    switch(_state)
    {
      case state_0:
        if (ch == '=')
          _state = state_value;
        else if (ch == '&')
          ;
        else if (ch == '%')
          _state = state_keyesc;
        else
        {
          _key = ch;
          _state = state_key;
        }
        break;

      case state_key:
        if (ch == '=')
          _state = state_value;
        else if (ch == '&')
        {
          _q.add(_key);
          _key.clear();
          _state = state_0;
        }
        else if (ch == '%')
          _state = state_keyesc;
        else
          _key += ch;
        break;

      case state_value:
        if (ch == '%')
          _state = state_valueesc;
        else if (ch == '&')
        {
          _q.add(_key, _value);
          _key.clear();
          _value.clear();
          _state = state_0;
        }
        else if (ch == '+')
          _value += ' ';
        else
          _value += ch;
        break;

      case state_keyesc:
      case state_valueesc:
        if (ch >= '0' && ch <= '9')
        {
          ++_cnt;
          _v = (_v << 4) + (ch - '0');
        }
        else if (ch >= 'a' && ch <= 'f')
        {
          ++_cnt;
          _v = (_v << 4) + (ch - 'a' + 10);
        }
        else if (ch >= 'A' && ch <= 'F')
        {
          ++_cnt;
          _v = (_v << 4) + (ch - 'A' + 10);
        }
        else
        {
          if (_cnt == 0)
          {
            if (_state == state_keyesc)
            {
              _key += '%';
              _state = state_key;
            }
            else
            {
              _value += '%';
              _state = state_value;
            }
          }
          else
          {
            if (_state == state_keyesc)
            {
              _key += static_cast<char>(_v);
              _state = state_key;
            }
            else
            {
              _value += static_cast<char>(_v);
              _state = state_value;
            }

            _cnt = 0;
            _v = 0;
          }

          parse(ch);
          break;
        }

        if (_cnt >= 2)
        {
          if (_state == state_keyesc)
          {
            _key += static_cast<char>(_v);
            _state = state_key;
          }
          else
          {
            _value += static_cast<char>(_v);
            _state = state_value;
          }
          _cnt = 0;
          _v = 0;
        }

        break;

    }

  }