From 350bdfde7e3c9e500d2ca8710f0f6dd369820e6a Mon Sep 17 00:00:00 2001 From: rakshasa Date: Tue, 17 Jul 2007 16:24:57 +0000 Subject: [PATCH] * Allow ';' as a separator for multi-command lines. Not recognized by the string parser, so it must be preceeded by whitespace. * Allow escaping of newlines in the resource file. * Cleanup of the parse_command_* commands. git-svn-id: svn://rakshasa.no/libtorrent/trunk/rtorrent@936 e378c898-3ddf-0310-93e7-cc216c733640 --- rak/functional_fun.h | 21 ++++++ src/command_download.cc | 90 +++++++++--------------- src/command_events.cc | 15 ++-- src/command_local.cc | 2 + src/command_network.cc | 8 +-- src/command_ui.cc | 17 +++-- src/control.cc | 2 +- src/core/manager.cc | 6 ++ src/core/manager.h | 4 +- src/rpc/Makefile.am | 2 + src/rpc/exec_file.cc | 117 ++++++++++++++++++++++++++++++ src/rpc/exec_file.h | 57 +++++++++++++++ src/rpc/parse.cc | 10 ++- src/rpc/parse.h | 7 +- src/rpc/parse_commands.cc | 121 +++++++++++++++++++++----------- src/rpc/parse_commands.h | 21 ++++-- src/ui/download_list.cc | 2 +- src/ui/element_download_list.cc | 2 +- 18 files changed, 368 insertions(+), 136 deletions(-) create mode 100644 src/rpc/exec_file.cc create mode 100644 src/rpc/exec_file.h diff --git a/rak/functional_fun.h b/rak/functional_fun.h index b47b16a5..10897280 100644 --- a/rak/functional_fun.h +++ b/rak/functional_fun.h @@ -351,6 +351,21 @@ private: Arg1 m_arg1; }; +template +class ptr_fn2_b1_t : public function_base2 { +public: + typedef Result (*Func)(Arg1, Arg2, Arg3); + + ptr_fn2_b1_t(Func func, const Arg1 arg1) : m_func(func), m_arg1(arg1) {} + virtual ~ptr_fn2_b1_t() {} + + virtual Result operator () (Arg2 arg2, Arg3 arg3) { return m_func(m_arg1, arg2, arg3); } + +private: + Func m_func; + Arg1 m_arg1; +}; + template class ftor_fn1_t : public function_base1 { public: @@ -519,6 +534,12 @@ bind_ptr_fn(Result (*func)(Arg1, Arg2), const Arg1 arg1) { return new ptr_fn1_b1_t(func, arg1); } +template +inline function_base2* +bind_ptr_fn(Result (*func)(Arg1, Arg2, Arg3), const Arg1 arg1) { + return new ptr_fn2_b1_t(func, arg1); +} + template inline function_base1* ftor_fn1(Ftor ftor) { diff --git a/src/command_download.cc b/src/command_download.cc index a2c34986..f8c72f86 100644 --- a/src/command_download.cc +++ b/src/command_download.cc @@ -64,23 +64,23 @@ retrieve_d_base_path(core::Download* download) { std::string retrieve_d_base_filename(core::Download* download) { - std::string base; + const std::string* base; if (download->file_list()->is_multi_file()) - base = download->file_list()->root_dir(); + base = &download->file_list()->root_dir(); else - base = download->file_list()->at(0)->frozen_path(); + base = &download->file_list()->at(0)->frozen_path(); - std::string::size_type split = base.rfind('/'); + std::string::size_type split = base->rfind('/'); if (split == std::string::npos) - return base; + return *base; else - return base.substr(split + 1); + return base->substr(split + 1); } torrent::Object -apply_d_create_link(core::Download* download, const torrent::Object& rawArgs) { +apply_d_change_link(int changeType, core::Download* download, const torrent::Object& rawArgs) { const torrent::Object::list_type& args = rawArgs.as_list(); if (args.size() != 3) @@ -119,56 +119,28 @@ apply_d_create_link(core::Download* download, const torrent::Object& rawArgs) { throw torrent::input_error("Unknown type argument."); } - if (symlink(target.c_str(), link.c_str()) == -1) -// control->core()->push_log("create_link failed: " + std::string(rak::error_number::current().c_str())); -// control->core()->push_log("create_link failed: " + std::string(rak::error_number::current().c_str()) + " to " + target); - ; // Disabled. + switch (changeType) { + case 0: + if (symlink(target.c_str(), link.c_str()) == -1) + // control->core()->push_log("create_link failed: " + std::string(rak::error_number::current().c_str())); + // control->core()->push_log("create_link failed: " + std::string(rak::error_number::current().c_str()) + " to " + target); + ; // Disabled. + break; - return torrent::Object(); -} + case 1: + { + rak::file_stat fileStat; + rak::error_number::clear_global(); -torrent::Object -apply_d_delete_link(core::Download* download, const torrent::Object& rawArgs) { - const torrent::Object::list_type& args = rawArgs.as_list(); + if (!fileStat.update_link(link) || !fileStat.is_link() || + unlink(link.c_str()) == -1) + ; // control->core()->push_log("delete_link failed: " + std::string(rak::error_number::current().c_str())); - if (args.size() != 3) - throw torrent::input_error("Wrong argument count."); - - torrent::Object::list_type::const_iterator itr = args.begin(); - - const std::string& type = (itr++)->as_string(); - const std::string& prefix = (itr++)->as_string(); - const std::string& postfix = (itr++)->as_string(); - - if (type.empty()) - throw torrent::input_error("Invalid arguments."); - - std::string link; - - if (type == "base_path") { - link = rak::path_expand(prefix + rpc::call_command_d_string("get_d_base_path", download) + postfix); - - } else if (type == "base_filename") { - link = rak::path_expand(prefix + rpc::call_command_d_string("get_d_base_filename", download) + postfix); - - } else if (type == "tied") { - link = rak::path_expand(rpc::call_command_d_string("get_d_tied_to_file", download)); - - if (link.empty()) - return torrent::Object(); - - link = rak::path_expand(prefix + link + postfix); - - } else { - throw torrent::input_error("Unknown type argument."); + break; + } + default: + break; } - - rak::file_stat fileStat; - rak::error_number::clear_global(); - - if (!fileStat.update_link(link) || !fileStat.is_link() || - unlink(link.c_str()) == -1) - ; // control->core()->push_log("delete_link failed: " + std::string(rak::error_number::current().c_str())); return torrent::Object(); } @@ -181,7 +153,7 @@ apply_d_delete_tied(core::Download* download) { return; if (::unlink(rak::path_expand(tie).c_str()) == -1) - control->core()->push_log("Could not unlink tied file: " + std::string(rak::error_number::current().c_str())); + control->core()->push_log_std("Could not unlink tied file: " + std::string(rak::error_number::current().c_str())); rpc::call_command_d("set_d_tied_to_file", download, std::string()); } @@ -295,8 +267,8 @@ initialize_command_download() { ADD_CD_VOID("base_path", &retrieve_d_base_path); ADD_CD_VOID("base_filename", &retrieve_d_base_filename); - ADD_CD_LIST("create_link", rak::ptr_fn(&apply_d_create_link)); - ADD_CD_LIST("delete_link", rak::ptr_fn(&apply_d_delete_link)); + ADD_CD_LIST("create_link", rak::bind_ptr_fn(&apply_d_change_link, 0)); + ADD_CD_LIST("delete_link", rak::bind_ptr_fn(&apply_d_change_link, 1)); ADD_CD_V_VOID("delete_tied", &apply_d_delete_tied); ADD_CD_F_VOID("start", rak::make_mem_fun(control->core()->download_list(), &core::DownloadList::start_normal)); @@ -316,6 +288,12 @@ initialize_command_download() { ADD_CD_VARIABLE_VALUE("state", "rtorrent", "state"); ADD_CD_VARIABLE_VALUE("complete", "rtorrent", "complete"); + // 0 off + // 1 scheduled, being controlled by a download scheduler. Includes a priority. + // 3 forced off + // 2 forced on + ADD_CD_VARIABLE_VALUE("mode", "rtorrent", "mode"); + // 0 - Not hashing // 1 - Normal hashing // 2 - Download finished, hashing diff --git a/src/command_events.cc b/src/command_events.cc index 8d2b5dae..c2b9141f 100644 --- a/src/command_events.cc +++ b/src/command_events.cc @@ -73,7 +73,7 @@ apply_on_state_change(core::DownloadList::slot_map* slotMap, const torrent::Obje if (args.back().as_string().empty()) slotMap->erase(key); else - (*slotMap)[key] = sigc::bind(sigc::ptr_fun(&rpc::parse_command_d_single_std), rpc::convert_list_to_command(++args.begin(), args.end())); + (*slotMap)[key] = sigc::bind(sigc::ptr_fun(&rpc::parse_command_d_multiple_std), rpc::convert_list_to_command(++args.begin(), args.end())); return torrent::Object(); } @@ -203,15 +203,18 @@ void apply_load_start(const std::string& arg) { control->core()->try_cre void apply_load_start_verbose(const std::string& arg) { control->core()->try_create_download_expand(arg, true, true, true); } void apply_import(const std::string& path) { if (!rpc::parse_command_file(path)) throw torrent::input_error("Could not open option file: " + path); } -void apply_try_import(const std::string& path) { if (!rpc::parse_command_file(path)) control->core()->push_log("Could not read resource file: " + path); } +void apply_try_import(const std::string& path) { if (!rpc::parse_command_file(path)) control->core()->push_log_std("Could not read resource file: " + path); } void apply_close_low_diskspace(int64_t arg) { - core::Manager::DListItr itr = control->core()->download_list()->begin(); + core::DownloadList* downloadList = control->core()->download_list(); - while ((itr = std::find_if(itr, control->core()->download_list()->end(), std::mem_fun(&core::Download::is_downloading))) != control->core()->download_list()->end()) { + core::Manager::DListItr itr = downloadList->begin(); + + while ((itr = std::find_if(itr, downloadList->end(), std::mem_fun(&core::Download::is_downloading))) + != downloadList->end()) { if ((*itr)->file_list()->free_diskspace() < (uint64_t)arg) { - control->core()->download_list()->close(*itr); + downloadList->close(*itr); (*itr)->set_hash_failed(true); (*itr)->set_message(std::string("Low diskspace.")); @@ -223,7 +226,7 @@ apply_close_low_diskspace(int64_t arg) { torrent::Object apply_download_list(const torrent::Object& rawArgs) { - const torrent::Object::list_type& args = rawArgs.as_list(); + const torrent::Object::list_type& args = rawArgs.as_list(); torrent::Object::list_type::const_iterator argsItr = args.begin(); core::ViewManager* viewManager = control->view_manager(); diff --git a/src/command_local.cc b/src/command_local.cc index e1953db7..3f2957de 100644 --- a/src/command_local.cc +++ b/src/command_local.cc @@ -81,4 +81,6 @@ initialize_command_local() { ADD_COMMAND_VALUE_TRI_OCT("umask", rak::make_mem_fun(control, &Control::set_umask), rak::make_mem_fun(control, &Control::umask)); ADD_COMMAND_STRING_TRI("working_directory", rak::make_mem_fun(control, &Control::set_working_directory), rak::make_mem_fun(control, &Control::working_directory)); + + ADD_COMMAND_LIST("execute", rak::mem_fn(&rpc::execFile, &rpc::ExecFile::execute_object)); } diff --git a/src/command_network.cc b/src/command_network.cc index 6b7b705a..fdaae4ea 100644 --- a/src/command_network.cc +++ b/src/command_network.cc @@ -82,7 +82,7 @@ apply_encryption(const torrent::Object& rawArgs) { else if (opt == "prefer_plaintext") options_mask |= torrent::ConnectionManager::encryption_prefer_plaintext; else - throw torrent::input_error("Invalid encryption option '" + opt + "'."); + throw torrent::input_error("Invalid encryption option."); } torrent::connection_manager()->set_encryption_options(options_mask); @@ -99,19 +99,14 @@ apply_tos(const torrent::Object& rawArg) { if (arg == "default") value = torrent::ConnectionManager::iptos_default; - else if (arg == "lowdelay") value = torrent::ConnectionManager::iptos_lowdelay; - else if (arg == "throughput") value = torrent::ConnectionManager::iptos_throughput; - else if (arg == "reliability") value = torrent::ConnectionManager::iptos_reliability; - else if (arg == "mincost") value = torrent::ConnectionManager::iptos_mincost; - else if (!rpc::parse_whole_value_nothrow(arg.c_str(), &value, 16, 1)) throw torrent::input_error("Invalid TOS identifier."); @@ -127,7 +122,6 @@ void apply_encoding_list(const std::string& arg) { torrent::encoding_list()->pus void apply_enable_trackers(int64_t arg) { for (core::Manager::DListItr itr = control->core()->download_list()->begin(), last = control->core()->download_list()->end(); itr != last; ++itr) { - torrent::TrackerList tl = (*itr)->download()->tracker_list(); for (int i = 0, last = tl.size(); i < last; ++i) diff --git a/src/command_ui.cc b/src/command_ui.cc index 6d16218c..060c3c8f 100644 --- a/src/command_ui.cc +++ b/src/command_ui.cc @@ -99,22 +99,27 @@ apply_view_sort(const torrent::Object& rawArgs) { torrent::Object apply_print(const torrent::Object& rawArgs) { - std::string output; + char buffer[1024]; + char* current = buffer; for (torrent::Object::list_type::const_iterator itr = rawArgs.as_list().begin(), last = rawArgs.as_list().end(); itr != last; itr++) { switch (itr->type()) { case torrent::Object::TYPE_STRING: - output += itr->as_string(); - break; + { + int len = std::min(itr->as_string().size(), buffer + 1024 - current); + std::memcpy(current, itr->as_string().c_str(), len); + current += len; + break; + } case torrent::Object::TYPE_VALUE: default: - output += ""; + current += snprintf(buffer, buffer + 1024 - current, "%lli", itr->as_value()); + break; } } - control->core()->push_log(output); - + control->core()->push_log(buffer); return torrent::Object(); } diff --git a/src/control.cc b/src/control.cc index 1bc7fb8b..bb7dc8e5 100644 --- a/src/control.cc +++ b/src/control.cc @@ -80,7 +80,7 @@ Control::Control() : m_taskShutdown.set_slot(rak::mem_fn(this, &Control::handle_shutdown)); - m_commandScheduler->set_slot_error_message(rak::mem_fn(m_core, &core::Manager::push_log)); + m_commandScheduler->set_slot_error_message(rak::mem_fn(m_core, &core::Manager::push_log_std)); } Control::~Control() { diff --git a/src/core/manager.cc b/src/core/manager.cc index 26c2c67b..c2240202 100644 --- a/src/core/manager.cc +++ b/src/core/manager.cc @@ -165,6 +165,12 @@ Manager::handshake_log(const sockaddr* sa, int msg, int err, const torrent::Hash } } +void +Manager::push_log(const char* msg) { + m_logImportant.push_front(msg); + m_logComplete.push_front(msg); +} + Manager::Manager() : m_hashingView(NULL), diff --git a/src/core/manager.h b/src/core/manager.h index 0ecdaee6..22eb8454 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -51,6 +51,7 @@ namespace core { class DownloadStore; class HttpQueue; + class View; class Manager { @@ -92,7 +93,8 @@ public: void shutdown(bool force); - void push_log(const std::string& msg) { m_logImportant.push_front(msg); m_logComplete.push_front(msg); } + void push_log(const char* msg); + void push_log_std(const std::string& msg) { m_logImportant.push_front(msg); m_logComplete.push_front(msg); } void handshake_log(const sockaddr* sa, int msg, int err, const torrent::HashString* hash); diff --git a/src/rpc/Makefile.am b/src/rpc/Makefile.am index 961613c5..30702696 100644 --- a/src/rpc/Makefile.am +++ b/src/rpc/Makefile.am @@ -14,6 +14,8 @@ libsub_rpc_a_SOURCES = \ command_slot.h \ command_variable.cc \ command_variable.h \ + exec_file.cc \ + exec_file.h \ parse.cc \ parse.h \ parse_commands.cc \ diff --git a/src/rpc/exec_file.cc b/src/rpc/exec_file.cc new file mode 100644 index 00000000..34897c17 --- /dev/null +++ b/src/rpc/exec_file.cc @@ -0,0 +1,117 @@ +// rTorrent - BitTorrent client +// Copyright (C) 2006, Jari Sundell +// +// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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. +// +// Contact: Jari Sundell +// +// Skomakerveien 33 +// 3185 Skoppum, NORWAY + +#include "config.h" + +#include + +#include "exec_file.h" + +namespace rpc { + +int +ExecFile::execute(const char* file, char* const* argv) { + pid_t childPid = fork(); + + if (childPid == -1) + throw torrent::input_error("ExecFile::execute(...) Fork failed."); + + if (childPid == 0) { + // Close all fd's. + for (int i = 0, last = sysconf(_SC_OPEN_MAX); i != last; i++) + ::close(i); + + int result = execvp(file, argv); + + _exit(result); + + } else { + int status; + + if (waitpid(childPid, &status, 0) != childPid) + throw torrent::internal_error("ExecFile::execute(...) waitpid failed."); + + // Check return value? + + return status; + } +} + +torrent::Object +ExecFile::execute_object(const torrent::Object& rawArgs) { + char* argsBuffer[128]; + char** argsCurrent = argsBuffer; + + // Size of strings are less than 24. + char valueBuffer[3072]; + char* valueCurrent = valueBuffer; + + const torrent::Object::list_type& args = rawArgs.as_list(); + + if (args.empty()) + throw torrent::input_error("Too few arguments."); + + for (torrent::Object::list_type::const_iterator itr = args.begin(), last = args.end(); itr != last; itr++, argsCurrent++) { + if (argsCurrent == argsBuffer + 128 - 1) + throw torrent::input_error("Too many arguments."); + + switch (itr->type()) { + case torrent::Object::TYPE_STRING: + *argsCurrent = const_cast(itr->as_string().c_str()); + break; + + case torrent::Object::TYPE_VALUE: + *argsCurrent = valueCurrent; + + valueCurrent += std::max(snprintf(valueCurrent, valueBuffer + 3072 - valueCurrent, "%lli", itr->as_value()), 0); + break; + + default: + throw torrent::input_error("Invalid type."); + } + } + + *argsCurrent = NULL; + + int status = execute(argsBuffer[0], argsBuffer); + + if (status != 0) + throw torrent::input_error("ExecFile::execute_object(...) status != 0."); + + return torrent::Object(); +} + +} diff --git a/src/rpc/exec_file.h b/src/rpc/exec_file.h new file mode 100644 index 00000000..6c05041a --- /dev/null +++ b/src/rpc/exec_file.h @@ -0,0 +1,57 @@ +// rTorrent - BitTorrent client +// Copyright (C) 2006, Jari Sundell +// +// 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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. +// +// Contact: Jari Sundell +// +// Skomakerveien 33 +// 3185 Skoppum, NORWAY + +#ifndef RTORRENT_RPC_EXEC_FILE_H +#define RTORRENT_RPC_EXEC_FILE_H + +#include + +namespace rpc { + +class ExecFile { +public: + + int execute(const char* file, char* const* argv); + + torrent::Object execute_object(const torrent::Object& rawArgs); + +private: + +}; + +} + +#endif diff --git a/src/rpc/parse.cc b/src/rpc/parse.cc index 29ea2182..06fae585 100644 --- a/src/rpc/parse.cc +++ b/src/rpc/parse.cc @@ -45,7 +45,7 @@ namespace rpc { const char* parse_skip_wspace(const char* first, const char* last) { - while (first != last && std::isspace(*first)) + while (first != last && parse_is_space(*first)) first++; return first; @@ -53,8 +53,7 @@ parse_skip_wspace(const char* first, const char* last) { const char* parse_skip_wspace(const char* first) { - // Assume isspace('\0') == false. - while (std::isspace(*first)) + while (parse_is_space(*first)) first++; return first; @@ -190,7 +189,7 @@ parse_list(const char* first, const char* last, torrent::Object* dest) { return first; } -void +const char* parse_whole_list(const char* first, const char* last, torrent::Object* dest) { std::string str; @@ -208,8 +207,7 @@ parse_whole_list(const char* first, const char* last, torrent::Object* dest) { *dest = str; } - if (first != last) - throw torrent::input_error("Junk at end of input."); + return first; } std::string diff --git a/src/rpc/parse.h b/src/rpc/parse.h index 496c3bd7..4d78ffbe 100644 --- a/src/rpc/parse.h +++ b/src/rpc/parse.h @@ -49,9 +49,10 @@ namespace rpc { // parse_whole_* functions allow for whitespaces and throw an // exception if there is any garbage at the end of the input. -inline bool parse_is_quote(const char c) { return c == '"'; } -inline bool parse_is_escape(const char c) { return c == '\\'; } +inline bool parse_is_quote(const char c) { return c == '"'; } +inline bool parse_is_escape(const char c) { return c == '\\'; } inline bool parse_is_seperator(const char c) { return c == ','; } +inline bool parse_is_space(const char c) { return c == ' ' || c == '\t'; } const char* parse_skip_wspace(const char* first); const char* parse_skip_wspace(const char* first, const char* last); @@ -66,7 +67,7 @@ void parse_whole_value(const char* src, int64_t* value, int base = 0, int bool parse_whole_value_nothrow(const char* src, int64_t* value, int base = 0, int unit = 1); const char* parse_list(const char* first, const char* last, torrent::Object* dest); -void parse_whole_list(const char* first, const char* last, torrent::Object* dest); +const char* parse_whole_list(const char* first, const char* last, torrent::Object* dest); std::string convert_list_to_string(const torrent::Object& src); std::string convert_list_to_string(torrent::Object::list_type::const_iterator first, torrent::Object::list_type::const_iterator last); diff --git a/src/rpc/parse_commands.cc b/src/rpc/parse_commands.cc index 4587c1b7..a0c05d50 100644 --- a/src/rpc/parse_commands.cc +++ b/src/rpc/parse_commands.cc @@ -50,44 +50,41 @@ namespace rpc { CommandMap commands; XmlRpc xmlrpc; +ExecFile execFile; struct command_map_is_space : std::unary_function { bool operator () (char c) const { - return std::isspace(c); + return c == ' ' || c == '\t'; } }; struct command_map_is_newline : std::unary_function { bool operator () (char c) const { - return c == '\n' || c == '\0'; + return c == '\n' || c == '\0' || c == ';'; } }; -// Use a static length buffer for dest. -const char* -parse_command_name(const char* first, const char* last, std::string* dest) { - if (first == last || !std::isalpha(*first)) - throw torrent::input_error("Invalid start of name."); +// Only escape eol on odd number of escape characters. We know that +// there can't be any characters in between, so this should work for +// all cases. +int +parse_count_escaped(const char* first, const char* last) { + int escaped = 0; - for ( ; first != last && (std::isalnum(*first) || *first == '_'); ++first) - dest->push_back(*first); + while (last != first && *--last == '\\') + escaped++; - return first; -} - -void -parse_command_single(const char* first) { - parse_command_single(first, first + std::strlen(first)); + return escaped; } // Set 'download' to NULL to call the generic functions, thus reusing // the code below for both cases. -torrent::Object -parse_command_d_single(core::Download* download, const char* first, const char* last) { +std::pair +parse_command(core::Download* download, const char* first, const char* last) { first = std::find_if(first, last, std::not1(command_map_is_space())); if (first == last || *first == '#') - return torrent::Object(); + return std::make_pair(torrent::Object(), first); std::string key; first = parse_command_name(first, last, &key); @@ -97,7 +94,19 @@ parse_command_d_single(core::Download* download, const char* first, const char* throw torrent::input_error("Could not find '='."); torrent::Object args; - parse_whole_list(first + 1, last, &args); + first = parse_whole_list(first + 1, last, &args); + + // Find the last character that is part of this command, skipping + // the whitespace at the end. This ensures us that the caller + // doesn't need to do this nor check for junk at the end. + first = std::find_if(first, last, std::not1(command_map_is_space())); + + if (first != last) { + if (*first != '\n' && *first != ';' && *first != '\0') + throw torrent::input_error("Junk at end of input."); + + first++; + } // Replace any strings starting with '$' with the result of the // following command. @@ -118,32 +127,30 @@ parse_command_d_single(core::Download* download, const char* first, const char* args = parse_command_d_single(download, str.c_str() + 1, str.c_str() + str.size()); } - return commands.call_command_d(key.c_str(), download, args); + return std::make_pair(commands.call_command_d(key.c_str(), download, args), first); } void -parse_command_multiple(const char* first) { - try { - while (first != '\0') { - const char* last = first; +parse_command_single(const char* first) { + parse_command(NULL, first, first + std::strlen(first)); +} - while (*last != '\n' && *last != '\0') last++; +void +parse_command_multiple(core::Download* download, const char* first, const char* last) { + while (first != last) { + // Should we check the return value? Probably not necessary as + // parse_args throws on unquoted multi-word input. + std::pair result = parse_command(download, first, last); - // Should we check the return value? Probably not necessary as - // parse_args throws on unquoted multi-word input. - parse_command_single(first, last); - - if (*last == '\0') - return; - - first = last + 1; - } - - } catch (torrent::input_error& e) { - throw torrent::input_error(std::string("Error parsing multi-line option: ") + e.what()); + first = result.second; } } +void +parse_command_d_multiple(core::Download* download, const char* first) { + parse_command_multiple(download, first, first + std::strlen(first)); +} + bool parse_command_file(const std::string& path) { std::fstream file(rak::path_expand(path).c_str(), std::ios::in); @@ -151,19 +158,37 @@ parse_command_file(const std::string& path) { if (!file.is_open()) return false; - int lineNumber = 0; - char buffer[2048]; + unsigned int lineNumber = 0; + char buffer[4096]; try { + unsigned int getCount = 0; + + while (file.getline(buffer + getCount, 4096 - getCount).good()) { + if (file.gcount() == 0) + throw torrent::internal_error("parse_command_file(...) file.gcount() == 0."); + + int escaped = parse_count_escaped(buffer + getCount, buffer + getCount + file.gcount() - 1); - while (file.getline(buffer, 2048).good()) { lineNumber++; + getCount += file.gcount() - 1; + + if (getCount == 4096 - 1) + throw torrent::input_error("Exceeded max line lenght."); + + if (escaped & 0x1) { + // Remove the escape characters and continue reading. + getCount -= escaped; + continue; + } + // Would be nice to make this zero-copy. - parse_command_single(buffer, buffer + std::strlen(buffer)); + parse_command(NULL, buffer, buffer + getCount); + getCount = 0; } } catch (torrent::input_error& e) { - snprintf(buffer, 2048, "Error in option file: %s:%i: %s", path.c_str(), lineNumber, e.what()); + snprintf(buffer, 2048, "Error in option file: %s:%u: %s", path.c_str(), lineNumber, e.what()); throw torrent::input_error(buffer); } @@ -171,4 +196,16 @@ parse_command_file(const std::string& path) { return true; } +// Use a static length buffer for dest. +const char* +parse_command_name(const char* first, const char* last, std::string* dest) { + if (first == last || !std::isalpha(*first)) + throw torrent::input_error("Invalid start of name."); + + for ( ; first != last && (std::isalnum(*first) || *first == '_'); ++first) + dest->push_back(*first); + + return first; +} + } diff --git a/src/rpc/parse_commands.h b/src/rpc/parse_commands.h index 1592013e..071d6911 100644 --- a/src/rpc/parse_commands.h +++ b/src/rpc/parse_commands.h @@ -40,6 +40,7 @@ #include #include "command_map.h" +#include "exec_file.h" #include "xmlrpc.h" namespace core { @@ -51,16 +52,24 @@ namespace rpc { // Move to another file? extern CommandMap commands; extern XmlRpc xmlrpc; +extern ExecFile execFile; -const char* parse_command_name(const char* first, const char* last, std::string* dest); +// The generic parse command function, used by the rest. At some point +// the 'download' parameter should be replaced by a more generic one. +std::pair parse_command(core::Download* download, const char* first, const char* last); -void parse_command_single(const char* first); +void parse_command_single(const char* first); +inline torrent::Object parse_command_single(const char* first, const char* last) { return parse_command(NULL, first, last).first; } +inline torrent::Object parse_command_d_single(core::Download* download, const char* first, const char* last) { return parse_command(download, first, last).first; } -torrent::Object parse_command_d_single(core::Download* download, const char* first, const char* last); -inline torrent::Object parse_command_single(const char* first, const char* last) { return parse_command_d_single(NULL, first, last); } +void parse_command_multiple(core::Download* download, const char* first, const char* last); -void parse_command_multiple(const char* first); -bool parse_command_file(const std::string& path); +void parse_command_d_multiple(core::Download* download, const char* first); +inline void parse_command_d_multiple_std(core::Download* download, const std::string& cmd) { parse_command_d_multiple(download, cmd.c_str()); } +inline void parse_command_multiple(const char* first) { parse_command_d_multiple(NULL, first); } + +bool parse_command_file(const std::string& path); +const char* parse_command_name(const char* first, const char* last, std::string* dest); inline void parse_command_single_std(const std::string& cmd) { diff --git a/src/ui/download_list.cc b/src/ui/download_list.cc index e0c41304..0134e1aa 100644 --- a/src/ui/download_list.cc +++ b/src/ui/download_list.cc @@ -288,7 +288,7 @@ DownloadList::receive_exit_input(Input type) { throw torrent::input_error("No download in focus to change root directory."); rpc::call_command_d("set_d_directory", *current_view()->focus(), rak::trim(input->str())); - control->core()->push_log("New root directory \"" + rpc::call_command_d_string("get_d_directory", *current_view()->focus()) + "\" for torrent."); + control->core()->push_log_std("New root directory \"" + rpc::call_command_d_string("get_d_directory", *current_view()->focus()) + "\" for torrent."); break; case INPUT_COMMAND: diff --git a/src/ui/element_download_list.cc b/src/ui/element_download_list.cc index f71e126f..8484d4cc 100644 --- a/src/ui/element_download_list.cc +++ b/src/ui/element_download_list.cc @@ -234,7 +234,7 @@ ElementDownloadList::receive_change_view(const std::string& name) { core::ViewManager::iterator itr = control->view_manager()->find(name); if (itr == control->view_manager()->end()) { - control->core()->push_log("Could not find view \"" + name + "\"."); + control->core()->push_log_std("Could not find view \"" + name + "\"."); return; }