/* * Copyright (C) 2010 Google Inc. All rights reserved. * Copyright (C) 2014 University of Washington. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * Neither the name of Google Inc. nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "InspectorValues.h" #include #include #include namespace Inspector { namespace { static const int stackLimit = 1000; enum Token { OBJECT_BEGIN, OBJECT_END, ARRAY_BEGIN, ARRAY_END, STRING, NUMBER, BOOL_TRUE, BOOL_FALSE, NULL_TOKEN, LIST_SEPARATOR, OBJECT_PAIR_SEPARATOR, INVALID_TOKEN, }; const char* const nullString = "null"; const char* const trueString = "true"; const char* const falseString = "false"; bool parseConstToken(const UChar* start, const UChar* end, const UChar** tokenEnd, const char* token) { while (start < end && *token != '\0' && *start++ == *token++) { } if (*token != '\0') return false; *tokenEnd = start; return true; } bool readInt(const UChar* start, const UChar* end, const UChar** tokenEnd, bool canHaveLeadingZeros) { if (start == end) return false; bool haveLeadingZero = '0' == *start; int length = 0; while (start < end && '0' <= *start && *start <= '9') { ++start; ++length; } if (!length) return false; if (!canHaveLeadingZeros && length > 1 && haveLeadingZero) return false; *tokenEnd = start; return true; } bool parseNumberToken(const UChar* start, const UChar* end, const UChar** tokenEnd) { // We just grab the number here. We validate the size in DecodeNumber. // According to RFC4627, a valid number is: [minus] int [frac] [exp] if (start == end) return false; UChar c = *start; if ('-' == c) ++start; if (!readInt(start, end, &start, false)) return false; if (start == end) { *tokenEnd = start; return true; } // Optional fraction part c = *start; if ('.' == c) { ++start; if (!readInt(start, end, &start, true)) return false; if (start == end) { *tokenEnd = start; return true; } c = *start; } // Optional exponent part if ('e' == c || 'E' == c) { ++start; if (start == end) return false; c = *start; if ('-' == c || '+' == c) { ++start; if (start == end) return false; } if (!readInt(start, end, &start, true)) return false; } *tokenEnd = start; return true; } bool readHexDigits(const UChar* start, const UChar* end, const UChar** tokenEnd, int digits) { if (end - start < digits) return false; for (int i = 0; i < digits; ++i) { UChar c = *start++; if (!(('0' <= c && c <= '9') || ('a' <= c && c <= 'f') || ('A' <= c && c <= 'F'))) return false; } *tokenEnd = start; return true; } bool parseStringToken(const UChar* start, const UChar* end, const UChar** tokenEnd) { while (start < end) { UChar c = *start++; if ('\\' == c) { c = *start++; // Make sure the escaped char is valid. switch (c) { case 'x': if (!readHexDigits(start, end, &start, 2)) return false; break; case 'u': if (!readHexDigits(start, end, &start, 4)) return false; break; case '\\': case '/': case 'b': case 'f': case 'n': case 'r': case 't': case 'v': case '"': break; default: return false; } } else if ('"' == c) { *tokenEnd = start; return true; } } return false; } Token parseToken(const UChar* start, const UChar* end, const UChar** tokenStart, const UChar** tokenEnd) { while (start < end && isSpaceOrNewline(*start)) ++start; if (start == end) return INVALID_TOKEN; *tokenStart = start; switch (*start) { case 'n': if (parseConstToken(start, end, tokenEnd, nullString)) return NULL_TOKEN; break; case 't': if (parseConstToken(start, end, tokenEnd, trueString)) return BOOL_TRUE; break; case 'f': if (parseConstToken(start, end, tokenEnd, falseString)) return BOOL_FALSE; break; case '[': *tokenEnd = start + 1; return ARRAY_BEGIN; case ']': *tokenEnd = start + 1; return ARRAY_END; case ',': *tokenEnd = start + 1; return LIST_SEPARATOR; case '{': *tokenEnd = start + 1; return OBJECT_BEGIN; case '}': *tokenEnd = start + 1; return OBJECT_END; case ':': *tokenEnd = start + 1; return OBJECT_PAIR_SEPARATOR; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '-': if (parseNumberToken(start, end, tokenEnd)) return NUMBER; break; case '"': if (parseStringToken(start + 1, end, tokenEnd)) return STRING; break; } return INVALID_TOKEN; } inline int hexToInt(UChar c) { if ('0' <= c && c <= '9') return c - '0'; if ('A' <= c && c <= 'F') return c - 'A' + 10; if ('a' <= c && c <= 'f') return c - 'a' + 10; ASSERT_NOT_REACHED(); return 0; } bool decodeString(const UChar* start, const UChar* end, StringBuilder& output) { while (start < end) { UChar c = *start++; if ('\\' != c) { output.append(c); continue; } c = *start++; switch (c) { case '"': case '/': case '\\': break; case 'b': c = '\b'; break; case 'f': c = '\f'; break; case 'n': c = '\n'; break; case 'r': c = '\r'; break; case 't': c = '\t'; break; case 'v': c = '\v'; break; case 'x': c = (hexToInt(*start) << 4) + hexToInt(*(start + 1)); start += 2; break; case 'u': c = (hexToInt(*start) << 12) + (hexToInt(*(start + 1)) << 8) + (hexToInt(*(start + 2)) << 4) + hexToInt(*(start + 3)); start += 4; break; default: return false; } output.append(c); } return true; } bool decodeString(const UChar* start, const UChar* end, String& output) { if (start == end) { output = emptyString(); return true; } if (start > end) return false; StringBuilder buffer; buffer.reserveCapacity(end - start); if (!decodeString(start, end, buffer)) return false; output = buffer.toString(); return true; } RefPtr buildValue(const UChar* start, const UChar* end, const UChar** valueTokenEnd, int depth) { if (depth > stackLimit) return nullptr; RefPtr result; const UChar* tokenStart; const UChar* tokenEnd; Token token = parseToken(start, end, &tokenStart, &tokenEnd); switch (token) { case INVALID_TOKEN: return nullptr; case NULL_TOKEN: result = InspectorValue::null(); break; case BOOL_TRUE: result = InspectorValue::create(true); break; case BOOL_FALSE: result = InspectorValue::create(false); break; case NUMBER: { bool ok; double value = charactersToDouble(tokenStart, tokenEnd - tokenStart, &ok); if (!ok) return nullptr; result = InspectorValue::create(value); break; } case STRING: { String value; bool ok = decodeString(tokenStart + 1, tokenEnd - 1, value); if (!ok) return nullptr; result = InspectorValue::create(value); break; } case ARRAY_BEGIN: { Ref array = InspectorArray::create(); start = tokenEnd; token = parseToken(start, end, &tokenStart, &tokenEnd); while (token != ARRAY_END) { RefPtr arrayNode = buildValue(start, end, &tokenEnd, depth + 1); if (!arrayNode) return nullptr; array->pushValue(WTFMove(arrayNode)); // After a list value, we expect a comma or the end of the list. start = tokenEnd; token = parseToken(start, end, &tokenStart, &tokenEnd); if (token == LIST_SEPARATOR) { start = tokenEnd; token = parseToken(start, end, &tokenStart, &tokenEnd); if (token == ARRAY_END) return nullptr; } else if (token != ARRAY_END) { // Unexpected value after list value. Bail out. return nullptr; } } if (token != ARRAY_END) return nullptr; result = WTFMove(array); break; } case OBJECT_BEGIN: { Ref object = InspectorObject::create(); start = tokenEnd; token = parseToken(start, end, &tokenStart, &tokenEnd); while (token != OBJECT_END) { if (token != STRING) return nullptr; String key; if (!decodeString(tokenStart + 1, tokenEnd - 1, key)) return nullptr; start = tokenEnd; token = parseToken(start, end, &tokenStart, &tokenEnd); if (token != OBJECT_PAIR_SEPARATOR) return nullptr; start = tokenEnd; RefPtr value = buildValue(start, end, &tokenEnd, depth + 1); if (!value) return nullptr; object->setValue(key, WTFMove(value)); start = tokenEnd; // After a key/value pair, we expect a comma or the end of the // object. token = parseToken(start, end, &tokenStart, &tokenEnd); if (token == LIST_SEPARATOR) { start = tokenEnd; token = parseToken(start, end, &tokenStart, &tokenEnd); if (token == OBJECT_END) return nullptr; } else if (token != OBJECT_END) { // Unexpected value after last object value. Bail out. return nullptr; } } if (token != OBJECT_END) return nullptr; result = WTFMove(object); break; } default: // We got a token that's not a value. return nullptr; } *valueTokenEnd = tokenEnd; return result.release(); } inline bool escapeChar(UChar c, StringBuilder& dst) { switch (c) { case '\b': dst.appendLiteral("\\b"); break; case '\f': dst.appendLiteral("\\f"); break; case '\n': dst.appendLiteral("\\n"); break; case '\r': dst.appendLiteral("\\r"); break; case '\t': dst.appendLiteral("\\t"); break; case '\\': dst.appendLiteral("\\\\"); break; case '"': dst.appendLiteral("\\\""); break; default: return false; } return true; } inline void doubleQuoteString(const String& str, StringBuilder& dst) { dst.append('"'); for (unsigned i = 0; i < str.length(); ++i) { UChar c = str[i]; if (!escapeChar(c, dst)) { if (c < 32 || c > 126 || c == '<' || c == '>') { // 1. Escaping <, > to prevent script execution. // 2. Technically, we could also pass through c > 126 as UTF8, but this // is also optional. It would also be a pain to implement here. dst.append(String::format("\\u%04X", c)); } else dst.append(c); } } dst.append('"'); } } // anonymous namespace Ref InspectorValue::null() { return adoptRef(*new InspectorValue); } Ref InspectorValue::create(bool value) { return adoptRef(*new InspectorValue(value)); } Ref InspectorValue::create(int value) { return adoptRef(*new InspectorValue(value)); } Ref InspectorValue::create(double value) { return adoptRef(*new InspectorValue(value)); } Ref InspectorValue::create(const String& value) { return adoptRef(*new InspectorValue(value)); } Ref InspectorValue::create(const char* value) { return adoptRef(*new InspectorValue(value)); } bool InspectorValue::asValue(RefPtr & value) { value = this; return true; } bool InspectorValue::asObject(RefPtr&) { return false; } bool InspectorValue::asArray(RefPtr&) { return false; } bool InspectorValue::parseJSON(const String& jsonInput, RefPtr& output) { // FIXME: This whole file should just use StringView instead of UChar/length and avoid upconverting. auto characters = StringView(jsonInput).upconvertedCharacters(); const UChar* start = characters; const UChar* end = start + jsonInput.length(); const UChar* tokenEnd; RefPtr result = buildValue(start, end, &tokenEnd, 0); if (!result || tokenEnd != end) return false; output = result.release(); return true; } String InspectorValue::toJSONString() const { StringBuilder result; result.reserveCapacity(512); writeJSON(result); return result.toString(); } bool InspectorValue::asBoolean(bool& output) const { if (type() != Type::Boolean) return false; output = m_value.boolean; return true; } bool InspectorValue::asDouble(double& output) const { if (type() != Type::Double) return false; output = m_value.number; return true; } bool InspectorValue::asDouble(float& output) const { if (type() != Type::Double) return false; output = static_cast(m_value.number); return true; } bool InspectorValue::asInteger(int& output) const { if (type() != Type::Integer && type() != Type::Double) return false; output = static_cast(m_value.number); return true; } bool InspectorValue::asInteger(unsigned& output) const { if (type() != Type::Integer && type() != Type::Double) return false; output = static_cast(m_value.number); return true; } bool InspectorValue::asInteger(long& output) const { if (type() != Type::Integer && type() != Type::Double) return false; output = static_cast(m_value.number); return true; } bool InspectorValue::asInteger(long long& output) const { if (type() != Type::Integer && type() != Type::Double) return false; output = static_cast(m_value.number); return true; } bool InspectorValue::asInteger(unsigned long& output) const { if (type() != Type::Integer && type() != Type::Double) return false; output = static_cast(m_value.number); return true; } bool InspectorValue::asInteger(unsigned long long& output) const { if (type() != Type::Integer && type() != Type::Double) return false; output = static_cast(m_value.number); return true; } bool InspectorValue::asString(String& output) const { if (type() != Type::String) return false; output = m_value.string; return true; } void InspectorValue::writeJSON(StringBuilder& output) const { switch (m_type) { case Type::Null: output.appendLiteral("null"); break; case Type::Boolean: if (m_value.boolean) output.appendLiteral("true"); else output.appendLiteral("false"); break; case Type::String: doubleQuoteString(m_value.string, output); break; case Type::Double: case Type::Integer: { NumberToLStringBuffer buffer; if (!std::isfinite(m_value.number)) { output.appendLiteral("null"); return; } DecimalNumber decimal = m_value.number; unsigned length = 0; if (decimal.bufferLengthForStringDecimal() > WTF::NumberToStringBufferLength) { // Not enough room for decimal. Use exponential format. if (decimal.bufferLengthForStringExponential() > WTF::NumberToStringBufferLength) { // Fallback for an abnormal case if it's too little even for exponential. output.appendLiteral("NaN"); return; } length = decimal.toStringExponential(buffer, WTF::NumberToStringBufferLength); } else length = decimal.toStringDecimal(buffer, WTF::NumberToStringBufferLength); output.append(buffer, length); break; } default: ASSERT_NOT_REACHED(); } } InspectorObjectBase::~InspectorObjectBase() { } bool InspectorObjectBase::asObject(RefPtr& output) { COMPILE_ASSERT(sizeof(InspectorObject) == sizeof(InspectorObjectBase), cannot_cast); output = static_cast(this); return true; } InspectorObject* InspectorObjectBase::openAccessors() { COMPILE_ASSERT(sizeof(InspectorObject) == sizeof(InspectorObjectBase), cannot_cast); return static_cast(this); } bool InspectorObjectBase::getBoolean(const String& name, bool& output) const { RefPtr value; if (!getValue(name, value)) return false; return value->asBoolean(output); } bool InspectorObjectBase::getString(const String& name, String& output) const { RefPtr value; if (!getValue(name, value)) return false; return value->asString(output); } bool InspectorObjectBase::getObject(const String& name, RefPtr& output) const { RefPtr value; if (!getValue(name, value)) return false; return value->asObject(output); } bool InspectorObjectBase::getArray(const String& name, RefPtr& output) const { RefPtr value; if (!getValue(name, value)) return false; return value->asArray(output); } bool InspectorObjectBase::getValue(const String& name, RefPtr& output) const { Dictionary::const_iterator findResult = m_map.find(name); if (findResult == m_map.end()) return false; output = findResult->value; return true; } void InspectorObjectBase::remove(const String& name) { m_map.remove(name); m_order.removeFirst(name); } void InspectorObjectBase::writeJSON(StringBuilder& output) const { output.append('{'); for (size_t i = 0; i < m_order.size(); ++i) { auto findResult = m_map.find(m_order[i]); ASSERT(findResult != m_map.end()); if (i) output.append(','); doubleQuoteString(findResult->key, output); output.append(':'); findResult->value->writeJSON(output); } output.append('}'); } InspectorObjectBase::InspectorObjectBase() : Inspector::InspectorValue(Type::Object) , m_map() , m_order() { } InspectorArrayBase::~InspectorArrayBase() { } bool InspectorArrayBase::asArray(RefPtr& output) { COMPILE_ASSERT(sizeof(InspectorArrayBase) == sizeof(InspectorArray), cannot_cast); output = static_cast(this); return true; } void InspectorArrayBase::writeJSON(StringBuilder& output) const { output.append('['); for (Vector>::const_iterator it = m_map.begin(); it != m_map.end(); ++it) { if (it != m_map.begin()) output.append(','); (*it)->writeJSON(output); } output.append(']'); } InspectorArrayBase::InspectorArrayBase() : InspectorValue(Type::Array) , m_map() { } RefPtr InspectorArrayBase::get(size_t index) const { ASSERT_WITH_SECURITY_IMPLICATION(index < m_map.size()); return m_map[index]; } Ref InspectorObject::create() { return adoptRef(*new InspectorObject); } Ref InspectorArray::create() { return adoptRef(*new InspectorArray); } } // namespace Inspector