diff options
| author | Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com> | 2011-03-09 23:07:27 +0900 |
|---|---|---|
| committer | Tatsuhiro Tsujikawa <tatsuhiro.t@gmail.com> | 2011-03-09 23:07:27 +0900 |
| commit | 7338a2503547ae1310d018d0fc2903fb85b309ea (patch) | |
| tree | c7111eb641f6bf1b661c03270a8f4407d2fada21 /src/json.cc | |
| parent | d541b350d21cd7747479f21b7c4b6d363248a450 (diff) | |
Added initial JSON-RPC support.
JSON-RPC is enabled using --enable-xml-rpc. We are implementing
JSON-RPC based on JSON-RPC 2.0 draft spec.
Diffstat (limited to 'src/json.cc')
| -rw-r--r-- | src/json.cc | 612 |
1 files changed, 612 insertions, 0 deletions
diff --git a/src/json.cc b/src/json.cc new file mode 100644 index 00000000..f389d854 --- /dev/null +++ b/src/json.cc @@ -0,0 +1,612 @@ +/* <!-- copyright */ +/* + * aria2 - The high speed download utility + * + * Copyright (C) 2011 Tatsuhiro Tsujikawa + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. If you + * do not wish to do so, delete this exception statement from your + * version. If you delete this exception statement from all source + * files in the program, then also delete it here. + */ +/* copyright --> */ +#include "json.h" + +#include <sstream> + +#include "array_fun.h" +#include "DlAbortEx.h" +#include "error_code.h" +#include "a2functional.h" +#include "util.h" +#include "fmt.h" + +namespace aria2 { + +namespace json { + +// Function prototype declaration +namespace { +std::pair<SharedHandle<ValueBase>, std::string::const_iterator> +decode +(std::string::const_iterator first, + std::string::const_iterator last, + size_t depth); +} // namespace + +namespace { +const char WS[] = { 0x20, 0x09, 0x0a, 0x0d }; +const char ESCAPE_CHARS[] = { '"', '\\', '/', '\b', '\f', '\n', '\r', '\t' }; +const size_t MAX_STRUCTURE_DEPTH = 100; +} // namespace + +namespace { +std::string::const_iterator skipWs +(std::string::const_iterator first, + std::string::const_iterator last) +{ + while(first != last && std::find(vbegin(WS), vend(WS), *first) != vend(WS)) { + ++first; + } + return first; +} +} // namespace + +namespace { +void checkEof +(std::string::const_iterator first, + std::string::const_iterator last) +{ + if(first == last) { + throw DL_ABORT_EX2("JSON decoding failed: unexpected EOF", + error_code::JSON_PARSE_ERROR); + } +} +} // namespace + +namespace { +std::string::const_iterator +decodeKeyword +(std::string::const_iterator first, + std::string::const_iterator last, + const std::string& keyword) +{ + size_t len = keyword.size(); + for(size_t i = 0; i < len; ++i) { + checkEof(first, last); + if(*first != keyword[i]) { + throw DL_ABORT_EX2(fmt("JSON decoding failed: %s not found.", + keyword.c_str()), + error_code::JSON_PARSE_ERROR); + } + ++first; + } + return first; +} +} // namespace + +namespace { +std::pair<SharedHandle<ValueBase>, std::string::const_iterator> +decodeTrue +(std::string::const_iterator first, + std::string::const_iterator last) +{ + first = decodeKeyword(first, last, "true"); + return std::make_pair(Bool::gTrue(), first); +} +} // namespace + +namespace { +std::pair<SharedHandle<ValueBase>, std::string::const_iterator> +decodeFalse +(std::string::const_iterator first, + std::string::const_iterator last) +{ + first = decodeKeyword(first, last, "false"); + return std::make_pair(Bool::gFalse(), first); +} +} // namespace + +namespace { +std::pair<SharedHandle<ValueBase>, std::string::const_iterator> +decodeNull +(std::string::const_iterator first, + std::string::const_iterator last) +{ + first = decodeKeyword(first, last, "null"); + return std::make_pair(Null::g(), first); +} +} // namespace + +namespace { +std::pair<SharedHandle<ValueBase>, std::string::const_iterator> +decodeString +(std::string::const_iterator first, + std::string::const_iterator last) +{ + // Consume first char, assuming it is '"'. + ++first; + std::string s; + std::string::const_iterator offset = first; + while(first != last) { + if(*first == '"') { + break; + } + if(*first == '\\') { + s += std::string(offset, first); + ++first; + checkEof(first, last); + if(*first == 'u') { + ++first; + std::string uchars; + for(int i = 0; i < 4; ++i, ++first) { + checkEof(first, last); + uchars += *first; + } + checkEof(first, last); + uint16_t codepoint = util::parseUInt(uchars, 16); + if(codepoint <= 0x007fu) { + s += static_cast<char>(codepoint); + } else if(codepoint <= 0x07ffu) { + unsigned char c2 = 0x80u | (codepoint & 0x003fu); + unsigned char c1 = 0xC0u | (codepoint >> 6); + s += c1; + s += c2; + } else if(in(codepoint, 0xD800u, 0xDBFFu)) { + // surrogate pair + if(*first != '\\' || first+1 == last || + *(first+1) != 'u') { + throw DL_ABORT_EX2("JSON decoding failed: bad UTF-8 sequence.", + error_code::JSON_PARSE_ERROR); + } + first += 2; + std::string uchars; + for(int i = 0; i < 4; ++i, ++first) { + checkEof(first, last); + uchars += *first; + } + checkEof(first, last); + uint16_t codepoint2 = util::parseUInt(uchars, 16); + if(!in(codepoint2, 0xDC00u, 0xDFFFu)) { + throw DL_ABORT_EX2("JSON decoding failed: bad UTF-8 sequence.", + error_code::JSON_PARSE_ERROR); + } + uint32_t fullcodepoint = 0x010000u; + fullcodepoint += (codepoint & 0x03FFu) << 10; + fullcodepoint += (codepoint2 & 0x03FFu); + unsigned char c4 = 0x80u | (fullcodepoint & 0x003Fu); + unsigned char c3 = 0x80u | ((fullcodepoint >> 6) & 0x003Fu); + unsigned char c2 = 0x80u | ((fullcodepoint >> 12) & 0x003Fu); + unsigned char c1 = 0xf0u | (fullcodepoint >> 18); + s += c1; + s += c2; + s += c3; + s += c4; + } else if(codepoint <= 0xffffu) { + unsigned char c3 = 0x80u | (codepoint & 0x003Fu); + unsigned char c2 = 0x80u | ((codepoint >> 6) & 0x003Fu); + unsigned char c1 = 0xE0u | (codepoint >> 12); + s += c1; + s += c2; + s += c3; + } + offset = first; + } else { + if(*first == 'b') { + s += '\b'; + } else if(*first == 'f') { + s += '\f'; + } else if(*first == 'n') { + s += '\n'; + } else if(*first == 'r') { + s += '\r'; + } else if(*first == 't') { + s += '\t'; + } else { + s += *first; + } + ++first; + offset = first; + } + } else { + ++first; + } + } + checkEof(first, last); + if(std::distance(offset, first) > 0) { + s += std::string(offset, first); + } + if(!util::isUtf8(s)) { + throw DL_ABORT_EX2("JSON decoding failed: Non UTF-8 string.", + error_code::JSON_PARSE_ERROR); + } + ++first; + return std::make_pair(String::g(s), first); +} +} // namespace + +namespace { +void checkEmptyDigit +(std::string::const_iterator first, + std::string::const_iterator last) +{ + if(std::distance(first, last) == 0) { + throw DL_ABORT_EX2("JSON decoding failed: zero DIGIT.", + error_code::JSON_PARSE_ERROR); + } +} +} // namespace + +namespace { +void checkLeadingZero +(std::string::const_iterator first, + std::string::const_iterator last) +{ + if(std::distance(first, last) > 2 && *first == '0') { + throw DL_ABORT_EX2("JSON decoding failed: leading zero.", + error_code::JSON_PARSE_ERROR); + } +} +} // namespace + +namespace { +std::pair<SharedHandle<ValueBase>, std::string::const_iterator> +decodeNumber +(std::string::const_iterator first, + std::string::const_iterator last) +{ + std::string s; + if(*first == '-') { + s += *first; + ++first; + } + std::string::const_iterator offset = first; + while(first != last && in(*first, '0', '9')) { + ++first; + } + checkEof(first, last); + checkEmptyDigit(offset, first); + checkLeadingZero(offset, first); + s += std::string(offset, first); + bool fp = false; + if(*first == '.') { + fp = true; + s += *first; + ++first; + offset = first; + while(first != last && in(*first, '0', '9')) { + ++first; + } + checkEof(first, last); + checkEmptyDigit(offset, first); + s += std::string(offset, first); + } + if(*first == 'e') { + fp = true; + s += *first; + ++first; + checkEof(first, last); + if(*first == '+' || *first == '-') { + s += *first; + ++first; + } + offset = first; + while(first != last && in(*first, '0', '9')) { + ++first; + } + checkEof(first, last); + checkEmptyDigit(offset, first); + s += std::string(offset, first); + } + if(fp) { + // Since we don't have floating point coutner part in ValueBase, + // we just treat it as string. + return std::make_pair(String::g(s), first); + } else { + Integer::ValueType val = util::parseLLInt(s); + return std::make_pair(Integer::g(val), first); + } +} +} // namespace + +namespace { +void checkDepth(size_t depth) +{ + if(depth >= MAX_STRUCTURE_DEPTH) { + throw DL_ABORT_EX2("JSON decoding failed: Structure is too deep.", + error_code::JSON_PARSE_ERROR); + } +} +} // namespace + +namespace { +std::pair<SharedHandle<ValueBase>, std::string::const_iterator> +decodeArray +(std::string::const_iterator first, + std::string::const_iterator last, + size_t depth) +{ + checkDepth(depth); + SharedHandle<List> list = List::g(); + // Consume first char, assuming it is '['. + ++first; + first = skipWs(first, last); + checkEof(first, last); + if(*first != ']') { + while(1) { + std::pair<SharedHandle<ValueBase>, std::string::const_iterator> + r = decode(first, last, depth); + list->append(r.first); + first = r.second; + first = skipWs(first, last); + if(first == last || (*first != ',' && *first != ']')) { + throw DL_ABORT_EX2("JSON decoding failed:" + " value-separator ',' or ']' is not found.", + error_code::JSON_PARSE_ERROR); + } + if(*first == ']') { + break; + } + ++first; + } + } + ++first; + return std::make_pair(list, first); +} +} // namespace + +namespace { +std::pair<SharedHandle<ValueBase>, std::string::const_iterator> +decodeObject +(std::string::const_iterator first, + std::string::const_iterator last, + size_t depth) +{ + checkDepth(depth); + SharedHandle<Dict> dict = Dict::g(); + // Consume first char, assuming it is '{' + ++first; + first = skipWs(first, last); + checkEof(first, last); + if(*first != '}') { + while(1) { + std::pair<SharedHandle<ValueBase>, std::string::const_iterator> + keyRet = decodeString(first, last); + first = keyRet.second; + first = skipWs(first, last); + if(first == last || *first != ':') { + throw DL_ABORT_EX2("JSON decoding failed:" + " name-separator ':' is not found.", + error_code::JSON_PARSE_ERROR); + } + ++first; + std::pair<SharedHandle<ValueBase>, std::string::const_iterator> + valueRet = decode(first, last, depth); + dict->put(asString(keyRet.first)->s(), valueRet.first); + first = valueRet.second; + first = skipWs(first, last); + if(first == last || (*first != ',' && *first != '}')) { + throw DL_ABORT_EX2("JSON decoding failed:" + " value-separator ',' or '}' is not found.", + error_code::JSON_PARSE_ERROR); + } + if(*first == '}') { + break; + } + ++first; + first = skipWs(first, last); + checkEof(first, last); + } + } + ++first; + return std::make_pair(dict, first); +} +} // namespace + +namespace { +std::pair<SharedHandle<ValueBase>, std::string::const_iterator> +decode +(std::string::const_iterator first, + std::string::const_iterator last, + size_t depth) +{ + first = skipWs(first, last); + if(first == last) { + throw DL_ABORT_EX2("JSON decoding failed:" + " Unexpected EOF in term context.", + error_code::JSON_PARSE_ERROR); + } + if(*first == '[') { + return decodeArray(first, last, depth+1); + } else if(*first == '{') { + return decodeObject(first, last, depth+1); + } else if(*first == '"') { + return decodeString(first, last); + } else if(*first == '-' || in(*first, '0', '9')) { + return decodeNumber(first, last); + } else if(*first == 't') { + return decodeTrue(first, last); + } else if(*first == 'f') { + return decodeFalse(first, last); + } else if(*first == 'n') { + return decodeNull(first, last); + } else { + throw DL_ABORT_EX2("JSON decoding failed:" + " Unexpected EOF in term context.", + error_code::JSON_PARSE_ERROR); + } +} +} // namespace + +SharedHandle<ValueBase> decode(const std::string& json) +{ + std::string::const_iterator first = json.begin(); + std::string::const_iterator last = json.end(); + first = skipWs(first, last); + if(first == last) { + throw DL_ABORT_EX2("JSON decoding failed:" + " Unexpected EOF in term context.", + error_code::JSON_PARSE_ERROR); + } + std::pair<SharedHandle<ValueBase>, std::string::const_iterator> r; + if(*first == '[') { + r = decodeArray(first, last, 1); + } else if(*first == '{') { + r = decodeObject(first, last, 1); + } else { + throw DL_ABORT_EX2("JSON decoding failed:" + " Unexpected EOF in term context.", + error_code::JSON_PARSE_ERROR); + } + return r.first; +} + +std::string jsonEscape(const std::string& s) +{ + std::string t; + for(std::string::const_iterator i = s.begin(), eoi = s.end(); i != eoi; + ++i) { + if(*i == '"' || *i == '\\' || *i == '/') { + t += '\\'; + t += *i; + } else if(*i == '\b') { + t += "\\b"; + } else if(*i == '\f') { + t += "\\f"; + } else if(*i == '\n') { + t += "\\n"; + } else if(*i == '\r') { + t += "\\r"; + } else if(*i == '\t') { + t += "\\t"; + } else if(in(static_cast<unsigned char>(*i), 0x00u, 0x1Fu)) { + t += "\\u00"; + char temp[3]; + temp[2] = '\0'; + temp[0] = (*i >> 4); + temp[1] = (*i)&0x0Fu; + for(int j = 0; j < 2; ++j) { + if(temp[j] < 10) { + temp[j] += '0'; + } else { + temp[j] += 'A'-10; + } + } + t += temp; + } else { + t += *i; + } + } + return t; +} + +std::string encode(const ValueBase* vlb) +{ + class JsonValueBaseVisitor:public ValueBaseVisitor { + private: + std::ostringstream out_; + public: + virtual void visit(const String& string) + { + const std::string& s = string.s(); + std::string t = jsonEscape(s); + out_ << '"'; + out_.write(t.data(), t.size()); + out_ << '"'; + } + + virtual void visit(const Integer& integer) + { + out_ << integer.i(); + } + + virtual void visit(const Bool& boolValue) + { + out_ << (boolValue.val() ? "true" : "false"); + } + + virtual void visit(const Null& nullValue) + { + out_ << "null"; + } + + virtual void visit(const List& list) + { + out_ << '['; + List::ValueType::const_iterator i = list.begin(); + if(!list.empty()) { + (*i)->accept(*this); + } + ++i; + for(List::ValueType::const_iterator eoi = list.end(); i != eoi; ++i){ + out_ << ','; + (*i)->accept(*this); + } + out_ << ']'; + } + + virtual void visit(const Dict& dict) + { + out_ << '{'; + Dict::ValueType::const_iterator i = dict.begin(); + if(!dict.empty()) { + std::string key = jsonEscape((*i).first); + out_ << '"'; + out_.write(key.data(), key.size()); + out_ << "\":"; + (*i).second->accept(*this); + } + ++i; + for(Dict::ValueType::const_iterator eoi = dict.end(); i != eoi; ++i){ + out_ << ','; + std::string key = jsonEscape((*i).first); + out_ << '"'; + out_.write(key.data(), key.size()); + out_ << "\":"; + (*i).second->accept(*this); + } + out_ << '}'; + } + + std::string getResult() const + { + return out_.str(); + } + }; + JsonValueBaseVisitor visitor; + vlb->accept(visitor); + return visitor.getResult(); +} + +// Serializes JSON object or array. +std::string encode(const SharedHandle<ValueBase>& json) +{ + std::ostringstream out; + return encode(out, json.get()).str(); +} + +} // namespace json + +} // namespace aria2 |
