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 | |
| 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.
| -rw-r--r-- | src/GZipEncoder.cc | 6 | ||||
| -rw-r--r-- | src/GZipEncoder.h | 4 | ||||
| -rw-r--r-- | src/HttpServerBodyCommand.cc | 56 | ||||
| -rw-r--r-- | src/Makefile.am | 3 | ||||
| -rw-r--r-- | src/ValueBase.cc | 99 | ||||
| -rw-r--r-- | src/ValueBase.h | 71 | ||||
| -rw-r--r-- | src/XmlRpcMethod.cc | 11 | ||||
| -rw-r--r-- | src/XmlRpcMethod.h | 10 | ||||
| -rw-r--r-- | src/XmlRpcMethodImpl.cc | 13 | ||||
| -rw-r--r-- | src/XmlRpcRequest.cc | 8 | ||||
| -rw-r--r-- | src/XmlRpcRequest.h | 5 | ||||
| -rw-r--r-- | src/XmlRpcResponse.cc | 61 | ||||
| -rw-r--r-- | src/XmlRpcResponse.h | 11 | ||||
| -rw-r--r-- | src/bencode2.cc | 3 | ||||
| -rw-r--r-- | src/bittorrent_helper.cc | 2 | ||||
| -rw-r--r-- | src/bittorrent_helper.h | 2 | ||||
| -rw-r--r-- | src/error_code.h | 3 | ||||
| -rw-r--r-- | src/json.cc | 612 | ||||
| -rw-r--r-- | src/json.h | 141 | ||||
| -rw-r--r-- | test/JsonTest.cc | 446 | ||||
| -rw-r--r-- | test/Makefile.am | 3 |
21 files changed, 1528 insertions, 42 deletions
diff --git a/src/GZipEncoder.cc b/src/GZipEncoder.cc index def1c281..c272ce97 100644 --- a/src/GZipEncoder.cc +++ b/src/GZipEncoder.cc @@ -143,4 +143,10 @@ GZipEncoder& GZipEncoder::operator<<(int64_t i) return *this; } +GZipEncoder& GZipEncoder::write(const char* s, size_t length) +{ + internalBuf_ += encode(reinterpret_cast<const unsigned char*>(s), length); + return *this; +} + } // namespace aria2 diff --git a/src/GZipEncoder.h b/src/GZipEncoder.h index b5cf66de..4ba83ac2 100644 --- a/src/GZipEncoder.h +++ b/src/GZipEncoder.h @@ -68,6 +68,10 @@ public: // in this class. GZipEncoder& operator<<(const std::string& s); + // Feeds binary data in s with size length to deflater. The + // deflated result is kept in this class. + GZipEncoder& write(const char* s, size_t length); + // Feeds integer to deflator. Before passed to deflator, i is // converted to std::string using util::itos(). The deflated result // is kept in this class. diff --git a/src/HttpServerBodyCommand.cc b/src/HttpServerBodyCommand.cc index e49c253c..3d39e111 100644 --- a/src/HttpServerBodyCommand.cc +++ b/src/HttpServerBodyCommand.cc @@ -54,6 +54,8 @@ #include "util.h" #include "fmt.h" #include "SocketRecvBuffer.h" +#include "json.h" +#include "DlAbortEx.h" namespace aria2 { @@ -92,8 +94,16 @@ bool HttpServerBodyCommand::execute() timeoutTimer_ = global::wallclock; if(httpServer_->receiveBody()) { + A2_LOG_DEBUG(fmt("%s", httpServer_->getBody().c_str())); + + std::string reqPath = httpServer_->getRequestPath(); + reqPath.erase(std::find(reqPath.begin(), reqPath.end(), '#'), + reqPath.end()); + std::string query(std::find(reqPath.begin(), reqPath.end(), '?'), + reqPath.end()); + reqPath.erase(reqPath.size()-query.size(), query.size()); // Do something for requestpath and body - if(httpServer_->getRequestPath() == "/rpc") { + if(reqPath == "/rpc") { xmlrpc::XmlRpcRequest req = xmlrpc::XmlRpcRequestProcessor().parseMemory(httpServer_->getBody()); @@ -108,6 +118,50 @@ bool HttpServerBodyCommand::execute() e_->addCommand(command); e_->setNoWait(true); return true; + } else if(reqPath == "/jsonrpc") { + // TODO handle query parameter + std::string callback;// = "callback"; + + SharedHandle<ValueBase> json = json::decode(httpServer_->getBody()); + const Dict* jsondict = asDict(json); + if(!jsondict) { + // TODO code: -32600, Invalid Request + throw DL_ABORT_EX("JSON-RPC Invalid Request"); + } + const String* methodName = asString(jsondict->get("method")); + if(!methodName) { + // TODO Batch request does not have method + throw DL_ABORT_EX("JSON-RPC No Method Found"); + } + SharedHandle<List> params; + const SharedHandle<ValueBase>& tempParams = jsondict->get("params"); + if(asList(tempParams)) { + params = static_pointer_cast<List>(tempParams); + } else if(!tempParams) { + params = List::g(); + } else { + // TODO No support for Named params + throw DL_ABORT_EX("JSON-RPC Named params are not supported"); + } + SharedHandle<ValueBase> id = jsondict->get("id"); + if(!id) { + // TODO Batch request does not have id + throw DL_ABORT_EX("JSON-RPC NO id found"); + } + xmlrpc::XmlRpcRequest req(methodName->s(), params, id); + + SharedHandle<xmlrpc::XmlRpcMethod> method = + xmlrpc::XmlRpcMethodFactory::create(req.methodName); + method->setJsonRpc(true); + xmlrpc::XmlRpcResponse res = method->execute(req, e_); + bool gzip = httpServer_->supportsGZip(); + std::string responseData = res.toJson(callback, gzip); + httpServer_->feedResponse(responseData, "application/json-rpc"); + Command* command = + new HttpServerResponseCommand(getCuid(), httpServer_, e_, socket_); + e_->addCommand(command); + e_->setNoWait(true); + return true; } else { return true; } diff --git a/src/Makefile.am b/src/Makefile.am index 05cb27c4..8a3b3d79 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -210,7 +210,8 @@ SRCS = Socket.h\ NullSinkStreamFilter.cc NullSinkStreamFilter.h\ uri.cc uri.h\ Triplet.h\ - cookie_helper.cc cookie_helper.h + cookie_helper.cc cookie_helper.h\ + json.cc json.h if ENABLE_XML_RPC SRCS += XmlRpcRequestParserController.cc XmlRpcRequestParserController.h\ diff --git a/src/ValueBase.cc b/src/ValueBase.cc index 5048970d..ab388c36 100644 --- a/src/ValueBase.cc +++ b/src/ValueBase.cc @@ -97,6 +97,45 @@ void Integer::accept(ValueBaseVisitor& v) const v.visit(*this); } +const SharedHandle<Bool> Bool::trueValue_(new Bool(true)); +const SharedHandle<Bool> Bool::falseValue_(new Bool(false)); + +SharedHandle<Bool> Bool::gTrue() +{ + return trueValue_; +} + +SharedHandle<Bool> Bool::gFalse() +{ + return falseValue_; +} + +bool Bool::val() const +{ + return val_; +} + +void Bool::accept(ValueBaseVisitor& v) const +{ + v.visit(*this); +} + +Bool::Bool(bool val):val_(val) {} + +const SharedHandle<Null> Null::nullValue_(new Null()); + +SharedHandle<Null> Null::g() +{ + return nullValue_; +} + +void Null::accept(ValueBaseVisitor& v) const +{ + v.visit(*this); +} + +Null::Null() {} + List::List() {} List::~List() {} @@ -262,7 +301,7 @@ void Dict::accept(ValueBaseVisitor& v) const const String* asString(const ValueBase* v) { if(v) { - return downcast<String, Integer, List, Dict>(v); + return downcast<String>(v); } else { return 0; } @@ -271,7 +310,7 @@ const String* asString(const ValueBase* v) String* asString(ValueBase* v) { if(v) { - return const_cast<String*>(downcast<String, Integer, List, Dict>(v)); + return const_cast<String*>(downcast<String>(v)); } else { return 0; } @@ -280,7 +319,7 @@ String* asString(ValueBase* v) String* asString(const SharedHandle<ValueBase>& v) { if(v.get()) { - return const_cast<String*>(downcast<String, Integer, List, Dict>(v)); + return const_cast<String*>(downcast<String>(v)); } else { return 0; } @@ -289,7 +328,7 @@ String* asString(const SharedHandle<ValueBase>& v) const Integer* asInteger(const ValueBase* v) { if(v) { - return downcast<Integer, String, List, Dict>(v); + return downcast<Integer>(v); } else { return 0; } @@ -298,7 +337,7 @@ const Integer* asInteger(const ValueBase* v) Integer* asInteger(ValueBase* v) { if(v) { - return const_cast<Integer*>(downcast<Integer, String, List, Dict>(v)); + return const_cast<Integer*>(downcast<Integer>(v)); } else { return 0; } @@ -307,7 +346,43 @@ Integer* asInteger(ValueBase* v) Integer* asInteger(const SharedHandle<ValueBase>& v) { if(v.get()) { - return const_cast<Integer*>(downcast<Integer, String, List, Dict>(v)); + return const_cast<Integer*>(downcast<Integer>(v)); + } else { + return 0; + } +} + +const Bool* asBool(const ValueBase* v) +{ + if(v) { + return downcast<Bool>(v); + } else { + return 0; + } +} + +Bool* asBool(const SharedHandle<ValueBase>& v) +{ + if(v.get()) { + return const_cast<Bool*>(downcast<Bool>(v)); + } else { + return 0; + } +} + +const Null* asNull(const ValueBase* v) +{ + if(v) { + return downcast<Null>(v); + } else { + return 0; + } +} + +Null* asNull(const SharedHandle<ValueBase>& v) +{ + if(v) { + return const_cast<Null*>(downcast<Null>(v)); } else { return 0; } @@ -316,7 +391,7 @@ Integer* asInteger(const SharedHandle<ValueBase>& v) const List* asList(const ValueBase* v) { if(v) { - return downcast<List, String, Integer, Dict>(v); + return downcast<List>(v); } else { return 0; } @@ -325,7 +400,7 @@ const List* asList(const ValueBase* v) List* asList(ValueBase* v) { if(v) { - return const_cast<List*>(downcast<List, String, Integer, Dict>(v)); + return const_cast<List*>(downcast<List>(v)); } else { return 0; } @@ -334,7 +409,7 @@ List* asList(ValueBase* v) List* asList(const SharedHandle<ValueBase>& v) { if(v.get()) { - return const_cast<List*>(downcast<List, String, Integer, Dict>(v)); + return const_cast<List*>(downcast<List>(v)); } else { return 0; } @@ -343,7 +418,7 @@ List* asList(const SharedHandle<ValueBase>& v) const Dict* asDict(const ValueBase* v) { if(v) { - return downcast<Dict, String, Integer, List>(v); + return downcast<Dict>(v); } else { return 0; } @@ -352,7 +427,7 @@ const Dict* asDict(const ValueBase* v) Dict* asDict(ValueBase* v) { if(v) { - return const_cast<Dict*>(downcast<Dict, String, Integer, List>(v)); + return const_cast<Dict*>(downcast<Dict>(v)); } else { return 0; } @@ -361,7 +436,7 @@ Dict* asDict(ValueBase* v) Dict* asDict(const SharedHandle<ValueBase>& v) { if(v.get()) { - return const_cast<Dict*>(downcast<Dict, String, Integer, List>(v)); + return const_cast<Dict*>(downcast<Dict>(v)); } else { return 0; } diff --git a/src/ValueBase.h b/src/ValueBase.h index a262b853..e13d12d2 100644 --- a/src/ValueBase.h +++ b/src/ValueBase.h @@ -58,19 +58,19 @@ public: class String; class Integer; +class Bool; +class Null; class List; class Dict; class ValueBaseVisitor { public: virtual ~ValueBaseVisitor() {} - virtual void visit(const String& string) = 0; - virtual void visit(const Integer& integer) = 0; - + virtual void visit(const Bool& boolValue) = 0; + virtual void visit(const Null& nullValue) = 0; virtual void visit(const List& list) = 0; - virtual void visit(const Dict& dict) = 0; }; @@ -133,6 +133,34 @@ private: ValueType integer_; }; +class Bool:public ValueBase { +public: + static SharedHandle<Bool> gTrue(); + static SharedHandle<Bool> gFalse(); + bool val() const; + virtual void accept(ValueBaseVisitor& visitor) const; +private: + Bool(bool val); + // Don't allow copying + Bool(const Bool&); + Bool& operator=(const Bool&); + bool val_; + static const SharedHandle<Bool> trueValue_; + static const SharedHandle<Bool> falseValue_; +}; + +class Null:public ValueBase { +public: + static SharedHandle<Null> g(); + virtual void accept(ValueBaseVisitor& visitor) const; +private: + Null(); + // Don't allow copying + Null(const Null&); + Null& operator=(const Null&); + static const SharedHandle<Null> nullValue_; +}; + class List:public ValueBase { public: typedef std::vector<SharedHandle<ValueBase> > ValueType; @@ -256,10 +284,19 @@ private: ValueType dict_; }; -template<typename T, typename T1, typename T2, typename T3> -class DowncastValueBaseVisitor:public ValueBaseVisitor { -private: - const T* result_; +class EmptyDowncastValueBaseVisitor:public ValueBaseVisitor { +public: + EmptyDowncastValueBaseVisitor() {} + virtual void visit(const String& v) {} + virtual void visit(const Integer& v) {} + virtual void visit(const Bool& v) {} + virtual void visit(const Null& v) {} + virtual void visit(const List& v) {} + virtual void visit(const Dict& v) {} +}; + +template<typename T> +class DowncastValueBaseVisitor:public EmptyDowncastValueBaseVisitor { public: DowncastValueBaseVisitor():result_(0) {} @@ -268,10 +305,6 @@ public: result_ = &t; } - virtual void visit(const T1& t1) {} - virtual void visit(const T2& t2) {} - virtual void visit(const T3& t3) {} - const T* getResult() const { return result_; @@ -281,12 +314,14 @@ public: { result_ = r; } +private: + const T* result_; }; -template<typename T, typename T1, typename T2, typename T3, typename VPtr> +template<typename T, typename VPtr> const T* downcast(const VPtr& v) { - DowncastValueBaseVisitor<T, T1, T2, T3> visitor; + DowncastValueBaseVisitor<T> visitor; v->accept(visitor); return visitor.getResult(); } @@ -303,6 +338,14 @@ Integer* asInteger(ValueBase* v); Integer* asInteger(const SharedHandle<ValueBase>& v); +const Bool* asBool(const ValueBase* v); + +Bool* asBool(const SharedHandle<ValueBase>& v); + +const Null* asNull(const ValueBase* v); + +Null* asNull(const SharedHandle<ValueBase>& v); + const List* asList(const ValueBase* v); List* asList(ValueBase* v); diff --git a/src/XmlRpcMethod.cc b/src/XmlRpcMethod.cc index 75b7ebca..7621b0f2 100644 --- a/src/XmlRpcMethod.cc +++ b/src/XmlRpcMethod.cc @@ -53,7 +53,8 @@ namespace aria2 { namespace xmlrpc { XmlRpcMethod::XmlRpcMethod() - : optionParser_(OptionParser::getInstance()) + : optionParser_(OptionParser::getInstance()), + jsonRpc_(false) {} XmlRpcMethod::~XmlRpcMethod() {} @@ -62,8 +63,8 @@ SharedHandle<ValueBase> XmlRpcMethod::createErrorResponse (const Exception& e) { SharedHandle<Dict> params = Dict::g(); - params->put("faultCode", Integer::g(1)); - params->put("faultString", std::string(e.what())); + params->put((jsonRpc_ ? "code" : "faultCode"), Integer::g(1)); + params->put((jsonRpc_ ? "message" : "faultString"), std::string(e.what())); return params; } @@ -71,10 +72,10 @@ XmlRpcResponse XmlRpcMethod::execute (const XmlRpcRequest& req, DownloadEngine* e) { try { - return XmlRpcResponse(0, process(req, e)); + return XmlRpcResponse(0, process(req, e), req.id); } catch(RecoverableException& e) { A2_LOG_DEBUG_EX(EX_EXCEPTION_CAUGHT, e); - return XmlRpcResponse(1, createErrorResponse(e)); + return XmlRpcResponse(1, createErrorResponse(e), req.id); } } diff --git a/src/XmlRpcMethod.h b/src/XmlRpcMethod.h index cbdf65c1..80a49fe5 100644 --- a/src/XmlRpcMethod.h +++ b/src/XmlRpcMethod.h @@ -64,6 +64,7 @@ struct XmlRpcResponse; class XmlRpcMethod { private: SharedHandle<OptionParser> optionParser_; + bool jsonRpc_; protected: // Subclass must implement this function to fulfil XmlRpcRequest // req. The return value of this method is used as a return value @@ -102,6 +103,15 @@ public: // Do work to fulfill XmlRpcRequest req and returns its result as // XmlRpcResponse. This method delegates to process() method. XmlRpcResponse execute(const XmlRpcRequest& req, DownloadEngine* e); + // Set whether JSON-RPC style parameter handling. + void setJsonRpc(bool f) + { + jsonRpc_ = f; + } + bool getJsonRpc() const + { + return jsonRpc_; + } }; } // namespace xmlrpc diff --git a/src/XmlRpcMethodImpl.cc b/src/XmlRpcMethodImpl.cc index 4e2a9007..1b9edb74 100644 --- a/src/XmlRpcMethodImpl.cc +++ b/src/XmlRpcMethodImpl.cc @@ -63,6 +63,7 @@ #include "SegmentMan.h" #include "TimedHaltCommand.h" #include "PeerStat.h" +#include "Base64.h" #ifdef ENABLE_BITTORRENT # include "bittorrent_helper.h" # include "BtRegistry.h" @@ -266,7 +267,11 @@ SharedHandle<ValueBase> AddTorrentXmlRpcMethod::process if(!torrentParam) { throw DL_ABORT_EX("Torrent data is not provided."); } - + SharedHandle<String> tempTorrentParam; + if(getJsonRpc()) { + tempTorrentParam = String::g(Base64::decode(torrentParam->s())); + torrentParam = tempTorrentParam.get(); + } std::vector<std::string> uris; extractUris(std::back_inserter(uris), req.getListParam(1)); @@ -309,7 +314,11 @@ SharedHandle<ValueBase> AddMetalinkXmlRpcMethod::process if(!metalinkParam) { throw DL_ABORT_EX("Metalink data is not provided."); } - + SharedHandle<String> tempMetalinkParam; + if(getJsonRpc()) { + tempMetalinkParam = String::g(Base64::decode(metalinkParam->s())); + metalinkParam = tempMetalinkParam.get(); + } SharedHandle<Option> requestOption(new Option(*e->getOption())); gatherRequestOption(requestOption, req.getDictParam(1)); diff --git a/src/XmlRpcRequest.cc b/src/XmlRpcRequest.cc index 08d58b68..0e361499 100644 --- a/src/XmlRpcRequest.cc +++ b/src/XmlRpcRequest.cc @@ -43,8 +43,14 @@ XmlRpcRequest::XmlRpcRequest(const std::string& methodName, : methodName(methodName), params(params) {} +XmlRpcRequest::XmlRpcRequest(const std::string& methodName, + const SharedHandle<List>& params, + const SharedHandle<ValueBase>& id) + : methodName(methodName), params(params), id(id) +{} + XmlRpcRequest::XmlRpcRequest(const XmlRpcRequest& c) - : methodName(c.methodName), params(c.params) + : methodName(c.methodName), params(c.params), id(c.id) {} XmlRpcRequest::~XmlRpcRequest() {} diff --git a/src/XmlRpcRequest.h b/src/XmlRpcRequest.h index 5b654e03..327b2904 100644 --- a/src/XmlRpcRequest.h +++ b/src/XmlRpcRequest.h @@ -48,10 +48,15 @@ namespace xmlrpc { struct XmlRpcRequest { std::string methodName; SharedHandle<List> params; + SharedHandle<ValueBase> id; XmlRpcRequest(const std::string& methodName, const SharedHandle<List>& params); + XmlRpcRequest(const std::string& methodName, + const SharedHandle<List>& params, + const SharedHandle<ValueBase>& id); + ~XmlRpcRequest(); XmlRpcRequest(const XmlRpcRequest& c); diff --git a/src/XmlRpcResponse.cc b/src/XmlRpcResponse.cc index 6cd47afc..107efa01 100644 --- a/src/XmlRpcResponse.cc +++ b/src/XmlRpcResponse.cc @@ -38,6 +38,7 @@ #include <sstream> #include "util.h" +#include "json.h" #ifdef HAVE_ZLIB # include "GZipEncoder.h" #endif // HAVE_ZLIB @@ -68,6 +69,10 @@ void encodeValue(const SharedHandle<ValueBase>& value, OutputStream& o) o_ << "<value><int>" << v.i() << "</int></value>"; } + virtual void visit(const Bool& boolValue) {} + + virtual void visit(const Null& nullValue) {} + virtual void visit(const List& v) { o_ << "<value><array><data>"; @@ -117,13 +122,16 @@ std::string encodeAll } // namespace XmlRpcResponse::XmlRpcResponse -(int code, const SharedHandle<ValueBase>& param) - : code(code), param(param) +(int code, + const SharedHandle<ValueBase>& param, + const SharedHandle<ValueBase>& id) + : code(code), param(param), id(id) {} XmlRpcResponse::XmlRpcResponse(const XmlRpcResponse& c) : code(c.code), - param(c.param) + param(c.param), + id(c.id) {} XmlRpcResponse::~XmlRpcResponse() {} @@ -153,6 +161,53 @@ std::string XmlRpcResponse::toXml(bool gzip) const } } +namespace { +template<typename OutputStream> +OutputStream& encodeJsonAll +(OutputStream& o, + int code, + const SharedHandle<ValueBase>& param, + const SharedHandle<ValueBase>& id, + const std::string& callback) +{ + if(!callback.empty()) { + o << callback << '('; + } + SharedHandle<Dict> dict = Dict::g(); + dict->put("jsonrpc", "2.0"); + // TODO id may be null? + if(id) { + dict->put("id", id); + } + if(code == 0) { + dict->put("result", param); + } else { + dict->put("error", param); + } + json::encode(o, dict).str(); + if(!callback.empty()) { + o << ')'; + } + return o; +} +} // namespace + +std::string XmlRpcResponse::toJson(const std::string& callback, bool gzip) const +{ + if(gzip) { +#ifdef HAVE_ZLIB + GZipEncoder o; + o.init(); + return encodeJsonAll(o, code, param, id, callback).str(); +#else // !HAVE_ZLIB + abort(); +#endif // !HAVE_ZLIB + } else { + std::stringstream o; + return encodeJsonAll(o, code, param, id, callback).str(); + } +} + } // namespace xmlrpc } // namespace aria2 diff --git a/src/XmlRpcResponse.h b/src/XmlRpcResponse.h index eea95e0d..8a978a3d 100644 --- a/src/XmlRpcResponse.h +++ b/src/XmlRpcResponse.h @@ -51,7 +51,12 @@ struct XmlRpcResponse { SharedHandle<ValueBase> param; - XmlRpcResponse(int code, const SharedHandle<ValueBase>& param); + SharedHandle<ValueBase> id; + + XmlRpcResponse + (int code, + const SharedHandle<ValueBase>& param, + const SharedHandle<ValueBase>& id); XmlRpcResponse(const XmlRpcResponse& c); @@ -60,6 +65,10 @@ struct XmlRpcResponse { XmlRpcResponse& operator=(const XmlRpcResponse& c); std::string toXml(bool gzip = false) const; + + // Encodes RPC response in JSON. If callback is not empty, the + // resulting string is JSONP. + std::string toJson(const std::string& callback, bool gzip = false) const; }; } // namespace xmlrpc diff --git a/src/bencode2.cc b/src/bencode2.cc index 10b39cda..0e24bfcf 100644 --- a/src/bencode2.cc +++ b/src/bencode2.cc @@ -249,6 +249,9 @@ std::string encode(const ValueBase* vlb) out_ << "i" << integer.i() << "e"; } + virtual void visit(const Bool& v) {} + virtual void visit(const Null& v) {} + virtual void visit(const List& list) { out_ << "l"; diff --git a/src/bittorrent_helper.cc b/src/bittorrent_helper.cc index bae8abd9..c1efcfb7 100644 --- a/src/bittorrent_helper.cc +++ b/src/bittorrent_helper.cc @@ -150,6 +150,8 @@ void extractUrlList } virtual void visit(const Integer& v) {} + virtual void visit(const Bool& v) {} + virtual void visit(const Null& v) {} virtual void visit(const List& v) { diff --git a/src/bittorrent_helper.h b/src/bittorrent_helper.h index 1dffe960..f9463a92 100644 --- a/src/bittorrent_helper.h +++ b/src/bittorrent_helper.h @@ -283,6 +283,8 @@ void extractPeer(const ValueBase* peerData, int family, OutputIterator dest) } virtual void visit(const Integer& v) {} + virtual void visit(const Bool& v) {} + virtual void visit(const Null& v) {} virtual void visit(const List& peerData) { diff --git a/src/error_code.h b/src/error_code.h index 18abc6f0..a721a3cc 100644 --- a/src/error_code.h +++ b/src/error_code.h @@ -72,7 +72,8 @@ enum Value { BITTORRENT_PARSE_ERROR = 26, MAGNET_PARSE_ERROR = 27, OPTION_ERROR = 28, - HTTP_SERVICE_UNAVAILABLE = 29 + HTTP_SERVICE_UNAVAILABLE = 29, + JSON_PARSE_ERROR = 30 }; } // namespace error_code 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 diff --git a/src/json.h b/src/json.h new file mode 100644 index 00000000..186f3a4c --- /dev/null +++ b/src/json.h @@ -0,0 +1,141 @@ +/* <!-- 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 --> */ +#ifndef D_JSON_H +#define D_JSON_H + +#include "common.h" +#include "ValueBase.h" + +namespace aria2 { + +namespace json { + +// Parses JSON text defined in RFC4627. +SharedHandle<ValueBase> decode(const std::string& json); + +std::string jsonEscape(const std::string& s); + +template<typename OutputStream> +OutputStream& encode(OutputStream& out, const ValueBase* vlb) +{ + class JsonValueBaseVisitor:public ValueBaseVisitor { + public: + JsonValueBaseVisitor(OutputStream& out):out_(out) {} + + 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_ << '}'; + } + private: + OutputStream& out_; + }; + JsonValueBaseVisitor visitor(out); + vlb->accept(visitor); + return out; +} + +template<typename OutputStream> +OutputStream& encode(OutputStream& out, const SharedHandle<ValueBase>& vlb) +{ + return encode(out, vlb.get()); +} + +// Serializes JSON object or array. +std::string encode(const ValueBase* json); +std::string encode(const SharedHandle<ValueBase>& json); + +} // namespace json + +} // namespace aria2 + + +#endif // D_JSON_H diff --git a/test/JsonTest.cc b/test/JsonTest.cc new file mode 100644 index 00000000..856b4ea2 --- /dev/null +++ b/test/JsonTest.cc @@ -0,0 +1,446 @@ +#include "json.h" + +#include <cppunit/extensions/HelperMacros.h> + +#include "RecoverableException.h" +#include "util.h" +#include "array_fun.h" + +namespace aria2 { + +class JsonTest:public CppUnit::TestFixture { + + CPPUNIT_TEST_SUITE(JsonTest); + CPPUNIT_TEST(testDecode); + CPPUNIT_TEST(testDecode_error); + CPPUNIT_TEST(testEncode); + CPPUNIT_TEST_SUITE_END(); +private: + +public: + void testDecode(); + void testDecode_error(); + void testEncode(); +}; + +CPPUNIT_TEST_SUITE_REGISTRATION( JsonTest ); + +void JsonTest::testDecode() +{ + { + // empty object + SharedHandle<ValueBase> r = json::decode("{}"); + const Dict* dict = asDict(r); + CPPUNIT_ASSERT(dict); + } + { + // empty object + SharedHandle<ValueBase> r = json::decode("{ }"); + const Dict* dict = asDict(r); + CPPUNIT_ASSERT(dict); + } + { + // empty array + SharedHandle<ValueBase> r = json::decode("[]"); + const List* list = asList(r); + CPPUNIT_ASSERT(list); + } + { + // empty array + SharedHandle<ValueBase> r = json::decode("[ ]"); + const List* list = asList(r); + CPPUNIT_ASSERT(list); + } + { + // empty string + SharedHandle<ValueBase> r = json::decode("[\"\"]"); + const List* list = asList(r); + CPPUNIT_ASSERT(list); + const String* s = asString(list->get(0)); + CPPUNIT_ASSERT_EQUAL(std::string(), s->s()); + } + { + // string + SharedHandle<ValueBase> r = json::decode("[\"foobar\"]"); + const List* list = asList(r); + CPPUNIT_ASSERT(list); + const String* s = asString(list->get(0)); + CPPUNIT_ASSERT_EQUAL(std::string("foobar"), s->s()); + } + { + // string with escape + SharedHandle<ValueBase> r = json::decode("[\"\\\\foo\\\"\\\"bar\"]"); + const List* list = asList(r); + CPPUNIT_ASSERT(list); + const String* s = asString(list->get(0)); + CPPUNIT_ASSERT_EQUAL(std::string("\\foo\"\"bar"), s->s()); + } + { + // string with escape + SharedHandle<ValueBase> r = json::decode("[\"foo\\\"\"]"); + const List* list = asList(r); + CPPUNIT_ASSERT(list); + const String* s = asString(list->get(0)); + CPPUNIT_ASSERT_EQUAL(std::string("foo\""), s->s()); + } + { + // string: utf-8 1 to 3 bytes. + SharedHandle<ValueBase> r = json::decode("[\"\\u0024\\u00A2\\u20AC\"]"); + const List* list = asList(r); + CPPUNIT_ASSERT(list); + const String* s = asString(list->get(0)); + CPPUNIT_ASSERT_EQUAL(std::string("$¢€"), s->s()); + } + { + // string: utf-8 4 bytes + SharedHandle<ValueBase> r = json::decode("[\"\\uD852\\uDF62\"]"); + const List* list = asList(r); + CPPUNIT_ASSERT(list); + const String* s = asString(list->get(0)); + const char arr[] = { 0xF0u, 0xA4u, 0xADu, 0xA2u }; + CPPUNIT_ASSERT_EQUAL(std::string(vbegin(arr), vend(arr)), s->s()); + } + { + // null + SharedHandle<ValueBase> r = json::decode("[null]"); + const List* list = asList(r); + CPPUNIT_ASSERT(list); + const Null* s = asNull(list->get(0)); + CPPUNIT_ASSERT(s); + } + { + // true, false + SharedHandle<ValueBase> r = json::decode("[true, false]"); + const List* list = asList(r); + CPPUNIT_ASSERT(list); + const Bool* trueValue = asBool(list->get(0)); + CPPUNIT_ASSERT(trueValue); + CPPUNIT_ASSERT(trueValue->val()); + const Bool* falseValue = asBool(list->get(1)); + CPPUNIT_ASSERT(falseValue); + CPPUNIT_ASSERT(!falseValue->val()); + } + { + // object: 1 member + SharedHandle<ValueBase> r = json::decode("{\"foo\":[\"bar\"]}"); + const Dict* dict = asDict(r); + CPPUNIT_ASSERT(dict); + const List* list = asList(dict->get("foo")); + CPPUNIT_ASSERT(list); + const String* s = asString(list->get(0)); + CPPUNIT_ASSERT_EQUAL(std::string("bar"), s->s()); + } + { + // object: 2 members + SharedHandle<ValueBase> r = json::decode("{\"\":[\"bar\"], " + "\"alpha\" : \"bravo\"}"); + const Dict* dict = asDict(r); + CPPUNIT_ASSERT(dict); + const List* list = asList(dict->get("")); + CPPUNIT_ASSERT(list); + const String* s = asString(list->get(0)); + CPPUNIT_ASSERT_EQUAL(std::string("bar"), s->s()); + const String* str = asString(dict->get("alpha")); + CPPUNIT_ASSERT_EQUAL(std::string("bravo"), str->s()); + } + { + // array: 2 values + SharedHandle<ValueBase> r = json::decode("[\"foo\", {}]"); + const List* list = asList(r); + CPPUNIT_ASSERT(list); + const String* s = asString(list->get(0)); + CPPUNIT_ASSERT_EQUAL(std::string("foo"), s->s()); + const Dict* dict = asDict(list->get(1)); + CPPUNIT_ASSERT(dict); + } + { + // Number: currently we handle floating point number as string + SharedHandle<ValueBase> r = json::decode("[0,-1,1.2,-1.2e-10,-1e10]"); + const List* list = asList(r); + CPPUNIT_ASSERT(list); + const Integer* i = asInteger(list->get(0)); + CPPUNIT_ASSERT_EQUAL((Integer::ValueType)0, i->i()); + const Integer* i1 = asInteger(list->get(1)); + CPPUNIT_ASSERT_EQUAL((Integer::ValueType)-1, i1->i()); + const String* s2 = asString(list->get(2)); + CPPUNIT_ASSERT_EQUAL(std::string("1.2"), s2->s()); + const String* s3 = asString(list->get(3)); + CPPUNIT_ASSERT_EQUAL(std::string("-1.2e-10"), s3->s()); + const String* s4 = asString(list->get(4)); + CPPUNIT_ASSERT_EQUAL(std::string("-1e10"), s4->s()); + } +} + +void JsonTest::testDecode_error() +{ + { + try { + // object + SharedHandle<ValueBase> r = json::decode("{"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // object + SharedHandle<ValueBase> r = json::decode("}"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // object + SharedHandle<ValueBase> r = json::decode("{\"\":"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // object + SharedHandle<ValueBase> r = json::decode("{\"\":\"\","); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // array + SharedHandle<ValueBase> r = json::decode("["); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // array + SharedHandle<ValueBase> r = json::decode("]"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // array + SharedHandle<ValueBase> r = json::decode("[\"\""); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // array + SharedHandle<ValueBase> r = json::decode("[\"\","); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // string + SharedHandle<ValueBase> r = json::decode("[\"foo]"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // string + SharedHandle<ValueBase> r = json::decode("[\"\\u\"]"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // string + SharedHandle<ValueBase> r = json::decode("[\"\\u"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // string + SharedHandle<ValueBase> r = json::decode("[\"\\u000\"]"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // string + SharedHandle<ValueBase> r = json::decode("[\"\\u000"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // string + SharedHandle<ValueBase> r = json::decode("[\"\\uD852foo\"]"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // string + SharedHandle<ValueBase> r = json::decode("[\"\\uD852"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // string + SharedHandle<ValueBase> r = json::decode("[\"\\uD852\\u\"]"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // string + SharedHandle<ValueBase> r = json::decode("[\"\\uD852\\u"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // string + SharedHandle<ValueBase> r = json::decode("[\"\\uD852\\u0000\"]"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // string + SharedHandle<ValueBase> r = json::decode("[\"\\uD852\\uDF62"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // object + SharedHandle<ValueBase> r = json::decode("{:\"\"}"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // object + SharedHandle<ValueBase> r = json::decode("{\"foo\":}"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // number + SharedHandle<ValueBase> r = json::decode("{00}"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // number + SharedHandle<ValueBase> r = json::decode("{1.}"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // number + SharedHandle<ValueBase> r = json::decode("{1.1e}"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } + { + try { + // bool + SharedHandle<ValueBase> r = json::decode("{t"); + CPPUNIT_FAIL("exception must be thrown."); + } catch(RecoverableException& e) { + // success + } + } +} + +void JsonTest::testEncode() +{ + { + Dict dict; + dict["name"] = String::g("aria2"); + dict["loc"] = Integer::g(80000); + SharedHandle<List> files = List::g(); + files->append(String::g("aria2c")); + dict["files"] = files; + SharedHandle<Dict> attrs = Dict::g(); + attrs->put("license", String::g("GPL")); + dict["attrs"] = attrs; + + CPPUNIT_ASSERT_EQUAL(std::string("{\"attrs\":{\"license\":\"GPL\"}," + "\"files\":[\"aria2c\"]," + "\"loc\":80000," + "\"name\":\"aria2\"}"), + json::encode(&dict)); + } + { + List list; + list.append("\"\\/\b\f\n\r\t"); + CPPUNIT_ASSERT_EQUAL(std::string("[\"\\\"\\\\\\/\\b\\f\\n\\r\\t\"]"), + json::encode(&list)); + } + { + List list; + std::string s; + s += 0x1Fu; + list.append(s); + CPPUNIT_ASSERT_EQUAL(std::string("[\"\\u001F\"]"), + json::encode(&list)); + } + { + List list; + list.append(Bool::gTrue()); + list.append(Bool::gFalse()); + list.append(Null::g()); + CPPUNIT_ASSERT_EQUAL(std::string("[true,false,null]"), + json::encode(&list)); + } +} + +} // namespace aria2 diff --git a/test/Makefile.am b/test/Makefile.am index 7633d8ed..7b2d1b33 100644 --- a/test/Makefile.am +++ b/test/Makefile.am @@ -76,7 +76,8 @@ aria2c_SOURCES = AllTest.cc\ UriTest.cc\ MockSegment.h\ TripletTest.cc\ - CookieHelperTest.cc + CookieHelperTest.cc\ + JsonTest.cc if ENABLE_XML_RPC aria2c_SOURCES += XmlRpcRequestParserControllerTest.cc\ |
