Преглед на файлове

Implement new RPC authorization using --rpc-secret option

Tatsuhiro Tsujikawa преди 11 години
родител
ревизия
7f6987a4b4
променени са 12 файла, в които са добавени 232 реда и са изтрити 79 реда
  1. 68 36
      doc/manual-src/en/aria2c.rst
  2. 49 36
      doc/xmlrpc/aria2rpc
  3. 8 0
      src/OptionHandlerFactory.cc
  4. 25 0
      src/RpcMethod.cc
  5. 2 0
      src/RpcMethod.h
  6. 8 0
      src/RpcMethodImpl.h
  7. 10 0
      src/ValueBase.cc
  8. 8 2
      src/ValueBase.h
  9. 2 0
      src/prefs.cc
  10. 2 0
      src/prefs.h
  11. 7 5
      src/usage_text.h
  12. 43 0
      test/RpcMethodTest.cc

+ 68 - 36
doc/manual-src/en/aria2c.rst

@@ -926,9 +926,9 @@ RPC Options
 
 .. option:: --enable-rpc[=true|false]
 
-  Enable JSON-RPC/XML-RPC server.  It is strongly recommended to set username
-  and password using :option:`--rpc-user` and :option:`--rpc-passwd`
-  option. See also :option:`--rpc-listen-port` option.  Default: ``false``
+  Enable JSON-RPC/XML-RPC server.  It is strongly recommended to set
+  secret authorization token using :option:`--rpc-secret` option.  See
+  also :option:`--rpc-listen-port` option.  Default: ``false``
 
 .. option:: --pause[=true|false]
 
@@ -1004,6 +1004,11 @@ RPC Options
   :func:`aria2.addTorrent` or :func:`aria2.addMetalink` will not be
   saved by :option:`--save-session` option. Default: ``false``
 
+.. option:: --rpc-secret=<TOKEN>
+
+   Set RPC secret authorization token. Read :ref:`rpc_auth` to know
+   how this option value is used.
+
 .. option:: --rpc-secure[=true|false]
 
   RPC transport will be encrypted by SSL/TLS.  The RPC clients must
@@ -2021,13 +2026,40 @@ GID
   <--gid>` option. When querying download by GID, you can specify the
   prefix of GID as long as it is a unique prefix among others.
 
+.. _rpc_auth:
+
+RPC authorization secret token
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As of 1.18.4, in addition to HTTP basic authorization, aria2 provides
+RPC method-level authorization. In the future release, HTTP basic
+authorization will be removed and RPC method-level authorization will
+become mandatory.
+
+To use RPC method-level authorization, user has to specify RPC secret
+authorization token using :option:`--rpc-secret` option. For each RPC
+method call, the caller has to include the token prefixed with
+``token:``. If :option:`--rpc-secret` option is not used and first
+parameter in the RPC method is a String and starts with ``token:``, it
+is removed from the parameter before being processed.
+
+For example, if RPC secret authorization token is ``$$secret$$``, to
+call `aria2.addUri` RPC method would look like this::
+
+  aria2.addUri("token::$$secret$$", ["http://example.org/file"])
+
+The `system.multicall` RPC method is treated specially. Since XML-RPC
+specification only allows one array as a paremter for this method, we
+don't specify token in its call. Instead, each nested method call has
+to provide token as 1st parameter as described above.
+
 Methods
 ~~~~~~~
 
 All code examples come from Python2.7 interpreter.
+For *secret* parameter, see :ref:`rpc_auth`.
 
-
-.. function:: aria2.addUri(uris[, options[, position]])
+.. function:: aria2.addUri([secret], uris[, options[, position]])
 
   This method adds new HTTP(S)/FTP/BitTorrent Magnet URI.  *uris* is of
   type array and its element is URI which is of type string.  For
@@ -2075,7 +2107,7 @@ All code examples come from Python2.7 interpreter.
     >>> s.aria2.addUri(['http://example.org/file'], {}, 0)
     'ca3d829cee549a4d'
 
-.. function:: aria2.addTorrent(torrent[, uris[, options[, position]]])
+.. function:: aria2.addTorrent([secret], torrent[, uris[, options[, position]]])
 
   This method adds BitTorrent download by uploading ".torrent" file.
   If you want to add BitTorrent Magnet URI, use :func:`aria2.addUri`
@@ -2125,7 +2157,7 @@ All code examples come from Python2.7 interpreter.
     >>> s.aria2.addTorrent(xmlrpclib.Binary(open('file.torrent').read()))
     '2089b05ecca3d829'
 
-.. function:: aria2.addMetalink(metalink[, options[, position]])
+.. function:: aria2.addMetalink([secret], metalink[, options[, position]])
 
   This method adds Metalink download by uploading ".metalink" file.
   *metalink* is of type base64 which contains Base64-encoded
@@ -2170,7 +2202,7 @@ All code examples come from Python2.7 interpreter.
     >>> s.aria2.addMetalink(xmlrpclib.Binary(open('file.meta4').read()))
     ['2089b05ecca3d829']
 
-.. function:: aria2.remove(gid)
+.. function:: aria2.remove([secret], gid)
 
   This method removes the download denoted by *gid*. *gid* is of type
   string. If specified download is in progress, it is stopped at
@@ -2200,14 +2232,14 @@ All code examples come from Python2.7 interpreter.
     >>> s.aria2.remove('2089b05ecca3d829')
     '2089b05ecca3d829'
 
-.. function:: aria2.forceRemove(gid)
+.. function:: aria2.forceRemove([secret], gid)
 
   This method removes the download denoted by *gid*.  This method
   behaves just like :func:`aria2.remove` except that this method removes
   download without any action which takes time such as contacting
   BitTorrent tracker.
 
-.. function:: aria2.pause(gid)
+.. function:: aria2.pause([secret], gid)
 
   This method pauses the download denoted by *gid*. *gid* is of type
   string. The status of paused download becomes ``paused``.  If the
@@ -2216,36 +2248,36 @@ All code examples come from Python2.7 interpreter.
   started.  To change status to ``waiting``, use :func:`aria2.unpause` method.
   This method returns GID of paused download.
 
-.. function:: aria2.pauseAll()
+.. function:: aria2.pauseAll([secret])
 
   This method is equal to calling :func:`aria2.pause` for every active/waiting
   download. This methods returns ``OK`` for success.
 
-.. function:: aria2.forcePause(pid)
+.. function:: aria2.forcePause([secret], pid)
 
   This method pauses the download denoted by *gid*.  This method
   behaves just like :func:`aria2.pause` except that this method pauses
   download without any action which takes time such as contacting
   BitTorrent tracker.
 
-.. function:: aria2.forcePauseAll()
+.. function:: aria2.forcePauseAll([secret])
 
   This method is equal to calling :func:`aria2.forcePause` for every
   active/waiting download. This methods returns ``OK`` for success.
 
-.. function:: aria2.unpause(gid)
+.. function:: aria2.unpause([secret], gid)
 
   This method changes the status of the download denoted by *gid* from
   ``paused`` to ``waiting``. This makes the download eligible to restart.
   *gid* is of type string.  This method returns GID of unpaused
   download.
 
-.. function:: aria2.unpauseAll()
+.. function:: aria2.unpauseAll([secret])
 
   This method is equal to calling :func:`aria2.unpause` for every active/waiting
   download. This methods returns ``OK`` for success.
 
-.. function:: aria2.tellStatus(gid[, keys])
+.. function:: aria2.tellStatus([secret], gid[, keys])
 
   This method returns download progress of the download denoted by
   *gid*. *gid* is of type string. *keys* is array of string. If it is
@@ -2443,7 +2475,7 @@ All code examples come from Python2.7 interpreter.
     >>> pprint(r)
     {'completedLength': '34896138', 'gid': '2089b05ecca3d829', 'totalLength': '34896138'}
 
-.. function:: aria2.getUris(gid)
+.. function:: aria2.getUris([secret], gid)
 
   This method returns URIs used in the download denoted by *gid*.  *gid*
   is of type string. The response is of type array and its element is of
@@ -2481,7 +2513,7 @@ All code examples come from Python2.7 interpreter.
     >>> pprint(r)
     [{'status': 'used', 'uri': 'http://example.org/file'}]
 
-.. function:: aria2.getFiles(gid)
+.. function:: aria2.getFiles([secret], gid)
 
   This method returns file list of the download denoted by *gid*. *gid*
   is of type string. The response is of type array and its element is of
@@ -2553,7 +2585,7 @@ All code examples come from Python2.7 interpreter.
       'uris': [{'status': 'used',
                 'uri': 'http://example.org/file'}]}]
 
-.. function:: aria2.getPeers(gid)
+.. function:: aria2.getPeers([secret], gid)
 
   This method returns peer list of the download denoted by *gid*. *gid*
   is of type string. This method is for BitTorrent only.  The response
@@ -2648,7 +2680,7 @@ All code examples come from Python2.7 interpreter.
       'seeder': 'false,
       'uploadSpeed': '6890'}]
 
-.. function:: aria2.getServers(gid)
+.. function:: aria2.getServers([secret], gid)
 
   This method returns currently connected HTTP(S)/FTP servers of the download denoted by *gid*. *gid* is of type string. The response
   is of type array and its element is of type struct and it contains
@@ -2701,14 +2733,14 @@ All code examples come from Python2.7 interpreter.
                    'downloadSpeed': '20285',
                    'uri': 'http://example.org/file'}]}]
 
-.. function:: aria2.tellActive([keys])
+.. function:: aria2.tellActive([secret], [keys])
 
   This method returns the list of active downloads.  The response is of
   type array and its element is the same struct returned by
   :func:`aria2.tellStatus` method. For *keys* parameter, please refer to
   :func:`aria2.tellStatus` method.
 
-.. function:: aria2.tellWaiting(offset, num, [keys])
+.. function:: aria2.tellWaiting([secret], offset, num, [keys])
 
   This method returns the list of waiting download, including paused
   downloads. *offset* is of type integer and specifies the offset from
@@ -2732,7 +2764,7 @@ All code examples come from Python2.7 interpreter.
   The response is of type array and its element is the same struct
   returned by :func:`aria2.tellStatus` method.
 
-.. function:: aria2.tellStopped(offset, num, [keys])
+.. function:: aria2.tellStopped([secret], offset, num, [keys])
 
   This method returns the list of stopped download.  *offset* is of type
   integer and specifies the offset from the oldest download. *num* is of
@@ -2745,7 +2777,7 @@ All code examples come from Python2.7 interpreter.
   The response is of type array and its element is the same struct
   returned by :func:`aria2.tellStatus` method.
 
-.. function:: aria2.changePosition(gid, pos, how)
+.. function:: aria2.changePosition([secret], gid, pos, how)
 
   This method changes the position of the download denoted by
   *gid*. *pos* is of type integer. *how* is of type string. If *how* is
@@ -2789,7 +2821,7 @@ All code examples come from Python2.7 interpreter.
     >>> s.aria2.changePosition('2089b05ecca3d829', 0, 'POS_SET')
     0
 
-.. function:: aria2.changeUri(gid, fileIndex, delUris, addUris[, position])
+.. function:: aria2.changeUri([secret], gid, fileIndex, delUris, addUris[, position])
 
   This method removes URIs in *delUris* from and appends URIs in
   *addUris* to download denoted by *gid*. *delUris* and *addUris* are
@@ -2837,7 +2869,7 @@ All code examples come from Python2.7 interpreter.
                           ['http://example.org/file'])
     [0, 1]
 
-.. function:: aria2.getOption(gid)
+.. function:: aria2.getOption([secret], gid)
 
   This method returns options of the download denoted by *gid*.  The
   response is of type struct. Its key is the name of option.  The value
@@ -2882,7 +2914,7 @@ All code examples come from Python2.7 interpreter.
      'async-dns': 'true',
      ....
 
-.. function:: aria2.changeOption(gid, options)
+.. function:: aria2.changeOption([secret], gid, options)
 
   This method changes options of the download denoted by *gid*
   dynamically.  *gid* is of type string.  *options* is of type struct.
@@ -2933,7 +2965,7 @@ All code examples come from Python2.7 interpreter.
     >>> s.aria2.changeOption('2089b05ecca3d829', {'max-download-limit':'20K'})
     'OK'
 
-.. function:: aria2.getGlobalOption()
+.. function:: aria2.getGlobalOption([secret])
 
   This method returns global options.  The response is of type
   struct. Its key is the name of option.  The value type is string.
@@ -2943,7 +2975,7 @@ All code examples come from Python2.7 interpreter.
   for the options of newly added download, the response contains keys
   returned by :func:`aria2.getOption` method.
 
-.. function:: aria2.changeGlobalOption(options)
+.. function:: aria2.changeGlobalOption([secret], options)
 
   This method changes global options dynamically.  *options* is of type
   struct.
@@ -2974,7 +3006,7 @@ All code examples come from Python2.7 interpreter.
   value. Note that log file is always opened in append mode. This method
   returns ``OK`` for success.
 
-.. function:: aria2.getGlobalStat()
+.. function:: aria2.getGlobalStat([secret])
 
   This method returns global statistics such as overall download and
   upload speed. The response is of type struct and contains following
@@ -3026,12 +3058,12 @@ All code examples come from Python2.7 interpreter.
      'numWaiting': '0',
      'uploadSpeed': '0'}
 
-.. function:: aria2.purgeDownloadResult()
+.. function:: aria2.purgeDownloadResult([secret])
 
   This method purges completed/error/removed downloads to free memory.
   This method returns ``OK``.
 
-.. function:: aria2.removeDownloadResult(gid)
+.. function:: aria2.removeDownloadResult([secret], gid)
 
   This method removes completed/error/removed download denoted by *gid*
   from memory. This method returns ``OK`` for success.
@@ -3061,7 +3093,7 @@ All code examples come from Python2.7 interpreter.
     >>> s.aria2.removeDownloadResult('2089b05ecca3d829')
     'OK'
 
-.. function:: aria2.getVersion()
+.. function:: aria2.getVersion([secret])
 
   This method returns version of the program and the list of enabled
   features. The response is of type struct and contains following keys.
@@ -3111,7 +3143,7 @@ All code examples come from Python2.7 interpreter.
                          'XML-RPC'],
      'version': '1.11.0'}
 
-.. function:: aria2.getSessionInfo()
+.. function:: aria2.getSessionInfo([secret])
 
   This method returns session information.
   The response is of type struct and contains following key.
@@ -3140,11 +3172,11 @@ All code examples come from Python2.7 interpreter.
     >>> s.aria2.getSessionInfo()
     {'sessionId': 'cd6a3bc6a1de28eb5bfa181e5f6b916d44af31a9'}
 
-.. function:: aria2.shutdown()
+.. function:: aria2.shutdown([secret])
 
   This method shutdowns aria2.  This method returns ``OK``.
 
-.. function:: aria2.forceShutdown()
+.. function:: aria2.forceShutdown([secret])
 
   This method shutdowns :func:`aria2. This method behaves like  aria2.shutdown`
   except that any actions which takes time such as contacting BitTorrent

+ 49 - 36
doc/xmlrpc/aria2rpc

@@ -239,6 +239,8 @@ OptionParser.new do |opt|
   opt.on("--user USERNAME", "XML-RPC username"){|val| options["user"]=val }
   opt.on("--passwd PASSWORD", "XML-RPC password"){|val| options["passwd"]=val }
 
+  opt.on("--secret SECRET", "XML-RPC secret authorization token"){|val| options["secret"]=val }
+
   opt.banner=<<EOS
 Usage: #{program_name} addUri URI... [options]
        #{program_name} addTorrent /path/to/torrent_file URI... [options]
@@ -300,6 +302,7 @@ end
 if not options.has_key?("port") then
   options["port"]="6800"
 end
+secret = if options.has_key?("secret") then "token:"+options["secret"] else nil end
 
 client=XMLRPC::Client.new3({:host => options["server"],
                              :port => options["port"],
@@ -311,79 +314,89 @@ options.delete("server")
 options.delete("port")
 options.delete("user")
 options.delete("passwd")
+options.delete("secret")
+
+def client_call client, secret, method, *params
+  if secret.nil?
+    client.call(method, *params)
+  else
+    client.call(method, secret, *params)
+  end
+end
 
 if command == "addUri" then
-  result=client.call("aria2."+command, resources, options)
+  result=client_call(client, secret, "aria2."+command, resources, options)
 elsif command == "addTorrent" then
   torrentData=IO.read(resources[0])
-  result=client.call("aria2."+command,
+  result=client_call(client, secret, "aria2."+command,
                      XMLRPC::Base64.new(torrentData), resources[1..-1], options)
 elsif command == "addMetalink" then
   metalinkData=IO.read(resources[0])
-  result=client.call("aria2."+command,
+  result=client_call(client, secret, "aria2."+command,
                      XMLRPC::Base64.new(metalinkData), options)
 elsif command == "tellStatus" then
-  result=client.call("aria2."+command, resources[0], resources[1..-1])
+  result=client_call(client, secret, "aria2."+command,
+                     resources[0], resources[1..-1])
 elsif command == "tellActive" then
-  result=client.call("aria2."+command, resources[0..-1])
+  result=client_call(client, secret, "aria2."+command, resources[0..-1])
 elsif command == "tellWaiting" then
-  result=client.call("aria2."+command, resources[0].to_i(), resources[1].to_i(),
-                     resources[2..-1])
+  result=client_call(client, secret, "aria2."+command, resources[0].to_i(),
+                     resources[1].to_i(), resources[2..-1])
 elsif command == "tellStopped" then
-  result=client.call("aria2."+command, resources[0].to_i(), resources[1].to_i(),
-                     resources[2..-1])
+  result=client_call(client, secret, "aria2."+command, resources[0].to_i(),
+                     resources[1].to_i(), resources[2..-1])
 elsif command == "getOption" then
-  result=client.call("aria2."+command, resources[0])
+  result=client_call(client, secret, "aria2."+command, resources[0])
 elsif command == "getGlobalOption" then
-  result=client.call("aria2."+command)
+  result=client_call(client, secret, "aria2."+command)
 elsif command == "pause" then
-  result=client.call("aria2."+command, resources[0])
+  result=client_call(client, secret, "aria2."+command, resources[0])
 elsif command == "pauseAll" then
-  result=client.call("aria2."+command)
+  result=client_call(client, secret, "aria2."+command)
 elsif command == "forcePause" then
-  result=client.call("aria2."+command, resources[0])
+  result=client_call(client, secret, "aria2."+command, resources[0])
 elsif command == "forcePauseAll" then
-  result=client.call("aria2."+command)
+  result=client_call(client, secret, "aria2."+command)
 elsif command == "unpause" then
-  result=client.call("aria2."+command, resources[0])
+  result=client_call(client, secret, "aria2."+command, resources[0])
 elsif command == "unpauseAll" then
-  result=client.call("aria2."+command)
+  result=client_call(client, secret, "aria2."+command)
 elsif command == "remove" then
-  result=client.call("aria2."+command, resources[0])
+  result=client_call(client, secret, "aria2."+command, resources[0])
 elsif command == "forceRemove" then
-  result=client.call("aria2."+command, resources[0])
+  result=client_call(client, secret, "aria2."+command, resources[0])
 elsif command == "changePosition" then
-  result=client.call("aria2."+command, resources[0], resources[1].to_i(),
-                     resources[2])
+  result=client_call(client, secret, "aria2."+command, resources[0],
+                     resources[1].to_i(), resources[2])
 elsif command == "getFiles" then
-  result=client.call("aria2."+command, resources[0])
+  result=client_call(client, secret, "aria2."+command, resources[0])
 elsif command == "getUris" then
-  result=client.call("aria2."+command, resources[0])
+  result=client_call(client, secret, "aria2."+command, resources[0])
 elsif command == "getPeers" then
-  result=client.call("aria2."+command, resources[0])
+  result=client_call(client, secret, "aria2."+command, resources[0])
 elsif command == "getServers" then
-  result=client.call("aria2."+command, resources[0])
+  result=client_call(client, secret, "aria2."+command, resources[0])
 elsif command == "purgeDownloadResult" then
-  result=client.call("aria2."+command)
+  result=client_call(client, secret, "aria2."+command)
 elsif command == "removeDownloadResult" then
-  result=client.call("aria2."+command, resources[0])
+  result=client_call(client, secret, "aria2."+command, resources[0])
 elsif command == "changeOption" then
-  result=client.call("aria2."+command, resources[0], options)
+  result=client_call(client, secret, "aria2."+command, resources[0], options)
 elsif command == "changeGlobalOption" then
-  result=client.call("aria2."+command, options)
+  result=client_call(client, secret, "aria2."+command, options)
 elsif command == "getVersion" then
-  result=client.call("aria2."+command)
+  result=client_call(client, secret, "aria2."+command)
 elsif command == "getSessionInfo" then
-  result=client.call("aria2."+command)
+  result=client_call(client, secret, "aria2."+command)
 elsif command == "shutdown" then
-  result=client.call("aria2."+command)
+  result=client_call(client, secret, "aria2."+command)
 elsif command == "forceShutdown" then
-  result=client.call("aria2."+command)
+  result=client_call(client, secret, "aria2."+command)
 elsif command == "getGlobalStat" then
-  result=client.call("aria2."+command)
+  result=client_call(client, secret, "aria2."+command)
 elsif command == "appendUri" then
-  result=client.call("aria2.changeUri", resources[0], resources[1].to_i(), [],
-                     resources[2..-1])
+  result=client_call(client, secret, "aria2.changeUri", resources[0],
+                     resources[1].to_i(), [], resources[2..-1])
 else
   puts "Command not recognized"
   exit 1

+ 8 - 0
src/OptionHandlerFactory.cc

@@ -865,6 +865,14 @@ std::vector<OptionHandler*> OptionHandlerFactory::createOptionHandlers()
     op->setChangeGlobalOption(true);
     handlers.push_back(op);
   }
+  {
+    OptionHandler* op(new DefaultOptionHandler
+                      (PREF_RPC_SECRET,
+                       TEXT_RPC_SECRET));
+    op->addTag(TAG_RPC);
+    op->setEraseAfterParse(true);
+    handlers.push_back(op);
+  }
   {
     OptionHandler* op(new BooleanOptionHandler
                       (PREF_RPC_SECURE,

+ 25 - 0
src/RpcMethod.cc

@@ -48,6 +48,7 @@
 #include "fmt.h"
 #include "DlAbortEx.h"
 #include "a2functional.h"
+#include "util.h"
 
 namespace aria2 {
 
@@ -68,9 +69,33 @@ std::unique_ptr<ValueBase> RpcMethod::createErrorResponse
   return std::move(params);
 }
 
+void RpcMethod::authorize(RpcRequest& req, DownloadEngine* e)
+{
+  std::string token;
+  // We always treat first parameter as token if it is string and
+  // starts with "token:" and remove it from parameter list, so that
+  // we don't have to add conditionals to all RPCMethod
+  // implementations.
+  if(req.params && !req.params->empty()) {
+    auto t = downcast<String>(req.params->get(0));
+    if(t) {
+      if(util::startsWith(t->s(), "token:")) {
+        token = t->s().substr(6);
+        req.params->pop_front();
+      }
+    }
+  }
+  if(e && e->getOption()->defined(PREF_RPC_SECRET)) {
+    if(token != e->getOption()->get(PREF_RPC_SECRET)) {
+      throw DL_ABORT_EX("Unauthorized");
+    }
+  }
+}
+
 RpcResponse RpcMethod::execute(RpcRequest req, DownloadEngine* e)
 {
   try {
+    authorize(req, e);
     auto r = process(req, e);
     return RpcResponse(0, std::move(r), std::move(req.id));
   } catch(RecoverableException& ex) {

+ 2 - 0
src/RpcMethod.h

@@ -93,6 +93,8 @@ public:
 
   virtual ~RpcMethod();
 
+  virtual void authorize(RpcRequest& req, DownloadEngine* e);
+
   // Do work to fulfill RpcRequest req and returns its result as
   // RpcResponse. This method delegates to process() method.
   RpcResponse execute(RpcRequest req, DownloadEngine* e);

+ 8 - 0
src/RpcMethodImpl.h

@@ -572,6 +572,14 @@ protected:
   virtual std::unique_ptr<ValueBase> process
   (const RpcRequest& req, DownloadEngine* e) CXX11_OVERRIDE;
 public:
+  virtual void authorize(RpcRequest& req, DownloadEngine* e) CXX11_OVERRIDE
+  {
+    // Batch calls (e.g., system.multicall) authorizes only nested
+    // methods. This is because XML-RPC system.multicall only accpets
+    // methods array and there is no room for us to insert token
+    // parameter.
+  }
+
   static const char* getMethodName()
   {
     return "system.multicall";

+ 10 - 0
src/ValueBase.cc

@@ -151,6 +151,16 @@ void List::set(size_t index, std::unique_ptr<ValueBase> v)
   list_[index] = std::move(v);
 }
 
+void List::pop_front()
+{
+  list_.pop_front();
+}
+
+void List::pop_back()
+{
+  list_.pop_back();
+}
+
 void List::append(std::unique_ptr<ValueBase> v)
 {
   list_.push_back(std::move(v));

+ 8 - 2
src/ValueBase.h

@@ -38,7 +38,7 @@
 #include "common.h"
 
 #include <string>
-#include <vector>
+#include <deque>
 #include <map>
 #include <memory>
 
@@ -170,7 +170,7 @@ private:
 
 class List:public ValueBase {
 public:
-  typedef std::vector<std::unique_ptr<ValueBase>> ValueType;
+  typedef std::deque<std::unique_ptr<ValueBase>> ValueType;
 
   List();
 
@@ -196,6 +196,12 @@ public:
   // Returns the const reference of the object at the given index.
   ValueBase* operator[](size_t index) const;
 
+  // Pops the value in the front of the list.
+  void pop_front();
+
+  // Pops the value in the back of the list.
+  void pop_back();
+
   // Returns a read/write iterator that points to the first object in
   // list.
   ValueType::iterator begin();

+ 2 - 0
src/prefs.cc

@@ -359,6 +359,8 @@ PrefPtr PREF_GID = makePref("gid");
 // values: 1*digit
 PrefPtr PREF_SAVE_SESSION_INTERVAL = makePref("save-session-interval");
 PrefPtr PREF_ENABLE_COLOR = makePref("enable-color");
+// value: string
+PrefPtr PREF_RPC_SECRET = makePref("rpc-secret");
 
 /**
  * FTP related preferences

+ 2 - 0
src/prefs.h

@@ -296,6 +296,8 @@ extern PrefPtr PREF_GID;
 extern PrefPtr PREF_SAVE_SESSION_INTERVAL;
 // value: true |false
 extern PrefPtr PREF_ENABLE_COLOR;
+// value: string
+extern PrefPtr PREF_RPC_SECRET;
 
 /**
  * FTP related preferences

+ 7 - 5
src/usage_text.h

@@ -770,11 +770,11 @@
     "                              option is useful when the system does not have\n" \
     "                              /etc/resolv.conf and user does not have the\n" \
     "                              permission to create it.")
-#define TEXT_ENABLE_RPC                                             \
-  _(" --enable-rpc[=true|false]    Enable JSON-RPC/XML-RPC server.\n"   \
-    "                              It is strongly recommended to set username and\n" \
-    "                              password using --rpc-user and --rpc-passwd\n" \
-    "                              option. See also --rpc-listen-port option.")
+#define TEXT_ENABLE_RPC                                               \
+  _(" --enable-rpc[=true|false]    Enable JSON-RPC/XML-RPC server.\n" \
+    "                              It is strongly recommended to set secret\n" \
+    "                              authorization token using --rpc-secret option.\n" \
+    "                              See also --rpc-listen-port option.")
 #define TEXT_RPC_MAX_REQUEST_SIZE                                   \
   _(" --rpc-max-request-size=SIZE  Set max size of JSON-RPC/XML-RPC request. If aria2\n" \
     "                              detects the request is more than SIZE bytes, it\n" \
@@ -960,3 +960,5 @@
     "                              when aria2 exits.")
 #define TEXT_ENABLE_COLOR                                               \
   _(" --enable-color[=true|false]  Enable color output for a terminal.")
+#define TEXT_RPC_SECRET                                                 \
+  _(" --rpc-secret=TOKEN           Set RPC secret authorization token.")

+ 43 - 0
test/RpcMethodTest.cc

@@ -33,6 +33,7 @@ namespace rpc {
 class RpcMethodTest:public CppUnit::TestFixture {
 
   CPPUNIT_TEST_SUITE(RpcMethodTest);
+  CPPUNIT_TEST(testAuthorize);
   CPPUNIT_TEST(testAddUri);
   CPPUNIT_TEST(testAddUri_withoutUri);
   CPPUNIT_TEST(testAddUri_notUri);
@@ -96,6 +97,7 @@ public:
        (std::vector<std::shared_ptr<RequestGroup>>{}, 1, option_.get()));
   }
 
+  void testAuthorize();
   void testAddUri();
   void testAddUri_withoutUri();
   void testAddUri_notUri();
@@ -159,6 +161,47 @@ RpcRequest createReq(std::string methodName)
 }
 } // namespace
 
+void RpcMethodTest::testAuthorize()
+{
+  // Select RPC method which takes non-string parameter to make sure
+  // that token: prefixed parameter is stripped before the call.
+  TellActiveRpcMethod m;
+  // no secret token set and no token: prefixed parameter is given
+  {
+    auto req = createReq(TellActiveRpcMethod::getMethodName());
+    auto res = m.execute(std::move(req), e_.get());
+    CPPUNIT_ASSERT_EQUAL(0, res.code);
+  }
+  // no secret token set and token: prefixed parameter is given
+  {
+    auto req = createReq(GetVersionRpcMethod::getMethodName());
+    req.params->append("token:foo");
+    auto res = m.execute(std::move(req), e_.get());
+    CPPUNIT_ASSERT_EQUAL(0, res.code);
+  }
+  e_->getOption()->put(PREF_RPC_SECRET, "foo");
+  // secret token set and no token: prefixed parameter is given
+  {
+    auto req = createReq(GetVersionRpcMethod::getMethodName());
+    auto res = m.execute(std::move(req), e_.get());
+    CPPUNIT_ASSERT_EQUAL(1, res.code);
+  }
+  // secret token set and token: prefixed parameter is given
+  {
+    auto req = createReq(GetVersionRpcMethod::getMethodName());
+    req.params->append("token:foo");
+    auto res = m.execute(std::move(req), e_.get());
+    CPPUNIT_ASSERT_EQUAL(0, res.code);
+  }
+  // secret token set and bad token: prefixed parameter is given
+  {
+    auto req = createReq(GetVersionRpcMethod::getMethodName());
+    req.params->append("token:foo2");
+    auto res = m.execute(std::move(req), e_.get());
+    CPPUNIT_ASSERT_EQUAL(1, res.code);
+  }
+}
+
 void RpcMethodTest::testAddUri()
 {
   AddUriRpcMethod m;