Procházet zdrojové kódy

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.
Tatsuhiro Tsujikawa před 14 roky
rodič
revize
7338a25035

+ 6 - 0
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

+ 4 - 0
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.

+ 55 - 1
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;
         }

+ 2 - 1
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\

+ 87 - 12
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;
   }

+ 57 - 14
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);

+ 6 - 5
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);
   }
 }
 

+ 10 - 0
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

+ 11 - 2
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));
 

+ 7 - 1
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() {}

+ 5 - 0
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);

+ 58 - 3
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

+ 10 - 1
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

+ 3 - 0
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";

+ 2 - 0
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)
     {

+ 2 - 0
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)
     {

+ 2 - 1
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

+ 612 - 0
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

+ 141 - 0
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

+ 446 - 0
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

+ 2 - 1
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\